第八章 I2C控制器
前面一章讲了LPC2138 UART控制器,它的硬件实现的核心是Tx/Rx FIFO,但是I2C控制器硬件上没有Tx/Rx FIFO,个人猜测这个原因可能是UART的读写速率比I2C要高,另外I2C协议有参考时钟SCL,而UART的TXD/RXD都是靠约定波特率和数据帧的奇偶校验位确保数据完整性。
特性
I2C控制器可以工作在Master模式或者Slave模式。(本文仅介绍Master模式)
I2C总线支持多个Master之间仲裁。
I2C总线参考PCLK时钟源,I2C时钟频率可配置。
I2C总线协议
关于I2C总线协议,有很多公开的资料介绍,而且比较简单,此处就不予复述。
其中Start信号、Stop信号、ACK信号、NACK信号、数据发送、数据接收各原子操作的时序都是固定的。但是具体到不同的I2C器件,各I2C器件对I2C的具体时序有具体的要求(比如随机读写和按页读写、按页读写时支持跨页操作或者不支持跨页操作、I2C偏移地址的位宽为1位或者2位等),需要结合I2C器件的datasheet进行编码。
I2C控制器寄存器
1) I2CCONSET
[2] AA Assert ACK Flag
当AA位置1时,Master接收到一个字节的数据时,会发送ACK信号
反之,当AA位置0时,Master接收到一个字节的数据之后,会发送NACK信号
[3] SI I2C Interrupt Flag
当I2C状态发生变化时,SI置位(除了I2CSTAT为0xF8时)
当SI置位时,SCL低电平拉长,数据传输暂停,但是SCL拉高不受SI位影响
SI位只能通过往I2CCONCLR的SIC位写1清除
[4] STO STOP Flag
STO位发送完Stop信号之后,自动清除
[5] STA START Flag
如果不处于Master模式,则进入Master模式并发送Start信号(如果此时总线忙,则等待Stop信号半个时钟)
如果处于Master模式,则发送ReStart信号
如果STA和STO同时置位,则先发送Stop再发送Start
[6] I2EN (不要使用I2EN位去释放I2C总线,而应该用AA位)
2) I2CCONCLR
[2] AAC Assert ACK Clear Bit
[3] SIC I2C Interrupt Clear Bit
[5] STAC
[6] I2ENC
3) I2CSTAT
一共有26种状态码,I2CSTAT值在SI置位期间一直保持。
4) I2CDAT
只有当SI置位时,才可以读写I2CDAT。
在SI置位期间,数据一直保持
EEPROM读写实例
使用I2C 0控制器和AT24C1024 EEPROM芯片实现,I2C控制器工作在Master模式。
在编码的过程中,最仔细研究的是I2C控制器的寄存器以及Master发送和接收的时序图:
Master Transmit模式
Master Receive模式
还有AT24C1024芯片的读写时序图:
另外需要注意的是,每次清SI位进行状态切换时,软件需要插入延迟才能进行数据读写,因为CPU的工作频率原高于I2C控制器的工作频率,需要通过一定的延迟等待硬件执行完成。
i2c.c
#include#include "i2c.h" #include "serial.h" #define I2C_DEBUG 0 #define I2C_ERROR 1 #define I2C_WRITE (0x00) #define I2C_READ (0x01) void i2c_delay(void) { int i = 1000; while(i--); } void i2c_init(void) { /* Configure P0.2~3 as SCL0 & SDA0 */ PINSEL0 &= ~0x000000F0; PINSEL0 |= 0x00000050; /* Configure I2C Freq to 150KHz while PCLK is 30MHz */ I2C0SCLH = 0x64; I2C0SCLL = 0x64; /* Enable I2C Bus */ I2C0CONSET = 0x40; } int i2c_start(void) { /* Start */ I2C0CONSET = 0x20; i2c_delay(); if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x08)) { /* Clear STA bit */ I2C0CONCLR = 0x20; /* return 0 if success */ return 0; } else { /* Clear STA bit */ I2C0CONCLR = 0x20; if (I2C_ERROR) { sendstr("i2c_start I2C0STAT="); sendhex(I2C0STAT); sendstr("\n"); } /* return -1 is failed */ return -1; } } int i2c_restart(void) { /* ReStart */ I2C0CONSET = 0x20; i2c_delay(); if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x10)) { /* Clear STA bit */ I2C0CONCLR = 0x20; /* return 0 if success */ return 0; } else { /* Clear STA bit */ I2C0CONCLR = 0x20; sendstr("i2c_start I2C0STAT="); sendhex(I2C0STAT); sendstr("\n"); /* return -1 is failed */ return -1; } } int i2c_stop(void) { /* Stop, STO is cleared automatically */ I2C0CONSET = 0x10; return 0; } int i2c_send_addr(unsigned char addr) { if (I2C_DEBUG) { sendstr(" I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); sendstr("i2c_send_addr "); sendhex(addr); sendstr("\n"); } I2C0DAT = addr; if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x08 || I2C0STAT == 0x10)) { /* Clear SI bit to send out addr */ I2C0CONCLR = 0x08; } else { sendstr("i2c bus 0 not started\n"); return -1; } i2c_delay(); if (addr & I2C_READ) { /* Master Read */ if (I2C0STAT == 0x40) { /* ACK */ return 0; } else if (I2C0STAT == 0x48) { /* NACK */ return -1; } } else { /* Master Write */ if (I2C0STAT == 0x18) { /* ACK */ return 0; } else if (I2C0STAT == 0x20) { /* NACK */ return -1; } } /* other exception */ if (addr & I2C_READ) { sendstr("i2c bus 0 read exception, I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); } else { sendstr("i2c bus 0 write exception, I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); } return -2; } int i2c_send_byte(unsigned char data) { if (I2C_DEBUG) { sendstr(" I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); sendstr("i2c_send_byte "); sendhex(data); sendstr("\n"); } /* Master Write */ if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x18 || I2C0STAT == 0x28)) { I2C0DAT = data; /* Clear SI bit to send out addr */ I2C0CONCLR = 0x08; } else { sendstr("i2c_send_byte exception, I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); return -2; } i2c_delay(); if (I2C0STAT == 0x28) { /* ACK */ return 0; } else if (I2C0STAT == 0x30) { /* NACK */ return -1; } else { /* exception */ sendstr("i2c bus 0 write data exception, I2CSTAT=0x"); sendhex(I2C0STAT); sendstr("\n"); return -2; } } int i2c_recv_byte(unsigned char *data, unsigned int ack) { unsigned char recv_byte; if (I2C_DEBUG) { sendstr("i2c_recv_byte I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); } if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x40)) { /* Assert ACK bit, send out ACK/NACK after one byte received */ if (ack) I2C0CONSET = 0x04; /* ACK */ else I2C0CONCLR = 0x04; /* NACK */ /* Clear SI bit to release I2CDAT */ I2C0CONCLR = 0x08; i2c_delay(); recv_byte = I2C0DAT; } *data = recv_byte; if (I2C_DEBUG) { sendstr("i2c_recv_byte "); sendhex(recv_byte); sendstr("\n"); } if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x50)) { /* ACK Send Out */ return 0; } else if ((I2C0CONSET & 0x08) && (I2C0STAT == 0x58)) { /* NACK Send Out */ return -1; } else { /* exception occurs */ sendstr("i2c bus 0 recv data exception, I2CSTAT="); sendhex(I2C0STAT); sendstr("\n"); return -2; } } int i2c_write(unsigned char addr, unsigned int offset, unsigned int off_len, unsigned char *data, unsigned int len) { int ret = 0; int i = 0; unsigned char byte; if (addr & 0x80) { sendstr("i2c address invalid\n"); return -3; } if (off_len > sizeof(offset)) { sendstr("i2c offset length out of range\n"); return -3; } if (0 == data) { sendstr("i2c data pointer is NULL\n"); return -3; } if (len <= 0) { sendstr("i2c data length should be lager than zero\n"); return -3; } ret = i2c_start(); if (ret < 0) { sendstr("i2c_write start failed\n"); return -1; } ret = i2c_send_addr((addr << 1) | I2C_WRITE); if (ret == -2) { sendstr("i2c_write send addr failed\n"); return -2; } else if (ret == -1) { /* NACK */ i2c_stop(); return -1; } for (i = 0; i < off_len; i++) { byte = (unsigned char)(offset >> (8 * (off_len - i - 1))); ret = i2c_send_byte(byte); if (ret == -2) { sendstr("i2c_write send offset failed\n"); return -2; } else if (ret == -1) { i2c_stop(); return -1; } } for (i = 0; i < len; i++) { ret = i2c_send_byte(data[len - i - 1]); if (ret == -2) { sendstr("i2c_write send data failed\n"); return -2; } else if (ret == -1) { i2c_stop(); return 0; } } i2c_stop(); return 0; } int i2c_read(unsigned char addr, unsigned int offset, unsigned int off_len, unsigned char *data, unsigned int len) { int ret = 0; int i = 0; unsigned char byte = 0; if (addr & 0x80) { sendstr("i2c address invalid\n"); return -3; } if (off_len > sizeof(offset)) { sendstr("i2c offset length out of range\n"); return -3; } if (0 == data) { sendstr("i2c data pointer is NULL\n"); return -3; } if (len <= 0) { sendstr("i2c data length should be lager than zero\n"); return -3; } ret = i2c_start(); if (ret < 0) { sendstr("i2c_read start failed\n"); return -1; } ret = i2c_send_addr((addr << 1) | I2C_WRITE); if (ret == -2) { sendstr("i2c_read send write addr failed\n"); return -2; } else if (ret == -1) { /* NACK */ i2c_stop(); return -1; } for (i = 0; i < off_len; i++) { byte = (unsigned char)(offset >> (8 * (off_len - i - 1))); ret = i2c_send_byte(byte); if (ret == -2) { sendstr("i2c_read send offset failed\n"); return -2; } else if (ret == -1) { i2c_stop(); return -1; } } ret = i2c_restart(); if (ret < 0) { sendstr("i2c_read restart failed\n"); return -1; } ret = i2c_send_addr((addr << 1) | I2C_READ); if (ret == -2) { sendstr("i2c_read send read addr failed\n"); return -2; } else if (ret == -1) { /* NACK */ i2c_stop(); return -1; } i = 0; do { if (i == (len - 1)) { /* Send Out NACK after last byte */ ret = i2c_recv_byte(&byte, 0); } else { /* Send Out ACK except last byte */ ret = i2c_recv_byte(&byte, 1); } data[len - i - 1] = byte; if (ret == -2) { /* exception occurs */ return ret; } if (I2C_DEBUG) { sendstr("after recv byte, I2CSTAT="); sendhex(I2C0STAT); sendstr(" I2CDAT="); sendhex(I2C0DAT); sendstr("\n"); } i++; } while (i < len); i2c_stop(); return 0; }
eeprom.c
#include "i2c.h" #include "eeprom.h" #define AT24C1024_OFFSET_LEN (2) #define AT24C1024_REG_LEN (1) int eeprom_write_byte(unsigned int offset, unsigned char data) { return i2c_write(I2C_ADDR, offset, AT24C1024_OFFSET_LEN, &data, AT24C1024_REG_LEN); } int eeprom_read_byte(unsigned int offset, unsigned char *data) { return i2c_read(I2C_ADDR, offset, AT24C1024_OFFSET_LEN, data, AT24C1024_REG_LEN); }