I2C总线驱动程序的实现

i2c总线驱动程序的实现
i2c 驱动程序的简介
本驱动程序为标准的51 系列cpu 编写,让cpu 模拟成一个i2c 总线主器件,并部分
支持多个主器件同时存在。当cpu 晶振为12mhz 时,i2c 总线频率为不超过100khz。
如果i2c 总线上有多个i2c 总线主器件,用户程序需要进行一些额外处理。本书配套光盘
也包含一个模拟400khz 的i2c 总线规范主器件的驱动程序。而dp-51proc 上的i2c 器
件并不是全都为400khz 的,如zlg7290 led 键盘控制芯片的速度就比较慢,是32khz,
所以该驱动程序不适合zlg7290。
驱动程序的使用
本驱动程序可以在没有small rtos51 的情况下使用。此时,要使用本驱动程序只需
要配置i2c 总线使用的io 口。在驱动程序的主文件iic_master.c 仅包含一个文件
config.h。用户需要的是在这个文件中设置i2c 总线使用的io 口sda 和scl。定义sda
和scl 的例子见程序清单4.13。如果用户单独使用iic_master.c,还要在config.h 包含
iic_master.h 文件和其它必须的文件如reg51 等;并定义宏true、false 和与编译器无
关的数据类型。在使用small rtos51 的情况下,如果用户只有一个任务使用i2c 总线,
用户只要在config.h 定义sda 和scl 和包含iic_master.h 就可以了。 如果用户不止一
个任务使用i2c 总线,则驱动程序需要使用信号量保证各个任务对i2c 总线的互斥操作。
这时,需要将宏iicsem 定义为分配给i2c 总线驱动程序的信号量的索引,并在使用驱动
程序前建立这个信号量。
在使用i2c 总线驱动程序前应该调用函数iicinit()初始化i2c 总线。单独使用或单任
务使用本驱动程序时,使用函数iicread()对i2c 总线进行读操作,使用iicwrite()对i2c
总线进行写操作。如果有多个任务需要对i2c 总线进行操作,则分别调用宏osiicread()
和osiicwrite()对其进行读写。
程序清单4. 13 定义i2c 使用的io 口例
sbit sda = p1^7; //i2c 总线驱动使用的数据线
sbit scl = p1^6; //i2c 总线驱动使用的时钟线
基本i2c 总线信号的产生
i2c 总线有很多基本总线信号,每一个基本总线信号由一个函数产生。这些函数都比较
简单,读者对比i2c 总线规范应该很容易读懂。
i2c 总线启动信号由函数iicstart 产生,代码见程序清单4.14。当操作成功,函数返
回 true。当函数返回false 时可能有别的总线主器件正在使用总线或总线故障。
程序清单4.14 产生i2c 总线启动信号
uint8 iicstart(void)
{
sda = 1;
scl = 1;
if (sda == 1)
{
sda = 0;
_nop_();
scl = 0;
sda = 1;
return true;
}
else
{
return false;
}
}
i2c 总线中止信号由函数iicstop 产生,代码见程序清单4.15。函数没有返回值。
程序清单4.15 产生i2c总线中止信号
void iicstop(void)
{
sda = 0;
_nop_();
_nop_();
scl = 1;
sda = 1;
_nop_();
_nop_();
_nop_();
scl = 0;
}
i2c 总线应答信号(ack)由函数iic_ack 产生,代码见程序清单4.16。函数没有
返回值。
程序清单4.16 产生i2c 总线应答(ack)信号
void iic_ack(void)
{
sda = 0;
_nop_();
_nop_();
scl = 1;
_nop_();
_nop_();
_nop_();
_nop_();
scl = 0;
}
i2c 总线非应答信号(no ack)由函数iic_no_ack 产生,代码见程序清单4.17。
函数没有返回值。
程序清单4.17 产生i2c 总线非应答(no ack)信号
void iic_no_ack(void)
{
sda = 1;
_nop_();
_nop_();
scl = 1;
_nop_();
_nop_();
_nop_();
_nop_();
scl = 0;
return;
}
i2c 总线初始化
在使用i2c 总线前必须初始化i2c 总线,使i2c 总线处于空闲状态。这是通过发送总
线停止信号来实现的。代码见程序清单4.18。
程序清单4.18 i2c 总线初始化
void iicinit(void)
{
scl = 0;
iicstop();
}
发送和接收一个字节
发送一个字节和接收一个字节也是i2c 总线的基本操作。对i2c 的写操作需要用到这
两个操作。发送一个字节使用函数iicsend()实现。函数iicsend()的代码见程序清单
4.19。流程图见图4.3。函数同时处理了应答位。
程序清单4.19 向i2c 发送一个字节
uint8 iicsend(uint8 iic_data)
{
uint8 i;
for (i = 0; i < 8; i++) (1)
{
iic_data = iic_data << 1; (2)
f0 = sda = cy; (3)
scl = 1; (4)
if (f0 != sda) (5)
{
scl = 0; (6)
return false; (7)
}
_nop_(); (8)
_nop_(); (9)
scl = 0; (10)
}
sda = 1; (11)
_nop_(); (12)
_nop_(); (13)
scl = 1; (14)
_nop_(); (15)
_nop_(); (16)
if (sda == 1) (17)
{
scl = 0; (18)
return false; (19)
}
else
{
scl = 0; (20)
return true; (21)
}
}
i2c 发送一个字节
有了流程图,程序应该比较容易看懂。唯一要注意的是程序清单4.19(2)、(3)句和
f0 的使用。在keil c51 中,左移()
操作会把最低位移到cy 标志中。这是不可移植的,但却是效率最高的方式。可移植的方式
见程序清单4.20。同理,f0 是51 单片机中用户可使用的标志,在psw 中。虽然使用f0
隐含不可移植性,但没有程序清单4.19(2)、(3)句那么严重,移植时只要定义一个全局
(或局部)变量f0 就可以了。程序清单4.20 可移植代码
if ((iic_data & 0x80) != 0)
{
f0 = sda = 1;
}
else
{
f0 = sda = 0;
}
iic_data = iic_data << 1;
接收一个字节使用函数iicreceive()实现,代码见程序清单4.21。由于接收一个字
节后发送的应答信号不尽相同,函数没有处理应答信号。程序比较简单,读者对照i2c 总线
规范应该可以读懂,这里不再说明。
程序清单4.21 从i2c 从器件接收一个字节
uint8 iicreceive(void)
{
uint8 i,r;
r = 0; (1)
sda = 1; (2)
for (i = 0; i < 8; i++) (3)
{
r = r * 2; (4)
scl = 1; (5)
_nop_(); (6)
_nop_(); (7)
if (sda == 1) (8)
{
r++; (9)
}
scl = 0; (10)
}
return r; (11)
}
对i2c 进行读操作
如果有多个任务需要访问i2c 总线,则使用osiicread()对i2c 总线进行读操作。如
果仅有一个任务需要i2c 总线,则使用iicread()对i2c 总线进行读操作。osiicread ()
是一个宏,代码见程序清单4.22。
程序清单4.22 多任务从i2c 读数据
#define osiicread(a,b,c)
if (ossempend(iicsem,10) == os_sem_ok) (1)
{
iicread(a,b,c); (2)
ossempost(iicsem); (3)
}
程序通过在对器件读之前等待信号量(程序清单4.22 (1))和在对器件读之后发送信
号量(程序清单4.22 (3))来实现对i2c 总线的互斥操作。这样做的原因可以参见本章的
4.1.1 节。在宏中调用函数iicread()对器件进行读操作(程序清单4.22 (2))。而
iicread()就是单任务情况下对i2c 总线进行读操作的函数,所以两者的参数相同。
函数iicread()的代码见程序清单4.23。函数iicread()的流程图见图4.4。函数
iicread()的第一个参数是一个指针,读出的数据将从这里开始存放。函数iicread()的
第二个参数是将要访问的从器件的器件地址。函数iicread()的第三个参数是将要读取的
字节数目。当函数iicread()成功读取数据时,返回true。函数iicread()返回false
时可能有别的i2c 总线主器件正在访问i2c 总线,或是总线故障,或是从器件故障。
程序清单4.23 单任务从i2c 读数据
uint8 iicread(uint8 os_sem_mem_sel *ret,uint8 addr,uint8 nbyte)
{
uint8 i;
addr = addr | 0x01; (1)
if (iicstart() == false) (2)
{
return false; (3)
}
if(iicsend(addr) == false) (4)
{
return false; (5)
}
i = nbyte - 1; (6)
if (i != 0) (7)
{
do
{
*ret++ = iicreceive(); (8)
iic_ack(); (9)
} while (--i != 0); (10)
}
*ret = iicreceive(); (11)
iic_no_ack(); (12)
iicstop(); (13)
return true; (14)
}
图4.4 从i2c 读取数据流程图
函数iicread()首先将地址的最低位设置为1(程序清单4.23(1))告诉从器件此次
操作为读。然后启动总线(程序清单4.23(2))。如果启动不成功,函数返回false(程序
清单4.23(3))。否则发送从器件地址(程序清单4.23(3))。如果发送不成功,函数返回
false(程序清单4.23(5))。否则就读取比指定读取的字节数少1 的字节数,在每次读取
一个字节后发送应答(ack)信号(程序清单4.23(6)~(10))。然后,接收最后一个字节
(程序清单4.23(11)),并发送非应答(no ack)信号通知从器件读取结束(程序清单
4.23(12))。最后,函数停止总线(程序清单4.23(13)),函数调用成功(程序清单4.23(14))。
4.3.7 对i2c 进行写操作
如果有多个任务需要访问i2c 总线,则使用osiicwrite()对i2c 总线进行写操作。如
果仅一个任务需要i2c 总线,则使用iicwrite()对i2c 总线进行写操作。osiicwrite()
是一个宏,代码见程序清单4.24。
程序清单4.24 多任务给i2c 从器件写数据
#define osiicwrite(a,b,c)
if (ossempend(iicsem,10) == os_sem_ok) (1)
{
iicwrite(a,b,c); (2)
ossempost(iicsem); (3)
}
程序通过在对器件写之前等待信号量(程序清单4.24(1))和在对器件写之后发送信
号量(程序清单4.24(3))来实现对i2c 总线的互斥操作。这样做的原因可以参见本章的
4.1.1 节。在宏中调用函数iicwrite()对器件进行写操作(程序清单4.24(2))。而
iicwrite()就是单任务情况下对i2c 总线进行写操作的函数,所以两者的参数相同。
函数iicwrite()的代码见程序清单4.25。函数iicwrite()的第一个参数是一个指针,
将要写入的数据将从这里开始存放。函数iicwrite()的第二个参数是将要访问的从器件的
器件地址。函数iicwrite()的第三个参数是将要写入的字节数。当函数iicwrite()成功写
入数据时,返回true。函数iicwrite()返回false 时可能有别的i2c 总线主器件正在访
问i2c 总线,或是总线故障,或是从器件故障。
程序清单4.25 单任务给i2c 从器件写数据
uint8 iicwrite(uint8 addr,uint8 os_sem_mem_sel *data,uint8 nbyte)
{
uint8 i;
addr = addr & 0xfe; (1)
if (iicstart() == false) (2)
{
return false; (3)
}
if (iicsend(addr) == false) (4)
{
return false; (5)
}
i = nbyte; (6)
do (7)
{
if (iicsend(*data++) == false) (8)
{
return false; (9)
}
} while (--i !=0 );
iicstop(); (10)
return true; (11)
}
图4.5 把数据写道i2c流程图
函数iicwrite()首先将地址的最低位设置为0(程序清单4.25(1))告诉从器件此次
操作为写。然后启动总线(程序清单4.25(2))。如果启动不成功,函数返回false(程序
清单4.25(3))。否则发送从器件地址(程序清单4.25(4))。如果发送不成功,函数返回
false(程序清单4.25(5))。否则就写入指定字节(程序清单4.25(6)~(9))。在每次写
入一个字节后读取发送应答判断发送一个字节的函数是否调用正确(程序清单4.25(8)),正确才继续执行,否则函数返回false(程序清单4.25(9))。)最后,函数停止总线(程
序清单4.25(10)),函数调用成功(程序清单4.25(11))。

New RS-232 ICs Feature 1µ
如何解决工控机的电磁干扰问题
小米空调的“第二次试水”雷军和董明珠的逐鹿之战爆发
科大讯飞全球1024开发者节最新内容发布 AI向新 数智万物
机床是一个国家制造业水平的象征
I2C总线驱动程序的实现
NTC热敏电阻在工业过程控制中的应用案例
相较红外,微波雷达为何是雾天行车诱导防撞系统车辆检测的选择?
AMD官方辟谣 CPU总代封仓属不实传言
多功能超声波焊接发生器电源
助力智慧工业,艾迈斯欧司朗推出面向工业激光雷达应用的红外激光器
如何选择空气净化器
三星Galaxy S21 Ultra现身Geekbench
middlebury数据集是什么
虹科这款支持WiFi连接和离线记录的CAN/CAN FD记录仪,也太好用了吧!
鼎阳科技发布新一代高刷新率手持示波表,SHS800X为测试加速!
锂电池的基本知识
西门子S7-1500 DI模块使用方法分享
为什么特斯拉 Model 3 没有仪表板?
工信部:2021年全国有望新建5G基站超过100万个