mirror of
				https://github.com/stargieg/bacnet-stack
				synced 2025-10-26 23:35:52 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			517 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			517 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**************************************************************************
 | |
| *
 | |
| * Copyright (C) 2009 Steve Karg <skarg@users.sourceforge.net>
 | |
| * Used algorithm and code from Joerg Wunsch and Ruwan Jayanetti.
 | |
| * http://www.nongnu.org/avr-libc/user-manual/group__twi__demo.html
 | |
| *
 | |
| * 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 <stdbool.h>
 | |
| #include <stdint.h>
 | |
| #include <stdlib.h>
 | |
| #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
 | |
| 
 | |
| /* maximum write cycle time in milliseconds - see datasheet */
 | |
| #ifndef EEPROM_WRITE_CYCLE
 | |
| #define EEPROM_WRITE_CYCLE 5
 | |
| #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
 | |
| 
 | |
| /* Number of iterations is the max amount to wait for write cycle
 | |
|    to complete a full page write */
 | |
| /* .005s/.000025=200 */
 | |
| #define MAX_ITER (((SEEPROM_I2C_CLOCK/1000)/10)*SEEPROM_WRITE_CYCLE)
 | |
| 
 | |
| /*************************************************************************
 | |
| * 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. */
 | |
|     uint16_t timeout = 0xFFFF;
 | |
| 
 | |
| #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
 | |
|     /* 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) {
 | |
|         timeout--;
 | |
|         if (timeout == 0) {
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
|     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:
 | |
|             /* Since the TWI bus is multi-master capable,
 | |
|                there is potential for a bus contention when
 | |
|                one master starts to access the bus. */
 | |
|             goto begin;
 | |
|         default:
 | |
|             /* error: not in start condition */
 | |
|             /* NB: do /not/ send stop condition */
 | |
|             return -1;
 | |
|     }
 | |
|     /* Next, the device slave is going to be reselected using a repeated
 | |
|        start condition which is meant to guarantee that the bus arbitration
 | |
|        will remain at the current master.  This uses the same slave address
 | |
|        (SLA), but this time with read intent (R/~W bit set to 1) in order
 | |
|        to request the device slave to start transfering data from the slave
 | |
|        to the master in the next packet. */
 | |
|     /* 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 */
 | |
|             /* If the EEPROM device is still busy writing one or more cells
 | |
|                after a previous write request, it will simply leave its bus
 | |
|                interface drivers at high impedance, and does not respond to
 | |
|                a selection in any way at all. */
 | |
|             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;
 | |
|     }
 | |
| 
 | |
|     /* This is called master receiver mode: the bus master still supplies
 | |
|        the SCL clock, but the device slave drives the SDA line with the
 | |
|        appropriate data. After 8 data bits, the master responds with an ACK
 | |
|        bit (SDA driven low) in order to request another data transfer from
 | |
|        the slave, or it can leave the SDA line high (NACK), indicating to
 | |
|        the slave that it is going to stop the transfer now.
 | |
|        Assertion of ACK is handled by setting the TWEA bit in TWCR when
 | |
|        starting the current transfer. */
 | |
|     /* 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;
 | |
|     }
 | |
|     /* The control word sent out in order to initiate the transfer of the
 | |
|        next data packet is initially set up to assert the TWEA bit.
 | |
|        During the last loop iteration, TWEA is de-asserted so the client
 | |
|        will get informed that no further transfer is desired. */
 | |
|     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:
 | |
|     /* Except in the case of lost arbitration, all bus transactions
 | |
|        must properly be terminated by the master initiating a
 | |
|        stop condition. */
 | |
|     /* 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;
 | |
|     uint16_t timeout = 0xFFFF;
 | |
| 
 | |
|     /* 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:
 | |
|     /* Writing to the EEPROM device is simpler than reading,
 | |
|        since only a master transmitter mode transfer is needed.
 | |
|        Note that the first packet after the SLA+W selection is
 | |
|        always considered to be the EEPROM address for the next operation.
 | |
|        This packet is exactly the same as the one above sent before
 | |
|        starting to read the device.
 | |
|        In case a master transmitter mode transfer is going to send
 | |
|        more than one data packet, all following packets will be considered
 | |
|        data bytes to write at the indicated address.
 | |
|        The internal address pointer will be incremented after each
 | |
|        write operation. */
 | |
|     /* send start condition */
 | |
|     TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
 | |
|     /* wait for transmission */
 | |
|     while ((TWCR & _BV(TWINT)) == 0) {
 | |
|         timeout--;
 | |
|         if (timeout == 0) {
 | |
|             return -1;
 | |
|         }
 | |
|     }
 | |
|     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;
 | |
| }
 | 
