前面两节分别介绍了IIC协议的硬件接口和jz2440开发板上的IIC控制器,这一节就来写程序进一步熟悉IIC协议的操作过程,首先来看一下本节程序的结构
1、程序结构
上面的图是本节程序的结构图,采用这种分层的方式编写程序,每一层都负责具体的一部分,这使得程序的结构性紧凑,降低程序之间的耦合性,在添加新设备的时候也是非常方便的
1、测试程序:测试程序它主要是提供测试菜单,并进行初始化,一般是由最上层的应用程序调用
2、at24cxx是使用的i2c设备的名称,这一层包含一个文件,主要是对下层操作接口的封装,直接供测试程序调用
3、i2c_controller:这一层主要是为下层提供规范,定义结构体类型,定义接口类型等,由下层直接继承,并填充相应的成员。这里来看,好像和我们学过的"面向对象编程"思想一致,^_^
4、s3c2440_i2c_controller:这一层就是最底层了,它包含直接对设备的操作接口,填充继承下来的数据结构,由上层统一调用
采用分层的思想编程有很多好处,整个程序的结构看起来就十分的清楚,而且各司其职,对后面的扩展性也是非常好的
2、硬件配置
IICCON寄存器
[7] : 在接收模式设置为1,接收ACK信号
[6] : 时钟源IICCLK的设置,可以设置成pclk / 16 或者pclk / 512
[5] : IIC中断,要使能
[4] : 中断标记位,读出1表示中断发生,清零恢复i2c操作
[3:0] : 配合设置i2c的时钟
IICCON[3:0] = x
clock = IICCLK/(x+1)
100 kHz = IICCLK / (x + 1)
100000 = 50000000 / 512 / (x+1) x = 0.97 - 1不合适,所以取IICCLK = pclk / 16,计算出[3:0] = 30
3、编程
先来看看为底层定义规范的i2c_controller,几个重要的结构体
/* i2c传输消息结构体 */
typedef struct i2c_msg {
unsigned int addr; /* 从机地址 */
int flags; /* 读写标记 */
int len; /* 数据长度 */
int err; /* 错误标记 */
int cnt_transfered; /* 已传输的字节数 */
unsigned char *buf; /* 数据缓冲区 */
}i2c_msg,*p_i2c_msg;
/* i2c_controller结构体 */
typedef struct i2c_controller {
int (*init)(void); /* 初始化函数指针 */
int (*master_xfer)(p_i2c_msg,int); /* 传输函数指针 */
char *name; /* 控制器名称 */
}i2c_controller,*p_i2c_controller;
static p_i2c_controller i2c_controllers[I2C_CONTROLLER_NUM]; /* 定义i2c控制器数组,用来存储各种设备的i2c控制器对象 */
static p_i2c_controller i2c_controller_selected; /* 被选择的i2c控制器的对象 */
/* i2c控制器的注册函数 */
void register_i2c_controller(p_i2c_controller controller)
{
int i;
for (i = 0; i < I2C_CONTROLLER_NUM; i++) {
if (!i2c_controllers[i]) {
i2c_controllers[i] = controller;
break;
}
}
}
/* 根据名字选择i2c控制器 */
int select_i2c_controller(char *name)
{
int i;
for (i = 0; i < I2C_CONTROLLER_NUM; i++) {
if (i2c_controller[i] && !strcmp(i2c_controller[i]->name,name)) {
i2c_controller_selected = i2c_controller[i];
return 0;
}
}
return -1;
}
/* i2c传输函数,直接调取底层的传输函数 */
int i2c_transfer(p_i2c_msg msgs,int num)
{
return i2c_controller_selected->master_xfer(msgs,num);
}
/* 初始化 */
void i2c_init(void)
{
/* 注册i2c控制器 */
s3c2440_i2c_add();
/* 根据名字选择i2c控制器 */
select_i2c_controller("s3c2440");
/* 调用初始化函数 */
i2c_controller_selected->init();
}
//OK。到这里为提供接口规范的i2c_controller的功能就已经全部完成,接下来根据这些规范来编写针对设备s3c2440_i2c_controller吧!
s3c2440_i2c_controller.c
static i2c_controller s3c2440_i2c_controller = {
.name = "s3c2440",
.init = s3c2440_i2c_init,
.master_xfer = s3c2440_master_xfer
};
//注册i2c控制器到数组中
void s3c2440_i2c_add(void)
{
register_i2c_controller(&s3c2440_i2c_controller);
}
void s3c2440_i2c_init(void)
{
//将使用的引脚配置为i2c模式
GPECON &= ~((3 << 28) | (3 << 30));
GPECON |= ((2 << 28) | (2 << 30));
//i2c控制器的设置
IICCON = (30 << 0) | (1 << 5) | (0 << 6) | (1 << 7);
/* 注册中断 */
register_irq(27,i2c_interrupt_func);
}
//传输函数
int s3c2440_master_xfer(p_i2c_msg msgs,int num)
{
int i;
int err;
for(i = 0; i < num; i++) {
if (msgs[i] == 0) /* 写 */ {
err = master_transmit(&msgs[i]);
} else {
err = master_receive(&msgs[i]);
}
if (err)
return err;
}
}
//发送数据函数
int master_transmit(p_i2c_msg msg)
{
p_cur_mesg = msg; /* 指向当前需要发送的消息结构体 */
msg->cnt_transfered = -1; /* 已传输初始值为-1 */
msg->err = 0;
IICSTAT = (1 << 4);
IICDS = (msg->addr << 1); /* 高7位为设备地址,最低位为0时表示写 */
IICSTAT = 0xf0; /* 包含了配置为发送模式,start */
/* 循环等待,后续由中断完成 */
while (!msg->err && msg->cnt_transfered != msg->len);
if (msg->err == -1)
return -1;
return 0;
}
//接收数据模式
int master_receive(p_i2c_msg msg)
{
p_cur_mesg = msg;
msg->cnt_transfered = -1;
msg->err = 0;
IICSTAT |= (1 <<4);
IICDS = (msg->addr << 1) | (1 << 0);/* 高7位为设备地址,最低位为1时表示读*/
IICSTAT = 0xb0; //start
while (!msg->err && msg->cnt_transfered != msg->len);
if (msg->err == -1)
return -1;
return 0;
}
//数据传输都在中断函数里实现,所以中断函数是重点
void i2c_interrupt_func(int irq)
{
int index;
unsigned int iicstat = IICSTAT;
p_cur_mesg->cnt_transfered++;
/* 第一个中断,是已经发出设备地址 */
if (p_cur_mesg->flags == 0) /* 写 */ {
if (p_cur_mesg->cnt_transfered == 0) { /* 第一次中断 */
if (IICSTAT & 1) { /* 没有ACK,停止传输 */
IICSTAT = 0xd0; /*停止信号*/
IICCON &= ~(1 << 4); /* 清除中断 */
p_cur_mesg->err = -1; /* 结束函数中的循环 */
delay(1000);
return;
}
if (p_cur_mesg->cnt_transfered < p_cur_mesg->len) {
/* 对于其他中断,表明数据发送出去,发送下一个数据 */
IICDS = p_cur_mesg->buf[p_cur_mesg->cnt_transfered];
IICCON &= ~(1 << 4); /* 清除中断,恢复操作 */
} else { /* 数据发送完 */
IICSTAT = 0xd0; /* stop */
IICCON &= ~(1 << 4); /* 清除中断 */
delay(1000);
}
} else { /* 读 */
if (IICSTAT & 1) { /* 没有ACK */
IICSTAT = 0x90;
IICCON &= ~(1 << 4); /* 清除中断 */
p_cur_mesg->err = -1;
delay(1000);
return;
} else { /* 如果是最后一个数据,启动传输时要设置不回应ACK */
if (isLastData())
resume_without_ack();
else
resume_with_ack();
return;
}
//正常传输
if (p_cur_mesg->cnt_transfered < p_cur_mesg->len) {
index = p_cur_mesg->cnt_transfered - 1;
p_cur_mesg->buf[index] = IICDS;
if (isLastData())
resume_without_ack();
else
resume_with_ack();
} else {
IICSTAT = 0x90;
IICCON &= ~(1 << 4);
delay(1000);
}
}
}
int isLastData(void)
{
if (p_cur_mesg->cnt_transfered == p_cur_mesg->len - 1)
return 1;
return 0;
}
void resume_with_ack(void)
{
unsigned int iiccon = IICCON;
iiccon |= (1 << 7); /* 回应ACK */
iiccon &= ~(1 << 4);/* 恢复传输 */
IICCON = iiccon;
}
void resume_without_ack(void)
{
unsigned int iiccon = IICCON;
iiccon &= ~((1 << 7) | (1 << 4));/* 恢复传输 */
IICCON = iiccon;
}
好了,关于at24cxx设备的i2c控制器的程序也已经完成,接下来继续编写at24cxx.c,去调用底层封装好的程序
//写函数,参数为要写的地址,数据缓冲区,以及数据长度
int at24cxx_write(unsigned int addr,unsigned char *dat,int len)
{
/* 构造i2c_message */
i2c_msg msg;
char buf[2];
int i;
int err;
for (i = 0; i < len; i++) {
buf[0] = addr++;
buf[1] = dat[i];
msg.addr = AT24CXXADDR;
msg.flags = 0;
msg.len = 2;
msg.buf = buf;
err = i2c_transfer(&msg, 1);
if (err)
return err;
}
return 0;
}
/*
几点说明:构造用于传输的i2c_msg结构体,每次只发送一个字节的数据,0表示写,先将buf[0]的设备内部地址发送,再发送buf[1]处要写
的数据,for循环里面每次让地址加一,在写一个地址处写下一个数据
*/
int at24cxx_read(unsigned int addr,unsigned char *dat,int len)
{
/* 构造i2c_message */
i2c_msg msg[2];
int i;
int err;
msg[0].addr = AT24CXXADDR;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &addr;
msg[1].addr = AT24CXXADDR;
msg[1].flags = 1;
msg[1].len = len;
msg[1].buf = dat;
err = i2c_transfer(&msg, 2);
if (err)
return err;
return 0;
}
/*
对于读需要构造两个i2c_msg结构体,第一个i2c_msg是要我们要读的地址给发送,所以对应的i2c_msg应该是写,第2个i2c_msg才是要读的消息结构体,保存我们的缓冲区
*/
好了,对于i2c的代码基本上就已经完成了,在调试的时候遇到了很多的问题,有一部分是由于自己的粗心大意造成的,也是对整个过程还不太清楚吧,最后,再对整个过程进行总结。
1、对于写操作来说,上层应用调用到at24cxx.c中的at24cxx_write函数,在该函数中每次构造一个i2c_msg结构体,
结构体中包含从设备的地址,以及我们要写的设备地址和数据,调用i2c_transfer函数,调用到最底层的s3c2440_master_xfer函数,
在该函数中判断是写操作之后就会调用master_transmit函数进行发送,在该函数中设置基本项之后发出start信号,
在发出start信号之后从设备地址以及被发送出去,这一步不需要人为来控制,所以在中断服务函数中的第一次中断就是来
自从设备接收从设备地址之后的相应信号,所以,中断程序中接下来的操作还是针对第一个([0])i2c_msg来操作的,
如果它有多个字节的数据,则一个字节一个字节的发送完毕,再回去at24cxx_write发送下一个i2c_msg,流程和第一个一样,
每一次开始之前都要把从设备地址发送出去,再进行操作
2、对于读操作,每一次定义两个i2c_msg,在s3c2440_master_xfer中总共有两个i2c_msg结构体,而第一个i2c_msg是要把
要读的地址写入从设备内部,所以第一个i2c_msg走的是写的流程,和上面一样
a、它在把从设备地址发出去之后第一次进入中断函数,在中断函数中接着把这个i2c_msg结构体中的设备内部地址发送出去,
在第二次进入到中断程序中进行判断,因为只有一个数据(就是设备内部地址),所以不需要再次进行发送数据,发出stop信号结束发送即可。
b、在操作完第一个i2c_msg之后,第二个i2c_msg结构体是进行读的消息,在s3c2440_master_xfer中走的是读的流程,
同样,先发出start信号,同时把从设备地址发送出去,在第一次进入到中断程序中,判断有没有ACK,若有则读取数据,
同时给从设备回应ACK,这里要注意,如果只读取一个数据直接发送stop信号即可,如果有多个数据要读取,需要给从设备回应ACK,
在下一次进入中断后再做判断。