NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Access Serial EEPROM using I2C on the Nerdkit

March 18, 2011
by Noter
Noter's Avatar

This is an example of using I2C to access a serial eeprom with the nerdkit. The eeprom used is the 24LC256 with a capacity of 32k bytes. I'm not sure yet of a real project where I will use it but maybe archiving data on a weather station so it can be uploaded from time to time. Or perhaps a min/max thermomitor that saves temp every minute and later can be uploaded for analysis. I'd like to know about your projects that will make use of external eeproms.

Anyway, there are 3 components to download -

  • 24LC256_eeprom.c is the test program.
  • TWI.c is the driver for the I2C interface.
  • TWI.h is the include file for the driver.

If you have the earlier versions of TWI.c & TWI.h then replace them with these updated files. Several enhancements have been completed to make use easier.

About the program: 24LC256_eeprom.c uses pointers and structures quite a bit. If they are new to you, it's a good time to become familiar with both as they are a two of the most powerful features of the C language. There are many tutorials on the web on pointers and structures and I will be glad to answer questions too. For a start, here are links to my favorite explainations of structures, pointers, typedefs, and cast operators. The program will make much more sense once you know a little about these.

Pointers, Structures, Typedef, Cast Operator

Source code will be posted next and in leiu of a schematic, here is my nerdkit setup - Nerdkit setup

March 18, 2011
by Noter
Noter's Avatar
//
// 24LC256_eeprom.c
// 
// Demonstrate writing/reading serial eeprom via TWI/I2C
//

#include <avr/interrupt.h>
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "../libnerdkits/lcd.h"
#include "../libNoter/TWI.h"

// Define format of eeprom data block.
typedef struct {
    bool        need_initalization;
    char        author[6];
    uint16_t    read_count;
    uint8_t     brightness;
    float       version;
} EEPROM_DATA;

// LCD stream file - enable printf in functions outside of main()
FILE lcd_stream;

// prototype local functions
void eeprom_init();
EEPROM_DATA read_eeprom(uint16_t memory_address);
void write_eeprom(uint16_t memory_address, EEPROM_DATA *w_data);
void show_eeprom_data(EEPROM_DATA *data);
void handle_TWI_result(uint8_t return_code);

// -----------------------------------------------------------------
int main() {
    // Initialize the lcd
    lcd_init();
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE); 
    lcd_clear_and_home();

    // initialize eeprom and TWI/I2C
    eeprom_init();

    // specify 7 bit device address of eeprom chip
    #define EEPROM_DEVICE_ID 0b1010000
    fprintf_P(&lcd_stream, PSTR("24LC256 ADDR: %02X"), EEPROM_DEVICE_ID);

    // specify eeprom memory address for data storage
    #define EEPROM_DATA_ADDRESS 0x0000

    // create local copy of eeprom data
    EEPROM_DATA e_data;

    // read eeprom contents at location 0000
    e_data = read_eeprom(0);

    // show what we read from the eeprom -
    // note: the very first read on a new eeprom
    // will show uninitalized data
    show_eeprom_data(&e_data);

    // process data
    if(e_data.need_initalization){
        // set all data item values if first time
        e_data.need_initalization = false;
        strcpy_P(e_data.author, PSTR("Noter"));
        e_data.read_count = 1;
        e_data.brightness = 0x33;
        e_data.version = 1.01;
    } else {
        // check contents against the values written when initializing
        if((e_data.version != 1.01)||
            (strcmp_P(e_data.author, PSTR("Noter")) !=0)||
            (e_data.brightness != 0x33)){
                    lcd_line_four();
                    fprintf_P(&lcd_stream, PSTR("DATA ERROR - ")); 
                    while(true);    // and freeze
                } else {
                    // increment read_count 
                    e_data.read_count += 1;
                }
    }

    // write data to eeprom memory at location 0000
    write_eeprom(0, &e_data);

    // and show the read count
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("READ COUNT = %d"), e_data.read_count);

    // done
    while(true);
}
// -----------------------------------------------------------------

// Define eeprom commands according to 24LC256 datasheet
typedef struct {
    uint8_t     high_byte;
    uint8_t     low_byte;
    EEPROM_DATA eeprom_data;
} WRITE_EEPROM;

typedef struct {
    uint8_t     high_byte;
    uint8_t     low_byte;
} SET_EEPROM_ADDRESS;

typedef struct {
    EEPROM_DATA eeprom_data;
} READ_EEPROM;

// Create structure pointers for the TWI/I2C buffer
WRITE_EEPROM            *p_write_eeprom;
SET_EEPROM_ADDRESS      *p_set_eeprom_address;
READ_EEPROM             *p_read_eeprom;

// Create TWI/I2C buffer, size to largest command
char    TWI_buffer[sizeof(WRITE_EEPROM)];

void eeprom_init(){
    // Specify startup parameters for the TWI/I2C driver
    TWI_init(   F_CPU,                      // clock frequency
                100000L,                    // desired TWI/IC2 bitrate
                TWI_buffer,                 // pointer to comm buffer
                sizeof(TWI_buffer),         // size of comm buffer
                &handle_TWI_result          // pointer to callback function
                );

    // Enable interrupts
    sei();

    // Set our structure pointers to the TWI/I2C buffer
    p_write_eeprom = (WRITE_EEPROM *)TWI_buffer;
    p_set_eeprom_address = (SET_EEPROM_ADDRESS *)TWI_buffer;
    p_read_eeprom = (READ_EEPROM *)TWI_buffer;

}

EEPROM_DATA read_eeprom(uint16_t memory_address){
    // send 'set memory address' command to eeprom and then read data
    while(TWI_busy);
    p_set_eeprom_address->high_byte = memory_address >> 8;
    p_set_eeprom_address->low_byte = memory_address & 0x0F;
    TWI_master_start_write_then_read(   EEPROM_DEVICE_ID,               // device address of eeprom chip
                                        sizeof(SET_EEPROM_ADDRESS),     // number of bytes to write
                                        sizeof(EEPROM_DATA)             // number of bytes to read
                                        );

    // nothing else to do - wait for the data
    while(TWI_busy);
    // return the data
    return(p_read_eeprom->eeprom_data);
}

// write eeprom - note: page boundaries are not considered in this example
void write_eeprom(uint16_t memory_address, EEPROM_DATA *w_data){
    while(TWI_busy);
    p_write_eeprom->high_byte = EEPROM_DATA_ADDRESS >> 8;
    p_write_eeprom->low_byte = EEPROM_DATA_ADDRESS & 0x0F;
    p_write_eeprom->eeprom_data = *w_data;
    TWI_master_start_write(     EEPROM_DEVICE_ID,       // device address of eeprom chip
                                sizeof(WRITE_EEPROM)    // number of bytes to write
                                );

}

// optional callback function for TWI/I2C driver
void handle_TWI_result(uint8_t return_code){
    if(return_code!=TWI_success){
        lcd_line_four();
        fprintf_P(&lcd_stream, PSTR("I2C ERROR - %02X"), return_code); 
    }
}

// format and display eeprom data on lcd
void show_eeprom_data(EEPROM_DATA *data){
    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR("%1d %s %04X %02X %4.2f"), 
        data->need_initalization,
        data->author,
        data->read_count,
        data->brightness,
        data->version       
        ); 
}
March 18, 2011
by Noter
Noter's Avatar
//
// TWI.h
//
//  v1.1  3/18/2011
// 
// Define status codes, buffers, and various variables used
// by TWI.c to communicate with other devices via the TWI/I2c buss.
//
#define TWI_TWSR_status_mask 0xF8

// Status codes for TWI Master Mode (TWSR)
#define TWI_MM_start_sent_x08 0x08
#define TWI_MM_repeated_start_sent_x10 0x10
#define TWI_MM_arbitration_lost_x38 0x38

// Status codes for TWI Master Transmitter Mode 
#define TWI_MT_SLA_W_sent_ack_received_x18 0x18
#define TWI_MT_SLA_W_sent_nack_received_x20 0x20
#define TWI_MT_data_sent_ack_received_x28 0x28
#define TWI_MT_data_sent_nack_received_x30 0x30

// Status codes for TWI Master Receiver Mode
#define TWI_MR_SLA_R_sent_ack_received_x40 0x40
#define TWI_MR_SLA_R_sent_nack_received_x48 0x48
#define TWI_MR_data_received_ack_returned_x50 0x50
#define TWI_MR_data_received_nack_returned_x58 0x58

// Status codes for TWI Slave Receiver Mode
#define TWI_SR_SLA_W_received_ack_sent_x60 0x60
#define TWI_SR_SLA_W_received_after_arbitration_lost_ack_sent_x68 0x68
#define TWI_SR_general_call_received_ack_sent_x70 0x70
#define TWI_SR_general_call_received_after_arbitration_lost_ack_sent_x78 0x78
#define TWI_SR_SLA_W_data_received_ack_sent_x80 0x80
#define TWI_SR_SLA_W_data_received_nack_sent_x88 0x88
#define TWI_SR_general_call_data_received_ack_sent_x90 0x90
#define TWI_SR_general_call_data_received_nack_sent_x98 0x98
#define TWI_SR_stop_or_repeated_start_received_xA0 0xA0

// Status codes for TWI Slave Transmitter Mode
#define TWI_ST_SLA_R_received_ack_sent_xA8 0xA8
#define TWI_ST_SLA_R_received_after_arbitration_lost_ack_sent_x80 0xB0
#define TWI_ST_byte_sent_ack_received_x88 0xB8
#define TWI_ST_byte_sent_nack_received_xC0 0xC0
#define TWI_ST_last_byte_sent_ack_received_xC8 0xC8

// successful return code
#define TWI_success 0x00

// buffers and variables
volatile uint16_t TWI_buffer_max;
volatile char*  p_TWI_buffer;
volatile uint16_t TWI_buffer_pos;
volatile uint8_t TWI_buffer_len;
volatile uint16_t TWI_read_bytes;
volatile uint16_t TWI_write_bytes;
volatile uint16_t TWI_bytes_returned;

volatile uint8_t TWI_target_slave_addr;

// control transition between write followed by read
//volatile uint8_t TWI_receive_status;
//#define TWI_RECEIVE_NACKED 0x01
//#define TWI_RECEIVE_ACKED 0x02

// keep track of current state
volatile uint8_t TWI_status;
#define TWI_WRITE_STATE 0x01
#define TWI_READ_STATE 0x02

// call types
volatile uint8_t TWI_master_state;
#define TWI_OP_WRITE_ONLY 0x01
#define TWI_OP_READ_ONLY 0x02
#define TWI_OP_WRITE_THEN_READ 0x03

// control variables
volatile uint8_t TWI_operation;
volatile uint8_t TWI_busy;
volatile uint8_t TWI_error;

// various states of hardware that will be set in response to interrupts
#define TWI_ENABLE  _BV(TWEN) | _BV(TWINT) | _BV(TWIE)
//
#define TWI_ACK     _BV(TWEA)  |    TWI_ENABLE
#define TWI_NACK                    TWI_ENABLE
#define TWI_START   _BV(TWSTA) |    TWI_ENABLE
#define TWI_STOP    _BV(TWSTO) |    TWI_ENABLE

// define callback function
void (*TWI_return_result)(volatile uint8_t TWI_return_code);

// define supported funcitons
void TWI_init(long cpu_freq, long bit_rate, char* buffer, uint16_t max, void (*callback)(volatile uint8_t TWI_return_code));
void TWI_master_start_write(uint8_t slave_addr, uint16_t write_bytes);
void TWI_master_start_read(uint8_t slave_addr, uint16_t read_bytes);
void TWI_master_start_write_then_read(uint8_t slave_addr, uint16_t write_bytes, uint16_t read_bytes);
void TWI_enable_slave_mode(uint8_t my_slave_addr, uint8_t enable_general_call, void (*TWI_return_fn)(uint8_t TWI_return_value));
March 18, 2011
by Noter
Noter's Avatar
// TWI.c
// 
//  v1.1  3/18/2011
//
// Object provides TWI/I2C communication functions.
//
// There are some known problems with TWI on ATmel AVR's ...
//
//      http://www.robotroom.com/Atmel-AVR-TWI-I2C-Multi-Master-Problem.html
//

#ifdef __AVR_ATmega328P__
#  define SIG_OVERFLOW0 TIMER0_OVF_vect
#  define SIG_OVERFLOW1 TIMER1_OVF_vect
#  define SIG_OVERFLOW2 TIMER2_OVF_vect
#include "../libnerdkits/io_328p.h"
#endif

#include <avr/interrupt.h>
#include "TWI.h"
#include <avr/io.h>

// initialize the component
void TWI_init(long cpu_freq, long bit_rate, char* buffer, uint16_t max, void (*callback)(volatile uint8_t TWI_return_code)){
    TWI_return_result = callback;
    p_TWI_buffer = buffer;
    TWI_buffer_max = max;
    TWBR = ((cpu_freq/bit_rate)-16)/2; // bit rate register 
    TWSR = 0; // prescaler
    TWI_busy=0;
}

// master write to slave
void TWI_master_start_write(uint8_t slave_addr, uint16_t write_bytes){
    TWI_busy=1;
    if(write_bytes>TWI_buffer_max){
        TWI_write_bytes=TWI_buffer_max;
    }else{
        TWI_write_bytes=write_bytes;
    }
    TWI_operation=TWI_OP_WRITE_ONLY;
    TWI_master_state = TWI_WRITE_STATE;
    TWI_target_slave_addr = slave_addr;
    TWCR = TWI_START; // start TWI master mode
}

// master read from slave
void TWI_master_start_read(uint8_t slave_addr, uint16_t read_bytes){
    TWI_busy=1;
    if(read_bytes>TWI_buffer_max){
        TWI_read_bytes=TWI_buffer_max;
    }else{
        TWI_read_bytes=read_bytes;
    }
    TWI_operation=TWI_OP_READ_ONLY;
    TWI_master_state = TWI_READ_STATE;
    TWI_target_slave_addr = slave_addr;
    TWCR = TWI_START; // start TWI master mode
}

// master write then read without releasing buss between
void TWI_master_start_write_then_read(uint8_t slave_addr, uint16_t write_bytes, uint16_t read_bytes){
    TWI_busy=1;
    if(write_bytes>TWI_buffer_max){
        TWI_write_bytes=TWI_buffer_max;
    }else{
        TWI_write_bytes=write_bytes;
    }
    if(read_bytes>TWI_buffer_max){
        TWI_read_bytes=TWI_buffer_max;
    }else{
        TWI_read_bytes=read_bytes;
    }
    TWI_operation=TWI_OP_WRITE_THEN_READ;
    TWI_master_state = TWI_WRITE_STATE;
    TWI_target_slave_addr = slave_addr;
    TWCR = TWI_START; // start TWI master mode
}

// enable slave and start receiving messages
void TWI_enable_slave_mode(uint8_t my_slave_addr, uint8_t enable_general_call, void (*TWI_return_fn)(uint8_t TWI_return_value)){
    TWI_return_result = TWI_return_fn;
    TWAR = (my_slave_addr<<1); // set my slave addr
    if(enable_general_call>0){
        TWAR |= _BV(TWGCE); // enable general call receipts
    }
    TWCR = TWI_ACK; // enable ACK on SLA_W/SLA_R
}

// Routine to service interrupts from the TWI hardware.
// The most important thing is that this routine runs fast and returns control
// to the hardware asap. So, when results are ready and the callback is made to
// your application, be sure you return as quickly as possible. Remove significant
// work from the callback and instead perform that work in your main execution loop.
//
// See pages 229, 232, 235, and 238 of the ATmega328 datasheed for detailed 
// explaination of the logic below.
SIGNAL(TWI_vect){
    TWI_status = TWSR & TWI_TWSR_status_mask;
    switch(TWI_status){

        case TWI_MM_repeated_start_sent_x10:
        case TWI_MM_start_sent_x08:
            switch(TWI_master_state){
                case TWI_WRITE_STATE:
                    TWI_buffer_pos=0; // point to 1st byte
                    TWDR = (TWI_target_slave_addr<<1) | 0x00; // set SLA_W
                    break;
                case TWI_READ_STATE:
                    TWI_buffer_pos=0; // point to first byte
                    TWDR = (TWI_target_slave_addr<<1) | 0x01; // set SLA_R
                    break;
            }
            TWCR = TWI_ACK; // transmit
            break;

        case TWI_MT_SLA_W_sent_ack_received_x18:
        case TWI_MT_data_sent_ack_received_x28:
            if(TWI_buffer_pos==TWI_write_bytes){
                if(TWI_operation==TWI_OP_WRITE_THEN_READ){
                    TWI_master_state=TWI_READ_STATE; // now read from slave
                    TWCR = TWI_START; // transmit repeated start
                }else{
                    if(TWI_return_result){
                        (*TWI_return_result)(TWI_success);// callback with results
                    }
                    TWCR = TWI_STOP; // release the buss
                    while(TWCR & (1<<TWSTO)); // wait for it
                    TWI_busy=0;
                }
            }else{
                TWDR = p_TWI_buffer[TWI_buffer_pos++]; // load data
                TWCR = TWI_ENABLE; // transmit
            }
            break;

        case TWI_MR_data_received_ack_returned_x50:
            p_TWI_buffer[TWI_buffer_pos++]=TWDR; // save byte
        case TWI_MR_SLA_R_sent_ack_received_x40: 
            if(TWI_buffer_pos==(TWI_read_bytes-1)){
                TWCR = TWI_NACK; // get last byte then nack
            }else{
                TWCR = TWI_ACK; // get next byte then ack
            }
            break;

        case TWI_MR_data_received_nack_returned_x58:            
            p_TWI_buffer[TWI_buffer_pos++]=TWDR; // save byte
            if(TWI_return_result){
                (*TWI_return_result)(TWI_success);// callback with results
            }
            TWCR = TWI_STOP; // release the buss
            while(TWCR & (1<<TWSTO)); // wait for it
            TWI_busy=0;
            break;

        case TWI_SR_SLA_W_received_ack_sent_x60:
        case TWI_SR_SLA_W_received_after_arbitration_lost_ack_sent_x68:
        case TWI_SR_general_call_received_ack_sent_x70:
        case TWI_SR_general_call_received_after_arbitration_lost_ack_sent_x78:
            TWI_buffer_pos=0; // point to start of input buffer
            TWCR = TWI_ACK;
            break;

        case TWI_SR_SLA_W_data_received_ack_sent_x80:
        case TWI_SR_general_call_data_received_ack_sent_x90:
            if(TWI_buffer_pos<TWI_buffer_max){
                p_TWI_buffer[TWI_buffer_pos++]=TWDR; // store data
            }
            TWCR = TWI_ACK; 
            break;

        case TWI_SR_stop_or_repeated_start_received_xA0:
            TWI_buffer_len=TWI_buffer_pos; // bytes returned
            if(TWI_return_result){
                (*TWI_return_result)(TWI_success); // callback with results
            }
            TWCR = TWI_ACK; 
            break;

        case TWI_ST_SLA_R_received_after_arbitration_lost_ack_sent_x80:
        case TWI_ST_SLA_R_received_ack_sent_xA8:
            TWI_buffer_pos=0; // point to start of input buffer
        case TWI_ST_byte_sent_ack_received_x88:
            if(TWI_buffer_pos<TWI_buffer_max){
                TWDR = p_TWI_buffer[TWI_buffer_pos++]; // load data
            }
        case TWI_SR_SLA_W_data_received_nack_sent_x88:
        case TWI_SR_general_call_data_received_nack_sent_x98:
        case TWI_ST_byte_sent_nack_received_xC0:
        case TWI_ST_last_byte_sent_ack_received_xC8:
            TWCR = TWI_ACK; 
            break;

        case TWI_MT_SLA_W_sent_nack_received_x20:
        case TWI_MT_data_sent_nack_received_x30:
        case TWI_MR_SLA_R_sent_nack_received_x48:
            if(TWI_return_result){
                (*TWI_return_result)(TWI_status);// callback with status
            }
        case TWI_MM_arbitration_lost_x38:
        default:
            TWCR=TWI_STOP; 
            while(TWCR & (1<<TWSTO)); // wait for it
            TWCR=TWI_START; // try again
            break;
    }

}
March 18, 2011
by Ralphxyz
Ralphxyz's Avatar

Noter, thank you so much XOXOXOXO, oops sorry didn't mean to slobber, but this is really going to help me.

I immediately need the EEPROM for my weather station for data storage. I also need to store a look up table for my Humidity sensor. I "might" be able to fit the humidity sensor look up table into the mcu's 512 Byte internal EEPROM but definitely will need the external EEPROM for data storage.

When I started looking at using a EEPROM I realized that I have absolutely no idea of how to do memory management using a mcu.

Using my computer the memory is managed by the OS but since there is no OS on a mcu the memory management has to be done in code.

I will go through your code in depth. I have been studying this How To blog. I think this is one of Rick's references or at least the blog is.

I almost can comprehend how the Array is being used but I do not see how a string of data would be addressed or retrieved.

So for my weather station I will have these data points: (I've never laid this out before so definitely subject to change)

Year:Month:Day:Hour:Minute (ymdhm)       Date/Time Stamp
Wind Speed                 (wndspd)      Average over past 15 minutes
Wind Speed Max             (wndspdmx)    Over past 15 minutes
WindDirection              (wnddir)      Average over past 15 minutes
Humidity                   (humdy)       Average over past 15 minutes
Barometric Pressure        (barop)       Average over past 15 minutes

So if I was doing this on my computer I would have a .CSV file that would look like this:

Date,Wind Speed,Max Wind Speed,Direction,Humidity,Pressure
ymdhm,wnsspd,wndspdmax,wnddir,humdy,barop  
ymdhm,wnsspd,wndspdmax,wnddir,humdy,barop
ymdhm,wnsspd,wndspdmax,wnddir,humdy,barop
ymdhm,wnsspd,wndspdmax,wnddir,humdy,barop
ymdhm,wnsspd,wndspdmax,wnddir,humdy,barop

I will be using ZigBee (which I do not know) to download the entire data drop or just asking for the Max wind speed or probable asking for the "current conditions" (which might come from the sensors directly instead of reading the EEPROM).

The window probable could be one hour instead of 15 minutes but we will see.

Like I said I haven't laid this out before, but just had it floating around in my head, so this is a good exercise just to see it out of my mind, wrong term I been going out of my mind trying to get Rick's DS3232 I2C (TWI) code working, this is actually starting to make sense.

Thanks for the Pointers, Structures, Typedef, Cast Operator links, I am familiar with all of these but as I am not a "programmer" I have rarely ever actually used them.

So I will definitely be needing your help, and everybody else's contributions here in/on the Nerdkit's forum.

Oh just to get it down I am also looking at "Power Harvesting" from RF to power my Nerdkit. I've got some Boost chips from TI on sample so I'll be playing around with that in another thread.

I have so many ideas going that I really need you and Rick and others to post working projects so that I can put them together. I really do not have the ability nor time to work all of these things out from scratch.

I am so thankful to/for the Nerdkit guys for putting the Nerdkit and this forum together. I've had my Nerdkit for a little more then a year and I am so amazed at what I have learned so far, I probable have not accomplished a whole lot but I certainly have learned a lot. The Nerdkit really enables me to learn, now isn't that amazing. If they ever need to change their name they could call it the AmazeKit.

Thanks,

Ralph

March 18, 2011
by Noter
Noter's Avatar

Wow, you do have a lot going on! I don't know how you keep it all straight in your mind and manage to any of it done.

I'm also interested in the ZigBee but don't know when I'll get going on it. I'm sure it needs a I2C interface if there isn't already one. And your RF Power Harvesting sounds really interesting too.

Storing weather data for retrieval later is a perfect application of the external eeprom. My sample program doesn't provide for spanning pages in the eeprom. To do that while writing fairly small chunks will require another function to write the eeprom randomly a byte at a time. Here's that function and it's pieces ready for integration into the sample program:

WRITE_EEPROM_BYTE       *p_write_eeprom_byte;
...
typedef struct {
    uint8_t     high_byte;
    uint8_t     low_byte;
    uint8_t     data_byte;
} WRITE_EEPROM_BYTE;
...
p_write_eeprom_byte = (WRITE_EEPROM_BYTE *)TWI_buffer;
...
// write eeprom byte at a time - can cross page boundaries
void write_eeprom_bytes(uint16_t memory_address, uint8_t *data, uint16_t size){
    uint16_t i;
    for(i=0;i<size;i++)
    {
        while(TWI_busy);
        p_write_eeprom_byte->high_byte = (memory_address + i) >> 8;
        p_write_eeprom_byte->low_byte = (memory_address + i) & 0xFF;
        p_write_eeprom_byte->data_byte = data[i];
        TWI_master_start_write( EEPROM_DEVICE_ID,       // device address of eeprom chip
                                3                       // 2 address bytes & 1 data byte
                                );
    }
}
...
write_eeprom_bytes(EEPROM_DATA_ADDRESS, (uint8_t *)&e_data, sizeof(EEPROM_DATA));

Also notice that the bitmask for the low byte above is 0xFF. The other low byte bitmasks in the sample program need to be changed from 0x0F to 0xFF for it to work with a non zero address.

March 18, 2011
by Ralphxyz
Ralphxyz's Avatar

Actually my Water Curtain project is probable going to be up and running before the Weather Station, it also will use a external EEPROM.

I will feed the EEPROM from a UART connection to a PC which will have a script (Python) running that will parse a file and feed it to the mcu then to the EEPROM the mcu will read the EEPROM and output what it sees to a Shift Register(s) that controls the solenoid valves.

So starting with a "Fill" and then my letter H and O, this would come from a file on the PC using UART:

Fill
    11111111,37.5
    11111111,75
    11111111,75
    11111111,75
    11111111,75
    11111111,75
    11111111,37.5
H   
    00111100,37.5
    00111100,75
    00111100,75
    00000000,75   
    00111100,75
    00111100,75
    00111100,37.5

O
    11100111,60
    11011011,35
    10111101,45
    10111101,45
    01111110,95
    01111110,45
    10111101,45
    10111101,35
    11011011,35
    11100111,60

With a Read from the EEPROM of Fill,H,Fill,O,Fill should give me a solid curtain of water followed by the letter H then a solid curtain of water then the letter O followed by a curtain of water.

Basically I would have a binary number and a delay_ms() factor. I would probable need to break it up into distinguished components such as a Fill component, a H component and a O component, so that I could show the H for a certain length of time. Currently I am thinking I will have a 500ms window of display time. That is just a guess for how long it takes a drop of water to fall 60". Once I get the water flowing (dripping) I'll know better what the display window is.

This flow has to write into the EEPROM and then read out on demand either serially or by component call.

Of course the whole alphabet will be needed plus there could be some custom figures (dolphins, smiley face etc.) added.

It would be nice to be able to enter "HELLO" on a keyboard and have it output to the curtain.

Ultimately I want to be able to make a sketch on a tablet and have the image appear in the curtain.

Now if I can get your code for I2C EEPROM programming to work I have a lot of work to do.

Ralph

March 18, 2011
by Noter
Noter's Avatar

What eeprom are you planning to use?

March 18, 2011
by Ralphxyz
Ralphxyz's Avatar

The ZigBee modules I have (ATmega128RFA1-EK1, DIGI XSTICK and DIGI ZigBee Modules) use UART for communications from a microprocessor or sensor.

Believe me I cannot keep it straight in my mind, besides being afflicted with the AGE virus I just never was that sharp to begin with. I tend to have to redo things a number of times. I have always had a curious mind (people have always wondered about that) but never had any education or more so any encouragement and guidance.

Luckily I am on unemployment, for the first time in 50 years, so I do have time (when I am not diligently sending out my resume).

Ralph

March 18, 2011
by Ralphxyz
Ralphxyz's Avatar

I have two EEPROMs, a ATmel AT24C1024B (1 meg) and a Microchip 24AA128 (128KB).

I'll get some 24LC256 to match you if needed.

I have had trouble understanding the Device Addressing, in fact I was going to ask you about that.

In your code you have:

// specify 7 bit device address of eeprom chip
#define EEPROM_DEVICE_ID 0b1010000
fprintf_P(&lcd_stream, PSTR("24LC256 ADDR: %02X"), EEPROM_DEVICE_ID);

And in you LCD shot you have on line one:

Darn, figured it out as I was asking and checking my numbers, you are showing the hex value (50) on the LCD not decimal (80).

Rick uses 8 bit addressing and most of my devices reference a 8 bit number, A0 (A1, A2 etc) for EEPROMs.

I do not have a good handle on device addressing and then with the EEPROM you get memory addressing and my head really starts to spin. Especially when I was playing around with the 1 meg EEPROM.

Ralph

March 18, 2011
by Noter
Noter's Avatar

I2C devices have 7 bit addresses. This is because the 8th bit in the TWI or I2C slave address byte is the read/write flag. If you were writing your own I2C code, you would take the 7 bit device address and shift it left one bit and then the rightmost bit would be the read/write flag. After that's done it's harder to recognize the address if you see it in hex because of the shift but the device address is always 7 bits, no alternatives to that.

Most devices will hardcode most of the address bits and then let you jump a few pins for the remainder of the address bits. They do that so you can have more than one of that type of device on the buss. The 24LC256 is hardcoded 1010xxx where the x's are pins I can ground or Vcc for 0's or 1's. I grounded all of them and that's where the 0x50 comes from.

Tomorrow I'll take a look at the datasheets on your eeproms and see if they are compatible with the sample program.

Memory addressing is easier, starts at zero and goes to the max. Paging is more difficult but we won't deal with that if we don't have to. Probably your eeproms have byte mode like the 24LC256 which is slower than page mode but I think still fast enough to meet your needs.

March 19, 2011
by Noter
Noter's Avatar

The demo program will work with the Microchip 24AA128 (128KB) so start with that one. A few modifications to the addressing will be necessary to use the ATmel AT24C1024B (1 meg) but it is certainly doable. After you get it working with the 128k byte chip we'll move on to the 1 meg.

March 19, 2011
by Noter
Noter's Avatar

Oops, I mean the 128k bit chip - 16k bytes. I'd better get another cup of coffee before I do any programming this morning. ;-)

March 19, 2011
by Ralphxyz
Ralphxyz's Avatar

Thanks Noter, I am setting up a new Nerdkits bread board so I will match your setup.

Some of your comments reference a 328 but it appears this code is for a 168, correct?

Ralph

March 19, 2011
by Noter
Noter's Avatar

Yes, I used the basic Nerdkit for this project with the 168 and a 14754600 crystal. You can run on a 328 if you wish, the 168 and 328 are functionally the same except the 328 has twice the amount of flash and eeprom.

March 19, 2011
by Noter
Noter's Avatar

I've been studying Eagle and I completed the schematic this evening ... please let me know if you spot any errors in the hookups shown here.

NerdKit Schematic

March 20, 2011
by Ralphxyz
Ralphxyz's Avatar

Man, I have tried to get to learning Eagle about three times this past year but have not gotten very far.

Please post your .sch file as that would be a good start for other projects.

In fact I really like working from a schematic, it is just so much clearer that working from my mind.

Shouldn't that be a 10k resistor on pin 1 (reset)?

Ralph

March 20, 2011
by Noter
Noter's Avatar

Eagle is a tough one to get a handle on. I continously have to reference tutorials when I want to do anything but the simplest of procedures. Like everything else, only practice will make it easier. I zipped up my Eagle lib for you too because I've made a couple of devices like the LCD display that I couldn't find anywhere else and have modified a few others. One of the 1st things I did with eagle was modify an existing ATmega328 symbol to use the pin names from the datasheet because that is what I was used to seeing. And it took a while the first time too. eagle sch and lbr download.

The resistor value on pin 1 for the reset switch is not critical. Just about anything that will not draw too much current when the switch is pressed will be fine. I have a bunch of 1k's so that's why I used it. Using Ohm's Law, the 1k draws 5ma when the switch is closed vs .5ma if using a 10k resistor.

March 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Noter, I made up a new breadboard and tested it so it runs programs and loads programs.

I setup a eeprom128 folder with the 24LC256_eeprom.c file renamed eeprom128.c.

I setup a libNoter folder in the Code folder and added twi.h and twi.c.

I used a generic Nerdkit MakeFile.

When I try to compile the program I get this:

miniMac:eeprom128 Me$ make
make -C ../libnerdkits
make[1]: Nothing to be done for `all'.
avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -o eeprom128.o eeprom128.c ../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o
/var/folders/gC/gCP9dLxxHKqW1I8ZoJmaME+++TI/-Tmp-//ccDayghQ.o: In function `write_eeprom':
/developer/nerdkits/code/eeprom128/eeprom128.c:169: undefined reference to `TWI_master_start_write'
/var/folders/gC/gCP9dLxxHKqW1I8ZoJmaME+++TI/-Tmp-//ccDayghQ.o: In function `read_eeprom':
/developer/nerdkits/code/eeprom128/eeprom128.c:152: undefined reference to `TWI_master_start_write_then_read'
/var/folders/gC/gCP9dLxxHKqW1I8ZoJmaME+++TI/-Tmp-//ccDayghQ.o: In function `eeprom_init':
/developer/nerdkits/code/eeprom128/eeprom128.c:130: undefined reference to `TWI_init'
make: *** [eeprom128.hex] Error 1
miniMac:eeprom128 Me$

I have never seen /var/folders/gC/gCP9dLxxHKqW1I8ZoJmaME+++TI/

It does exist:

/var/folders/gC/gCP9dLxxHKqW1I8ZoJmaME+++TI/

miniMac:folders Me$ cd /var/folders/gC/gCP9dLxxHKqW1I8ZoJmaME+++TI/
miniMac:gCP9dLxxHKqW1I8ZoJmaME+++TI Me$ ls
-Caches-        Cleanup At Startup  TemporaryItems
-Tmp-           DocSet_Downloads

I cannot view -Tmp-

Do I need to put a reference to twi.h in the MakeFile?

I'll try tomorrow on my Windows 7 x64 machine.

Ralph

Ralph

March 21, 2011
by Noter
Noter's Avatar

Hi Ralph,

Yes, you need to reference the TWI.h file in libNoter in the same way you reference include files that are in libnerdkits. Likewise on the object files, they will have to be included in the linker options to be found and eliminate the undefined references.

Or, you could copy the TWI files to libnerdkits directory and then not have to change your make file.

What was the resolution to the black bar problem you were having on the new setup?

Paul

March 21, 2011
by Noter
Noter's Avatar

But if you do move the files to libnerdkits, you'll need to update the include statements in the program to reference them there.

Here's the make file I use ... It should work for you too. Copy it to the 24LC256_eeprom directory.

MCU=atmega168

F_CPU=14745600UL

all: $(patsubst %.c,%.hex,$(wildcard *.c))

program: $(patsubst %.c,%.pgm,$(wildcard *.c))

.SECONDARY:

%.o: %.c ../libNoter/libNoter.a ../libnerdkits/libNerdKit.a
    avr-gcc -g -Os -fno-jump-tables -Wall -mmcu=$(MCU) -mcall-prologues \
        -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm \
        $< -o $@ ../libNoter/libNoter.a ../libnerdkits/libNerdKit.a\
        -DF_CPU=$(F_CPU)

%.ass: %.o
    avr-objdump -S -d $< > $@

%.hex: %.o %.ass
    avr-objcopy -j .text -O ihex $< $@

%.pgm: %.hex

    avrdude -C ../libNoter/avrdude.rs -c Noter -p $(MCU) -b 115200 -P COM2 \
        -U flash:w:$<:a

clean: 
    -rm *.o *.hex *.ass
March 21, 2011
by Noter
Noter's Avatar

Heck, I forgot that I changed device def's in avrdude.rs so I guess my makefile won't work for you after all without changes - sorry about that.

March 22, 2011
by Ralphxyz
Ralphxyz's Avatar

Darn, now you are complicating it for me forcing me to think. Instead of just being able to do step1, step2, step3 then RUN.

I prefer step1 step2 etc.

I have the reference to twi.h in the libNoter library (I have the libNoter library).

No twi.o file is being made so I do not know what changes I might make to the MakeFile. I will go over your MakeFile.

When I try my Windows computer I can use AVR Studio and build a new Makefile automatically.

My "resolution" to the Black Bar problem was to put together a new breadboard. I needed more space than the Nerdkit's breadboard provides so with my handy dandy Dremel I modified some other breadboard pieces to put this together!

The other components are for my Weather Station, all I2C modules. There is a Compass for Wind Direction, a DS3232 RTC (Real Time Clock) and a Barometric Pressure sensor. Also a Humidity sensor that uses ADC, and of course my modified Dremel Anemometer for Wind Speed will be added, that uses PB0 with a timmer interrupt for wind speed. Oh the Shift Register for the Water Curtain Project is also there. Also there will be a ZigBee module and hopefully a "RF Power Harvesting" module to power all of this, otherwise I'll need to add a Generator and power management modules or a Solar Cell with power management.

The expandable breadboard is rather neat (if I may say so myself) as it is expandable I can easily put more rows to the top or the bottom. Other commercial expandable breadboards, I have only allow one direction expansion either to the top or bottom and some of them only allow adding one more row. With my modifications I can add any number of rows to the top or bottom. Obviously I will need more rows.

I am using my tablet with XP to view my oscilloscope (attempting to that is), so I'll use that for testing your I2C-TWI code instead of using my Mac.

It helps to have common environments when asking/answering questions.

Ralph

March 22, 2011
by Noter
Noter's Avatar

As you see, my makefile scans the directory for all files with c in the extension and then builds them. So to keep things simple, be sure you only have one .c file in your program directory. The reason I like it that way is I don't have to edit the makefile when I copy it to a new directory where the program has a different name.

My lib build makefile is similar but instead of making the hex file it inserts the .o into a lib file. Again, it scans for c files and does them all. If I edit one of the c files in the lib, it is the only one to be recompiled and updated in the library. But if a clean is done first then all of the c's are compiled and inserted into the lib.

all: $(patsubst %.c,%.o,$(wildcard *.c))

%.o: %.c %.h
    avr-gcc -g -O0 -c -Wall -mmcu=atmega328p $< -o $@
    avr-ar rcsv libNoter.a $@

clean: 
    -rm *.o
March 22, 2011
by Ralphxyz
Ralphxyz's Avatar

Comprehending a MakeFile is slooowly coming through but at the moment no I do not see your makefile scanning the directory for all files with c in the extension. But the more I look at your makefile with that exact directive then I can start to see which sections are "not" the ones to build files with .c extensions.

I tried building the project (without modifying my MakeFile, using the same as I used on my Mac)in Programmers Notepad on my XP computer. I think it gets the same equivalent error as I got on my Mac:

> "make.exe" all
make -C ../libnerdkits
make[1]: Entering directory `C:/NerdKits/Code/libnerdkits'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `C:/NerdKits/Code/libnerdkits'
avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -o eeprom128.o eeprom128.c ../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o
C:\DOCUME~1\rph\LOCALS~1\Temp\ccwCwkgU.o: In function `write_eeprom':
C:\NerdKits\Code\EEPROM128/eeprom128.c:169: undefined reference to `TWI_master_start_write'
C:\DOCUME~1\rph\LOCALS~1\Temp\ccwCwkgU.o: In function `read_eeprom':
C:\NerdKits\Code\EEPROM128/eeprom128.c:152: undefined reference to `TWI_master_start_write_then_read'
C:\DOCUME~1\rph\LOCALS~1\Temp\ccwCwkgU.o: In function `eeprom_init':
C:\NerdKits\Code\EEPROM128/eeprom128.c:130: undefined reference to `TWI_init'
make.exe: *** [eeprom128.hex] Error 1

> Process Exit Code: 2
> Time Taken: 00:06

So I guess I need to learn more about the MakeFile. I'll also try moving twi.h into the Project Root.

Ralph

March 22, 2011
by Noter
Noter's Avatar

The "undefined reference" means that the linker cannot fine the TWI.o file. At the end of your arv-gcc line where the libnerdkits stuff is, add " ../libNoter/libNoter.a" and then it will find TWI.o in the libNoter.a library.

avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -o eeprom128.o eeprom128.c ../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o ../libNoter/libNoter.a

When you see "$(patsubst %.c,%.o,$(wildcard .c))" on the "all:" line of my makefile, that is where the directory is scanned for .c and then the list of file names changed to .o. Later the .o's are used to invoke steps of the build. It takes hours to get through the make GNU manual but that is where I learned how to do it. If you have trouble getting to sleep at night, try reading the GNU make manual.

March 22, 2011
by Ralphxyz
Ralphxyz's Avatar

libNoter.a is not found.

Where/what is the libNoter.a library.

I have actually gone through the make manual but did not come away with much.

Ralph

March 22, 2011
by Noter
Noter's Avatar

Oh, I though you had the lib. I guess I posted that somewhere else. Anyway, in leiu of using the lib, change that last entry from libNoter.a to TWI.o and you should be fine as long as the TWI.o file exists where you say it does. If the TWI.o is in libNoter then, ../libNoter/TWI.o. If it's in your program directory then just plain TWI.o.

March 22, 2011
by Ralphxyz
Ralphxyz's Avatar

twi.o does not exist either.

Ralph

March 22, 2011
by Noter
Noter's Avatar

Did you compile and link TWI.c? The output of a compile and link is an object (.o) file.

If not, copy TWI.c and TWI.h from the initial code post in the begining of this thread into the libNoter directory. Then copy the libNoter makefile to the libNoter directory and run it to compile and link TWI.c into the TWI.o file. Or you can create your own makefile and use it. If you use mine then you should have both TWI.o and libNoter.a created in the directory. Here is the libNoter makefile:

all: $(patsubst %.c,%.o,$(wildcard *.c))

%.o: %.c %.h
    avr-gcc -g -O0 -c -Wall -mmcu=atmega328p $< -o $@
    avr-ar rcsv libNoter.a $@

clean: 
    -rm *.o

Put all three files in libNoter directory and run the makefile. There should be no errors but let me know if problems.

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

I figured that would be my next step. I thought that any involved program would get compiled if it was not already, automatically.

Thanks,

Ralph

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

Noter, I tried making up a MakeFile from what said and posted:

MCU=atmega168

F_CPU=14745600UL

    avrdude -C ../libNoter/avrdude.rs -c Noter -p $(MCU) -b 115200 -P /dev/cu.PL2303-0000101D \
        -U flash:w:$<:a

all: $(patsubst %.c,%.o,$(wildcard *.c))

%.o: %.c %.h
    avr-gcc -g -O0 -c -Wall -mmcu=atmega328p $< -o $@
    avr-ar rcsv libNoter.a $@

clean: 
    -rm *.o

I am getting this error:

miniMac:libNoter Me$ make
Makefile:5: *** target pattern contains no `%'.  Stop.
miniMac:libNoter Me$

Ralph

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

Foret my last thread, I used your Makefile from yourMaster/Slave project and it works right out of the box, I have twi.o now.

Now to try the EEPROM build again!!

Ralph

March 23, 2011
by Noter
Noter's Avatar

Great! Getting close now!

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

Well I tried making the EEPROM project now that I have my twi.o file.

I get the biggest error I have ever gotten. It is even complaining about lcd.h, now that's a first:

miniMac:eeprom128 Me$ make
cc    -c -o eeprom128.o eeprom128.c
eeprom128.c:16:27: error: avr/interrupt.h: No such file or directory
eeprom128.c:18:26: error: avr/pgmspace.h: No such file or directory
In file included from eeprom128.c:19:
../libnerdkits/lcd.h:11: error: expected ‘)’ before ‘in’
../libnerdkits/lcd.h:12: error: expected ‘)’ before ‘in’
../libnerdkits/lcd.h:18: error: expected ‘)’ before ‘row’
In file included from eeprom128.c:20:
../libNoter/twi.h:58: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_buffer_max’
../libNoter/twi.h:60: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_buffer_pos’
../libNoter/twi.h:61: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_buffer_len’
../libNoter/twi.h:62: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_read_bytes’
../libNoter/twi.h:63: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_write_bytes’
../libNoter/twi.h:64: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_bytes_returned’
../libNoter/twi.h:66: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_target_slave_addr’
../libNoter/twi.h:74: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_status’
../libNoter/twi.h:79: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_master_state’
../libNoter/twi.h:85: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_operation’
../libNoter/twi.h:86: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_busy’
../libNoter/twi.h:87: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘TWI_error’
../libNoter/twi.h:98: error: expected ‘;’, ‘,’ or ‘)’ before ‘TWI_return_code’
../libNoter/twi.h:101: error: expected declaration specifiers or ‘...’ before ‘uint16_t’
../libNoter/twi.h:101: error: expected ‘;’, ‘,’ or ‘)’ before ‘TWI_return_code’
../libNoter/twi.h:102: error: expected ‘)’ before ‘slave_addr’
../libNoter/twi.h:103: error: expected ‘)’ before ‘slave_addr’
../libNoter/twi.h:104: error: expected ‘)’ before ‘slave_addr’
../libNoter/twi.h:105: error: expected ‘)’ before ‘my_slave_addr’
eeprom128.c:27: error: expected specifier-qualifier-list before ‘uint16_t’
eeprom128.c:37: error: expected ‘)’ before ‘memory_address’
eeprom128.c:38: error: expected ‘)’ before ‘memory_address’
eeprom128.c:40: error: expected ‘)’ before ‘return_code’
eeprom128.c: In function ‘main’:
eeprom128.c:46: error: ‘_FDEV_SETUP_WRITE’ undeclared (first use in this function)
eeprom128.c:46: error: (Each undeclared identifier is reported only once
eeprom128.c:46: error: for each function it appears in.)
eeprom128.c:54:56: error: invalid suffix "b1010000" on integer constant
eeprom128.c:63: error: incompatible types in assignment
eeprom128.c:75: error: ‘EEPROM_DATA’ has no member named ‘read_count’
eeprom128.c:76: error: ‘EEPROM_DATA’ has no member named ‘brightness’
eeprom128.c:77: error: ‘EEPROM_DATA’ has no member named ‘version’
eeprom128.c:80: error: ‘EEPROM_DATA’ has no member named ‘version’
eeprom128.c:82: error: ‘EEPROM_DATA’ has no member named ‘brightness’
eeprom128.c:88: error: ‘EEPROM_DATA’ has no member named ‘read_count’
eeprom128.c:97: error: ‘EEPROM_DATA’ has no member named ‘read_count’
eeprom128.c: At top level:
eeprom128.c:106: error: expected specifier-qualifier-list before ‘uint8_t’
eeprom128.c:112: error: expected specifier-qualifier-list before ‘uint8_t’
eeprom128.c: In function ‘eeprom_init’:
eeprom128.c:134: error: ‘handle_TWI_result’ undeclared (first use in this function)
eeprom128.c:135: error: too many arguments to function ‘TWI_init’
eeprom128.c: At top level:
eeprom128.c:147: error: expected ‘)’ before ‘memory_address’
eeprom128.c:152:41: error: invalid suffix "b1010000" on integer constant
eeprom128.c:164: error: expected ‘)’ before ‘memory_address’
eeprom128.c:169:33: error: invalid suffix "b1010000" on integer constant
eeprom128.c:176: error: expected ‘)’ before ‘return_code’
eeprom128.c: In function ‘show_eeprom_data’:
eeprom128.c:189: error: ‘EEPROM_DATA’ has no member named ‘read_count’
eeprom128.c:190: error: ‘EEPROM_DATA’ has no member named ‘brightness’
eeprom128.c:191: error: ‘EEPROM_DATA’ has no member named ‘version’
make: *** [eeprom128.o] Error 1
miniMac:eeprom128 Me$
March 23, 2011
by Noter
Noter's Avatar

What is eeprom128.c?

I've got to go right now but will be back in a couple of hours.

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

re: the lines 3 -8 in the above error code. I can build other projects without errors calling avr/interrupt.h,avr/pgmspace.h and lcd.h.

This was on my mac I will switch over to XP and Programmers Notepad.

This is progress so I am encouraged :-)

Ralph

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

Here is my error when trying to compile the EEPROM program in Windows XP using Programmers Notepad:

> "make.exe" all
cc    -c -o eeprom128.o eeprom128.c
process_begin: CreateProcess(NULL, cc -c -o eeprom128.o eeprom128.c, ...) failed.
make (e=2): The system cannot find the file specified.

make.exe: *** [eeprom128.o] Error 2

> Process Exit Code: 2
> Time Taken: 00:01

It doesn't even get started. What program is it looking for? eeprom128.c certainly is there, there is no eeprom128.o file of course.

Ralph

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

eeprom128.c is my file name your 24LC256_eeprom.c file.

Here is the error I get when using the Command Line:

C:\NerdKits\Code\EEPROM128>make
make -C ../libnerdkits
make[1]: Entering directory `C:/NerdKits/Code/libnerdkits'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `C:/NerdKits/Code/libnerdkits'
avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscan
f -lscanf_flt -lm -o eeprom128.o eeprom128.c ../libnerdkits/delay.o ../libnerdki
ts/lcd.o ../libnerdkits/uart.o
C:\DOCUME~1\rph\LOCALS~1\Temp\ccvKoUJU.o: In function `write_eeprom':
C:\NerdKits\Code\EEPROM128/eeprom128.c:169: undefined reference to `TWI_master_s
tart_write'
C:\DOCUME~1\rph\LOCALS~1\Temp\ccvKoUJU.o: In function `read_eeprom':
C:\NerdKits\Code\EEPROM128/eeprom128.c:152: undefined reference to `TWI_master_s
tart_write_then_read'
C:\DOCUME~1\rph\LOCALS~1\Temp\ccvKoUJU.o: In function `eeprom_init':
C:\NerdKits\Code\EEPROM128/eeprom128.c:130: undefined reference to `TWI_init'
make: *** [eeprom128.hex] Error 1

C:\NerdKits\Code\EEPROM128>

This is using your Makefile.

What is the best environment to be using? I'll stick to just one so as to not be too confusing.

Ralph

March 23, 2011
by Ralphxyz
Ralphxyz's Avatar

Well now I get the same error in Programmers Notepad so I guess that at least levels the playing field.

Ralph

March 23, 2011
by Noter
Noter's Avatar

I think you're almost there. The problem is that the linker can't fine the TWI functions so we have to tell it where they are. Add ../libNoter/TWI.o to the end of the line after the libnerdkits objects. (I assume TWI.o is in the libNoter directory?).

March 23, 2011
by Noter
Noter's Avatar

As far as the best environment ... if you mean windows or mac, I don't know, I never had an apple computer.

March 24, 2011
by Ralphxyz
Ralphxyz's Avatar

WhoooWheeee!!! :-) I did it, I actually have the same output on the LCD as your example.

Plus each time I hit the reset switch the READ COUNT increments by one. Hot Dog, It Works.

I could not get your Makefile to work, so I used a generic Nerdkits Makefile (which leads to another forum question):

GCCFLAGS=-g -Os -Wall -mmcu=atmega168 
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
#AVRDUDEFLAGS=-c avr109 -p m168 -b 115200 -P /dev/ttyUSB0
AVRDUDEFLAGS=-c avr109 -p m168 -F -b 115200 -P com15
LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o ../libNoter/TWI.o

ProjectName=eeprom128

all:    $(ProjectName)-upload

$(ProjectName).hex: $(ProjectName).c
    make -C ../libnerdkits
    avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o $(ProjectName).o $(ProjectName).c ${LINKOBJECTS}
    avr-objcopy -j .text -O ihex $(ProjectName).o $(ProjectName).hex

$(ProjectName).ass: $(ProjectName).hex
    avr-objdump -S -d $(ProjectName).o > $(ProjectName).ass

$(ProjectName)-upload:  $(ProjectName).hex
    avrdude ${AVRDUDEFLAGS} -U flash:w:$(ProjectName).hex:a

Of course now I am really going to load on the questions.

First of which will be getting my ATMEL AT24C1024B EEPROM working.

I have not even thought of this for a couple of weeks.

Then of course I need to get the DS3232 Real Time Clock working and then my other I2C modules.

This is great, now I can take a breath and go have a cup of coffee and relax, they rest of this will be a cake walk, right.

Thanks Noter, and thanks Rick for getting me going.

Ralph

March 24, 2011
by Ralphxyz
Ralphxyz's Avatar

Well that was easy I just replaced the Microchip 24AA128 with the ATmel AT24C1024B and it works right out of the box.

I did not have to do anything. The pinout is the same so I did not even need to change the wiring.

The output on the LCD was kinda messy the first time turning it on (same as happend with the 24AA128) but after a reset it cleared right up.

Noter I believe you had some error code which would have appeared if the chip was not actually working.

Wow this is so cool.

Now to really study yours and Rick's code to get the other modules working.

I can also start to play (learn) around with multiple device addressing which I will need.

Man I got a lot of work to do.

Ralph

March 24, 2011
by Noter
Noter's Avatar

Good news Ralph!

Now, don't forget to put in the patches for non-zero addresses and spanning pages. That is the next step. Implement the change (earlier post) to the program and then change the memory address from 0 to 0x00FF and verify it still works.

After those changes are working, another minor change to be able to use the full address range of the 1m bit chip is needed. I'll hold off on that until you get the first set of changes working.

March 24, 2011
by Ralphxyz
Ralphxyz's Avatar

I would like to get your Makefile to work but it doesn't even try.

Of course I would like to understand the Makefile better so I started this thread.

By non-zero addresses you mean using the A1, A2 etc pins?

This whole device addressing is going to take some work but at least now I can make changes and have a working base line to return to.

I am having trouble comprehending "use the full address range of the 1m bit chip". But I am working on it.

I am trying to understand the structuring of the data stored on the EEPROM, I can see how a program writes to the EEPROM using a struct as you did but I do not understand how a different program would be able to read the EEPROM. This goes back to not having a file system to manage memory I believe. It's not required that a different mcu and program be able to access a EEPROM but say we had your Master/Slave setup could both mcus access the same EEPROM? And then but again not important at the moment how do you delete data from the EEPROM?

Well like I said now I am going to be able to ask a lot of questions.

Ralph

March 24, 2011
by Noter
Noter's Avatar

By non-zero addresses I mean the eeprom memory address. If you examine the program closely you will see that the test data is currently written and read from eeprom address 0. If you change to use an address that is not 0, the program will not work correctly as will be evident by the display showing trash all the time like it did the very first time on a new chip. That is why the changes are necessary before going any further.

None of that other stuff matters until the test program works correctly for the entire memory address range of your 1m bit chip.

If you go back to the early part of this thread you will find the post where I gave some changes to deal with the memory address of zero problem. Implement those changes into the test program and we'll go from there.

March 24, 2011
by Ralphxyz
Ralphxyz's Avatar

I added your code from 5 days ago starting at line 42 in the main program or does this need to go in the twi.c program?
"WRITE_EEPROM_BYTE *p_write_eeprom_byte;"

I also changed the low_byte in the main program to 0x00FF, in three places.

This of course broke the working code:

make[1]: Entering directory `C:/NerdKits/Code/libnerdkits'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `C:/NerdKits/Code/libnerdkits'
avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -o eeprom128.o eeprom128.c ../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o ../libNoter/TWI.o
eeprom128.c:42: error: expected '=', ',', ';', 'asm' or '__attribute__' before '*' token
eeprom128.c:50: warning: data definition has no type or storage class
eeprom128.c:50: warning: type defaults to 'int' in declaration of 'p_write_eeprom_byte'
eeprom128.c:50: error: 'TWI_buffer' undeclared here (not in a function)
eeprom128.c: In function 'write_eeprom_bytes':
eeprom128.c:58: error: invalid type argument of '->' (have 'int')
eeprom128.c:59: error: invalid type argument of '->' (have 'int')
eeprom128.c:60: error: invalid type argument of '->' (have 'int')
eeprom128.c:61: error: 'EEPROM_DEVICE_ID' undeclared (first use in this function)
eeprom128.c:61: error: (Each undeclared identifier is reported only once
eeprom128.c:61: error: for each function it appears in.)
eeprom128.c: At top level:
eeprom128.c:67: error: expected ')' before '(' token
make.exe: *** [eeprom128.hex] Error 1

> Process Exit Code: 2
> Time Taken: 00:02

Should this go in your "// Define eeprom commands according to 24LC256 datasheet" Section instead? Line 131.

Thanks so much for your patience I sure hope others are getting benefit from this thread, I do not like

hogging all of your time but this really should be usable in so many situations. I had looked at using a

SD card but using a EEPROM just made more sense (I'll do the SD card later on).

Is anyone else reading this thread, I'd like to hear your comments.

Ralph

March 24, 2011
by Noter
Noter's Avatar

Time to put on your programmer hat.

It doesn't all got together in the same place. The "..." divide the new parts that stay together. You're right on the new typedef, it goes in with the eeprom commands.

March 25, 2011
by Noter
Noter's Avatar

Hey Ralph,

I ordered a 1mb eeprom so I can test and be sure it works before posting an updated test program. In the mean time, I'll explain my approach.

The AT24C1024B is 1m bit in a single package but logically it is two 512k bit devices. By I2C definition each device has a unique 7 bit address and the AT24C1024B has two device addresses, one for the lower 512k bit memory and another for the upper 512k bit memory. Therefore, logically it must be two devices. The I2C does not know or care that these two devices are in a single package. I believe the reason behind having the two devices is to remain compatible with smaller eeprom chips where the complete memory range can be addressed with two bytes.

So, the test program must be modified to handle multiple 512k bit devices and when done will be able to address and use the maxium number of these devices which logically is 8 or physically 4 of the AT24C1024B. Here's my approach to change the test program to support the AT24C1024B:

  • Change all variables that hold the memory address from 16 bit integers to 32 bit integers.
  • The lower 16 of the new memory address is the eeprom memory adress for one device.
  • Bits 17 to 19 become the lower 3 bits of the device address to select the appropriate eeprom chip.
  • Split the memory address and modify the 3 lower bits of the device address just prior to writing to the device.

Hopefully I've explained it well enough that you can follow what I mean. Of course there's always more than one way to do things so this may not be the best approach but I think it will work. Maybe I'll have my chip next week and can get the program tested/posted.

March 26, 2011
by Ralphxyz
Ralphxyz's Avatar

Noter, thank you so much. wow that really clarifies things. The two device addressing makes sense (finally).

Claiming my ignorance I was going to ask you to start over begging at square one to explain the memory addressing but I think you just did, fascinating.

I am taking a day or two and re-reading the AT24C1024B spec sheet a few more times.

I also printed out your code and Rick's DS3232 RTC code and this code from ermicro.

The ermicro code does largely what I need to do, it feeds binary numbers into the EEPROM and reads them back. That is almost explicitly what I need for for my Water Curtain project. That plus having a potentiometer to vary the timing is just what I need for initial timing since I have no idea what timing factors I will be needing. The ermicro code uses an array instead of your struct so that gives me another perspective.

Of course all three code methods are using different libraries, well the ermicro has the twi commands inline, which will complicate my comparisons but should lead to a better understanding.

And then I am really intrigued with Rick's I2C LCD project and the work you and Eric did with the I2C Master/Slave. I haven't yet printed the Master/Slave code (but will).

To bad winter is all most over, I have a lot of projects to be working on outside so I'll be slowed down a little but I 'll keep plugging ahead.

Thanks again this has really been a great thread I hope others have found it useful.

Ralph

March 26, 2011
by Noter
Noter's Avatar

Hi Ralph,

I'm glad you made sense of my explaination and approach to the 1m bit eeprom addressing solution. If you get it working first, post your code otherwise I'll post mine once I receive the chip and get it tested.

Paul

March 29, 2011
by Ralphxyz
Ralphxyz's Avatar

Well I have been modifying my breadboard. I also retried using Rick's I2C Real Time Clock (RTC) and ta dah it works.

Here is some test code that ATmel puts out for testing the AT24C1024.

It is for a different class of mcus but I figured the C code might help to illustrate programming/using the AT24C1024.

Here is the link for the whole project.

It covers a number of I2C devices including the AT24C1024 EEPROM and a RTC.

Here is a ATmel Application Note outlining the code use.

Now how would I write the clock output to the EEPROM? For the weather station I will also need the other devices (barometric pressure, relative humidity, temperature, wind direction and wind speed) data. But it seems like a god place to start wit a Date Time Stamp.

Ralph

March 29, 2011
by Rick_S
Rick_S's Avatar

I'm so happy to hear you finally got the I2C RTC going. Did you nail down the exact cause of the problem or just do a rebuild of the breadboard?

Now you can get on to even more fun stuff :D

Rick

March 29, 2011
by Ralphxyz
Ralphxyz's Avatar

It was just a complete new breadboard, I had gone from not being able to run your code to having the two black bars in run mode so I just said the heck with it and started over. Plus I needed more room for all of the components.

I'd like to get all of the components running off the same bus instead of doing them separately as I have read that having multiple devices on the same bus changes the capacitance which can effect timing. So if there is going to be a problem I would rather it rear it's ugly head rather during development time than having everything run independently and then fail when I put them together.

Ralph

April 06, 2011
by Ralphxyz
Ralphxyz's Avatar

Paul, did you get your big I2C EEPROM? What do you think of the ATmel method of addressing the 1 meg EEPROM?

That whole Application Note project is interesting with the TWI LCD, TWI EEPROM and TWI RTC all of which have been hashed out here in the Nerdkits forums, most recently thanks to you, Rick and Eric.

Ralph

April 06, 2011
by Noter
Noter's Avatar

Yes, I have four of them and they are installed on my TWI master breadboard but I've been working on the DOW (Dallas One Wire) interface and haven't made it back to the eeprom code yet. I figure I'll be finished with the DOW this week and get back to the eeprom next week. I'm using the DOW to access multiple DS18B20's for temperature readings. I think they will be much more accurate and stable than the ADC variety used with the nerdkit plus I can string a lot of them on a single buss which will be great for my ground temp project where I measure temp every 6 inches up to a 6 foot depth. I'll have DOW results in another day or so and then get back to the eeprom code.

Paul

April 06, 2011
by Noter
Noter's Avatar

I hadn't looked at the ATmel Application Note but just did and I see that they also refer to the 1mb eeprom as being addressed by 17 bits with the overflow in the I2C address bit. Same as the spec sheet but not as clear to me as viewing the 1mb chip to be two 512 devices. The results are the same but I like to stick with the I2C definition of device addresses which means the 1mb eeprom is really two 512kb devices because there are two I2C device addresses.

April 06, 2011
by Ralphxyz
Ralphxyz's Avatar

Damm, I almost (finally) am actually following what you are saying. Possible it is because I actually looked at the ATmel code and saw the reference to the 17bit address and almost understood what they were doing.

I agree with you treating the device as two separate devices makes sense.

But,but what about having multiple devices I think the specsheet says you can have four on the same I2C buss.

How does that work with the device split in two? Does that mean you only can have two 1meg eeproms instead of 4?

Ralph

April 06, 2011
by Noter
Noter's Avatar

If you have 4 of something and you split them in two then you have 8 halves. Up to 8 of the 512k bit devices can be on the same IC2 buss. If you look at the 512k bit eeprom spec sheet you will see that you can have max of 8. That's the three address pins on the eeprom chip - 2^3 = 8 ... 0 thru 7.

April 06, 2011
by Ralphxyz
Ralphxyz's Avatar

Here is what I am hung up on:

2. Pin Description (pg 3 of specsheet)

DEVICE/ADDRESSES (A1/A2): The A1, A2 pin is a device address input that can be hardwired or left not 
connected for hardware compatibility with other AT24Cxx devices. When the A1, A2 pins are hardwired, 
as many as four 1024K devices may be addressed on a single bus system (device addressing is discussed 
in detail under the Device Addressing section). If the A1/A2 pins are left floating, the A1/A2 pin will be 
internally pulled down to GND if the capacitive coupling to the circuit board VCC plane is <3 pF. If coupling    
is >3 pF, Atmel recommends connecting the A1/A2 pin to GND.

There are only two address pins (additional) on the AT24C1024B (A1 and A2).

For the first device you are not using the address pins correct, pin A1 and pin A2 are either floating or at ground?

So what is the address of the second 1/2 512k bit device?

The first device is 10100000 so what is the the second (1/2) device? A1 is my logical answer 10100001 but that does not work form my understanding of what you have said so far.

Strange I almost understand but but ...

Ralph

April 06, 2011
by Noter
Noter's Avatar

Look at the top of page 11 in the datasheet. It shows that the 7 bit device address is

1 0 1 0 A2 A1 P

P is the address bit that selects one or the other 512k bit bank of eeprom. So your answer is correct, the 2nd bank on the 1st device is 1010001.

Just as a note, don't let the A's float, connect either to GND or Vcc.

April 07, 2011
by Ralphxyz
Ralphxyz's Avatar

Thanks Paul, wow my heaad is sloowly staarting to spin less. I really do not need multiple 1meg eeproms but it would be nice to understand this (at least functionally).

Ok new subject(s).

How do you erase a memory location or does just overwriting without a delete work?

I am starting to think about putting actual data into the eeprom.

First I need to input the alphabet using something like the H I have shown in the Water Curtain thread and else where (March 18 in this thread)

As of the moment the H looks something like this:

H   
    00111100,37.5
    00111100,75
    00111100,75
    00000000,75   
    00111100,75
    00111100,75
    00111100,37.5

I guess I am essentially building a font table that in code I would like to be able to call with a "H" and feed the binary bit pattern to my solenoids with the timing factor determining how long to turn the solenoids on. Would each letter be a struct?

And then I need to do a lookup table for my Relative Humidty sensor.

Relative Humidity

Glad you have a truckload of patience, thanks.

What a interesting thread, I am sure glad you wanted to share your experience.

Ralph

April 07, 2011
by Noter
Noter's Avatar

I guess you could write 0xFF's and consider that erasing the eeprom but there is really no need, just overwrite with new data.

You could use a struct to define each character but you will still need to have a method to figure the address in eeprom where any given struct is located. Think of it as an array and define the order of characters in your array. Then use the character value to calc the eeprom address of the data.

Let's say the order of structs in eeprom is 0, 1, 2, ... 9, A, B, B ... Z and the struct is defined as:

struct {
uint8_t row[8];
}

Just calculate the eeprom address where the data is stored based on the character value:

'5' - '0' = 5, 5 * sizeof(struct) = 40, 40 is the eeprom address of the '5' struct data.

Same for alpha but add 10 to account for the digits.

('H' - 'A' + 10) * sizeof(struct) = 136 is the eeprom address of the 'H' struct data.

This is just one method of many but I tend to like calculating addresses when I can instead of using some sort of look up or search.

April 07, 2011
by Noter
Noter's Avatar

I missed the other number in the struct ...

struct {
uint8_t row[8];
float duration[8];
}

and using the sizeof(struct) in code saves a lot of editing as changes to the struct are applied. So the answers I came up with above will be different now because of the size of the new struct.

April 07, 2011
by bretm
bretm's Avatar

Wouldn't the "H" just be

00111100,187.5
00000000,75  
00111100,187.5

So you'd need one table with the "data offset" for each letter, and the number of pattern entries for each symbol would be variable, so you have to look up the next symbol's offset to know when to stop.

uint16_t index = 2 * ('H' - firstChar);
uint16_t offset = readWord(index);
uint16_t nextOffset = readWord(index + 2);

while (offset < nextOffset)
{
    uint8_t pattern = readByte(offset);
    offset++;
    uint16_t duration = readWord(offset);
    offset += 2;
    doPattern(pattern);
    wait(duration);
}

I'd use uint16_t instead of float for the duration, e.g. 187.5 would be 1875. Takes half the space and is faster to compute with.

April 07, 2011
by Noter
Noter's Avatar

Or perhaps use a fixed duration for each line in the original array and not include the float/int16 at all. If you're low on memory then a scheme like bretm suggests is one way to reduce usage. Given plenty of memory, I'd opt for the simpler approach. Have you done an estimate of how much memory you need?

April 07, 2011
by Ralphxyz
Ralphxyz's Avatar

Paul said "Have you done an estimate of how much memory you need?" Gee a quote mechanism would be nice in the forum.

All of what I have done so far is really off the top of my head, I can picture my H structure working as such. I can picture 8 solenoids in a row simultaneously turning on for a split second and a drip falling. But I do not know how practical or feasible that schema might be. So NO I have not done any memory analysis. This is all speculation I do not know if it will even remotely work or how feasible it will be. Possible I will need a lot more computing power than a ATmega mcu can provide.

That is why I am really counting on the feedback from the forum to help me work this through.

If someone would only say "Ralph this will not work, because ..." then I could get on with my life and get back to my Weather Station which probable is a lot more feasible (easy) project.

bretm said: Wouldn't the "H" just be

00111100,187.5
00000000,75  
00111100,187.5

Yes that would work for a H, but I believe for other letters and shapes (diving dolphin) or the letter O or X I think it needs to be broken up into segments. Plus there might be a ripple in the visual effect making it more readable (recognizable).

I am close to actually trying to do this, once I get the water flowing (dripping) I probable will discover that I have to do major revisions to my implementation.

Paul said "Or perhaps use a fixed duration for each line in the original array "

I am sorry I thought my timing scheme was implicit with a fixed duration designated, I suppose I failed to say that recently sorry.

H   
    00111100,37.5
    00111100,75
    00111100,75
    00000000,75   
    00111100,75
    00111100,75
    00111100,37.5

Line one 00111100,37.5 is made up as:

Solenoid 1 OFF 
Solenoid 2 OFF
Solenoid 3 ON for 37.5 ms
Solenoid 4 ON for 37.5 ms
Solenoid 5 ON for 37.5 ms
Solenoid 6 ON for 37.5 ms
Solenoid 7 OFF
Solenoid 8 OFF

Possible I will have to time the OFF but for now it is just assumed.

Line two would be:

Solenoid 1 OFF 
Solenoid 2 OFF
Solenoid 3 ON for 75 ms
Solenoid 4 ON for 75 ms
Solenoid 5 ON for 75 ms
Solenoid 6 ON for 75 ms
Solenoid 7 OFF
Solenoid 8 OFF

This "assumes" it takes a drop of water 500ms to fall 5 feet.

So once I get water dripping I'll have to refine my timings.

I have been devising a drip timer in my spare time.

Ok now for a hard question:

How would I read/write the eeprom with multiple devices?

I know for some computing devices a eeprom is used to load the operating system or program.

I just got a device that uses a 8051 mcu and it's bootloader looks into a eeprom to load the program that is to be run.

But I am thinking of using one mcu to load (write) the eeprom with another mcu to read the eeprom.

With a DOS (disk operating system) as used in a pc there is a file system operator (OS) to read files from a specified folder (directory). Without a OS how does the reader mcu know where to look for the "H".

Ralph

April 07, 2011
by Noter
Noter's Avatar

Ralph, I described a simple method of addressing the eeprom for you data in my posts prior to bretm's method. Take another look at those and see if you can make sense of it.

April 08, 2011
by Ralphxyz
Ralphxyz's Avatar

Paul from your programing example above:

struct {
uint8_t row[8];
float duration[8];
}

Why are you using arrays?

Wouldn't uint8_t row[8] take up 8 bytes(+) to hold a 8 bit binary number.

And why a float for duration it seems a uint8_t would hold any of my timing values.

Or is uint8_t row[8]; saying make 8 "rows"?

I am back to studying up on structs. Remember I do not consider myself to be a "programmer".

I used to have a Signature on AVRfreaks that said:

"I am not a programmer but I play one on TV"

Ralph

April 08, 2011
by Ralphxyz
Ralphxyz's Avatar

Ok, I went back and read your suggested Structures reading

And I see that:

struct {
uint8_t row[8];
float duration[8];
}

Would make one line of my letter character.

So for my letter H I would need 7 of these structs.

Now I am working on populating each row with it's duration.

My current H has 7 rows and my O has 10 rows.

H   
    00111100,37.5
    00111100,75
    00111100,75
    00000000,75   
    00111100,75
    00111100,75
    00111100,37.5

O
    11100111,60
    11011011,35
    10111101,45
    10111101,45
    01111110,95
    01111110,45
    10111101,45
    10111101,35
    11011011,35
    11100111,60

Could I do something like this:

 struct {
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    uint8_t row[8][8];
    }

Assuming that my largest letter would occupy 10 rows I can adjust the H to use 10 rows by modifying the timing:

   H   
        00111100,18.75
        00111100,18.75
        00111100,75
        00111100,75
        00000000,37.5   
        00000000,37.5
        00111100,75
        00111100,75
        00111100,18.75
        00111100,18.75

Of course this is all a work in progress.

Ralph

April 08, 2011
by Noter
Noter's Avatar

I didn't look closely at your design before keying in that example. With only 7 rows, you only need 7 items in the array. I thought you were going to keep the duration in the array too but after your last email I guess not so 7 rows, 8 bits each is all you need - right? The specific structure doesn't matter so much when working out the method of addressing your eeprom. I am focused on the method so I miss some of your project detail in my examples.

struct {
    uint8_t row[7];
} PATTERN;
PATTERN pattern[36];

pattern['H'-'A'+10].row[0]=0b00111100;

Using a struct for a simple table seems like overkill but I think it allows for more readable code. And if you ever do add another element, the struct is definately and advantage.

using a 2 dimensional array it looks like this -

uint8_t pattern[36,7];

pattern['H'-'A'+10][0]=0b00111100;

The method of calculating the eeprom address works about the same with either one.

You need to consider being a programmer to go much beyond the nerdkit examples since the majority of effort on these projects is programming. Maybe you would like taking a few programming courses at a local community college?

April 08, 2011
by Ralphxyz
Ralphxyz's Avatar

Ooops, got to maintain 500 ms total for the entire letter exposure so that should be:

H   
     00111100,18.75
     00111100,43.75
     00111100,75
     00111100,75
     00000000,37.5   
     00000000,37.5
     00111100,75
     00111100,75
     00111100,43.75
     00111100,18.75

Ralph

April 08, 2011
by Noter
Noter's Avatar

Why not stick to a fixed number of rows like regular fonts do?

Otherwise for a variable number of rows you'll have to use something like bretm's scheme.

April 08, 2011
by Ralphxyz
Ralphxyz's Avatar

Are you kidding, at 63 I am not likely to "go to college" (notice I did not say "go back" to college) even though I really would love to take some programming classes. I tried to get into the local community college (local being a hour away) a few years back to take a JAVA course but they could not get enough students.

Besides I am learning so much from your instruction and that of others like Rick and bretm and so many more.

I will get this, it might take a while but I can see that I am really making progress. I am almost ready to "try" to put some data into the eeprom. Well first I will hardcode H E L L O and light some leds and Dean Camera has such a excellant tutorial on programing the internal EEPROM over at AVRfreaks that I'll use the internal eeprom and then graduate up to using the external eeprom the smaller one and then the 1 meg.

Are you ready to start a new thread about using the DOW (Dallas One Wire) interface for your ground measurement project? I have thought about doing a ground temperature project such as you have described.

Of course my biggest deterrent to getting things done or effectively (quickly) learning something is that I keep adding more things to do. Besides my mcu programming projects you ought to see my list of projects to do around the house this summer but hey I have lots of time and probable another 50 - 60 years.

Ralph

April 08, 2011
by Noter
Noter's Avatar

You might see if there is an online course that you'd like. You will likely learn faster and learn more if in a structured environment compared to hit and miss on forums.

I am happy with the DS18B20 temp sensor. Three out of the box and they all measure within .2 degree F of each other and are very stable. There's a lot of example code on the net and Maxim has an application note with source code so I don't know if I will start another thread on DOW. I had it all working in a couple of days. Less than 1 minute to hook up the wires and probably 10 hours of programming. I like to rewrite all the code in my own fashion to be sure I have learned it. Otherwise figure maybe an hour or so to get one of the samples to work ... if they work.

April 08, 2011
by Ralphxyz
Ralphxyz's Avatar

Wow .2 degree of each other is good. The LM34 that comes with the Nerdkit has three or four batch groupings with accuracy determining the grouping. The ones that come with the NerdkitI are guaranteed for are something like + - 4 degrees while the LM34CZ is much more refined (accurate). Of course the LM34CZ cost four times as much.

I think it is great to have these different projects and device utilizations published here in the Nerdkits forum, you just never know who might come along and be searching the forum and find your thread and maybe all of these tutorial type thread will get consolidated into a repository here on the Nerdkits forum so they will be an easy reference.

I now I can and will search the web for the Dallas One Wire interface but I really like the discussion and the perspectives that get introduced.

Ralph

April 08, 2011
by bretm
bretm's Avatar

-> Why not stick to a fixed number of rows like regular fonts do?

I would imagine, once he's got it working for text, the next stage would be arbitrary symbols or patterns. With a million bits and a cunning arrangement of data I would imagine you could store a day-long water extravaganza. A megabit is a lot of data for something like this.

April 08, 2011
by Noter
Noter's Avatar

Probably could send the each row of the pattern from the PC via the rs232 line and never have to store anything with at mcu. It could come off disk, over the internet, or from anywhere.

April 08, 2011
by Ralphxyz
Ralphxyz's Avatar

I might not have a computer at the Water Curtain display certainly not at the at the Weather Station. Of course the font table is for the Water Curtain not the Weather Station.

It seems like using something like a font table is what I want to do.

For programming the eeprom with the font table I would like to use a PC.

In fact I mentioned the other day that I had gotten a device using the 8051 mcu with the bootloader loading the application from a eeprom.

That device is a USB I2C bridge.

That is why I was wondering/asking about having multiple I2C device accessing one eeprom one to read and one to write.

So anybody use a USB I2C bridge?

Hey bretm, yeah you are getting the picture. Here is the link to my inspiration again it is from Canal City in Japan.

Right now I am just experimenting with 8 solenoids or about a 6" display. If this works and I can get recognizable letters I would like to expand the display to 32 solenoids 24" display. So I definitely am thinking of arbitrary symbols and patterns.

Of course it would be unreal to do a really big display but if I can make recognizable objects then there is no reason I couldn't do a big one. Of course someone would have to pay for it. This could be a another water fountain career for someone. I already made one career for one guy building animated (to music) water fountains. I came up with the water flow formulas that he uses.

Plus Singlecoilx3 has just given me a new idea (inspiration) with his Higher Resolution Large LED Display text messaging.

Now that would be cool imagine sending a text message from your phone and having the message come out in/on the water curtain.

I also am thinking about being able to draw a image on a tablet computer and have it replicated in/on the water curtain.

Both of those ideas would really differentiate my Water Curtain from others. I could probable hire it out for wedding receptions or parties and store openings where ever there are people.

So I want/need static displays but I really want dynamic displays. That will differentiate my Water Curtain

Ralph

April 08, 2011
by Noter
Noter's Avatar

Here is the megabit eeprom demo. It uses the latest TWI master/slave code which I will post next.

//
// AT24C1024B_eeprom.c
// 
// Demonstrate writing/reading 4 megabit serial eeproms via TWI/I2C
//

#include <avr/interrupt.h>
#include <stdbool.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <string.h>
#include "../libnerdkits/lcd.h"
#include "../libNoter/TWI.h"

// prototype local functions
void eeprom_init();
uint8_t *read_eeprom_bytes(uint32_t memory_address, uint16_t length);
void write_eeprom_bytes(uint32_t memory_address, uint8_t *data, uint16_t size);

// LCD stream file - enable printf in functions outside of main()
FILE lcd_stream;

// Define format of eeprom data
typedef struct {
    uint8_t     row[7];
} PATTERN;

PATTERN char_H PROGMEM =    {{  0b10000001,
                                0b10000001,
                                0b10000001,
                                0b11111111,
                                0b10000001,
                                0b10000001,
                                0b10000001
                            }};

// this example expects 4 1024k bit eeproms or 8 512k bit eeproms
prog_int32_t    demo_eeprom_address[]  =    {   0x00000,// lower 512 chip 1 byte 0
                                                0x10000,// upper 512 chip 1 byte 0
                                                0x20000,// lower 512 chip 2 byte 0
                                                0x30000,// upper 512 chip 2 byte 0
                                                0x40000,// lower 512 chip 3 byte 0
                                                0x50000,// upper 512 chip 3 byte 0
                                                0x60000,// lower 512 chip 4 byte 0
                                                0x70000,// upper 512 chip 4 byte 0
                                                -1  // end of list
                                            };
// if you have only one 1024 eeprom, don't use
// eeprom addresses above (0x1FFFF - size of data).
// Just change the above list but don't
// remove the -1 as it flags the end of the list.
// You can add more addresses or have fewer as long
// as they stay within the range and the -1 is at
// the end.

// -----------------------------------------------------------------
int main() {
    int i;
    int j;
    // Initialize the lcd
    lcd_init();
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE);

    // initialize eeprom and TWI/I2C
    eeprom_init();

    // create local ram copy of eeprom data
    PATTERN e_data;

    // copy H character pattern from flash
    for(i=0;i<7;i++)
        e_data.row[i] = pgm_read_byte_near(&char_H.row[i]);

    // write once in each 512k bit bank of each chip
    #define DEMO_EEPROM_ADDR(o) (int32_t)pgm_read_dword_near(&demo_eeprom_address[o])
    for(i=0;DEMO_EEPROM_ADDR(i)>=0;i++){
        lcd_clear_and_home();
        fprintf_P(&lcd_stream, PSTR("test %d -> %05"PRIX32" - "), i, DEMO_EEPROM_ADDR(i));

        // write it
        write_eeprom_bytes(DEMO_EEPROM_ADDR(i), (uint8_t*)&e_data, sizeof(PATTERN));

        // read it back
        e_data = *(PATTERN*)read_eeprom_bytes(DEMO_EEPROM_ADDR(i), sizeof(PATTERN));

        // does it match
        for(j=0;j<7;j++)
            if(e_data.row[j]!=pgm_read_byte_near(&char_H.row[j]))
                break;

        // if we didn't reach the end of the loop it didn't match       
        if(j<7)
            fprintf_P(&lcd_stream, PSTR("XX")); 
        else
            fprintf_P(&lcd_stream, PSTR("OK"));

        // take a sec to read the lcd
        _delay_ms(1000);    
    }

    // now put the H pattern where it would belong in eeprom memory
    // if organized as 0 thru 9 then A thru Z starting at base of array
    #define CHAR_ARRAY_BASE 0x00000
    int32_t char_addr;
    char_addr = CHAR_ARRAY_BASE + (('H'-'A'+ 10) * sizeof(PATTERN));
    write_eeprom_bytes(char_addr, (uint8_t*)&e_data, sizeof(PATTERN));

    // show where it is
    lcd_clear_and_home();
    fprintf_P(&lcd_stream, PSTR("char_H now @ %05X "), char_addr);

    // done
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("done ...")); 
    while(true);
}
// -----------------------------------------------------------------

// Define eeprom commands according to datasheets
typedef struct {
    uint8_t     high_byte;
    uint8_t     low_byte;
    uint8_t     data_byte;
} WRITE_EEPROM_BYTE;

typedef struct {
    uint8_t     high_byte;
    uint8_t     low_byte;
} SET_EEPROM_ADDRESS;

// Create structure pointers for the TWI/I2C buffer
WRITE_EEPROM_BYTE       *p_write_eeprom_byte;
SET_EEPROM_ADDRESS      *p_set_eeprom_address;

// Create TWI/I2C buffer, size to largest block
uint8_t     TWI_buffer[sizeof(PATTERN)];

void eeprom_init(){
    // Specify startup parameters for the TWI/I2C driver
    TWI_init(   F_CPU,                      // clock frequency
                100000L,                    // desired TWI/IC2 bitrate
                TWI_buffer,                 // pointer to comm buffer
                sizeof(TWI_buffer),         // size of comm buffer
                0                           // optional pointer to callback function
                );

    // set pointers to buffer
    p_write_eeprom_byte = (WRITE_EEPROM_BYTE*)TWI_buffer;
    p_set_eeprom_address = (SET_EEPROM_ADDRESS*)TWI_buffer;

    // Enable interrupts
    sei();
}

// turns out the chip device addressing is different for the
// Amtel and Microchip 1mbit eeproms. The bit order and pin
// assignments don't match so define the one you have for
// the following conditional assembly in make_dev_addr().
#define _AT24C1024B
uint32_t make_dev_addr(uint32_t memory_address){
    #ifdef _AT24C1024B
        return(0b1010000 | ((memory_address >> 16) & 7));
    #endif
    #ifdef _24AA1025
        uint8_t chip = (memory_address >> 17) & 3; 
        uint8_t block = ((memory_address >> 16) & 1)<<2;
        return(0b1010000 | block | chip);
    #endif
}

uint8_t *read_eeprom_bytes(uint32_t memory_address, uint16_t length){
    // send 'set memory address' command to eeprom and then read data
    while(TWI_busy);
    p_set_eeprom_address->high_byte = (memory_address >> 8) & 0xFF;
    p_set_eeprom_address->low_byte = memory_address & 0xFF;
    TWI_master_start_write_then_read(   make_dev_addr(memory_address),                      // device address of eeprom chip
                                        sizeof(SET_EEPROM_ADDRESS),                         // number of bytes to write
                                        length<sizeof(TWI_buffer)?length:sizeof(TWI_buffer) // number of bytes to read
                                        );
    // nothing else to do - wait for the data
    while(TWI_busy);
    // return the data
    return(TWI_buffer);
}

// write eeprom byte at a time - can cross page boundaries
void write_eeprom_bytes(uint32_t memory_address, uint8_t *data, uint16_t size){
    int i;
    for(i=0;i<size;memory_address++,data++,i++)
    {
        while(TWI_busy);
        p_write_eeprom_byte->high_byte = (memory_address >> 8) & 0xFF;
        p_write_eeprom_byte->low_byte = memory_address & 0xFF;
        p_write_eeprom_byte->data_byte = *data;
        TWI_master_start_write( make_dev_addr(memory_address),  // device address of eeprom chip
                                sizeof(WRITE_EEPROM_BYTE)       // number of bytes to write
                                );
    }
}
April 08, 2011
by Noter
Noter's Avatar
// TWI.c
// 
//  v1.2  3/18/2011
//
// Object provides TWI/I2C communication functions.
//
// There are some known problems with TWI on ATmel AVR's ...
//
//      http://www.robotroom.com/Atmel-AVR-TWI-I2C-Multi-Master-Problem.html
//

#ifdef __AVR_ATmega328P__
#  define SIG_OVERFLOW0 TIMER0_OVF_vect
#  define SIG_OVERFLOW1 TIMER1_OVF_vect
#  define SIG_OVERFLOW2 TIMER2_OVF_vect
#include "../libnerdkits/io_328p.h"
#endif

#include <avr/interrupt.h>
#include "TWI.h"
#include <avr/io.h>

// initialize the component
void TWI_init(long cpu_freq, long bit_rate, uint8_t* buffer, uint16_t max, void (*callback)(volatile uint8_t TWI_match_addr, volatile uint8_t TWI_return_code)){
    TWI_return_result = callback;
    p_TWI_buffer = buffer;
    TWI_buffer_max = max;
    TWBR = ((cpu_freq/bit_rate)-16)/2; // bit rate register 
    TWSR = 0; // prescaler
    TWI_busy=0;
}

// master write to slave
void TWI_master_start_write(uint8_t slave_addr, uint16_t write_bytes){
    TWI_busy=1;
    if(write_bytes>TWI_buffer_max){
        TWI_write_bytes=TWI_buffer_max;
    }else{
        TWI_write_bytes=write_bytes;
    }
    TWI_operation=TWI_OP_WRITE_ONLY;
    TWI_master_state = TWI_WRITE_STATE;
    TWI_target_slave_addr = slave_addr;
    TWCR = TWI_START; // start TWI master mode
}

// master read from slave
void TWI_master_start_read(uint8_t slave_addr, uint16_t read_bytes){
    TWI_busy=1;
    if(read_bytes>TWI_buffer_max){
        TWI_read_bytes=TWI_buffer_max;
    }else{
        TWI_read_bytes=read_bytes;
    }
    TWI_operation=TWI_OP_READ_ONLY;
    TWI_master_state = TWI_READ_STATE;
    TWI_target_slave_addr = slave_addr;
    TWCR = TWI_START; // start TWI master mode
}

// master write then read without releasing buss between
void TWI_master_start_write_then_read(uint8_t slave_addr, uint16_t write_bytes, uint16_t read_bytes){
    TWI_busy=1;
    if(write_bytes>TWI_buffer_max){
        TWI_write_bytes=TWI_buffer_max;
    }else{
        TWI_write_bytes=write_bytes;
    }
    if(read_bytes>TWI_buffer_max){
        TWI_read_bytes=TWI_buffer_max;
    }else{
        TWI_read_bytes=read_bytes;
    }
    TWI_operation=TWI_OP_WRITE_THEN_READ;
    TWI_master_state = TWI_WRITE_STATE;
    TWI_target_slave_addr = slave_addr;
    TWCR = TWI_START; // start TWI master mode
}

// enable slave and start receiving messages
void TWI_enable_slave_mode(uint8_t my_slave_addr, uint8_t my_slave_addr_mask, uint8_t enable_general_call){
    TWAR = (my_slave_addr<<1);      // set my slave addr
    TWAMR = (my_slave_addr_mask<<1);    // set the addr mask for multi-device mode
    if(enable_general_call>0){
        TWAR |= _BV(TWGCE);             // enable general call receipts
    }
    TWCR = TWI_ACK;                     // enable ACK on SLA_W/SLA_R
}

// Routine to service interrupts from the TWI hardware.
// The most important thing is that this routine runs fast and returns control
// to the hardware asap. So, when results are ready and the callback is made to
// your application, be sure you return as quickly as possible. Remove significant
// work from the callback and instead perform that work in your main execution loop.
//
// See pages 229, 232, 235, and 238 of the ATmega328 datasheed for detailed 
// explaination of the logic below.
SIGNAL(TWI_vect){
    TWI_status = TWSR & TWI_TWSR_status_mask;
    switch(TWI_status){

        case TWI_MM_repeated_start_sent_x10:
        case TWI_MM_start_sent_x08:
            switch(TWI_master_state){
                case TWI_WRITE_STATE:
                    TWI_buffer_pos=0; // point to 1st byte
                    TWDR = (TWI_target_slave_addr<<1) | 0x00; // set SLA_W
                    break;
                case TWI_READ_STATE:
                    TWI_buffer_pos=0; // point to first byte
                    TWDR = (TWI_target_slave_addr<<1) | 0x01; // set SLA_R
                    break;
            }
            TWCR = TWI_ACK; // transmit
            break;

        case TWI_MT_SLA_W_sent_ack_received_x18:
        case TWI_MT_data_sent_ack_received_x28:
            if(TWI_buffer_pos==TWI_write_bytes){
                if(TWI_operation==TWI_OP_WRITE_THEN_READ){
                    TWI_master_state=TWI_READ_STATE; // now read from slave
                    TWCR = TWI_START; // transmit repeated start
                }else{
                    if(TWI_return_result){
                        (*TWI_return_result)(0, TWI_success);// callback with results
                    }
                    TWCR = TWI_STOP; // release the buss
                    while(TWCR & (1<<TWSTO)); // wait for it
                    TWI_busy=0;
                }
            }else{
                TWDR = p_TWI_buffer[TWI_buffer_pos++]; // load data
                TWCR = TWI_ENABLE; // transmit
            }
            break;

        case TWI_MR_data_received_ack_returned_x50:
            p_TWI_buffer[TWI_buffer_pos++]=TWDR; // save byte
        case TWI_MR_SLA_R_sent_ack_received_x40: 
            if(TWI_buffer_pos==(TWI_read_bytes-1)){
                TWCR = TWI_NACK; // get last byte then nack
            }else{
                TWCR = TWI_ACK; // get next byte then ack
            }
            break;

        case TWI_MR_data_received_nack_returned_x58:            
            p_TWI_buffer[TWI_buffer_pos++]=TWDR; // save byte
            if(TWI_return_result){
                (*TWI_return_result)(0, TWI_success);// callback with results
            }
            TWCR = TWI_STOP; // release the buss
            while(TWCR & (1<<TWSTO)); // wait for it
            TWI_busy=0;
            break;

        case TWI_SR_SLA_W_received_ack_sent_x60:
        case TWI_SR_SLA_W_received_after_arbitration_lost_ack_sent_x68:
        case TWI_SR_general_call_received_ack_sent_x70:
        case TWI_SR_general_call_received_after_arbitration_lost_ack_sent_x78:
            TWI_match_addr=TWDR>>1; // save the match address
            TWI_buffer_pos=0; // point to start of input buffer
            TWCR = TWI_ACK;
            break;

        case TWI_SR_SLA_W_data_received_ack_sent_x80:
        case TWI_SR_general_call_data_received_ack_sent_x90:
            if(TWI_buffer_pos<TWI_buffer_max){
                p_TWI_buffer[TWI_buffer_pos++]=TWDR; // store data
            }
            TWCR = TWI_ACK; 
            break;

        case TWI_SR_stop_or_repeated_start_received_xA0:
            TWI_buffer_len=TWI_buffer_pos; // bytes returned
            if(TWI_return_result){
                (*TWI_return_result)(TWI_match_addr, TWI_success); // callback with results
            }
            TWCR = TWI_ACK; 
            break;

        case TWI_ST_SLA_R_received_after_arbitration_lost_ack_sent_x80:
        case TWI_ST_SLA_R_received_ack_sent_xA8:
            TWI_match_addr=TWDR>>1; // save the match address
            TWI_buffer_pos=0; // point to start of input buffer
        case TWI_ST_byte_sent_ack_received_x88:
            if(TWI_buffer_pos<TWI_buffer_max){
                TWDR = p_TWI_buffer[TWI_buffer_pos++]; // load data
            }
        case TWI_SR_SLA_W_data_received_nack_sent_x88:
        case TWI_SR_general_call_data_received_nack_sent_x98:
        case TWI_ST_byte_sent_nack_received_xC0:
        case TWI_ST_last_byte_sent_ack_received_xC8:
            TWCR = TWI_ACK; 
            break;

        case TWI_MT_SLA_W_sent_nack_received_x20:
        case TWI_MT_data_sent_nack_received_x30:
        case TWI_MR_SLA_R_sent_nack_received_x48:
            if(TWI_return_result){
                (*TWI_return_result)(TWI_match_addr, TWI_status);// callback with status
                }
        case TWI_MM_arbitration_lost_x38:
        default:
            TWCR=TWI_STOP; 
            while(TWCR & (1<<TWSTO)); // wait for it
            TWCR=TWI_START; // try again
            break;
    }

}
April 08, 2011
by Noter
Noter's Avatar
//
// TWI.h
//
//  v1.2  3/18/2011
// 
// Define status codes, buffers, and various variables used
// by TWI.c to communicate with other devices via the TWI/I2c buss.
//
#define TWI_TWSR_status_mask 0xF8

// Status codes for TWI Master Mode (TWSR)
#define TWI_MM_start_sent_x08 0x08
#define TWI_MM_repeated_start_sent_x10 0x10
#define TWI_MM_arbitration_lost_x38 0x38

// Status codes for TWI Master Transmitter Mode 
#define TWI_MT_SLA_W_sent_ack_received_x18 0x18
#define TWI_MT_SLA_W_sent_nack_received_x20 0x20
#define TWI_MT_data_sent_ack_received_x28 0x28
#define TWI_MT_data_sent_nack_received_x30 0x30

// Status codes for TWI Master Receiver Mode
#define TWI_MR_SLA_R_sent_ack_received_x40 0x40
#define TWI_MR_SLA_R_sent_nack_received_x48 0x48
#define TWI_MR_data_received_ack_returned_x50 0x50
#define TWI_MR_data_received_nack_returned_x58 0x58

// Status codes for TWI Slave Receiver Mode
#define TWI_SR_SLA_W_received_ack_sent_x60 0x60
#define TWI_SR_SLA_W_received_after_arbitration_lost_ack_sent_x68 0x68
#define TWI_SR_general_call_received_ack_sent_x70 0x70
#define TWI_SR_general_call_received_after_arbitration_lost_ack_sent_x78 0x78
#define TWI_SR_SLA_W_data_received_ack_sent_x80 0x80
#define TWI_SR_SLA_W_data_received_nack_sent_x88 0x88
#define TWI_SR_general_call_data_received_ack_sent_x90 0x90
#define TWI_SR_general_call_data_received_nack_sent_x98 0x98
#define TWI_SR_stop_or_repeated_start_received_xA0 0xA0

// Status codes for TWI Slave Transmitter Mode
#define TWI_ST_SLA_R_received_ack_sent_xA8 0xA8
#define TWI_ST_SLA_R_received_after_arbitration_lost_ack_sent_x80 0xB0
#define TWI_ST_byte_sent_ack_received_x88 0xB8
#define TWI_ST_byte_sent_nack_received_xC0 0xC0
#define TWI_ST_last_byte_sent_ack_received_xC8 0xC8

// successful return code
#define TWI_success 0x00

// buffers and variables
volatile uint16_t TWI_buffer_max;
volatile uint8_t*  p_TWI_buffer;
volatile uint16_t TWI_buffer_pos;
volatile uint16_t TWI_buffer_len;
volatile uint16_t TWI_read_bytes;
volatile uint16_t TWI_write_bytes;
volatile uint16_t TWI_bytes_returned;

volatile uint8_t TWI_target_slave_addr;
volatile uint8_t TWI_match_addr;

// keep track of current state
volatile uint8_t TWI_status;
#define TWI_WRITE_STATE 0x01
#define TWI_READ_STATE 0x02

// call types
volatile uint8_t TWI_master_state;
#define TWI_OP_WRITE_ONLY 0x01
#define TWI_OP_READ_ONLY 0x02
#define TWI_OP_WRITE_THEN_READ 0x03

// control variables
volatile uint8_t TWI_operation;
volatile uint8_t TWI_busy;
volatile uint8_t TWI_error;

// various states of hardware that will be set in response to interrupts
#define TWI_ENABLE  _BV(TWEN) | _BV(TWINT) | _BV(TWIE)
//
#define TWI_ACK     _BV(TWEA)  |    TWI_ENABLE
#define TWI_NACK                    TWI_ENABLE
#define TWI_START   _BV(TWSTA) |    TWI_ENABLE
#define TWI_STOP    _BV(TWSTO) |    TWI_ENABLE

// define callback function
void (*TWI_return_result)(volatile uint8_t TWI_match_addr, volatile uint8_t TWI_return_code);

// define supported funcitons
void TWI_init(long cpu_freq, long bit_rate, uint8_t* buffer, uint16_t max, void (*callback)(volatile uint8_t TWI_match_addr, volatile uint8_t TWI_return_code));
void TWI_master_start_write(uint8_t slave_addr, uint16_t write_bytes);
void TWI_master_start_read(uint8_t slave_addr, uint16_t read_bytes);
void TWI_master_start_write_then_read(uint8_t slave_addr, uint16_t write_bytes, uint16_t read_bytes);
void TWI_enable_slave_mode(uint8_t my_slave_addr, uint8_t my_slave_addr_mask, uint8_t enable_general_call);
April 09, 2011
by Ralphxyz
Ralphxyz's Avatar

Wow, Paul that is fantastic. And thanks so much for the PATTERN. I'll need a couple of days (weeks) to really digest this.

Thanks for the informative comments they really help.

Ralph

April 12, 2011
by Zachariah
Zachariah's Avatar

I ordered some 24LC256 so I could learn what this form as been talking about. I got everything working as advertized. Well then I decided to start learning more by changing little things in the programming to see what happens. Using two EEPROM chips I changed the code to get both to count up in the same way that was demonstrated for one. I tried to then change the code so that it would save the read count to the EEPROM LOCATION 00001…

Ok so I screwed something up. I only changed line 47 of 24LC256_eeprom.c

// specify eeprom memory address for data storage
    #define EEPROM_DATA_ADDRESS 0x0000

Changed to :

// specify eeprom memory address for data storage
    #define EEPROM_DATA_ADDRESS 0x0001

As some might expect I got the DATA ERROR message on the LCD. I pushed rest button a few times and still got it. Then changed the code back to exactly how examples show. Then removed second 24LC256 chip. Now I am still getting Error code as follows:


24LC256 ADDR: 50

0 0600 00 -0.00
DATA ERROR –

I am sorry if this is a bit lengthy. I want to be clear on what happen. Anyone have any ideas of what I did by changing that one number? Then anyone know how to fix this?

April 12, 2011
by Noter
Noter's Avatar

24LC256.c needs the changes shown in the 5th post from the top of the thread to work with non-zero locations.

You might want to get AT24C1024B_eeprom.c above along with the latest version of the TWI master/slave code and go with it. For a single chip all you would need to do is limit your eeprom addresses to a max of 0x31 since you have only 256k bits. Otherwise the 24LC256 is pin/command compatible with the AT24C1024B.

June 09, 2011
by carlhako
carlhako's Avatar

Thanks a heap Noter. I purchased a 24lc64 with 64k of storage for my weather station. So far my weather station is reading temps and dumping the data to a mysql db and graphing. ill post all my code and everything when done. Im now working on storing the data so if for example my pc is off the data is stored into the eeprom, 64k will let me store months of data.

My plan this weekend was get this chip working, I think you have saved me alot of time! Im pretty sure your code will work without much modification.

June 13, 2011
by carlhako
carlhako's Avatar

Hi Noter I am running into similar problems as Ralphxyz where I cannot get a TWI.o file compiled and am not sure how he fixed it. Maybe Ralphxyz could you post your Makefile you used for this one?

I realized reading through this thread that the chips are rated in bits not bytes so my 64K chip actually only holds 8KB which is not enough for my needs. Ill upgrade to a 1024kbit one but I still should be able to get this running with my current one.

The error I am getting when compiling error:

carl@carl:~/Dropbox/Code/AT24C$ make
make -C ../libnerdkits
make[1]: Entering directory `/home/carl/Dropbox/Code/libnerdkits'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `/home/carl/Dropbox/Code/libnerdkits'
avr-gcc -g -Os -Wall -mmcu=atmega168  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -       lscanf_flt -lm -o main.o main.c ../libnerdkits/delay.o ../libnerdkits/lcd.o    ../libnerdkits/uart.o 
/tmp/cc87OJe3.o: In function `write_eeprom_bytes':
/home/carl/Dropbox/Code/AT24C/main.c:197: undefined reference to     `TWI_master_start_write'
/tmp/cc87OJe3.o: In function `read_eeprom_bytes':
/home/carl/Dropbox/Code/AT24C/main.c:178: undefined reference to     `TWI_master_start_write_then_read'
/tmp/cc87OJe3.o: In function `eeprom_init':
/home/carl/Dropbox/Code/AT24C/main.c:142: undefined reference to `TWI_init'

Dropbox is handy for this stuff btw I can sync my code folder with any of my windows/linux machines floorlessly.

This is what i have done: in main.c i have tried "#include ../libnerdkits/TWI.h" and ./TWI.h with the 2 files copied to appropriate locations.

I have edited my Makefile to include a ../libnerdkits/TWI.o this just give the error that the .o file is missing.

What i am not understanding is if i delete ../libnerdkits/lcd.o and then rerun the make file lcd.o is regenerated but TWI.h does not operate the same way? I have gone through lcd.h and lcd.c comparing to TWI.h and TWI.c and cant see what its missing to auto generate the .o file?

June 13, 2011
by Noter
Noter's Avatar

Hi Carl,

With twi.c and twi.h files in your libnerdkits directory then you need a little change to the libnerdkits makefile to do the compile. Add twi.o to the "all:" line and two lines at the end to create the twi.o file.

GCCFLAGS=-g -Os -Wall -mmcu=atmega168

all: delay.o lcd.o uart.o twi.o

delay.o: delay.c
    avr-gcc ${GCCFLAGS} -o delay.o -c delay.c

lcd.o: lcd.c
    avr-gcc ${GCCFLAGS} -o lcd.o -c lcd.c

uart.o: uart.c
    avr-gcc ${GCCFLAGS} -o uart.o -c uart.c

twi.o: twi.c
    avr-gcc ${GCCFLAGS} -o twi.o -c twi.c
June 13, 2011
by Ralphxyz
Ralphxyz's Avatar

I used a [Code/libNoter] library. I "think" I compiled twi.c and twi.h to make the twi.o file.

Here is the libNoter folder:

Here is my EEPROM Makefile:

GCCFLAGS=-g -Os -Wall -mmcu=atmega168 
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
#AVRDUDEFLAGS=-c avr109 -p m168 -b 115200 -P /dev/ttyUSB0
AVRDUDEFLAGS=-c avr109 -p m168 -F -b 115200 -P /dev/cu.PL2303-0000101D
LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o ../libNoter/TWI.o

ProjectName=eeprom128

all:    $(ProjectName)-upload

$(ProjectName).hex: $(ProjectName).c
    make -C ../libnerdkits
    avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o $(ProjectName).o $(ProjectName).c ${LINKOBJECTS}
    avr-objcopy -j .text -O ihex $(ProjectName).o $(ProjectName).hex

$(ProjectName).ass: $(ProjectName).hex
    avr-objdump -S -d $(ProjectName).o > $(ProjectName).ass

$(ProjectName)-upload:  $(ProjectName).hex
    avrdude ${AVRDUDEFLAGS} -U flash:w:$(ProjectName).hex:a

Notice the LINKOBJECTS line ends with ../libNoter/TWI.o.

Ralph

June 13, 2011
by carlhako
carlhako's Avatar

Awesome thanks Guys

Thanks what i was missing! I didn't notice the makefile in the libnerdkits folder ill make that change.

Ralphxyz have you managed to get full access to the 1024Kbit on your chip?

June 14, 2011
by carlhako
carlhako's Avatar

Made the changes to libnerdkits/Makefile as Noters suggestion and added "#define F_CPU 14745600" to the start of 24LC256_eeprom.c it then compiled straight off the bat :).

pic

Just not sure what the 0 and the 33 are on line 3.

June 14, 2011
by Noter
Noter's Avatar

Line 3 shows the data after it is read back from eeprom. According to the format in the structure, the 0 is the need_initialization variable value and the 33 is the brightness variable value.

// Define format of eeprom data block.
typedef struct {
    bool        need_initalization;
    char        author[6];
    uint16_t    read_count;
    uint8_t     brightness;
    float       version;
} EEPROM_DATA;
June 14, 2011
by Ralphxyz
Ralphxyz's Avatar

Hi carlhako, I have not gone further than to get Noter's code and suggestions working.

I'am having a hard time understanding accessing stored data on the EEPROM.

I think I understand writing serial data but am lost when it comes to accessing random data.

I know I can look at a specific address but how would I know what address to look for.

I can calculate the address when I am doing a write but where would I store that information for reference the next day or month after the mcu has been turned off.

Paul did a nice job of explaining writing to memory which I "think" I understand but I am lost on reading stored data. Short of doing a serial read every time I turn the mcu on.

Also I would like to be able to access the EEPROM from other devices, using I2C.

Part of my problem is I am still thinking of a computer with a OS reading the EEPROM.

I am used to using a EEPROM burner to fill a EEPROM with data and then being able to access that EEPROM from various different devices (all with the same OS).

I need to adjust my thinking.

My specific data might look something like this.

Date|Time|Temperature|Humidity|Barometric Pressure|Wind Speed|Wind Direction|High Temp|Low Temp|High Wind Speed|Low Wind Speed cr\lf

I am thinking of writing this data to the EEPROM every 15 minutes so the values are actually averages (over 15 minutes) with the high and low temperature and high and low wind speed for that period.

So how would I view a random date's high wind speed?

I am in the occasional thinking about this stage while working on other projects.

Ralph

June 14, 2011
by Noter
Noter's Avatar

If I was going to store weather station type data I think I would just write records one after the other to the eeprom. I might use a few bytes in the begining of the eeprom to save a count of records so I would be able to calculate where the end is and where to put the next one. Then if I had to randomly find a particular one by timestamp I would do a binary search which should work fine since they will be written in chronological order anyway.

June 14, 2011
by Ralphxyz
Ralphxyz's Avatar

Oh that's interesting (as usual) thanks Paul.

Like I said I only am occasionally thinking about this mode so we will see what happens when I actually try to write and read data.

There are different scenarios thatI I have pictured.

The most likely is that I would never need to to read anything using the AVR mcu but everything would be transferred to a PC for any possible display.

This is referencing my weather station project which will be mounted up on a pole so I certainly will not have a LCD or other display device directly attached.

I am picturing doing a Wireless (ZigBee) read of the EEPROM data so I would just be doing a serial read and would not care where any particulars might be stored.

Of course this is just for my weather station for other projects I might want to access a particular piece of data directly.

This is all so fascinating and so much fun, Thanks again for your help.

Ralph

June 15, 2011
by carlhako
carlhako's Avatar

I am using this for my weather station too which will be temps/humidity/rain. If i know the time interval between recorded records (5min) i can use the first 8 or 16 bits to keep a record of how many records there are. My setup is only going to store data into the eeprom when communication between the pc and mcu is broken say if i switch the pc off or loose power last year we had a cyclone and lost power for a week. Otherwise every 5min the data is stored into a mysql database. I have that all working and even graphing with php. Ill post all my code php and perl scripts in a separate thread when i have it complete, hopefully someone might find it useful

Carl

July 25, 2011
by Ralphxyz
Ralphxyz's Avatar

Carl I'd really like to see more about your Weather Station!

Ok, now I have a great (my humble opinion of course) "Pattern Generator" thanks to Cliff over on AVRFreaks.

Now "we" need to have a discussion about putting these patterns onto the EEPROM.

I really have no idea of where to start, well I do we have this thread and Paul has mentioned a couple of methods.

My head is just not clear on exactly what it is I should do and exactly how.

My pattern generator is using a single Struct with a number of patterns in arrays:

Here is the code sorry it's a bit long because of the patterns.

/*
 *  8 bit Pattern Generator for Water Curtain Project
 *  Cliff "clawson" @ AVRFreaks.net
 *  http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=852331#852331
 *  Ralph Hulslander
 *  rhulslander-at-gmail.com
 *  http://www.nerdkits.com/forum/thread/1369/
*/

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"

#define NUM_ELEMENTS(array) (sizeof(array) / sizeof(array[0])) 
//*

 uint8_t pattern1[] PROGMEM = 
 {    
 //Fill 
 0b11111111 
 }; 
 uint8_t pattern2[] PROGMEM = 
 {    
 //slantLeft1    
 0b00000001, 
 0b00000010, 
 0b00000100, 
 0b00001000, 
 0b00010000, 
 0b00100000, 
 0b01000000, 
 0b10000000, 
 0b01000000, 
 0b00100000, 
 0b00010000, 
 0b00001000, 
 0b00000100, 
 0b00000010, 
 0b00000001 
 }; 
 uint8_t pattern3[] PROGMEM = 
 {    
 //slantLeft2 
 0b00000011, 
 0b00000110, 
 0b00001100, 
 0b00011000, 
 0b00110000, 
 0b01100000, 
 0b11000000 
 }; 
 uint8_t pattern4[] PROGMEM = 
 {    
 //slantRight2 
 0b11000000, 
 0b01100000, 
 0b00110000, 
 0b00011000, 
 0b00001100, 
 0b00000110, 
 0b00000011 
 }; 
 uint8_t pattern5[] PROGMEM = 
 {

 //up2center 
 0b00011000, 
 0b00011000, 
 0b00011000, 
 0b00011000, 
 0b00011000, 
 0b00011000, 
 0b00011000, 
 0b00011000            // watch no comma on last line
 };

typedef struct 
{ 
    uint8_t len; 
    uint8_t * data; 
} pattern_inf_t;

 pattern_inf_t patterns[] PROGMEM = { 
 { NUM_ELEMENTS(pattern1), pattern1 }, // len = NUM_ELEMENTS(pattern1), pattern1 == * data == FILL
 { NUM_ELEMENTS(pattern2), pattern2 }, 
 { NUM_ELEMENTS(pattern3), pattern3 }, 
 { NUM_ELEMENTS(pattern4), pattern4 }, 
 { NUM_ELEMENTS(pattern5), pattern5 } 
 };

//*/

uint8_t i;

int main(void) 
{ 
    uint8_t current_pat = 3; 
    uint8_t * d_ptr;

    DDRD = 0xFF; 
    UCSR0B = 0;             //Turn off UART 
    while (1) 
        { 
            d_ptr = (uint8_t *)pgm_read_word(&patterns[current_pat].data); 
            for (   i=0; 
                 i < pgm_read_byte(&patterns[current_pat].len); 
                 i++) 
                { 
                    PORTD = pgm_read_byte(d_ptr + i); 
                    delay_ms(1000); 
                } 
        } 
}

That NUM_ELEMENTS macro is really clever it enables me to have different sized arrays (patterns).

The thing I do not know about the EEPROM is how will I find the patterns?

I was thinking maybe I would have a lookup table in the first 5k(?) of memory where I could store the memory address and what ever else was needed. Is that feasible. Would that be a good/acceptable way?

I have no idea how many patterns I will have. I picture at least a hundred and probable lots more once I start doing images.

So if not a lookup table than how else would I find my way around?

So I am open to any and all suggestions. What is the advantage to using a Struct versus just storing the binary data inline?

I will need to serially read the eeprom to serially feed it to the the Shift Register so if I know where to start reading and how many bytes to read what is the advantage to have the data stored in a structure?

All of your help is really appreciated and obviously needed!

Ralph

September 06, 2011
by Ralphxyz
Ralphxyz's Avatar

Hey Paul, I am ready to "think" about EEPROM storage of my patterns for my Water Curtain.

I have started a new thread.

I sure appreciate all of your help so far and am looking forward to your insight and knowledge.

Ralph

October 20, 2012
by esoderberg
esoderberg's Avatar

Noter et al.,

The TWI code you posted (and that I modified and put in the NK library) works great 99.9% of the time. The problem I'm having trouble overcoming with the modified code is that when it doesn't work, my code hangs. I'm trying to make it a little more robust so that if there is some temporary glitch, it may miss a few communication cycles, but will eventually pick back up and keep running. I've tried several various bit of code in the ISR(TWI_vect) "default" and other switch cases indicating a disruption, but with no luck; once it stops working, it never recovers. Any pointers on where I should focus my attention would be appreciated.

Eric

October 20, 2012
by esoderberg
esoderberg's Avatar

More info on the question above - I have one TWI Master communicating with one MPU-6050 gyro/accel and one slave 328P. The slave code seems to continue running regardless of TWI bus status and the Master hanging up.

Eric

October 20, 2012
by Noter
Noter's Avatar

I haven't had problems with the interface yet but when I came across the following in the DS3232 spec I2c section I thought it would be a good feature to add to the TWI code at some point.

The I2C interface is accessible whenever either VCC or
VBAT is at a valid level. If a microcontroller connected to
the DS3232 resets because of a loss of VCC or other
event, it is possible that the microcontroller and DS3232
I2C communications could become unsynchronized,
e.g., the microcontroller resets while reading data from
the DS3232. When the microcontroller resets, the
DS3232 I2C interface may be placed into a known state
by toggling SCL until SDA is observed to be at a high
level. At that point the microcontroller should pull SDA
low while SCL is high, generating a START condition.
October 20, 2012
by esoderberg
esoderberg's Avatar

Noter,

My multi-meter showed SDA and SCL being held low when the original code hung up, so my problem may be as suggested in your post. The additional code below is one of many attempts to fix this; now when the code hangs it shows a steady 5v on SCL vice something closer to 3v that I see when it's running normally. SDA is still being held low. Obviously more work required.

default:

        twi_error=2;

        //SDA may be getting held low by slave

        TWCR &=  ~(1<<TWEN);//disable twi to toggle SCL with GPIO
        DDRC |=  (1<<PC5);
        DDRC &=  ~(1<<PC4);//let TWI pullup work - see if slave is holding low
        while (twi_error>1){
                    //toggle SCL/PC5 until SDA is high
                    PORTC &= ~(1<<PC5); 
                    delay_us(6);
                    PORTC |= (1<<PC5);
                    delay_us(6);

                     if (PINC&(1<<PC4)) {
                         DDRC |=  (1<<PC4);
                         PORTC &= ~(1<<PC4);//pull SDA low with SCL high for START
                         delay_us(6);
                         twi_error=1; }

                    }

            TWCR=TWI_STOP;
            while(TWCR & (1<<TWSTO)); // wait for it
            TWCR=TWI_START; // try again
                break;
October 21, 2012
by Noter
Noter's Avatar

Better detail is in this document. It says a NAK is in order to stop the slave from transmitting the next byte if a multi byte stream. Worse case, build a circuit to reset the slave.

I code my master to always reset slaves when it initializes and that is probably why I have not seen this problem.

May 13, 2013
by dvdsnyd
dvdsnyd's Avatar

All,

This may be a silly question, but what is the difference between your TWI code and Peter Fleury's library for TWI? I am trying to get back into my altimeter project, and am in the process of rewriting the entire project. If there are benefits to changing, now is a good time to commit.

Thanks,

Dave

May 14, 2013
by pcbolt
pcbolt's Avatar

Dave -

I think the Fleury code is poll driven so it sits in a "while" loop until completed and therefore it blocks execution. Noter's code is interrupt driven so it won't block. It is more complicated so you might want to stick to the Fleury code, but it is very flexible and will report very helpful error codes when things aren't working. It also handles many error situations in the ISR.

I re-wrote the Fleury code so it used polling but with timeouts so the program wouldn't lock up. I was tempted to use the interrupt driven library but my program wasn't doing anything but waiting for data from the I2C lines. In other words, unless I got the data I needed there wasn't anything else the program needed to do. I was able to report error codes based on R/W errors, timeouts, init problems, etc. so it helped in debugging problems.

I guess it boils down to what type of program you are running.

May 15, 2013
by dvdsnyd
dvdsnyd's Avatar

pcbolt,

Thanks for your reply.

I have a pressure sensor that uses I2C for communication. I tried using the Fleury code about a year ago, with some success. However, it was very prone to freezing. With just the altimeter, all I need to do is continually read the pressure sensor, once the highest point is reached, save that value and we are done. Could I use your polling code with timeouts to see why the program is locking up? It runs just fine sitting on my desk, but if I blow over the sensor, or shake it sometimes it will lock up. Any suggestions for I2C debugging?

The second iteration of my altimeter has plans for eeprom data storage of the pressure values for later analysis. Would something like that require an interrupt driven I2C protocol?

Thanks, Dave

May 15, 2013
by pcbolt
pcbolt's Avatar

I think in both cases you could use the timeout version. You programs depend on getting good data before testing it (1st iteration) or saving it (2nd iteration). The code I adapted isn't exactly compatible with the Fleury code, so you'd have to change function calls with different parameters. An easier way would be to just change the Fleury code yourself (save the original first). Using the "i2c_start()" function as an example:

unsigned char i2c_start(unsigned char address)
{
    uint8_t   twst;

    // send START condition
    TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

    // wait until transmission completed
    while(!(TWCR & (1<<TWINT)));

    // check value of TWI Status Register. Mask prescaler bits.
    twst = TW_STATUS & 0xF8;
    if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;

    // send device address
    TWDR = address;
    TWCR = (1<<TWINT) | (1<<TWEN);

    // wail until transmission completed and ACK/NACK has been received
    while(!(TWCR & (1<<TWINT)));

    // check value of TWI Status Register. Mask prescaler bits.
    twst = TW_STATUS & 0xF8;
    if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;

    return 0;

}/* i2c_start */

You can see at lines 9 and 20 the program stops here and waits forever if it has to. Line 9 is less likely of a concern but line 20 could be. You can add a timeout to both wait points by modifying it to read:

unsigned char i2c_start(unsigned char address)
{
    uint8_t   twst;
    uint16_t  max_time = 10000;    // 10000 * 1 uSec - 10 mSec

    // send START condition
    TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

    // wait until transmission completed
    while(!(TWCR & (1<<TWINT))){
        max_time--;
        delay_us(1);
        if (max_time == 0) return 2;    // return code for timeout
    }

    // check value of TWI Status Register. Mask prescaler bits.
    twst = TW_STATUS & 0xF8;
    if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;

    // send device address
    TWDR = address;
    TWCR = (1<<TWINT) | (1<<TWEN);

    // wail until transmission completed and ACK/NACK has been received
    max_time = 10000; 
    while(!(TWCR & (1<<TWINT))){
        max_time--;
        delay_us(1);
        if (max_time == 0) return 2;
    }

    // check value of TWI Status Register. Mask prescaler bits.
    twst = TW_STATUS & 0xF8;
    if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;

    return 0;

}/* i2c_start */

You just have to remember to add the "delay.h" header file to this source file before you can use the delay functions. (You won't need to add it to your main code after that). You can do this to the "i2c_write()" code as well but the "i2c_readAck()" and "i2c_readNak()" are slightly different since they return the data read and not an error code like the other functions. You can modify those to return an error code and send a pointer as a function parameter that will hold the data to be read out (or use a global variable).

May 16, 2013
by dvdsnyd
dvdsnyd's Avatar

pcbolt,

Thanks for your help again. You said:

" The code I adapted isn't exactly compatible with the Fleury code, so you'd have to change function calls with different parameters"

Silly question, but what do you mean by this? Why would you have different parameters?

I understood the timeout in the i2c_start() function, however, when you started talking about returning error codes and sending pointers as function parameters, I am lost.

for i2c_readAck() and i2c_readNak(), you modify these, to return an error code when the the transmission gets hung up in one of these routines? But, the data is still held to be read out after the timeout has occurred? Sorry, I am just trying to figure out the logic behind how this works... Let me know if I am even close to the target. Thanks, Dave

May 16, 2013
by pcbolt
pcbolt's Avatar

It's been a little while since I made the code library for I2C so I'm not exactly positive why I made all the changes I did. That said, I think I changed the "i2c_start()" function so it would tell me which of the 2 "while()" loops got hung up. Looking back, it most likely wasn't needed but it still works. I combined the two "i2c_readAck()/i2c_readNak()" functions into one by sending a flag to the function indicating which sequence of code to run. This is what I meant by changing the function parameters.

In the "i2c_readAck()/i2c_readNak()" functions, if the timeout occurs, the function will return immediately with an error code of 2. The data isn't held since it never was received. If all went correctly, the function should return an error code of 0 and the data will be available. The problem is how to get that data back to your main program, since you can only return one value. The way I do it is to send the address of a variable from your main program as a function parameter. The function puts the data in that address and your main program can now access it. For example, if I were to re-write the "i2c_readAck()" I'd change the original...

unsigned char i2c_readAck(void)
{
    TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
    while(!(TWCR & (1<<TWINT)));

    return TWDR;

}/*

To something like this...

unsigned char i2c_readAck(uint8_t *data)
{
    uint16_t  max_time = 10000;    // 10000 * 1 uSec - 10 mSec
    TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
    while(!(TWCR & (1<<TWINT))){
        max_time--;
        delay_us(1);
        if (max_time == 0) return 2;
    }

    *data = TWDR;

    return 0;

}/*

Now in your main code you can call the function like this

uint8_t data, error = 0;

error = i2c_readAck(&data)  //'&' symbol is the "address of" operator
if (error){  // do something like write to LCD or log error code
else{ // record data

I usually end up chaining a bunch of calls together like this...

uint8_t init_i2c_device(address){

    uint8_t error = 0;

    error = i2c_start(address);
    if (!error) error = i2c_write(0x03);
    if (!error) error = i2c_write(0x02);    
    if (!error) error = i2c_readAck(&data);  // data is a global variable
    i2c_stop();
    return error;
}
May 18, 2013
by dvdsnyd
dvdsnyd's Avatar

pcbolt, Thanks for explaining how that works. I am going to have to brush up on pointers. What is the main function of the timeout? To notify you that an error occurred in the data transmission?

Like I mentioned, I had issues with the i2c interface. It would start up and run, return data and then stop. Once it froze up, I had do a power reset for it to work again. This won't work for an altimeter that I am going to be relying on to help recover a rocket.

What are some potential causes of a freezing i2c data transfer? Are there ways to make it more robust? Will adding the timeout help? Let me know if I should make this a new thread...?

Thanks again!

David

May 18, 2013
by pcbolt
pcbolt's Avatar

David -

I was thinking of starting a new thread as well. This one is pretty long but fortunately doesn't have too many photos which can be a hassle when it first loads.

The major benefit of the timeouts is to be able to report errors. Without them the program just gets stuck and you have no idea what went wrong. You could report your progress in the program to the LCD before each call to an I2C procedure. That will also tell you where things went wrong. The other advantage of timeouts is if an I2C command times out, you can try it again and again just in case it was a one time fail.

Most of the I2C errors I've dealt with are bad connections to the sensor chips. Sometimes just a nudge can make the connectors fail. I've ended up actually soldering the so-called "solderless" crimp connectors. The other problem is in programming, by not resetting the I2C by issuing "i2c_stop()" after a fail. Usually if it works a few times I go back to trying to bulletproof the connections and checking power to the sensor chip.

May 20, 2013
by dvdsnyd
dvdsnyd's Avatar

pcbolt,

I just recently bought some crimp connectors and a pair of crimping pliers. I have definitely noticed that the connection sometimes are not very reliable. I have had to redo a fair share of cables.

I think I am going to focus on getting my code back up and running, and see what kind of issues I begin to have. I'll try to get the connections as solid as I can first.

What do you mean by "The other problem is in programming, by not resetting the I2C by issuing "i2c_stop()" after a fail."

Thanks for helping me understand by answering all my silly questions!

Dave

May 20, 2013
by pcbolt
pcbolt's Avatar

Sometimes the sensor chips on the I2C bus get caught in a "waiting for command or data" state. Usually sending the "i2c_stop()" resets everything to a known state.

I would try adding a tiny bit of solder to you connectors. Too much and the pin won't fit in the housing. If you are using ribbon wires, you also have to be careful not to melt the insulator jacket.

May 30, 2013
by MaxWolfMarrin
MaxWolfMarrin's Avatar

This thread seems the most relevant to what I am trying to do- I would like to make a sound effect device using an external eeprom with the ATtiny85- I saw some code for converting a wave file into 8kbps nibbles, but my question is how to get the data onto the eeprom in the first place- how do I initialize the eeprom with the sound data if it exceeds the memory of the AVR chip?

May 30, 2013
by pcbolt
pcbolt's Avatar

Hi Max -

I started a new thread for this topic HERE. This thread is just getting too fat :-)

November 17, 2013
by photofoo
photofoo's Avatar

Hi I have done some hacking on Noter's 24LC256 I2C code in order to communicate with a DS1307 Real Time Clock. apologize for the long post but just trying to give max info.

My hack sort of works but I seem to be off one address location in my data retrieval from the DS1307. I think I have looked too long at the code and probably overlooking something obvious.

Trying to pull data from clock into this struct

// Define format of RTC data block
typedef struct {
  uint8_t rtc_sec;
  uint8_t rtc_min;
  uint8_t rtc_hour;
  uint8_t rtc_wkDay;
  uint8_t rtc_date;
  uint8_t rtc_mo;
  uint8_t rtc_yr;
  uint8_t rtc_ctl;
} RTC_DATA;

When I print to LCD via this code:

void show_rtc_data(RTC_DATA *data){
    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR("%02X %02X %02X %02X" ), 
        data->rtc_sec,
        data->rtc_min,
        data->rtc_hour,
        data->rtc_wkDay
        );
    lcd_line_four();
    fprintf_P(&lcd_stream,PSTR("%02X %02X %02X %02X" ),
        data->rtc_date,
        data->rtc_mo,
        data->rtc_yr,
        data->rtc_ctl     
        );

I get this on LCD Display

DS1307 ADDR: 68

31 49 01 17

11 13 03 92

Two minutes later I get:

32 49 01 17

11 13 03 92

Note that the first byte seems to be minutes when I expect Seconds All of the other fields offset by one byte are consistent with the actual time & date

Here is the entire hacked file

//
// DS1307rtc.c
// 
// Demonstrate writing/reading DS1307 RTC via TWI/I2C
//
#define F_CPU 14745600

#include <avr/interrupt.h>
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "../libnerdkits/lcd.h"
#include "../libNoter/TWI.h"

// Define format of RTC data block
typedef struct {
  uint8_t rtc_sec;
  uint8_t rtc_min;
  uint8_t rtc_hour;
  uint8_t rtc_wkDay;
  uint8_t rtc_date;
  uint8_t rtc_mo;
  uint8_t rtc_yr;
  uint8_t rtc_ctl;
} RTC_DATA;

// LCD stream file - enable printf in functions outside of main()
FILE lcd_stream;

// prototype local functions
void rtc_init();
RTC_DATA read_rtc(uint16_t memory_address);

// add for rtc
void write_rtc(uint16_t memory_address, RTC_DATA *w_data);
void show_rtc_data(RTC_DATA *data);
//

void handle_TWI_result(uint8_t return_code);

// -----------------------------------------------------------------
int main() {
    // Initialize the lcd
    lcd_init();
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE); 
    lcd_clear_and_home();

    // initialize eeprom and TWI/I2C
    rtc_init();

    // specify 7 bit device address of eeprom chip
    #define RTC_DEVICE_ID 0b1101000

    fprintf_P(&lcd_stream, PSTR("DS1307 ADDR: %02X"), RTC_DEVICE_ID);

    // specify eeprom memory address for data storage
    #define RTC_DATA_ADDRESS 0x0000
    //#define RTC_DATA_ADDRESS 0x0037

    // create local copy of eeprom data
    RTC_DATA rtc_data;

    // read rtc contents at location 0000
    rtc_data = read_rtc(0);

    // show what we read from the eeprom -
    // note: the very first read on a new eeprom
    // will show uninitalized data
    show_rtc_data(&rtc_data);

    // done
    while(true);
}
// -----------------------------------------------------------------

// define rtc commants according to DS1307 datasheet
typedef struct {
    uint8_t      high_byte;
    uint8_t      low_byte;
    RTC_DATA     rtc_data;
} WRITE_RTC;

typedef struct {
    uint8_t      high_byte;
    uint8_t      low_byte;
} SET_RTC_ADDRESS;

typedef struct  {
    RTC_DATA  rtc_data;
} READ_RTC;

// Create structure pointers for the TWI/I2C buffer
WRITE_RTC               *p_write_rtc;
SET_RTC_ADDRESS         *p_set_rtc_address;
READ_RTC                *p_read_rtc;

// Create TWI/I2C buffer, size to largest command
//            choose 1 ???
//char    TWI_buffer[sizeof(WRITE_EEPROM)];
char    TWI_buffer[sizeof(READ_RTC)];

void rtc_init(){
    // Specify startup parameters for the TWI/I2C driver
    TWI_init(   F_CPU,                      // clock frequency
                100000L,                    // desired TWI/IC2 bitrate
                TWI_buffer,                 // pointer to comm buffer
                sizeof(TWI_buffer),         // size of comm buffer
                &handle_TWI_result          // pointer to callback function
                );

    // Enable interrupts
    sei();

    // Set our structure pointers to the TWI/I2C buffer
    p_write_rtc = (WRITE_RTC *)TWI_buffer;
    p_set_rtc_address = (SET_RTC_ADDRESS *)TWI_buffer;
    p_read_rtc = (READ_RTC *)TWI_buffer;

}

RTC_DATA read_rtc(uint16_t memory_address){
    // send 'set memory address' command to rtc and then read data
    while(TWI_busy);
    p_set_rtc_address->high_byte = memory_address >> 8;
    //p_set_rtc_address->low_byte = memory_address & 0x0F;
    p_set_rtc_address->low_byte = memory_address & 0xFF;
    TWI_master_start_write_then_read(
                        RTC_DEVICE_ID,               // device address of eeprom chip
                        sizeof(SET_RTC_ADDRESS),     // number of bytes to write
                        sizeof(RTC_DATA)             // number of bytes to read
                        );

    // nothing else to do - wait for the data
    while(TWI_busy);
    // return the data
    return(p_read_rtc->rtc_data);
}

// write rtc
void write_rtc(uint16_t memory_address, RTC_DATA *w_data){
    while(TWI_busy);
    p_write_rtc->high_byte = RTC_DATA_ADDRESS >> 8;
    //p_write_rtc->low_byte = RTC_DATA_ADDRESS & 0x0F;
    p_write_rtc->low_byte = memory_address & 0xFF;
    p_write_rtc->rtc_data = *w_data;
    TWI_master_start_write(     RTC_DEVICE_ID,       // device address of eeprom chip
                                sizeof(WRITE_RTC)    // number of bytes to write
                                );
}

// optional callback function for TWI/I2C driver
void handle_TWI_result(uint8_t return_code){
    if(return_code!=TWI_success){
        lcd_line_four();
        fprintf_P(&lcd_stream, PSTR("I2C ERROR - %02X"), return_code); 
    }
}

// format and display rtc data on lcd
void show_rtc_data(RTC_DATA *data){
    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR("%02X %02X %02X %02X" ), 
        data->rtc_sec,
        data->rtc_min,
        data->rtc_hour,
        data->rtc_wkDay
        );
    lcd_line_four();
    fprintf_P(&lcd_stream,PSTR("%02X %02X %02X %02X" ),
        data->rtc_date,
        data->rtc_mo,
        data->rtc_yr,
        data->rtc_ctl     
        );         
}

Post a Reply

Please log in to post a reply.

Did you know that interrupts can be used to trigger pieces of code when events happen? Learn more...