IIC是Philips推出的芯片间串行传输总线,它以二根连线实现完善的全双工同步数据传送,可以极方便地构成多机系统和外围器件扩展系统。由于其接口简单灵活,很多外围器件均提供了IIC接口,如手机、平板常用的重力传感器、地磁感应、陀螺仪、电容屏接口等均是采用IIC接口的。这些器件采用IIC接口可减少芯片封装的引脚,使之更小型化,同时也可以降低布线难度,这对于手机、平板这些PCB芯片集成度相当高的产品来说是很有必要的。笔者此处就s3c2416的IIC接口应用作一个简单的介绍。

1. IIC总线概述

IIC总线物理上包括两条总线线路,一条串行数据线SDA,一条串行时钟线SCL。为了使各个IIC设备线与相连在总线上,IIC总线接口均采用开集电极或开漏输出。因此,在IIC总线中是必须接上拉电阻的,上拉电阻的大小通常为1k~10k。上拉电阻小了,则IIC总线功耗增加,上拉电阻大了,负载能力弱,并且影响总线的允许传输速率。

IIC总线可构成多主和主从系统,在多主系统结构中,系统通过硬件或软件仲裁获得总线控制使用权。应用系统中IIC总线多采用主从结构,即总线上只有一个主控节点,总线上的其他设备都作为从设备。由于IIC通信使用7比特地址空间和16个保留地址,因此理论上同一总线能够支持的最大通信结点数为112个。但实际应用时,应尽可能地减小总线上的通信结点数,以增强总线的稳定性。因为通信结点的引入,同时也会引入寄生电容,而IIC总线结点数量受到总线最大电容400pF的限制。总线的传输速率为100Kbit/s(标准模式),400Kbit/s(快速模式),1Mbit/s(快速附加模式),3.4Mbit/s(高速模式)。

其它的总线时序等请参考相关的IIC总线标准,笔者在此不再详述。

2. IIC驱动实现

s3c2416具有一路多主IIC串行接口,可作为主机,也可作为从机。对于IIC的操作,s3c2416的spec给出了非常详细的编程步骤,见下图:

12_IIC驱动实现 - 图1

图2-1 主机发送操作

12_IIC驱动实现 - 图2

图2-2 主机接收操作

12_IIC驱动实现 - 图3

图2-3 从机发送操作

12_IIC驱动实现 - 图4

图2-4 从机接收操作

此处主要介绍s3c2416 IIC外设作为主机的编程实现。IIC外设首先应进行初始化,包括引脚配置成IIC功能引脚,设置IIC的时钟等。对于外设通信,推荐用中断的方式进行收发数据,因为外设通信相对于cpu执行速度都是极其慢的,用查询的方法会让cpu进入空等状态,在大量数据要通过外设收发时,将造成cpu效率及其低下。一方面其它任务需要cpu执行,另一方面cpu在空等外设收发数据的完成。因此笔者采用的是中断方式进行IIC通信,需要在初始化函数中注册相应的IIC中断处理函数,并开启中断。异常处理在启动代码中的Exception.c统一处理,把IIC中断处理函数注册进中断向量表中,并编写IIC中断处理即可。在有操作系统应用中,用中断的方式让cpu等待或接收信号量来完成IIC的通信,可以让cpu资源得到充分的利用。

IIC模块中应提供最基本的底层IIC读和IIC写这两个功能函数实现,以供上层调用。IIC_WriteBytes用来向某一从机(SlaveAdd)中相应内部地址(WriteAddr)进行写数据,待写数据在pData中,写入长度为Length。函数原型如下:

  1. intIIC_WriteBytes(unsigned char SlaveAddr, unsignedchar WriteAddr, unsigned char *pData, int Length)

IIC_ReadBytes用来从某一从机(SlaveAdd)中相应内部地址(ReadAddr)进行读数据,读取的数据存放在pData中,读取长度为Length。函数原型如下:

  1. intIIC_ReadBytes(unsigned char SlaveAddr, unsignedchar ReadAddr, unsigned char *pData, intLength)

IIC模块实现IIC.c内容如下:

  1. #include"s3c2416.h"
  2. #include"IIC.h"
  3. #include"Exception.h"
  4. #define IIC_ReadMode 1 // 连续读数据模式
  5. #define IIC_WriteMode 2 // 连续写数据模式
  6. #defineIIC_ReadSlaveMode 3 // 读从机地址模式
  7. #defineIIC_WriteSlaveMode 4 // 写从机地址模式
  8. // IIC状态,记录总线接口出错的信息
  9. static volatile intIIC_Status;
  10. // 跟踪IIC的状态转移,在中断中需确定IIC的状态,确定写或读
  11. static volatile intIIC_Mode;
  12. // 上层应用请求通过IIC接口发送或接收的数据长度计数
  13. static volatile intIIC_DataCount;
  14. // 数据发送或接收的存放位置
  15. static volatile unsigned char *pIIC_Data;
  16. static void Delay_us(unsigned int nCount)
  17. {
  18. //延时1us,共延时nCount(R0) us
  19. __asm__ __volatile__ (
  20. "Delay1:\n\t"
  21. "LDR R1, =100\n\t" // Arm clock为400M
  22. "Delay2:\n\t"
  23. "SUBS R1, R1, #1\n\t" // 一个Arm clock
  24. "BNE Delay2\n\t" // 跳转会清流水线,3个Arm clock
  25. "SUBS R0, R0, #1\n\t" // 调用者确保nCount不为0
  26. "BNE Delay1\n\t"
  27. "BX LR\n\t"
  28. );
  29. }
  30. static void IIC_IRQ()
  31. {
  32. unsigned char Status;
  33. Status = rIICSTAT;
  34. if (Status & (1<<3)) {
  35. // bus arbitration is failed
  36. IIC_Status |= ArbitrationFailed;
  37. }
  38. if (Status & (1<<2)) {
  39. // a slave address is matched withIICADD
  40. IIC_Status |= AddressMatche;
  41. }
  42. if (Status & (1<<1)) {
  43. // a slave address is 0000000b
  44. IIC_Status |= AddressZeros;
  45. }
  46. if (Status & (1<<0)) {
  47. // ACK isn't received
  48. IIC_Status |= NoAck;
  49. }
  50. switch (IIC_Mode) {
  51. case IIC_ReadMode:
  52. IIC_DataCount--;// 读了一字节,读计数减1
  53. if (IIC_DataCount == 1) {
  54. // 读最后一个数据,主机不应应答,不然从机再发送数据,应直接停止总线
  55. *pIIC_Data = rIICDS;
  56. pIIC_Data++;
  57. rIICCON &= ~(1 <<7); // 读最后一字节禁止主机应答
  58. rIICCON &= ~(1<< 4); // 恢复操作,读下一个数据
  59. } else if(IIC_DataCount == 0) {
  60. *pIIC_Data =rIICDS; // 所有数据接收完
  61. // 若有操作系统,应用在数据等待发送完时通过信号量
  62. // 或标志等待而挂起OSSemPend(ucos),这样不会让cpu查询
  63. // 等待,极大提高效率。发送信号量或标志,唤醒等待的
  64. // 应用OSSemPost(ucos)
  65. } else { // 数据未接收完
  66. *pIIC_Data = rIICDS;
  67. pIIC_Data++;
  68. rIICCON &= ~(1<< 4);// 恢复操作,连续读下一个数据
  69. }
  70. break;
  71. case IIC_WriteMode:
  72. IIC_DataCount--;// 写了一字节,写计数减1
  73. if (IIC_DataCount != 0){
  74. pIIC_Data++; // 数据未写完,写下一数据
  75. rIICDS =*pIIC_Data;
  76. rIICCON &= ~(1<< 4); // 恢复操作,连续下一个数据
  77. } else {
  78. // 所有数据写完,若有操作系统,应用在数据等待发送完时
  79. // 通过信号量或标志等待而挂起OSSemPend(ucos),这样不会
  80. // 让cpu查询等待,极大提高效率。发送信号量或标志,
  81. // 唤醒等待的应用OSSemPost(ucos)
  82. }
  83. break;
  84. case IIC_ReadSlaveMode:
  85. // 读从机地址已发送完,转换成连续读模式进行连接读数据
  86. IIC_Mode = IIC_ReadMode;
  87. if (IIC_DataCount == 1) {
  88. rIICCON &= ~(1 <<7); // 只读一字节禁止主机应答
  89. } else {
  90. rIICCON |= (1<< 7); // 读多字节应启用主机应答
  91. }
  92. rIICCON &= ~(1<< 4);
  93. break;
  94. case IIC_WriteSlaveMode:
  95. // 写从机地址已发送完,转成写模式准备写从机1字节内部地址
  96. IIC_Mode = IIC_WriteMode;
  97. rIICDS = *pIIC_Data;
  98. rIICCON &= ~(1 << 4);
  99. break;
  100. default:
  101. break;
  102. }
  103. rSRCPND1 |= (1 << INT_IIC0); //Clearpending bit
  104. rINTPND1 |= (1 << INT_IIC0);
  105. }
  106. static void IIC_Start()
  107. {
  108. rIICCON &= ~(1 << 4);// 清除中断标志位
  109. rIICSTAT |= (1<<5); // IIC总线开始传输
  110. }
  111. static void IIC_Stop()
  112. {
  113. rIICSTAT &= ~(1<<5);// 不要禁止总线输出
  114. rIICCON &= ~(1 << 4);// 清除中断标志位
  115. // 清除中断标志位后才发给停止信号
  116. Delay_us(10); // 延时使IIC总线停止
  117. }
  118. int IIC_WriteBytes(unsigned char SlaveAddr, unsigned char WriteAddr,
  119. unsigned char *pData, int Length)
  120. {
  121. rIICSTAT |= (0x3<<6); // 主机传送模式
  122. IIC_Mode = IIC_WriteSlaveMode; // 写从机地址模式
  123. rIICDS = SlaveAddr << 1;// 发送从机地址
  124. IIC_DataCount = 1; // 写一个字节从机内部地址
  125. pIIC_Data = &WriteAddr;// 内部地址值传递到中断服务函数中待发送
  126. IIC_Start();
  127. while(IIC_DataCount != 0) {
  128. // 等待IIC数据发送完
  129. }
  130. if (Length == 0) {
  131. IIC_Stop(); // 读取数据长度为0,返回
  132. return 0;
  133. }
  134. IIC_Mode = IIC_WriteMode; // 写入了从机内部地址后,从该地址进行连续写
  135. IIC_DataCount = Length; // 连续写长度传递到中断写请求长度变量中
  136. pIIC_Data = pData; // 写数据的位置传递到中断请求中数据发送位置处
  137. rIICDS = *pData; // 写入第一字节开始连续写操作
  138. rIICCON &= ~(1 << 4);// 清除中断标志,恢复操作
  139. while (IIC_DataCount != 0) {
  140. // 等待IIC数据发送完
  141. // 可改成操作系统信号量等待函数,提高cpu效率,如OSSemPend(ucos)
  142. }
  143. IIC_Stop(); // 所有数据发送完,总线挂起
  144. return 0;
  145. }
  146. int IIC_ReadBytes(unsigned char SlaveAddr, unsigned char ReadAddr,
  147. unsigned char *pData, int Length)
  148. {
  149. if ((Length==0) || (pData==(unsigned char*)0)) {
  150. return 0;
  151. }
  152. IIC_WriteBytes(SlaveAddr, ReadAddr, (void*)0, 0); // 写入从机内部地址
  153. rIICSTAT &= ~(0x3<<6);
  154. rIICSTAT |= (0x2<<6); // 转换成接收模式
  155. IIC_Mode = IIC_ReadSlaveMode; // 转换成从机读模式
  156. IIC_DataCount = Length; // 数据的读取长度传递到中断读请求中
  157. pIIC_Data = pData; // 数据的保存位置传递到中断读请求中
  158. rIICDS = (SlaveAddr<<1) + 1; // 写入从机读
  159. IIC_Start();
  160. while(IIC_DataCount != 0) {
  161. // 等待IIC数据接收完
  162. // 可改成操作系统信号量等待函数,提高cpu效率,如OSSemPend(ucos)
  163. }
  164. IIC_Stop(); // 所有数据读取完,总线挂起
  165. return 0;
  166. }
  167. void IIC_Init()
  168. {
  169. rPCLKCON |= (1 << 4); // 使能IIC时钟
  170. rGPEUDP &= ~((0x3<<28) |0xC0000000); // GPE14,15禁止上下拉
  171. rGPECON &= ~((0x3<<28) |0xC0000000);
  172. rGPECON |= (0x2<<28) | 0x80000000;// GPE14,15配置成IIC接口
  173. // ACK,中断使能,PCLK(66M) 512分频再1分频产生IIC传输时钟130k
  174. rIICCON = (1<<7) |(1<<6) | (1<<5) | (0x0<<0);
  175. // 主机发送模式,使能数据输出
  176. rIICSTAT = (0x3<<6) |(1<<4);
  177. rIICADD = 0x20; // 作为设备时的地址
  178. rIICLC = (1 << 2); // IIC总线过滤使能
  179. IRQ_Register(INT_IIC0, IIC_IRQ);//IIC中断入口函数加入到向量表
  180. rINTMOD1 &= ~(1 <<INT_IIC0); // IIC IRQ
  181. rINTMSK1 &= ~(1 <<INT_IIC0); // IIC开启中断
  182. }

IIC模块的头文件IIC.h引出接口函数:

  1. #ifndef __IIC_H__
  2. #define __IIC_H__
  3. #ifdef __cplusplus
  4. extern "C"{
  5. #endif
  6. #define ArbitrationFailed (1<<3) // 总线仲裁失败
  7. #define AddressMatche (1<<2) // 从机地址相配
  8. #define AddressZeros (1<<1) // 接收的地址为0
  9. #define NoAck (1<<0) // 没有回复信号
  10. extern int IIC_WriteBytes(unsigned char SlaveAddr, unsigned char WriteAddr,
  11. unsigned char *pData, int Length);
  12. extern int IIC_ReadBytes(unsigned char SlaveAddr, unsigned char ReadAddr,
  13. unsigned char *pData, int Length);
  14. extern void IIC_Init(void);
  15. #ifdef __cplusplus
  16. }
  17. #endif
  18. #endif /*__IIC_H__*/

3. 附录

IIC.rar,包含IIC接口模块实现IIC.c/IIC.h。

源码下载: http://pan.baidu.com/s/1i3BR1Jn