Mini2440裸机开发之I2C(AT24C08)
在通信协议-I2C小节,我们已经对I2C协议以及AT24C08芯片进行了详细的介绍,这里就不在重复赘述。
一、S3C2440上的I2C
1.1 I2C概述
I2C的使用位于S3C2440芯片手册的第20章。S3C2440可以支持一个多主控I2C总线串行接口。一条专用串行数据线(SDA)和一条专用串行时钟线(SCL)连接到I2C总线的总线主控和外设之间的信息。SDA和SCL线都为双向的。
为了控制多主控IIC总线操作,必须写入值到以下寄存器中:
- 多主控IIC总线控制寄存器,IICCON;
- 多主控IIC总线控制/状态寄存器,IICSTAT;
- 多主控IIC总线Tx/Rx数据移位寄存器,IICDS;
- 多主控IIC总线地址寄存器,IICADD;
1.2 I2C方块图
可以通过编程IICCON寄存器的4位预分频器值来控制串行时钟SCL的频率,I2C总线接口地址被存储在IICADD寄存器。
1.3 I2C总线接口
S3C2440的I2C总线接口有 4 种工作模式:
- 主机发送模式;
- 主机接收模式;
- 从机发送模式;
- 从机接收模式;
二、I2C相关寄存器
2.1 IIC总线控制寄存器IICCON
寄存器 | 地址 | R/W | 描述 | 复位值 |
IICCON | 0x54000000 | R/W | IIC总线控制寄存器 | 0x0X |
寄存器位信息:
IICCON | 位 | 描述 | 初始状态 |
应答发生 | [7] |
IIC总线应答使能位 0:禁止 1:允许 对于发送模式。不需要配置ack应答信号 对于接收模式,设置为1,让它在第9个CLK发出ack应答信号 |
0 |
TX时钟源选择 | [6] |
SCL时钟源选择位 0:IICCLK = PCLK/16 1:IICCLK=PCLK/512 |
0 |
TX/RX中断 | [5] |
IIC总线TX/RX中断使能/禁止位 0:禁止 1:允许 |
0 |
中断挂起标志 | [4] |
IIC总线TX/RX中断标志位。 当此位为1时,SCL被拉低,IIC传输停止; 如果要继续传输,需要写入0清除它; 当读出此位为0时,表示没有中断发生,当读出此位为1时表示有中断发生; |
0 |
发送时钟值 | [3:0] |
IIC总线发送时钟预分频系数: SCL = IICCLK/(IICCON[3:0]+1) |
未定义 |
2.2 IIC总线控制/状态寄存器IICSTAT
寄存器 | 地址 | R/W | 描述 | 复位值 |
IICSTAT | 0x54000004 | R/W | IIC总线控制/状态寄存器 | 0x0 |
寄存器位信息:
IICSTAT | 位 | 描述 | 初始状态 |
模式选择 | [7:6] |
模式选择 00:从接收模式 01:从发送模式 10:主接收模式 11:主发送模式 |
00 |
忙信号状态/起始停止条件 | [5] |
IIC总线忙信号状态位 当读的时候,0表示not busy,1表示busy 当写的时候,0表示写入STOP,1表示写入START |
0 |
串行输出 | [4] |
IIC总线数据输出使能/禁止: 0:禁止 1:使能 |
00 |
仲裁状态标志位 | [3] |
IIC总线仲裁过程状态标志位 0:总线仲裁成功 1:串行I/O间总线仲裁失败 |
0 |
从地址状态标志 | [2] |
IIC总线从地址状态标志位 0:发现起始/停止条件清除 1:接收到从地址与IICADD中地址值匹配 |
0 |
地址零状态标志 | [1] |
IIC总线地址零状态标志位 0:发现起始/停止条件清除 1:接收从地址为0x000000b |
0 |
最后收到位状态标志 | [0] |
IIC总线最后收到位状态标志 表示I2C总线上的第9个时钟周期有没有ack,0表示有ack, 1表示无ack |
0 |
2.3 IIC总线地址寄存器IICADD
寄存器 | 地址 | R/W | 描述 | 复位值 |
IICADD | 0x54000008 | R/W | IIC总线地址寄存器 | 0x0XX |
寄存器位信息:
IICADD | 位 | 描述 | 初始状态 |
从设备地址 | [7:0] |
从设备7位地址 当IICSTAT中串行输出使能=0时,IICADD为写使能; 从地址:[7:1] 未映射:[0] |
XXXXXXXX |
2.4 IIC总线发送/接收以为寄存器IICDS
寄存器 | 地址 | R/W | 描述 | 复位值 |
IICDS | 0x5400000C | R/W | IIC总线发送/接收移位寄存器 | 0x0XX |
寄存器位信息:
IICDS | 位 | 描述 | 初始状态 |
从设备地址 | [7:0] |
IIC总线TX/RX操作的8位数据移位寄存器 当IICSTAT中串行输出使能=0时,IICADS为写使能; |
XXXXXXXX |
三、读写操作流程
3.1. 主机发送模式
其流程如下:
- 设置IICCON寄存器;;
- 允许应答,位[7]=1;
- 使能中断,位[5]=1;
- 定义SCL周期,位[6]、[3:0];
- 设置IICSTAT位[4]使能串行输出;
- 写从设备的地址到IIICDS寄存器;
- 写0xF0到IICSTAT发送IICDS中的数据;
- 使能串行输出,位[4]=1;
- 配置主机为TX模式,位[7:6]=11;
- 主设备开始发送START信号;
- IICDS中配置的数据(从设备地址7位 + 读写1位位)被发送出去,每传输完一个字节数据将产生一个中断;
- 清除IICCON挂起位位[4];
- 通过查询IICSTAT位[0]判断是否接收到从设备的ACK应答;
- 如果没有接收到从设备ACK应答:写0xD0到IICSTAT,写0xAF到ICCCON,主设备发送停止信号,等待一会等待停止信号生效;
- 如果接收到从设备ACK应答:继续向IICDS写入要发送的数据,数据被发送出去,继续IIC数据传输;
3.2 主机接收模式
其流程如下:
- 设置IICCON寄存器;
- 允许应答,位[7]=1;
- 使能中断,位[5]=1;
- 定义SCL周期,位[6]、[3:0];
- 设置IICSTAT位4使能串行输出;
- 写从设备的地址到IIICDS寄存器;
- 写0xB0到IICSTAT发送IICDS中的数据;
- 使能串行输出,位[4]设置为1;
- 配置主机为RX模式,位[7:6]设置为10;
- 主设备开始发送START信号;
- IICDS中配置的数据(从设备地址7位 + 读写1位位)被发送出去,每传输完一个字节数据将产生一个中断;
- 清除IICCON挂起位位[4];
- 通过查询IICSTAT位[0]判断是否接收到从设备的ACK应答;
- 如果没有接收到从设备ACK应答:写0x90到IICSTAT,主设备发送停止信号,等待一会等待停止信号生效;
- 如果接收到从设备ACK应答:从IICDS读取数据,并清除IICCON挂起位,数据被接收到,主设备发送应答信号,继续IIC数据读取;
四、代码
4.1 硬件接线
Mini2440开发板外接了一个I2C信号引脚的EEPROM芯片AT24C08,它有1024字节,供用户测试I2C总线。
这里I2CSCL连接的S3C2440引脚GPE14、I2CSCL连接的S3C2440引脚GPE15。
4.2 I2C初始化
4.2.1 IO状态设置
设置GPE14、GPE15功能复用为I2C。设置GPE控制寄存器,同时禁止GPE14、GPE15为上拉使能。
GPECON &= ~((3<<28) | (3 <<30)); /* 清零 */ GPECON |= ((2<<28) | (2<<30)); /* 设置为IIC */ GPEUP |= 0xc000; /* 禁止内部上拉 */
4.2.2 设置TX/RX中断、SCL周期
/* 允许应答、使能TX/RX中断、SCL周期 */ IICCON |= (1<<7 | 0<<6 | 1<<5 | 0xf); IICSTAT |= (1<<4); /* 使能TX/RC */
允许应答。PLCK为50MHz,IICCLK= PCLK/`16,允许中断,发送时钟 = IICLK/16。
SCL时钟频率为:$int(\frac{50000000}{16*(15+1)})=1953125=0.198MHZ$
4.2.3 清除SRCPND、INTPND,取消中断屏蔽
SRCPND |= BIT_IIC; /* 向相应位置写1清除源挂起寄存器 */ INTPND |= BIT_IIC; /* 向相应位置写1清除挂起寄存器 */ INTMSK &= ~BIT_IIC; /* 关闭UART0中断屏蔽,总中断 */
4.3 向AT24C08写入n个字节数据
参考主机发送模式流程图编写代码,需要注意的是代码和流程图有部分出入,以下代码测试是可以运行的:
/************************************************************* * * Function : 主设备发送一个字节数据后,等待从设备应答 * Return : 0 有应答 1无应答 * **************************************************************/ u8 iic_wait_ack() { IICCON &= ~(1<<4); /* 清除IICCON挂起位,必须先清除 */ /* 数据发送完成,会进入中断函数,设置标志位;一旦进入IIC中断,即可跳出该死循环 */ while(iic_isr_flag == 0); /* 等待从设备应答信号 */ delay_us(10); /* 没有应答,发送停止信息 */ if(IICSTAT & 0x01 == 1){ IICSTAT = 0xD0; /* 主设备发送停止信号 */ IICCON = 0xAF; /* 禁止TX/RX中断,为下次通讯做准备 */ delay_us(10); /* 等待一会等待停止信号生效 */ printf("tx err, no ack\r\n"); return 1; }else{ /* 清除中断标志位 */ iic_isr_flag = 0; return 0; } } /************************************************************* * * Function : 向AT24C08写入0~16个字节数据,如果超过16字节,可以分段调用该函数 * Input : block 0~3 * address 0~255 * buffer 写入的数据 AT24C08的页缓冲区是16个字节,所有这里的循环最多也只能发送16个字节, 写入的数据必须位于同一页,页16字节对齐 * **************************************************************/ void iic_write_at24c08(u8 block, u8 address,u8 *buffer) { u8 i = 0; u8 length = 0; /* 设置从设备地址 */ IICDS = 0xA0 + block * 2 ; /* 写入AT24C08地址 */ IICSTAT = 0xf0; /* 主发送模式,发送从设备地址,发送完成触发中断 */ /* 等待从设备应答 */ if(iic_wait_ack() == 1){ return; } /* 发送AT24C08内存地址 */ IICDS = address; /* 发送完成触发中断 */ /* 等待从设备应答 */ if(iic_wait_ack() == 1){ return; } /* 发送数据 */ length = strlen((char *)buffer); for(i=0;i<length;i++) { IICDS = buffer[i]; /* 发送完成触发中断 */ /* 等待从设备应答 */ if(iic_wait_ack() == 1){ return; } } IICSTAT = 0xD0; /* 主设备发送停止信号 */ IICCON = 0xAF; /* 禁止TX/RX中断,为下次通讯做准备 */ delay_ms(10); /* 这个延时一定要足够长,否则会出错。因为24c08在从sda上取得数据后,还需要一定时间的烧录过程 */ }
4.4 从AT24C08读取n个字节数据
参考主机接收模式流程图编写代码,需要注意的是代码和流程图有部分出入,以下代码测试是可以运行的:
/************************************************************* * * Function : 主设备接收到一个字节数据后,发送应答信号 * **************************************************************/ void iic_ack() { IICCON &= ~(1<<4); /* 清除IICCON挂起位,必须先清除 */ /* 数据接收完成,会进入中断函数,设置标志位;一旦进入IIC中断,即可跳出该死循环 */ while(iic_isr_flag == 0); /* 清除中断标志位 */ iic_isr_flag = 0; /* 等待主设备发送应答信号 */ IICCON |= (1<<7); /* 回应ACK */ delay_us(10); } /************************************************************* * * Function : 从AT24C08读取n个字节数据 * Input : block 0~3 * address 0~255 * buffer 缓冲区 长度为length + 1 * length 需要读取的数据长度 最长255 * **************************************************************/ void iic_read_at24c08(u8 block,u8 address,u8 *buffer,u8 length) { u8 i=0; u8 temp; /* 设置从设备地址 */ IICDS = 0xA0 + block * 2 ; /* 写入AT24C08地址 */ IICSTAT = 0xf0; /* 主发送模式,发送从设备地址,发送完成触发中断 */ /* 等待从设备应答 */ if(iic_wait_ack() == 1){ return; } /* 发送AT24C08内存地址 */ IICDS = address; /* 发送完成触发中断 */ /* 等待从设备应答 */ if(iic_wait_ack() == 1){ return; } /* 设置从设备地址 */ IICDS = 0xA1 + block * 2 ; /* 写入AT24C08地址 */ IICSTAT = 0xB0; /* 主接收模式吗,发送从设备地址,发送完成触发中断 */ /* 等待从设备应答 */ if(iic_wait_ack() == 1){ return; } /* 先读取器件地址 */ temp = IICDS; /* 接收到数据触发中断 */ iic_ack(); /* 主机发送应答信号 */ /* 再读取数据 */ for(i=0;i<length;i++) { buffer[i] = IICDS; /* 接收到数据触发中断 */ iic_ack(); /* 主机发送应答信号 */ } buffer[i]='\0'; IICSTAT = 0x90; /* 主设备发送停止信号 */ IICCON = 0xAF; /* 禁止TX/RX中断,为下次通讯做准备 */ delay_ms(10); }
4.5 中断函数
/* 全局变量 */ int iic_isr_flag = 0 ; /************************************************************************* * * Function : 中断源27 IIC 中断 * 主设备字节数据发送完成会进入该函数 * 主设备接收到从设备字节数据会进入该函数 * *************************************************************************/ void IIC_IRQHandler() { /* 设置中断标志位 */ iic_isr_flag = 1; /* 清中断 */ SRCPND |= BIT_IIC; INTPND |= BIT_IIC; }
4.6 测试代码
#include "led.h" #include "common.h" #include "uart.h" #include "iic.h" int main() { u8 i = 0; u8 *write_buffer = "Dear my baby"; // 最长16个字符 u8 read_buffer[32]; vector_enable(); // 初始化 led_init(); uart_init(); delay_ms(1000); printf("iic init ...\r\n"); iic_init(); while(1) { printf("iic write ... \r\n"); iic_write_at24c08(1, 0x00,write_buffer); led_turn(LED1); delay_ms(1000); printf("iic read ...\r\n"); iic_read_at24c08(1, 0x00,read_buffer,sizeof(read_buffer)-1); printf("read value %s",read_buffer); printf("\r\n"); } return 0; }
将代码下载到Nand Flash,启动开发板运行输出如下:
iic init ... iic write ... iic read ... read value Dear my baby▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ iic write ... iic read ... read value Dear my baby▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ iic write ... iic read ... read value Dear my baby▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ iic write ... iic read ... read value Dear my baby▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
五、代码下载
Young / s3c2440_project【12.iic】
参考文章:
[1] IIC通信协议,搞懂这篇就够了