手上的一块S3C2410的开发板搭载VxWorks 5.5操作系统,今天开始继续学习BSP源码和驱动程序,争取在硕士毕业前能自己实现BSP的配置。
IIC接口
这里简单复习一下IIC接口的相关知识。
IIC/I2C(Inter-Integrated Circuit)总线由Philips公司针对MCU需要研制的二线式串行总线,用于MCU及外围设备通信。
IIC总线主要特点:
- IIC总线长度可高达7.6m,并能够以100kb/s的最大传输速率支持40个组件。
- IIC总线支持多主控。主控能控制信息传输和时钟频率。任一时刻只有一个主控。
IIC总线要求:
- 各个设备必须共地
- 两根信号线必须接上拉电阻
多IIC设备接口示意图:
IIC总线的状态及信号
由于上拉电阻的存在,SCL和SDA在总线处于空闲状态均为高电平。
主控器想使用总线就应当先拉低SCL,控制总线,传送完成后应当释放总线。
启动信号:SCL为高,SDA上产生下降沿
结束信号:SCL为高,SDA上产生上升沿
应答/响应信号(A/NA) — 数据接收者接收到1字节后,应当主动向数据发出者发送一个应答信号。对应SCL的第9个应答时钟脉冲,SDA为低表示应答,继续发送;SDA为高表示非应答,结束发送。
地址信号(寻址字节) —
bit | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
DA3 | DA2 | DA1 | DA0 | A2 | A1 | A0 | R/nW |
其中,(DA3-DA0)为器件的固有地址编码,由器件生产厂家给定;(A2、A1、A0)为器件地址引脚电平,高为1,接地为0;读写控制位(R/nW),为1表示主机读,为0表示主机写。
等待状态: 从机接收完一个数据字节后,可以拉低SCL使总线系统进入等待状态;当从机完成数据处理后,再释放SCL线,主机方可继续发送数据。
IIC总线数据传输格式
- 一般格式
- 主控制器写操作格式
- 主控制器读操作格式
- 主控制器读/写操作格式
好了,IIC总线接口相关知识就复习到此,现在来看看S3C2410的IIC接口
S3C2410 IIC接口
先来看看S3C2410下的IIC串行总线控制器框图:
S3C2410的端口E的GPE15用作数据线SDA,GPE14用作连续时钟线SCL。使用时要记得外部加上拉电阻。
发送数据写入到 IICDS
接收数据从IICDS读取
S3C2410的4个IIC专用寄存器:
Register | Address | Read or Write | Description |
---|---|---|---|
IICCON | 0x54000000 | R/W | IIC总线控制寄存器 |
IICSTAT | 0x54000004 | R/W | IIC总线控制/状态寄存器 |
IICADD | 0x54000008 | R/W | IIC总线地址寄存器 |
IICDS | 0x5400000C | R/W | IIC数据发送/接收寄存器 |
IICSTAT的常用控制字:
- 启动主设备发送的控制字为 0xF0 (Tx)
- 结束主设备发送的控制字为 0xD0 (Tx)
- 启动主设备接收的控制字为 0xB0 (Rx)
- 结束主设备接收的控制字为 0x90 (Rx)
IICADD 地址寄存器仅对从设备有意义。
S3C2410 IIC串行总线编程
直接看图吧:
IIC主发送模式流程
IIC主接收模式流程
S3C2410 IIC 驱动程序
/* 文件模块说明:
* 2410Iic.c S3C2410内部驱动,初始化驱动器,外部EERPROM采用
* ATMEL AT24C128-128kbit
* 文件版本:
* 开发人员:
* 创建时间:
* Copyright(c) t0 - t1 CompanyName Limited Co.
*/
#include "intLib.h"
#include "2410addr.h" // 寄存器地址宏头文件
// 24C128IIC总线定义
#define CN_IIC_VOLUME (0x4000) // IIC容量,16KB
#define AT24128256_w (0xa0 ) // 写地址:原理图中 A2A1A0 均接地
#define AT24128256_r (0xa1 ) // 读地址
#define IIC_mode_mask (0x3f )
#define IIC_mtx (0xc0 ) // 主机发送模式
#define IIC_mrx (0x80 ) // 主机接收模式
#define pagelength ( 64 )
//------------------------------------------------------------------------------
// 初始化IIC程序
void IIC_init(void)
{
UINT32 dwValReg;
// GPE14 ~ GPE15为IIC接口
// GPECON[31:28] = 0b 1010 = 配置GPE14为IICSCL, GPE15为IICSDA
SNGS3C_REG_READ( rGPECON, dwValReg );
dwValReg = (dwValReg & 0xAFFFFFFF);
dwValReg = (dwValReg | 0xA0000000);
SNGS3C_REG_WRITE( rGPECON, dwValReg );
// IICCON[7] ENABLE
// IICCON[6] Tx Clock Source, 0 = IICCLK = fPCLK / 16; 1 = IICCLK = fPCLK / 512
// IICCON[5] Interrupt Enable
// 50Mhz/16/(9+1) = 312.5Khz
// AT24C128 在 3.3V 时的最大工作频率为 400KHz
*(volatile UINT32 *)rIICCON=(1<<7)|(0<<6)|(1<<5)|9; // = 0xa9
*(volatile UINT32 *)rIICSTAT=0xd0; // 状态控制字:0xd0 结束主设备发送
*(volatile UINT32 *)rIICADD=0x00; // 器件地址 0x00
}
// 函数名称:IIC_WaitBus
// 函数功能:等待IIC总线操作结束
// 返回值: 若操作等待超时返回 false; 正确则返回 true
char IIC_WaitBus(void)
{
UINT i=0;
// IICCON[4] - Interrupt is depending
while( !( (*(volatile UINT32*)rIICCON) & 0x10 ) ) // no interruption
{
i++;
if(i > 1000) // 超时
return (FALSE);
}
return (TRUE); // 还未超时就有中断产生
}
// 函数名称:IIC_WaitAck
// 函数功能:等待IIC总线应答信号
// 返回值: 若操作等待超时或得不到应答信号,返回 false; 正确则返回 true
// 说明: 先等待操作结束信号,然后判断是否有应答信号
char IIC_WaitAck(void)
{
UINT i=0;
while( !( (*(volatile UINT32*)rIICCON) & 0x10) ) // 查询中断信号
{
i++;
if(i>1000)
return (FALSE);
}
if( (*(volatile UINT32*)rIICSTAT) & 0x1 ) // ACK was not received
return (FALSE);
return (TRUE);
}
// 函数名称:IIC_WaitEnd
// 函数功能:等待写操作结束
// 返回值:
char IIC_WaitEnd(void)
{
int i, k;
// 尝试100次写地址并等待应答,只要收到一次即认为写操作成功,否则写操作失败
for(i=0; i<100; i++)
{
*(volatile UINT32*) rIICDS = AT24128256_w; // IIC器件AT24C128写地址
for(k=0; k<100; k++) ; // 延时
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
*(volatile UINT32*) rIICSTAT = 0xf0; // 0XF0 启动发送状态字
k = 0;
if(TRUE == IIC_WaitAck())
return (TRUE);
}
return (FALSE);
}
// 函数名称:IIC_Read
// 函数功能:读芯片
// 返回值: 0 读成功; 1 读失败
// 读24C128
UINT32 IIC_Read(UINT32 start, char * buf, UINT length)
{
int i, iicbusy, k;
// 读出起始地址+数据长度大于芯片容量(字节)则退出
if( (start+length) > CN_IIC_VOLUME ) return;
iicbusy = IIC_WaitEnd(); // 等待写操作结束
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICDS = (UINT8) (start/256); // 地址高字节
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
*(volatile UINT32*) rIICDS = (UINT8) (start%256); // 地址低字节
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
*(volatile UINT32*) rIICSTAT = 0xb0; // 状态控制字: 0XB0 启动主设备接收
*(volatile UINT32*) rIICDS = AT24128256_r; // IIC器件AT24C128读地址
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9; // = 0xa9
if(FALSE == IIC_WaitAck())
return 1;
for(i=0; i<length; i++)
{
if(i == length - 1)
{
*(volatile UINT32*) rIICCON = 0xa9; // (1<<7)|(0<<6)|(1<<5)|9
*(volatile UINT32*) rIICCON = 0x29; // 禁止应答
}
else
{
*(volatile UINT32) rIICCON = 0xa9;
}
if(FALSE == IIC_WaitBus()) // 等待总线操作结束
return ;
buf[i] = *(volatile UINT32*) rIICDS;
}
*(volatile UINT32*) rIICSTAT = 0x90; // 状态控制字: 0x90 停止主设备接收
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = 0xa9; // (1<<7)|(0<<6)|(1<<5)|9
return 0;
}
// 函数名称:IIC_Write
// 函数功能:写芯片
// 返回值: 0 读成功; 1 读失败
// 写24C128
UINT32 IIC_Write(UINT32 start, char * buf, UINT length)
{
int i, j, iicbusy, k, addr = start;
// 写入地址大于芯片容量(字节)则退出
if( (addr+length) > CN_IIC_VOLUME ) return;
iicbusy = IIC_WaitEnd(); // 等待写操作结束
*(volatile UINT32*) rIICDS = (UINT8) (addr/256); // 地址高字节
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
*(volatile UINT32*) rIICDS = (UINT8) (addr%256); // 地址低字节
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
j = FALSE;
for(i=0; i<length; i++)
{
if(j)
{
j = FALSE;
iicbusy = IIC_WaitEnd(); // 等待写操作结束
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICDS = (UINT8) ((addr+i)/256); // 地址高字节
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
*(volatile UINT32*) rIICDS = (UINT8) (addr+i); // 地址低字节
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
}
*(volatile UINT32*) rIICDS = buf[i]; // 发送字节数据
for(k=0; k<100; k++) ;
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
if(FALSE == IIC_WaitAck())
return 1;
// 没有写完全部length字节长度的数据 并且还没有写满一整页,则继续写入
if( (i<length) && (0 == ((addr+i+1) % pagelength)) )
{
*(volatile UINT32*) rIICSTAT = 0xd0; // 状态控制字:0xd0 主设备发送结束, 产生停止信号
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
taskDelay(1);
j = TRUE;
}
}
if(!j)
{
*(volatile UINT32*) rIICSTAT = 0xd0; // 状态控制字:0xd0 主设备发送结束, 产生停止信号
*(volatile UINT32*) rIICCON = (1<<7)|(0<<6)|(1<<5)|9;
}
taskDelay(1);
return 0;
}
// 函数功能:向定值区写入数据,先读,再擦除,最后写
// 输入参数:wAddress 写入区地址 0-0x10 0000
// pbyBuff 为待写入字节流指针
// dwLen 为字节流长度
// 返回值: 0 成功; 1 失败
STATUS Iic_Write_Data(UINT32 wAddress, UINT8* pbyBuf, UINT32 dwLen)
{
UINT32 ret;
ret = IIC_Write(wAddress, pbyBuf, dwLen);
return ret;
}
// 函数功能:从定值区读取数据
// 输入参数:wAddress 为读取区地址
// pbyBuf 为待读取字节流指针
// dwLen 为字节流长度
// 返回值: 0 成功; 1 失败
STATUS Iic_Read_Data(UINT32 wAddress, UINT8 *pbyBuf, UINT32 dwLen)
{
UINT32 ret;
ret = IIC_Read(wAddress, pbyBuf, dwLen);
return ret;
}
// 函数功能:向定值区写入半字(16位)
// 输入参数:wAddress 为写入区地址, wBuf 为待写入数据
// 返回值: 0 成功; 1 失败
STATUS Iic_Write_Word(UINT32 wAddress, UINT16 wData)
{
UINT32 ret;
UINT8 buf[2];
buf[0] = wData & 0x00ff;
buf[1] = (wData >> 8) & 0x00ff;
ret = Iic_Write_Data(wAddress, &buf[0], 2);
return ret;
}
// 函数功能:从定值区读取半字(16位)
// 输入参数:wAddress 为读取区地址
// 返回值: 待读取数据,低16位有效
UINT16 Iic_Read_Word(UINT32 wAddress)
{
UINT8 buf[2];
UINT16 wData, wData1, wData2;
Iic_Read_Data(wAddress, &buf[0], 2);
wData1 = buf[0];
wData2 = buf[1];
wData = (wData1 & 0x00ff | (wData2&0x00ff) << 8);
return (wData);
}
今晚就把IIC驱动代码分享到这里,明天到系统上实际运行试试,看看能否成功写入并读取数据。
验证结果
2016/09/28
在这里做了一个简单的测试,申请一个长度为50的字符数组并清空,向IIC器件写入字符串 “I love China!”,短暂延时后,从IIC器件里面读回来存到 字符数组中,并打印出来。可以看到,结果正确。
证明以上驱动程序,完全正确。