1、I2C
本文以韦东山视频为基础,作如下笔记。
I2C硬件上的接法:
如下图所示,主控芯片引出两条线SCL,SDA线,在一条I2C总线上可以接很多I2C设备。数据可以从主设备传到从设备上,从设备也能传数据到主设备上,即双向传输。
读写操作:
刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是往哪一个芯片写数据),读/写(0表示写,1表示读)。
回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。如下图:白色为主设备到从设备传输,灰色为从设备传输到主设备。
传输是以8位为单元数据传输的,先传输最高位(MSB),主芯片发出start信号之后,然后发出9个时钟传输数据。
(1)开始信号(S):SCL为高电平时,SDA高电平向低电平跳变,开始传送数据。
(2)结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
(3)响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA
SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化。如图
双向传输原理:
SDA上一根线实现双向传输,还不互相影响各自的发送?
主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面有两个引脚(发送引脚/接受引脚),使用开极(极电集开发出去作为输出)电路选择功能,如下图
当某一个芯片不想影响SDA线时,那就不驱动这个三极管。
想输出高电平时(读);都不驱动(高电平就由上拉电阻决定)。
想输出低电平(写),就驱动三极管。
对于IIC协议它只能规定怎么传输数据,数据什么含义它完全不能够控制,数据的含义有从设备决定。
S3C2440的I2C控制器:
在嵌入式系统里面的主控芯片一般都会有I2C控制器,要是没有可以根据I2C协议用GPIO管脚模拟,但是非常麻烦,我们要发送数据时,可以把数据放到某个寄存器,它就会自动的发出时钟,并且把数据发送给从设备,同时会等待从设备会返回回应信号。
当我们想发送一个数据的时候,要设置某个寄存器启动传输,它也一样会产生时钟,然后从设备就会把数据通过SDA传到I2C控制器里面,组装进某个寄存器里面,最终寄存器会把接收到的8位数据返回给我们的程序,从这里可以看到I2C控制器简化了I2C的操作。简短电路连接图,如图:
根据上图,我们首先设置IICCON(来设置时钟),时钟源是PCLK(是50MHZ)太快了我们需要设置这个分频系数,把时钟降低,降低到我们想要的SCL,然后我们要发出start信号,我们需要设置寄存器发出start信号,之后我们需要发出数据,程序可以把数据写入到IICDS寄存器,一写入就会自动的发出时钟,并且把这8位数据从SDA发送给从设备,数据发送之后,在第九个时钟会收到回应信号,可以查询IICSTAT是否有ACK(有ACK表示数据发送成功了),可以继续发送数据,等发完数据之后,再来设置IICSTAT让它发出P信号。
在第九个CLK,就会产生一个中断,在中断处理过程中SCL被拉为低电平,谁都不能再使用IIC总线,等待中断处理完成.
处理中断:
写操作:
若无ACK,出错,然后发出P信号结束,
:: 若有ACK信号表示上一个字节成功发送出去
:: 若仍有数据,写入IICDS寄存器,然后清中断,一清中断就会释放SCL信号,继续发出时钟,把数据再次发送出去。
:: 若没有数据了,发出P信号结束。
读操作:
读到8位数时,应该回应一个ACK信号。
:: 还想读数据,清中断,启动传输。等它再次发生中断时,再来读取IICDS寄存器,得到数据。不想读取数据,发出P信号结束。
重点: 发生中断时,我们的IIC控制器会把SCL拉低,阻止任何设备不再使用IIC总线,清中断之后才能继续使用,这种机制就给我们中断服务程序的执行提供了时间。
读-写操作
在发送模式:
:: 1.往寄存器IICDS寄存器放入一个val值。
:: 2.发完,产生中断,并且会把 SCL拉低。
:: 3.在中断程序里,判断状态,然后往IICDS里面写入下一个数据,一旦写入下一个数据IIC继续操作,若再次发完,就会再次产生中断。
在接受模式:
:: 1.我的程序发起传输,接受数据。
:: 2.接收到数据之后,产生中断,SCL被拉低。
:: 3.中断程序里,判断数据是否要继续接受等,如果还有继续接受的话,再次设置,设置好之后读IICDS寄存器,一但读出来IIC。
:: 继续接受下一个数据,收到新数据之后,又会产生一个中断(就是这样循环操作)。
编程:
(l)IICCON寄存器(Multi-masterIIC-buscontrol)
IICCON寄存器用于控制是否发出ACK信号、设置发送器的时钟、开启,i2c中断,并标识中断是否发生。它的各位含义如表:
使用IICCON寄存器时,有如下注意事项。
1.发送模式的时钟频率由位[6]、位[3:0]联合决定,另外,llCCON[6]=0,IICCON[3:0]
不能取0或10
2.I2c中断在以下3种情况下发生:当发出地址信息或接收到一个从机地址并且吻合时,当总线仲裁失败时,当发送/接收完一个字节的数据(包括响应位)时。
3.基于SDA、SCL线上时间特性的考虑,要发送数据时,先将数据写入IICDS寄存器,然后再清除中断。
4.如果IICCON[5]=0,IICCON[4]将不能正常工作。所以,即使不使用12c中断,也要将IICCON[5]设为1。
(2)IICSTAT寄存器(Multi-masterIIC-buscontrol/status)
IICSTAT寄存器用于选择i2c接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机时是否被寻址、是否接收到0地址、是否接收到ACK信号等。IICSTAT寄存器的各位如表:
(3)IICADD寄存器(Multi-masterIlC-busaddress)
用到IICADD寄存器的位[7:11],表示从机地址。IICADD寄存器在串行输出使能位IICSTAT[4]为0时,才可以写入:在任何时间都可以读出。IICADD寄存器的各位如表:
(4)IICDS寄存器(Multi-masterIIC-busTx/Rxdatashift)
用到IICDS寄存器的位丨7:0],其中保存的是要发送或己经接收的数据。IICDS寄存器在串行输出使能位IICSTAT()1为1时,就可以写入;在任何时间都可以读出。IICDS寄存器的各位如表:
读写操作流程图
编程:
/* FILE: i2c.c
* 用于主机发送/接收
*/
#include
#include "s3c24xx.h"
#include "i2c.h"
void Delay(int time);
#define WRDATA (1)
#define RDDATA (2)
typedef struct tI2C {
unsigned char *pData; /* 数据缓冲区 */
volatile int DataCount; /* 等待传输的数据长度 */
volatile int Status; /* 状态 */
volatile int Mode; /* 模式:读/写 */
volatile int Pt; /* pData中待传输数据的位置 */
}tS3C24xx_I2C, *ptS3C24xx_I2C;
static tS3C24xx_I2C g_tS3C24xx_I2C;
/*
* I2C初始化
*/
void i2c_init(void)
{
GPEUP |= 0xc000; // 禁止内部上拉
GPECON |= 0xa0000000; // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL
INTMSK &= ~(BIT_IIC);
/* bit[7] = 1, 使能ACK
* bit[6] = 0, IICCLK = PCLK/16
* bit[5] = 1, 使能中断
* bit[3:0] = 0xf, Tx clock = IICCLK/16
* PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz
*/
IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf
IICADD = 0x10; // S3C24xx slave address = [7:1]
IICSTAT = 0x10; // I2C串行输出使能(Rx/Tx)
}
/* 主机发送
* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度
*/
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = WRDATA; // 写操作
g_tS3C24xx_I2C.Pt = 0; // 索引值初始为0
g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址
g_tS3C24xx_I2C.DataCount = len; // 传输长度
IICDS = slvAddr;
IICSTAT = 0xf0; // 主机发送,启动
/* 等待直至数据传输完毕 */
while (g_tS3C24xx_I2C.DataCount != -1);
}
/*
* 主机接收
* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度
*/
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = RDDATA; // 读操作
g_tS3C24xx_I2C.Pt = -1; // 索引值初始化为-1,表示第1个中断时不接收数据(地址中断)
g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址
g_tS3C24xx_I2C.DataCount = len; // 传输长度
IICDS = slvAddr;
IICSTAT = 0xb0; // 主机接收,启动
/* 等待直至数据传输完毕 */
while (g_tS3C24xx_I2C.DataCount != -1);
}
/*
* I2C中断服务程序
* 根据剩余的数据长度选择继续传输或者结束
*/
void I2CIntHandle(void)
{
unsigned int iicSt,i;
// 清中断
SRCPND = BIT_IIC;
INTPND = BIT_IIC;
iicSt = IICSTAT;
if(iicSt & 0x8){ printf("Bus arbitration failed\n\r"); }
switch (g_tS3C24xx_I2C.Mode)
{
case WRDATA:
{
if((g_tS3C24xx_I2C.DataCount--) == 0)
{
// 下面两行用来恢复I2C操作,发出P信号
IICSTAT = 0xd0;
IICCON = 0xaf;
Delay(10000); // 等待一段时间以便P信号已经发出
break;
}
IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++];
// 将数据写入IICDS后,需要一段时间才能出现在SDA线上
for (i = 0; i < 10; i++);
IICCON = 0xaf; // 恢复I2C传输
break;
}
case RDDATA:
{
if (g_tS3C24xx_I2C.Pt == -1)
{
// 这次中断是发送I2C设备地址后发生的,没有数据
// 只接收一个数据时,不要发出ACK信号
g_tS3C24xx_I2C.Pt = 0;
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢复I2C传输,开始接收数据,接收到数据时不发出ACK
else
IICCON = 0xaf; // 恢复I2C传输,开始接收数据
break;
}
if ((g_tS3C24xx_I2C.DataCount--) == 0)
{
g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;
// 下面两行恢复I2C操作,发出P信号
IICSTAT = 0x90;
IICCON = 0xaf;
Delay(10000); // 等待一段时间以便P信号已经发出
break;
}
g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;
// 接收最后一个数据时,不要发出ACK信号
if(g_tS3C24xx_I2C.DataCount == 0)
IICCON = 0x2f; // 恢复I2C传输,接收到下一数据时无ACK
else
IICCON = 0xaf; // 恢复I2C传输,接收到下一数据时发出ACK
break;
}
default:
break;
}
}
/*
* 延时函数
*/
void Delay(int time)
{
for (; time > 0; time--);
}
调用i2c设置m41t11实时时钟:
/*
* FILE: m41t11.c
* 调用I2C读写函数,设置、读取RTC芯片m41t11
*/
#include
#include "m41t11.h"
#include "i2c.h"
struct rtc_registers {
unsigned char secs;
unsigned char mins;
unsigned char hours;
unsigned char wday;
unsigned char mday;
unsigned char mon;
unsigned char year;
unsigned char cs;
};
#define BCD_TO_BIN(val) (((val)&15) + ((val)>>4)*10)
#define BIN_TO_BCD(val) ((((val)/10)<<4) + (val)%10)
static unsigned long epoch = 2000; /* 芯片中”年”为0x00时,对应2000年 */
static const unsigned char days_in_mo[] =
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/*
* 写m41t11,设置日期与时间
*/
int m41t11_set_datetime(struct rtc_time *dt)
{
unsigned char leap_yr;
struct {
unsigned char addr;
struct rtc_registers rtc;
} __attribute__ ((packed)) addr_and_regs;
memset(&addr_and_regs, 0, sizeof(addr_and_regs));
leap_yr = ((!(dt->tm_year % 4) && (dt->tm_year % 100))
|| !(dt->tm_year % 400));
if ((dt->tm_mon < 1) || (dt->tm_mon > 12) || (dt->tm_mday == 0)) {
return -1;
}
if (dt->tm_mday > (days_in_mo[dt->tm_mon] + ((dt->tm_mon == 2)
&& leap_yr))) {
return -2;
}
if ((dt->tm_hour >= 24) || (dt->tm_min >= 60) || (dt->tm_sec >= 60)) {
return -3;
}
if ((dt->tm_year -= epoch) > 255) {
/* They are unsigned */
return -4;
}
if (dt->tm_year >= 100) {
dt->tm_year -= 100;
}
addr_and_regs.rtc.secs = BIN_TO_BCD(dt->tm_sec);
addr_and_regs.rtc.mins = BIN_TO_BCD(dt->tm_min);
addr_and_regs.rtc.hours = BIN_TO_BCD(dt->tm_hour);
addr_and_regs.rtc.mday = BIN_TO_BCD(dt->tm_mday);
addr_and_regs.rtc.mon = BIN_TO_BCD(dt->tm_mon);
addr_and_regs.rtc.year = BIN_TO_BCD(dt->tm_year);
addr_and_regs.rtc.wday = BIN_TO_BCD(dt->tm_wday);
addr_and_regs.rtc.cs = 0;
i2c_write(0xD0, (unsigned char *)&addr_and_regs, sizeof(addr_and_regs));
return 0;
}
/*
* 读取m41t11,获得日期与时间
*/
int m41t11_get_datetime(struct rtc_time *dt)
{
unsigned char addr[1] = { 0 };
struct rtc_registers rtc;
memset(&rtc, 0, sizeof(rtc));
i2c_write(0xD0, addr, 1);
i2c_read(0xD0, (unsigned char *)&rtc, sizeof(rtc));
dt->tm_year = epoch;
rtc.secs &= 0x7f; /* seconds */
rtc.mins &= 0x7f; /* minutes */
rtc.hours &= 0x3f; /* hours */
rtc.wday &= 0x07; /* day-of-week */
rtc.mday &= 0x3f; /* day-of-month */
rtc.mon &= 0x1f; /* month */
rtc.year &= 0xff; /* year */
dt->tm_sec = BCD_TO_BIN(rtc.secs);
dt->tm_min = BCD_TO_BIN(rtc.mins);
dt->tm_hour = BCD_TO_BIN(rtc.hours);
dt->tm_wday = BCD_TO_BIN(rtc.wday);
dt->tm_mday = BCD_TO_BIN(rtc.mday);
dt->tm_mon = BCD_TO_BIN(rtc.mon);
dt->tm_year += BCD_TO_BIN(rtc.year);
return 0;
}