I2C Master routines for Baby O

I wanted to add the Devantech 2D compass (CMPS01 - Rev 7), best used in the I2C two-wire interface mode, to my robomagellan project. With the aid of Peter Fleury’s delightfully comprehensive I2C code it was quickly running. Then, the compass module died.

The code should be useful to others, so I added an example to read/write the 24LC256 EEPROM, which has 32K bytes of space.

Peter’s site has much useful AVR code and can be found at homepage.hispeed.ch/peterfleury/ … tware.html
I’ve extracted the relevant parts of Peter’s I2C code, which take advantage of the TWI hardware module, with microscopic adaptions for the Baby O.

It works flawlessly! A .zip file of the collected routines for WinAVR can be found at uoxray.uoregon.edu/orangutan/i2c.zip

To demonstrate what I2C communications entail, the main code is pasted in below. In my project the Baby O communicates with a PC using the RS232 interface, via the Pololu USB-to-serial adapter. For more information (such as it is), see
uoxray.uoregon.edu/orangutan
If anyone encounters difficulties with these routines, let me know.

Cheers, Jim

/*
Baby Orangutan I2C example--read/write 24LC256 EEPROM 

This program utilizes Peter Fleury's I2Master routines to communicate with an EEPROM chip
See I2C routines and other examples at Peter's site: [jump.to/fleury](http://jump.to/fleury)

Note: Peter's routines implement both hardware and "bit-bang" I2C routines.
I followed the example code, buried within the comments to i2cmaster.h, testing the 
software features using the 24C02 EEPROM.

This version uses the "TWI" (two wire interface=I2C) hardware on the atmega168.

Connections required between 24LC256 and Baby O
 
Baby O -> 24LC256
+5 (Vdd) -> Pin 8
PC4 (SDA) -> Pin 5	Don't forget pullup resistors to +5V on each of SDA and SCL! I used 4.7 K
PC5 (SCL) -> Pin 6
Gnd (Vss) -> Pin 4,3,2,1 and 7  (WP=A0=A1=A2=low)

CPU Clock speed is 20 MHz, I2C clock=100 kHz max.

Changes to I2C code by Peter Fleury (minimal):
Set clock (F_CPU), I2C clock (100 kHz) and check that TWI clock prescaler = 1 is appropriate.

RS232 routines are used to communicate between Hyperterm on a PC, via Pololu USB to serial converter
For more information, see [uoxray.uoregon.edu/orangutan](http://www.uoxray.uoregon.edu/orangutan)

Jim Remington, sjames_remington at yahoo dot com
*/

#define F_CPU 20000000UL
/* UART baud rate */
#define UART_BAUD  9600

#include <stdio.h>
#include <inttypes.h>

#include <avr/io.h>
#include <util/delay.h>

#include <uart.h>
#include <i2cmaster.h>

// setup serial routines to become stdio

extern int uart_putchar(char c, FILE *stream);
extern int uart_getchar(FILE *stream);
FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);


#include "uart.c"
#include "i2cmaster.c"

int main(void)
{
    unsigned char lowbyte;

    uart_init();  //initialize UART hardware
    i2c_init();  // initialize I2C interface

    stdout = stdin = &uart_str;  //setup stdio = RS232 port

    puts("Test I2C routines with 24lc256 EEprom");  //chip address lines A0,1,2 are wired to ground.
		
		
// debugging: use calls similar to the following to help debug the interface with new device
//		if (i2c_write(0)==1) puts("write 0 failed");  
//		if (i2c_rep_start(0xA0+I2C_READ)==1) puts ("rep_start failed"); //set read mode

// 24LC256 device address is 0xA0 plus the values set by connections to external pins A0,A1,A2 (see data sheet)

    i2c_start_wait(0xA0+I2C_WRITE);     // set device address and write mode
    i2c_write(0x00);                        // high byte of write address 0x05
    i2c_write(0x05);                        // low byte of write address 0x05
    i2c_write(0x75);                        // write value 0x75 to EEPROM address 0x05
    i2c_stop();                             // set stop conditon = release bus


    // read previously written value back from EEPROM address 0x05 
    i2c_start_wait(0xA0+I2C_WRITE);     // set device address and write mode
    i2c_write(0x00);                        // high byte address = 05
    i2c_write(0x05);                        // low byte address = 05
    if(i2c_rep_start(0xA0+I2C_READ)==1) puts("rep_start failed");       // set device address and read mode

    lowbyte = i2c_readNak();                    // read one byte from EEPROM
    i2c_stop(); //stop I2C bus

    printf("Byte read = %u\n",lowbyte);  //should be 117 decimal

    return 0;
}
1 Like

BADASS!!

Thanks for posting this. I’ve got the same EEPROM you did this for, as well as an I2C 8-bit digital port expander. This is cool!

No fun hearing about your compass, though. Any chance it’s got a bit stuck or something? Or do you really think it’s dead-dead?

I have to ask: The UART stuff really is that straightforward? You just set your baud, set the stdio to the port you set up, and then everything just works? (I know I used “just” twice in one paragraph… never good luck… But it sure looks like a “just” kind of thing!)

Please bear with my ignorance on this. I really am ignorant about it. I tried to pick up a serial converter on my last order, but it was out of stock. So I haven’t had a chance to play with it at all.

Thanks again for posting this.

Tom

Hate replying to my own post, but I downloaded the I2C example off your web site and read through it. Now I get how the UART stuff is working. That’s nice! It really is that straightforward.

Thanks again,

Tom

Tom:

Yes, the compass really does seem to be malfunctioning, although Gerald Coe of Devantech has kindly offered some ideas for diagnosis (but no free replacement). I’m looking at the Sparkfun modules now.

Indeed, RS232 is pretty easy and essential for many applications. There are two sticking points with RS232: the first is the required hardware inverter to provide +/- signals, but many example circuits (e.g. MAX232 or transistors) are available on the web.

The Pololu USB-serial converter provides this functionality and really is convenient as you just need 3 wires to the ATmega (with the caveat that the powered USB module can inadvertently drive an unpowered ATmega through RXD). As a bonus, you can use the USB module to power an entire project from the USB port (up to 100 mA, or higher with firmware modifications to the USB module).

The second issue concerns the poorly documented mechanisms in AVR-GCC to connect the UART put and get character routines to stdin and stdout – the global FILE declaration in my examples. This is not really needed, so I’ll post some much simpler RS232 routines in a day or so. On the other hand, the mechanism allows one to use printf() and scanf() which is a good thing.

Cheers, Jim

PS Thanks for your constructive and instructive site! Many people will find such efforts very helpful.

Sorry to hear about your compass. If you do get a Sparkfun compass, I’d be curious to hear how it works out.

I’m glad the RS232 is so straightforward. I’ve been eyeballing the Sparkfun GPS modules, so it’d be a necessity for talking to them.

I hadn’t thought about the supplied power on the USB modules! I was trying to get the RS-232 9-pin serial adapter. HMMM… Now the USB module’s looking even more attractive. Thanks for pointing that out.

I wouldn’t mind seeing the simpler RS-232 code. I like the ability to use printf() and scanf(), but I’d imagine they take up a fair bit of code space (I haven’t tried yet, so I could be dead wrong. I’d LIKE to be dead wrong.) If I do wind up interfacing to a GPS module, scanf() will probably be the route to go in any case.

Hey, glad you like the site! I finally printed out the AVR datasheet, so I’ve got a lot of forehead-slapping going on. I’m hoping to start writing new examples and functions soon.

Tom

Yep, printf() and scanf() take up about 4K bytes of code space. The floating point math routines take up another 4K or so. Surprisingly, including a single call to _delay_ms() also took up ~4K bytes! This is ridiculous, and I plan to track down why–might be the math routines, again.

Jim

That’s weird!

What are your compiler’s optimization flags set to? I know with -O0 WinAVR tends to make bloaty code. It unrolls loops, making code efficient, but large. With -O2 things tend to be a lot smaller. I can’t say about floating point or printf() but the _delay_ms() winds up not adding a whole lot.

olib-small.c compiles to something like 612 bytes with -O2 optimization, even with the delay routine in there. With -O0 it’s bigger than 2k. Only reason I’ve been dinking with this is that one of the “small” programs I wrote loaded fine in my ATMega168 based Baby-O, but failed to load in someone else’s ATMega48 based Baby-O. Changing optimization flags made all the difference in the world.

Since delays are basically loops, I’d be willing to bet unrolled loops are at least part of the problem.

Tom

As a test, I wrote the heroic program

#define F_CPU 8000000UL
#include <util/delay.h>
int main(void)
{
_delay_ms(1);  // also tried (3.2)
return 0;
}

which produces 3318 bytes of loaded code in the Orangutan, regardless of optimization level 0,1,2,3,s or value of argument.

On the other hand, if we are not compiling library code or expanding macros in this example, the size of _delay_ms() was fixed when the library was compiled. I’m using gcc 3.4.6, with switches:
avr-gcc -c -mmcu=atmega168 -I. -g -O3 -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-ahlms=main.lst main.c -o main.o

Although I’m not even vaguely familiar with run-time libraries on AVR-GCC, from the labels on the machine-code listing, it does look like some floating point stuff is loaded.

This is a puzzle but I won’t have time to fool with it for a week or so. Maybe someone at AVRFreaks would know.

Jim

Something funky’s going on…

Running in AVR Studio 4 with WinAVR here’s what I got:

Opt: Code / Data

-O0: 3318 / 8

-O1: 2550 / 8

-O2: 188 / 0

-O3: 188 / 0

-Os: 188 / 0

Here’s the output of my compile:

rm -rf delay_ms.o  delay_ms.elf dep/* delay_ms.hex delay_ms.eep
Build succeeded with 0 Warnings...
avr-gcc.exe  -mmcu=atmega168 -Wall -gdwarf-2  -O2 -fsigned-char -MD -MP -MT delay_ms.o -MF dep/delay_ms.o.d  -c  ../delay_ms.c
avr-gcc.exe -mmcu=atmega168  delay_ms.o     -o delay_ms.elf
avr-objcopy -O ihex -R .eeprom  delay_ms.elf delay_ms.hex
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 -O ihex delay_ms.elf delay_ms.eep

AVR Memory Usage
----------------
Device: atmega168

Program:     188 bytes (1.1% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)


Build succeeded with 0 Warnings...

I’ve never tried going through AVR Studio with WinAVR – I just use WinAVR “out of the box” and that is the result that I get. I made sure that I was using the supplied Makefile, making minimal changes for the CPU, programmer, etc. and regardless of optimization, a simple prog calling _delay_ms() compiles and loads to 3318 bytes.

AVR Studio makes some pretty gawdawful Makefiles, but here’s what it did for the -O2 optimization:

###############################################################################
# Makefile for the project delay_ms
###############################################################################

## General Flags
PROJECT = delay_ms
MCU = atmega168
TARGET = delay_ms.elf
CC = avr-gcc.exe

## Options common to compile, link and assembly rules
COMMON = -mmcu=$(MCU)

## Compile options common for all C compilation units.
CFLAGS = $(COMMON)
CFLAGS += -Wall -gdwarf-2  -O2 -fsigned-char
CFLAGS += -MD -MP -MT $(*F).o -MF dep/$(@F).d 

## Assembly specific flags
ASMFLAGS = $(COMMON)
ASMFLAGS += -x assembler-with-cpp -Wa,-gdwarf2

## Linker flags
LDFLAGS = $(COMMON)
LDFLAGS += 


## Intel Hex file production flags
HEX_FLASH_FLAGS = -R .eeprom

HEX_EEPROM_FLAGS = -j .eeprom
HEX_EEPROM_FLAGS += --set-section-flags=.eeprom="alloc,load"
HEX_EEPROM_FLAGS += --change-section-lma .eeprom=0


## Objects that must be built in order to link
OBJECTS = delay_ms.o 

## Objects explicitly added by the user
LINKONLYOBJECTS = 

## Build
all: $(TARGET) delay_ms.hex delay_ms.eep size

## Compile
delay_ms.o: ../delay_ms.c
	$(CC) $(INCLUDES) $(CFLAGS) -c  $<

##Link
$(TARGET): $(OBJECTS)
	 $(CC) $(LDFLAGS) $(OBJECTS) $(LINKONLYOBJECTS) $(LIBDIRS) $(LIBS) -o $(TARGET)

%.hex: $(TARGET)
	avr-objcopy -O ihex $(HEX_FLASH_FLAGS)  $< $@

%.eep: $(TARGET)
	avr-objcopy $(HEX_EEPROM_FLAGS) -O ihex $< $@

%.lss: $(TARGET)
	avr-objdump -h -S $< > $@

size: ${TARGET}
	@echo
	@avr-size -C --mcu=${MCU} ${TARGET}

## Clean target
.PHONY: clean
clean:
	-rm -rf $(OBJECTS) delay_ms.elf dep/* delay_ms.hex delay_ms.eep

## Other dependencies
-include $(shell mkdir dep 2>/dev/null) $(wildcard dep/*)

There’s always a chance some other flag that’s being passed to gcc is paring it down.

Tom

I installed AVR Studio 4 and upgraded to SP4. I then recompiled the _delay_ms() example and bingo! -O3 results in 188 bytes of code.

I conclude that there is something wrong with the stock Makefile that I’m using, and that the optimization flag value is not being passed correctly to gcc.

Jim

Hot dang! That’s good news to hear. Strange, though, that WinAVR isn’t picking up that flag on its own.

Any word on your compass?

Tom