/************************************************************************** * * Copyright (C) 2009 Steve Karg * Used algorithm and code from Joerg Wunsch and Ruwan Jayanetti. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *********************************************************************/ #include #include #include #include "hardware.h" /* me */ #include "seeprom.h" /* the SEEPROM chip select bits A2, A1, and A0 are grounded */ /* control byte is 0xAx */ #ifndef SEEPROM_I2C_ADDRESS #define SEEPROM_I2C_ADDRESS 0xA0 #endif /* SEEPROM Clock Frequency */ #ifndef SEEPROM_I2C_CLOCK #define SEEPROM_I2C_CLOCK 400000UL #endif /* max number of bytes that can be written in a single write */ #ifndef SEEPROM_PAGE_SIZE #define SEEPROM_PAGE_SIZE 128 #endif /* word addressing - is it 8-bit or 16-bit */ #ifndef SEEPROM_WORD_ADDRESS_16BIT #define SEEPROM_WORD_ADDRESS_16BIT 1 #endif /* The lower 3 bits of TWSR are reserved on the ATmega163 */ #define TW_STATUS_MASK (_BV(TWS7)|_BV(TWS6)|_BV(TWS5)|_BV(TWS4)|_BV(TWS3)) /* start condition transmitted */ #define TW_START 0x08 /* repeated start condition transmitted */ #define TW_REP_START 0x10 /* ***Master Transmitter*** */ /* SLA+W transmitted, ACK received */ #define TW_MT_SLA_ACK 0x18 /* SLA+W transmitted, NACK received */ #define TW_MT_SLA_NACK 0x20 /* data transmitted, ACK received */ #define TW_MT_DATA_ACK 0x28 /* data transmitted, NACK received */ #define TW_MT_DATA_NACK 0x30 /* arbitration lost in SLA+W or data */ #define TW_MT_ARB_LOST 0x38 /* ***Master Receiver*** */ /* arbitration lost in SLA+R or NACK */ #define TW_MR_ARB_LOST 0x38 /* SLA+R transmitted, ACK received */ #define TW_MR_SLA_ACK 0x40 /* SLA+R transmitted, NACK received */ #define TW_MR_SLA_NACK 0x48 /* data received, ACK returned */ #define TW_MR_DATA_ACK 0x50 /* data received, NACK returned */ #define TW_MR_DATA_NACK 0x58 /* SLA+R address */ #define TW_READ 1 /* SLA+W address */ #define TW_WRITE 0 /* * Maximal number of iterations to wait for a device to respond for a * selection. Should be large enough to allow for a pending write to * complete, but low enough to properly abort an infinite loop in case * a slave is broken or not present at all. With 100 kHz TWI clock, * transfering the start condition and SLA+R/W packet takes about 10 * s. The longest write period is supposed to not exceed ~ 10 ms. * Thus, normal operation should not require more than 100 iterations * to get the device to respond to a selection. */ #define MAX_ITER 200 /************************************************************************* * DESCRIPTION: Return bytes from SEEPROM memory at address * RETURN: number of bytes read, or -1 on error * NOTES: none **************************************************************************/ int seeprom_bytes_read( uint16_t eeaddr, /* SEEPROM starting memory address */ uint8_t * buf, /* data to store */ int len) { /* number of bytes of data to read */ uint8_t sla, twcr, n = 0; int rv = 0; uint8_t twst; /* status - only valid while TWINT is set. */ #if SEEPROM_WORD_ADDRESS_16BIT /* 16bit address devices need only TWI Device Address */ sla = SEEPROM_I2C_ADDRESS; #else /* patch high bits of EEPROM address into SLA */ sla = SEEPROM_I2C_ADDRESS | (((eeaddr >> 8) & 0x07) << 1); #endif /* Note [8] First cycle: master transmitter mode */ restart: if (n++ >= MAX_ITER) { return -1; } begin: /* send start condition */ TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_REP_START: /* OK, but should not happen */ case TW_START: break; case TW_MT_ARB_LOST: /* Note [9] */ goto begin; default: /* error: not in start condition */ /* NB: do /not/ send stop condition */ return -1; } /* Note [10] */ /* send SLA+W */ TWDR = sla | TW_WRITE; /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_SLA_ACK: break; case TW_MT_SLA_NACK: /* nack during select: device busy writing */ /* Note [11] */ goto restart; case TW_MT_ARB_LOST: /* re-arbitrate */ goto begin; default: /* must send stop condition */ goto error; } #if SEEPROM_WORD_ADDRESS_16BIT /* 16 bit word address device, send high 8 bits of addr */ TWDR = (eeaddr >> 8); /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_DATA_ACK: break; case TW_MT_DATA_NACK: goto quit; case TW_MT_ARB_LOST: goto begin; default: /* must send stop condition */ goto error; } #endif /* low 8 bits of addr */ TWDR = eeaddr; /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_DATA_ACK: break; case TW_MT_DATA_NACK: goto quit; case TW_MT_ARB_LOST: goto begin; default: /* must send stop condition */ goto error; } /* Note [12] Next cycle(s): master receiver mode */ /* send repeated start condition */ TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_START: /* OK, but should not happen */ case TW_REP_START: break; case TW_MT_ARB_LOST: goto begin; default: goto error; } /* send SLA+R */ TWDR = sla | TW_READ; /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MR_SLA_ACK: break; case TW_MR_SLA_NACK: goto quit; case TW_MR_ARB_LOST: goto begin; default: goto error; } /* Note [13] */ twcr = _BV(TWINT) | _BV(TWEN) | _BV(TWEA); for (; len > 0; len--) { if (len == 1) { /* send NAK this time */ twcr = _BV(TWINT) | _BV(TWEN); } /* clear int to start transmission */ TWCR = twcr; /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MR_DATA_NACK: /* force end of loop */ len = 0; /* FALLTHROUGH */ case TW_MR_DATA_ACK: *buf = TWDR; buf++; rv++; break; default: goto error; } } quit: /* Note [14] */ /* send stop condition */ TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); return rv; error: rv = -1; goto quit; } /************************************************************************* * DESCRIPTION: Write some data and wait until it is sent * RETURN: number of bytes written, or -1 on error * NOTES: only writes from offset to end of page. **************************************************************************/ static int seeprom_bytes_write_page( uint16_t eeaddr, /* SEEPROM starting memory address */ uint8_t * buf, /* data to send */ int len) { /* number of bytes of data */ uint8_t sla, n = 0; int rv = 0; uint16_t endaddr; uint8_t twst; /* status - only valid while TWINT is set. */ uint16_t page_end_addr; /* limit the length to end of the EEPROM page */ page_end_addr = eeaddr | (SEEPROM_PAGE_SIZE - 1); if ((eeaddr + len) > page_end_addr) { endaddr = page_end_addr + 1; len = endaddr - eeaddr; } #if SEEPROM_WORD_ADDRESS_16BIT /* 16bit address devices need only TWI Device Address */ sla = SEEPROM_I2C_ADDRESS; #else /* patch high bits of EEPROM address into SLA */ sla = SEEPROM_I2C_ADDRESS | (((eeaddr >> 8) & 0x07) << 1); #endif restart: if (n++ >= MAX_ITER) { return -1; } begin: /* Note [15] */ TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); /* send start condition */ while ((TWCR & _BV(TWINT)) == 0); /* wait for transmission */ twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_REP_START: /* OK, but should not happen */ case TW_START: break; case TW_MT_ARB_LOST: goto begin; default: /* error: not in start condition */ /* NB: do /not/ send stop condition */ return -1; } /* send SLA+W */ TWDR = sla | TW_WRITE; /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_SLA_ACK: break; case TW_MT_SLA_NACK: /* nack during select: device busy writing */ goto restart; case TW_MT_ARB_LOST: /* re-arbitrate */ goto begin; default: /* must send stop condition */ goto error; } #if SEEPROM_WORD_ADDRESS_16BIT /* 16 bit word address device, send high 8 bits of addr */ TWDR = (eeaddr >> 8); /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_DATA_ACK: break; case TW_MT_DATA_NACK: goto quit; case TW_MT_ARB_LOST: goto begin; default: /* must send stop condition */ goto error; } #endif /* low 8 bits of addr */ TWDR = eeaddr; /* clear interrupt to start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0) { }; twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_DATA_ACK: break; case TW_MT_DATA_NACK: goto quit; case TW_MT_ARB_LOST: goto begin; default: /* must send stop condition */ goto error; } for (; len > 0; len--) { TWDR = *buf; /* start transmission */ TWCR = _BV(TWINT) | _BV(TWEN); /* wait for transmission */ while ((TWCR & _BV(TWINT)) == 0); twst = TWSR & TW_STATUS_MASK; switch (twst) { case TW_MT_DATA_NACK: /* device write protected -- Note [16] */ goto error; case TW_MT_DATA_ACK: buf++; rv++; break; default: goto error; } } quit: /* send stop condition */ TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); return rv; error: rv = -1; goto quit; } /************************************************************************* * DESCRIPTION: Write some data and wait until it is sent * RETURN: number of bytes written, or -1 on error * NOTES: * When the word address, internally generated, * reaches the page boundary, the following * byte is placed at the beginning of the same * page. If more than 64 data words are * transmitted to the EEPROM, the data word * address will "roll over" and previous data will be * overwritten. The address "roll over" during write * is from the last byte of the current page to the * first byte of the same page. **************************************************************************/ int seeprom_bytes_write( uint16_t off, /* SEEPROM starting memory address */ uint8_t * buf, /* data to send */ int len) { /* number of bytes of data */ int status = 0; int rv = 0; while (len) { status = seeprom_bytes_write_page(off, buf, len); if (status <= 0) { if (rv == 0) { rv = status; } break; } buf += status; off += status; len -= status; rv += status; } return rv; } /************************************************************************* * Description: Initialize the SEEPROM TWI connection * Returns: none * Notes: none **************************************************************************/ void seeprom_init( void) { /* bit rate prescaler */ TWSR = 0; TWCR = _BV(TWEN) | _BV(TWEA); /* bit rate */ /* SCL freq = F_CPU/(16+2*TWBR*4^TWPS) */ /* since TWPS in TWSR is set to zero, 4^TWPS resolves to 1 */ TWBR = (F_CPU / SEEPROM_I2C_CLOCK - 16) / 2; /* my address */ TWAR = 0; }