十三.I2C使用2——主从机程序编写

⼗三.I2C使⽤2——主从机程序编写
在前⾯⼀章我们已经铺垫了I2C的使⽤流程,下⾯我们就按照I2C的通讯流程写对应的代码,这个流程应该严格按照参考⼿册给出的定义
上⾯两幅图就是I2C通讯的流程
master代码流程
I2C的代码流程⽐较复杂,我们⼀个个函数来说
初始化
⾸先是初始化
void i2c_init(I2C_Type *base)
{
base->I2CR &= ~(1<<7);    //disable I2C
base->IFDR= 0x15;       //640分频,clk=103.125KHz
base->I2CR |= (1<<7);     //I2C Enable
}
初始化⾥只是设置了个分频器,我们使⽤的时钟源是66MHz,选择640分频,速率为103.125KHz,设置分频器前要将I2C停⽌,设置完成后⼀定要使能I2C,其他寄存器操作时有些是要求I2C使能的。
产⽣Start流程
在完成初始化后,数据可以有主机先⾏发送。发送数据时,要先检查I2C的BUSY标志位是否为0(I2C_I2SR[IBB]),标志位为0时才能发送数据。发送第⼀组数据时要将从机的地址写⼊I2C_I2DR寄存器,从机地址是7位的,地址后⾯要跟⼀个读写标志。
/**
* @brief ⽣成start信号
*
* @param base I2C结构体
拉丝钢板* @param address 从机地址
* @param direction ⽅向
* @return unsigned char
*/
unsigned char i2c_master_start(I2C_Type *base,
unsigned char address,
enum i2c_direction direction)
{
if((base->I2SR) & (1<<5)){      //IBB,I2C忙标志位
return1;              //IBB=1时,I2C忙,返回1
}
//设置为主机模式
base->I2CR |= ((1<<5) | (1<<4));  //[5]MSTA master mode,[4]MXT 发送模式
/
/产⽣Start信号
base->I2DR = ((unsigned int)address <<1) | ((direction== kI2C_Read)?1 :0);  //address位⾼7位为地址,低位为读写标志
return0;                      //⽆异常,返回0
}
函数中先判定是否为总线忙,如果总线忙跳出函数,抛出异常,如果正常就将设备设置成master、发送模式,将数据寄存器写⼊从机地址。注意地址左移1位,最低位留给读写标志位,⽤了个3元运算符(A?1:2,当A为True时值为1,否则为2)。
I2C Stop
master结束通讯,要发送⼀个Stop标志。
//产⽣stop信号
unsigned char i2c_master_stop(I2C_Type *base)
{
unsigned short timeout = 0xffff;                //超时等待值
//清除I2CRbit5:3
base->I2CR &= ~(7<<3);                          //MSTA=0 Slave模式、MTX=0 接收模式 TXAK=0 发送NoACK
//等待I2C空闲
while(base->I2SR &(1<<5))                      //BUSY位
{
timeout--;
if(timeout ==0)
return I2C_STATUS_TIMEOUT;                  //返回超时异常
}
return I2C_STATUS_OK;
}
这个Stop标志的产⽣要求我不太明⽩为什么要把I2C设置成slave模式。按照⼿册31.5.4 Generation of Stop章节说的,停⽌标志的产⽣要在数据倒数第⼆个Byte时master告诉从机最后⼀个byte发送完成后跟⼀个NoACK信号
重新发送Start
在数据传输中,如果master还需要占据总线,他需要重新发个Start信号,这个Start标志后还需要跟⼀个地址,这个地址可以是设备地址也可以是寄存器地址,restart信号和start信号之间是不能有Stop信号的。
//重复发送Start信号
unsigned char i2c_master_repeated_start(I2C_Type *base,
unsigned char address,
enum i2c_direction direction)
{
//检查I2C是否空闲或在从机模式下
if(((base->I2SR & (1<<5)) && ((base->I2CR) &(1<<5))) == 0 )    //I2SR[5]:IBB 总线忙标志 I2CR[5]:MSTA master模式
{return1;}  //总线空闲模式⽆法重新⽣成Restart slave模式也⽆法⽣产Restart信号,跳出
base->I2CR |=(1<<4) |(1<<2);    //I2CR[4]MTX=1发送模式  [2]产⽣Repeat Start
base->I2DR = ((unsigned int)address <<1) | ((direction== kI2C_Read)?1 :0);  //address位⾼7位为地址,低位为读写标志
return I2C_STATUS_OK;
}
ReStart的流程和Start差不多,都是先判定是否总线忙,当总线不在空闲状态且I2C为master模式下,重新⽣成Start信号,信号是发送⼀个地址和读写标志。前⾯的if判断⽐较绕,就是在IBB位不能为0或MSTA不能为0,就是I2C不能是空闲或设备不是Slave模式,否则跳出
异常处理
这⾥的异常处理主要是通过I2SR寄存器的状态判定两种异常状态:仲裁丢失和⽆应答
* @brief 检查并处理异常
*
* @param base I2C
* @param state 状态(I2SR寄存器)
* @return unsigned char
*/
unsigned char i2c_check_and_clear_error(I2C_Type *base,unsigned int state)
{
//先检查是否为仲裁丢失错误
if(state & (1<<4))              //state值为I2SR,bit[4]:IAL仲裁丢失
{
base->I2SR &= ~(1<<4);      //清除IAL异常
base->I2CR &= ~(1<<7);      //禁⽌I2C
base->I2CR |= (1<<7);      //使能I2C
return I2C_STATUS_ARBITRATIONLOST;  //抛出异常:仲裁丢失
}
else if(state & (1<<0))        //I2SR[0]RXAK 1时收到NoACK信号
{
return I2C_STATUS_NAK;      //抛出异常:⽆应答
}
return I2C_STATUS_OK;          //返回正常
}
仲裁丢失时需要重新使能I2C,抛出异常状态(宏在头⽂件⾥定义了),⽆应答时不⽤重启I2C,直接抛异常。
数据发送
数据发送要按照本章节最开始那个流程图去进⾏,先看代码
/**
* @brief I2C,master发送数据
*
* @param base I2C结构体
* @param buf  待发送数据
* @param size 发送数据⼤⼩
*/
void i2c_master_write(I2C_Type *base,const unsigned char *buf,unsigned int size)
{
while(!(base->I2SR & (1<<7)));  //ICF,等待传输完成,1时数据传输中,0时跳出循环
base->I2SR &= ~(1<<1);          //IIF=0,清除中断
base->I2CR |= (1<<4);          //MTX=1设置为发送
while(size--){
base->I2DR = *buf ++ ;          //将待写⼊的值写⼊寄存器
while(!(base->I2SR & (1<<1)));  //等待传输完成,这⾥⽤到时IIT,没有⽤传输完成标志位(bit7)
base->I2SR &= ~(1<<1);          //清除标志
/*检查ACK*/
if(i2c_check_and_clear_error(base,base->I2SR))
break;                      //⽆异常时状态字为I2C_STATUS_OK=0,否则跳出循环
}
base->I2SR &= ~(1<<1);              //清除标志
i2c_master_stop(base);              //停⽌传输
}
这⾥有个BUG:在发送过程中等待过程,按照⼿册上所说要检查I2SR的bit7
当该位为1的时候说明传输完成。但是按照官⽅的驱动,是通过检查bit[1],即IIF来实现的。这个不知道是为什么,如果检查ICF标志I2C通讯⽆法进⾏。但开始有个等待传输的过程⼜是检查的ICF。官⽅给出的硬件驱动就是这样写的整个流程就是
1. 等待I2C上⼀发送过程结束
纤维素纤维2. 清除IIF中断标志
3. 设置MTX为传输
4. 将要发送到数据依次写⼊I2DR寄存器,写⼊后等待发送完毕标志
5. 所有数据发送完毕,清除IIF标志
6. 发送Stop标志停⽌传输
数据读取
数据读取的流程和发送基本⼀致,但是要判定是否是倒数第⼆个Byte的数据,因为I2C协议要求读数据为8bit数据加1bit的ACK标志,但最后⼀个Byte的数据后要跟的是NoACK,并且主机要在读取倒数第⼆个Byte数据时告诉从机下⼀组数据准备发个NoACK。
* @brief 读数据
*
* @param base I2C结构体
* @param buf  读取数据写⼊的缓存
* @param size 读取数据⼤⼩
*/
void i2c_master_read(I2C_Type *base,unsigned char *buf,unsigned size){
volatile uint8_t dummy = 0;        //假读数据
dummy++;                            //防⽌编译报错,具体原因不详
while(!(base->I2SR & (1<<7)));      //ICF,1时数据传输中,0时跳出循环
base->I2SR &= ~(1<<1);              //IIF=0,清除中断标志
base->I2CR &= ~((1<<4)|(1<<3));    //[4]MTX=0:接收数据 [3]TXAK=0 8位数据后加1位ACK响应
if(size==1)                        //发送数据⼤⼩为1
base->I2CR |= (1<<3);          //发送NoACK
dummy = base->I2DR;                //假读(个⼈觉得通知从机NoACK必须在读倒数第⼆个数据时发送,所以假读⼀次)
while(size--){
while(!(base->I2SR & (1<<1)));      //IIF是否为0?等待传输完成
base->I2SR &= ~(1<<1);              //IIF=0,清除标志位
if(size==0){                        //最后⼀个数据读取完毕
i2c_master_stop(base);          //主机发送Stop标志
}
if(size==1){                        //倒数第⼆个数据
base->I2CR |= (1<<3);          //NoACK通知
}
*buf++ = base->I2DR;                //读取信息
}
}
程序⾥做了个假读的流程,我估计原因就是如果读取数据长度为1时要假装读⼀下,以便从机发送NoACK。读取的流程和写的流程基本⼀样,也是通过IIF判定是否在读写操作。但是这⾥没有做异常检查和处理,我估计是因为要读操作时前⾯必须有个写操作,如果⽆响应就会在写的流程中出现异常。
I2C通讯函数
教程⾥最后把发送和接受的功能集成到了⼀个函数中,这个函数是最后我们调⽤的接⼝。
/*
* @description    : I2C数据传输,包括读和写
* @param - base: 要使⽤的IIC
* @param - xfer: 传输结构体
* @return        : 传输结果,0 成功,其他值失败;
*/
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
unsigned char ret = 0;
enum i2c_direction direction = xfer->direction;
base->I2SR &= ~((1 << 1) | (1 << 4));            /* 清除标志位 */
/* 等待传输完成 */
while(!((base->I2SR >> 7) & 0X1)){};
/* 如果是读的话,要先发送寄存器地址,所以要先将⽅向改为写 */
if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
{
direction = kI2C_Write;
}
ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */
if(ret)
{鸡眼镜
return ret;
}
来书网
while(!(base->I2SR & (1 << 1))){};            /* 等待传输完成 */
ret = i2c_check_and_clear_error(base, base->I2SR);    /* 检查是否出现传输错误 */
if(ret)
节能减排设备
杨木皮子{
i2c_master_stop(base);                        /* 发送出错,发送停⽌信号 */
return ret;
}
/* 发送寄存器地址 */
if(xfer->subaddressSize)

本文发布于:2024-09-21 19:44:55,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/100060.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:发送   标志   数据   流程   传输   地址   等待
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议