fx2/firmware/lib/i2c.c

314 lines
9.3 KiB
C

/**
* Copyright (C) 2009 Ubixum, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**/
#include <stdio.h> // NOTE this needs deleted
#include <fx2regs.h>
#include <fx2macros.h>
#include <i2c.h>
#include <delay.h>
//#define DEBUG_I2C 1
#ifdef DEBUG_I2C
#define i2c_printf(...) printf(__VA_ARGS__)
#else
#define i2c_printf(...)
#endif
volatile __xdata BOOL cancel_i2c_trans;
#define CHECK_I2C_CANCEL() if (cancel_i2c_trans) return FALSE
/**
*
1. Set START=1. If BERR=1, start timer*.
2. Write the 7-bit peripheral address and the direction bit (0 for a write) to I2DAT.
3. Wait for DONE=1 or for timer to expire*. If BERR=1, go to step 1.
4. If ACK=0, go to step 9.
5. Load I2DAT with a data byte.
6. Wait for DONE=1*. If BERR=1, go to step 1.
7. If ACK=0, go to step 9.
8. Repeat steps 5-7 for each byte until all bytes have been transferred.
9. Set STOP=1. Wait for STOP = 0 before initiating another transfer.
**/
BOOL
i2c_write(BYTE addr, WORD len, BYTE * addr_buf, WORD len2, BYTE * data_buf)
{
WORD cur_byte;
WORD total_bytes = len + len2; // NOTE overflow error?
BYTE retry_count = 2; // two tries to write address/read ack
cancel_i2c_trans = FALSE;
//BOOL wait=FALSE; // use timer if needed
// 1. Set START=1. If BERR=1, start timer*.
step1:
CHECK_I2C_CANCEL();
cur_byte = 0;
I2CS |= bmSTART;
if (I2CS & bmBERR) {
i2c_printf("Woops.. need to do the timer\n");
delay(10); // way too long probably
goto step1;
}
// 2. Write the 7-bit peripheral address and the direction bit (0 for a write) to I2DAT.
I2DAT = addr << 1;
// 3. Wait for DONE=1 or for timer to expire*. If BERR=1, go to step 1.
while (!(I2CS & bmDONE) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
if (I2CS & bmBERR) {
i2c_printf("bmBERR, going to step 1\n");
goto step1;
}
// 4. If ACK=0, go to step 9.
if (!(I2CS & bmACK)) {
I2CS |= bmSTOP;
while ((I2CS & bmSTOP) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
--retry_count;
if (!retry_count) {
i2c_printf("No ack after writing address.! Fail\n");
return FALSE;
}
delay(10);
goto step1;
}
// 8. Repeat steps 5-7 for each byte until all bytes have been transferred.
while (cur_byte < total_bytes) {
// 5. Load I2DAT with a data byte.
I2DAT =
cur_byte <
len ? addr_buf[cur_byte] : data_buf[cur_byte - len];
++cur_byte;
// 6. Wait for DONE=1*. If BERR=1, go to step 1.
while (!(I2CS & bmDONE) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
if (I2CS & bmBERR) {
i2c_printf("bmBERR on byte %d. Going to step 1\n",
cur_byte - 1);
goto step1;
//return FALSE;
}
// 7. If ACK=0, go to step 9.
if (!(I2CS & bmACK)) {
I2CS |= bmSTOP;
while ((I2CS & bmSTOP) && !cancel_i2c_trans);
i2c_printf("No Ack after byte %d. Fail\n", cur_byte - 1);
return FALSE;
}
}
// 9. Set STOP=1. Wait for STOP = 0 before initiating another transfer.
//real step 9
I2CS |= bmSTOP;
while ((I2CS & bmSTOP) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
return TRUE;
}
/*
trm 13.4.4
1. Set START=1. If BERR = 1, start timer*.
2. Write the 7-bit peripheral address and the direction bit (1 for a read) to I2DAT.
3. Wait for DONE=1 or for timer to expire*. If BERR=1, go to step 1.
4. If ACK=0, set STOP=1 and go to step 15.
5. Read I2DAT to initiate the first burst of nine SCL pulses to clock in the first byte from the slave.
Discard the value that was read from I2DAT.
6. Wait for DONE=1. If BERR=1, go to step 1.
7. Read the just-received byte of data from I2DAT. This read also initiates the next read transfer.
8. Repeat steps 6 and 7 for each byte until ready to read the second-to-last byte.
9. Wait for DONE=1. If BERR=1, go to step 1.
10. Before reading the second-to-last I2DAT byte, set LASTRD=1.
11. Read the second-to-last byte from I2DAT. With LASTRD=1, this initiates the final byte read on
the bus.
12. Wait for DONE=1. If BERR=1, go to step 1.
13. Set STOP=1.
14. Read the final byte from I2DAT immediately (the next instruction) after setting the STOP bit. By
reading I2DAT while the "stop" condition is being generated, the just-received data byte will be
retrieved without initiating an extra read transaction (nine more SCL pulses) on the I²Cbus.
15. Wait for STOP = 0 before initiating another transfer
*/
/*
* timer should be at least as long as longest start-stop interval on the bus
serial clock for i2c bus runs at 100khz by default and can run at 400khz for devices that support it
start-stop interval is about 9 serial clock cycles
400KHZ bit 0=100khz, 1=400khz
how many cycles at XTAL cycles/second = 9 cycles at 400k (or 100k) cycles/second
timeout = n i2c cycles / I2C cycles/sec = timeout seconds
timeout seconds * XTAL cycles/sec = XTAL cycles
9 / 400 (or 100) * (XTAL)
*/
BOOL
i2c_read(BYTE addr, WORD len, BYTE * buf)
{
BYTE tmp;
WORD cur_byte;
cancel_i2c_trans = FALSE;
//WORD timeout_cycles = (WORD)(9.0 * XTAL / I2CFREQ );
// 1. Set START=1. If BERR = 1, start timer*.
start:
CHECK_I2C_CANCEL();
cur_byte = 0;
I2CS |= bmSTART;
if (I2CS & bmBERR) {
i2c_printf("Woops, step1 BERR, need to do timeout\n");
delay(10); // NOTE way too long
goto start;
}
// 2. Write the 7-bit peripheral address and the direction bit (1 for a read) to I2DAT.
I2DAT = (addr << 1) | 1; // last 1 for read
// 3. Wait for DONE=1 or for timer to expire*. If BERR=1, go to step 1.
while (!(I2CS & bmDONE) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
if (I2CS & bmBERR)
goto start;
// 4. If ACK=0, set STOP=1 and go to step 15.
if (!(I2CS & bmACK)) {
I2CS |= bmSTOP;
while ((I2CS & bmSTOP) && !cancel_i2c_trans);
return FALSE;
}
// with only one byte to read, this needs set here.
// (In this case, the tmp read is the 2nd to last read)
if (len == 1)
I2CS |= bmLASTRD;
// 5. Read I2DAT to initiate the first burst of nine SCL pulses to clock in the first byte from the slave.
// Discard the value that was read from I2DAT.
tmp = I2DAT; // discard read
while (len > cur_byte + 1) { // reserve last byte read for after the loop
// 6. Wait for DONE=1. If BERR=1, go to step 1.
// 9. Wait for DONE=1. If BERR=1, go to step 1.
while (!(I2CS & bmDONE) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
if (I2CS & bmBERR)
goto start;
// 10. Before reading the second-to-last I2DAT byte, set LASTRD=1.
if (len == cur_byte + 2) // 2nd to last byte
I2CS |= bmLASTRD;
// 7. Read the just-received byte of data from I2DAT. This read also initiates the next read transfer.
// 11. Read the second-to-last byte from I2DAT. With LASTRD=1, this initiates the final byte read on
// the bus.
buf[cur_byte++] = I2DAT;
// 8. Repeat steps 6 and 7 for each byte until ready to read the second-to-last byte.
}
//12. Wait for DONE=1. If BERR=1, go to step 1.
while (!(I2CS & bmDONE) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
if (I2CS & bmBERR)
goto start;
// 13. Set STOP=1.
I2CS |= bmSTOP;
// 14. Read the final byte from I2DAT immediately (the next instruction) after setting the STOP bit. By
// reading I2DAT while the "stop" condition is being generated, the just-received data byte will be
// retrieved without initiating an extra read transaction (nine more SCL pulses) on the I²Cbus.
buf[cur_byte] = I2DAT; // use instead of buffer addressing so next instruction reads I2DAT
while ((I2CS & bmSTOP) && !cancel_i2c_trans);
CHECK_I2C_CANCEL();
return TRUE;
}
BOOL
eeprom_write(BYTE prom_addr, WORD addr, WORD length, BYTE * buf)
{
BYTE addr_len = 0;
// 1st bytes of buffer are address and next byte is value
BYTE data_buffer[3];
WORD cur_byte = 0;
#ifdef DEBUG_I2C
if (EEPROM_TWO_BYTE) {
i2c_printf("Two Byte EEProm Address detected.\n");
} else {
i2c_printf("Single Byte EEProm address detected.\n");
}
#endif
while (cur_byte < length) {
addr_len = 0;
if (EEPROM_TWO_BYTE) {
data_buffer[addr_len++] = MSB(addr);
}
data_buffer[addr_len++] = LSB(addr);
data_buffer[addr_len++] = buf[cur_byte++];
i2c_printf("%02x ", data_buffer[addr_len - 1]);
if (!i2c_write(prom_addr, addr_len, data_buffer, 0, NULL))
return FALSE;
++addr; // next byte goes to next address
}
return TRUE;
}
BOOL
eeprom_read(BYTE prom_addr, WORD addr, WORD length, BYTE * buf)
{
BYTE eeprom_addr[2];
BYTE addr_len = 0;
if (EEPROM_TWO_BYTE)
eeprom_addr[addr_len++] = MSB(addr);
eeprom_addr[addr_len++] = LSB(addr);
// write the address we want to read to the prom
//printf ("Starting Addr Write with addr len %d\n", addr_len);
if (!i2c_write(prom_addr, addr_len, eeprom_addr, 0, NULL))
return FALSE;
//printf ( "Starting read\n" );
if (!i2c_read(prom_addr, length, buf))
return FALSE;
return TRUE;
}