I2C总线在嵌入式系统中占据重要的地位,由于占用的引脚少及其传输稳定,所以很多设备接口都使用I2C总线。
I2C是主从设备。典型的接口如下图。每个设备都有着唯一的地址(地址可以配置、有些芯片是通过配置硬件的引脚或者是配置相应的寄存器),以便和主设备进行通信。IIC属于主从结果,每次通信都是由master来发起,从方被动接受通信。通信过程中的时钟始终都是由master来产生。
IIC的协议:
- 空闲状态:SDA和SCL都应该保持高电平。master发起起始条件后总线会进入busy状态,通信结束后立刻进入空闲。
- 数据的有效性:在时钟为高电平期间,进行数据采集。时钟为低电平的时候数据线上的数据可以发生变化。
- 起止条件:SCL为高电平时,SDA由高变低是起始信号,反则是停止信号。起止信号都是由主设备发出。
- 应答:每次传输完一个字节的数据都需要有ACK,应答信号由接收方发出,用来表示已经收到数据。是否需要ACK可以通过设置寄存器来控制。
- 传输单位:每次都是以字节为单位进行传输的(MSB),没传输完一个字节,在没有新的数据写入IICDS前,SCL一直为低电平,只有在写入新的数据后,SCL才会被释放。接受也是同样的,只有在读出数据后SCL才会被释放。从而进行下次数据的传输。
- 数据格式:在IIC数据线上传输数据是高位在前,起始地址后的数据是从机的地址(7bit+方向 w/r)。
- 进入master模式:发送起始信号进入。SCL由主设备生成。
- 传输终止:①master发出从设备的地址后,没有收到从设备的地址ACK时。SDA保持高电平。这种情况下master应该生成结束条件用来终止传输。②在master接收要终止传输,传输完最后一个字节数据不生成ACK,slave释放SDA,master产生结束条件。
在很多处理器中都集成了IIC控制器,帮助我们实现了这些复杂的时序要求。我们可以通过控制IIC控制器来控制IIC的工作。
S3C2440中集成了IIC控制器,所以我们只需要操作寄存器就可以实现对IIC的通信了。下面的内容是针对s3c2440芯片的IIC操作。别的芯片也是类似的,需要参考芯片手册。
s3c2240 master rx和tx如下:
在rx/tx中接收到一个字节的数据会产生中断(都在中断里面处理,保持中断处理当前的数据)。①在发送数据模式中,一个字节的数据发送完后,产生中断。SCL线被拉低,在需要发送的新数据写入IICDS后,SCL才会释放。②在接收数据模式中,一个字节的数据接收完成后,也会产生中断,同样SCL线被拉低,在IICDS寄存器中数据被读取完成后,SCL线才会被释放,用来传输新的数据。
操作EEPROM时,在读取最后一个字节的数据后不产生ACK信号,这样是为了产生结束条件。在发送模式需要先写入数据到IICDS然后在清除IIC中断。s3c2440中每次操作IIC都需要往IICSTAT中bit4写入1.
#include "iic_controller.h"
#define IICCON (*(volatile unsigned int*)0x54000000)
#define IICSTAT (*(volatile unsigned int*)0x54000004)
#define IICADD (*(volatile unsigned int*)0x54000008)
#define IICDS (*(volatile unsigned int*)0x5400000C)
#define GPECON (*(volatile unsigned int*)0x56000040)
#define IIC_IRQ_NUM (27)
#define IIC_NO_SLAVE (-2)
#define IIC_ERR (-1)
#define IIC_END (0)
static P_iic_msg p_current_transfer_msg;
/*具体的芯片实现自己的iic控制器*/
/*name
* init 初始化函数
*master_xfer 传输函数
*/
void iic_tx_end(int flags)
{
IICSTAT = 0xd0;
IICCON &=~(1<<4);
p_current_transfer_msg->status = flags;
head_delayms(1);
}
void iic_rx_end(int flags)
{
IICSTAT = 0x90;
IICCON &=~(1<<4);
p_current_transfer_msg->status = flags;
head_delayms(1);
}
int istransfer_last(void)
{
if(p_current_transfer_msg->cnt_complete == p_current_transfer_msg->len-1){
return 1;
}else{
return 0;
}
}
void iic_irq_handler(int irq)
{
puts_("s3c2440 iic irq handler\n\r");
/*中断服务函数,在中断中处理数据的传输,每传输完一个字节的数据都会产生一个中断*/
p_current_transfer_msg->cnt_complete++;
/*每次传输的第一个中断都是处理从设备地址的ACK*/
if(p_current_transfer_msg->flags ==0){/*write*/
/*对于第一个中断是检测从设备是否存在*/
/*如果ACK,则存在从设备,继续传输,否则结束传输
*/
if(p_current_transfer_msg->cnt_complete ==0){/*第一次传输的ACK*/
if(IICSTAT &1){
/*没有ACK结束传输
*往IICSTAT中写入0XD0,清除中断pending。等待停止信号的产生。
*/
iic_tx_end(IIC_NO_SLAVE);
puts_("---no ack---\n\r");
return ;
}
}
if(p_current_transfer_msg->cnt_complete < p_current_transfer_msg->len){
if(IICSTAT &1){/*没有产生ACK错误结束*/
iic_tx_end(IIC_ERR);
puts_("---tx is no ack---\n\r");
return;
}else{/*有ACK,继续传输数据*/
/*把数据写入到IICDS中,然后清除pending*/
IICDS = p_current_transfer_msg->buf[p_current_transfer_msg->cnt_complete];
IICCON &=~(1<<4);
}
}else{
/*数据完成结束*/
iic_tx_end(IIC_END);
puts_("---tx is complete---\n\r");
return ;
}
}else{/*read*/
if(p_current_transfer_msg->cnt_complete ==0){
/*第一次中断,如果有ACK则恢复传输,否则的结束*/
if(IICSTAT&1){
iic_rx_end(IIC_NO_SLAVE);
puts_("---rx is no ack---\n\r");
return ;
}else{
/*恢复继续传输*/
IICCON &=~(1<<4);
return ;
}
}else{
if(p_current_transfer_msg->cnt_complete < p_current_transfer_msg->len){
/*没有传输结束继续传输*/
p_current_transfer_msg->buf[p_current_transfer_msg->cnt_complete-1] = IICDS;
if(istransfer_last()){
/*最后一个数据传输,不用ACK*/
IICCON&=~(1<<7);
}
IICCON &= ~(1<<4);
return;
}else{
/*传输完成*/
iic_rx_end(IIC_END);
puts_("---rx is complete---\n\r");
return;
}
}
}
}
int s3c2440_i2c_con_init(void)
{
puts_("s3c2440 iic init\n\r");
/*初始化IIC使用的引脚,配置成IIC*/
GPECON &=~(0xf << 28);
GPECON |= (0xa <<28);
/*设置时钟*/
/*bit 7 :ACK是否使能,bit6选择IIC的时钟源bit5:IIC中断是否使能 bit 中断挂起bit[3:0]:时钟预分频*/
IICCON = (1<<7) | (0<<6) | (1<<5) |(7<<0);
/*注册中断服务函数*/
register_irq(IIC_IRQ_NUM,iic_irq_handler);
}
int do_master_tx(P_iic_msg msg)
{
msg->cnt_complete = -1;
msg->status = 0;
p_current_transfer_msg = msg;
/*设置寄存器启动传输*/
/*1.配置为tx模式*/
IICSTAT =(1<<4);
/*2.把从设备的地址写入数据寄存器*/
/*3.往IICSTAT中写入0xf0数据即将被发送出去,将导致中断产生*/
IICDS = (msg->addr<<1) | 0;
IICSTAT = 0Xf0;
/*后续的传输由中断驱动*/
/*循环等待中断处理完毕*/
while(!msg->status && msg->cnt_complete < msg->len);
if(msg->status){
return -1;
}else{
return 0;
}
}
int do_master_rx(P_iic_msg msg)
{
msg->cnt_complete = -1;
msg->status = 0;
p_current_transfer_msg = msg;
/*设置寄存器启动传输*/
/*1.配置为rx模式*/
IICSTAT =(1<<4);
/*2.把从设备的地址写入数据寄存器*/
/*3.往IICSTAT中写入0xb0数据即将被发送出去,将导致中断产生*/
IICDS = (msg->addr<<1) | 1;
IICSTAT = 0Xb0;
/*后续的传输由中断驱动*/
/*循环等待中断处理完毕*/
while(!msg->status && msg->cnt_complete <msg->len) ;
if(msg->status){
return -1;
}else{
return 0;
}
}
int s3c2440_master_xfer(P_iic_msg msgs, int num)
{
int i,ret ;
for(i = 0;i< num;i++){
if(msgs[i].flags ==0){
ret =do_master_tx(&msgs[i]);
if(ret){return ret;}
}else{
ret =do_master_rx(&msgs[i]);
if(ret){return ret;}
}
}
return 0;
}
static iic_controller_t s3c2440_iic_con={
.name = "s3c2440",
.init = s3c2440_i2c_con_init,
.master_xfer = s3c2440_master_xfer,
};
void add_s3c2440_con(void)
{
register_i2c_controller(&s3c2440_iic_con);
}