使用CDK在RVB2061上编写IIC软件驱动——快来测体温队

简介: 本文将为大家带来一个完整的iIIC驱动解析

前言

1.疑问:

看到标题的诸君可能有疑虑了:"CDK里面不是有现成的IIC外设接口吗?为什么还要多此一举自己写IIC驱动呢?"

官方关于iic外设使用链接:IIC接口实现

2.辩解(吐槽)

诸君稍安勿躁,待我慢慢道来。我打算自己写的原因有二:

  • 其一,IIC引脚不够用

下面这张图是官方提供的RVB2601开发板用户手册里面附录带的一个“GPIO 复用关系表”

里面黄框代表IIC复用引脚,红框代表它连接对象,因为我的项目需要用到音频、W800所以,引脚不够用 1.png

  • 其二,官方提供的使用硬件IIC接口,效果不好把控。

因为我在STM32上使用过硬件IIC,配置也比较简单,使用官方手册里面提供的IIC外设接口方法,效果可以实现,但是不同引脚的效果也不一样,让人心烦。

所以,我打算自己用软件写IIC驱动,个人使用效果—很nice。

IIC

1.简介要点

  • iic总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL,所有操作都靠这两根线完成。
  • iic总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。参考图1可以看出SDA高低电平的变化都是在SCL低电平期间。

2.png

2.时序图详解

我写的驱动包括:起始信号、终止信号、主机等待从机应答信号、主机读取数据的应答信号、主机读取最后一个数据的非应答信号、主机发送一个字节数据、主机读取一个字节数据、主机给从机写数据、主机读取从机数据。 下面给大家详细介绍下,希望可以带大家回顾下IIC协议的时序。

(1)引脚定义和基本函数实现

1. //iic.h
2. //需要的头文件省略没写,这里重点说时序
3. 
4. csi_gpio_pin_t pin_SCL;   
5. csi_gpio_pin_t pin_SDA;  
6. 
7. #define IIC_SCL PA21    //这里引脚可以自己选
8. #define IIC_SDA PA22
9. 
10. #define SDA_IN()  { csi_gpio_pin_dir(&pin_SDA, GPIO_DIRECTION_INPUT);}  //SDA为输入引脚
11. #define SDA_OUT() { csi_gpio_pin_dir(&pin_SDA, GPIO_DIRECTION_OUTPUT);} //SDA为输出引脚
12. 
13. 
14. #define  IIC_SCL1 {csi_gpio_pin_write(&pin_SCL, GPIO_PIN_HIGH);}  //给SCL写高电平
15. #define  IIC_SCL0 {csi_gpio_pin_write(&pin_SCL, GPIO_PIN_LOW);}   //给SCL写低电平
16. 
17. #define  IIC_SDA1 {csi_gpio_pin_write(&pin_SDA, GPIO_PIN_HIGH);}  //给SDA写高电平
18. #define  IIC_SDA0 {csi_gpio_pin_write(&pin_SDA, GPIO_PIN_LOW);}   //给SDA写低电平
19. 
20. #define  READ_SDA   csi_gpio_pin_read(&pin_SDA)                   //读取SDA的值
c++
1. //iic引脚初始化
2. void IIC_Init(void)
3. {  
4. csi_pin_set_mux(IIC_SCL, PIN_FUNC_GPIO);   //SCL复用为GPIO
5. csi_pin_set_mux(IIC_SDA, PIN_FUNC_GPIO);   //SDA复用为GPIO
6. 
7. csi_gpio_pin_init(&pin_SCL, IIC_SCL);
8. csi_gpio_pin_dir(&pin_SCL, GPIO_DIRECTION_OUTPUT);  //SCL为输出
9. csi_gpio_pin_init(&pin_SDA, IIC_SDA);
10. csi_gpio_pin_dir(&pin_SDA, GPIO_DIRECTION_OUTPUT); //先配置成输出
11. 
12.     IIC_SCL1;  //先把SCL和SDA拉高的目的:给SDA一个下降沿,时序就开始了
13.     IIC_SDA1;
14. }
c++

(2)起始信号和终止信号

如下图

  • SCL信号线为高电平期间,SDA线由高电平向低电平变化表示起始信号
  • SCL信号线为高电平期间,SDA线由低电平向高电平变化表示终止信号 3.png

因此:

1. //函 数 名:void IIC_Start(void)
2. //功    能:IIC起始信号
3. void IIC_Start(void)
4. {
5.  SDA_OUT();     //sda线输出
6.  IIC_SDA1; 
7.  delay_us(4);  
8.  IIC_SCL1;
9.  delay_us(4);
10.   IIC_SDA0;//START:when CLK is high,DATA change form high to low 
11.   delay_us(4);
12.   IIC_SCL0;//钳住I2C总线,准备发送或接收数据 
13.   //delay_us(4);
14. } 
15. 
16. //函 数 名:void IIC_Stop()
17. //功    能:IIC停止信号
18. void IIC_Stop(void)
19. {
20.   SDA_OUT();//sda线输出
21.   IIC_SCL0;
22.   delay_us(4);
23.   IIC_SDA0;//STOP:when CLK is high DATA change form low to high
24.   delay_us(4);
25.   IIC_SCL1; 
26.   delay_us(4);
27.   IIC_SDA1;//发送I2C总线结束信号
28.   delay_us(4);                      
29. }  
c++

(3)等待从机应答ACK

数据传送格式:每一个字节必须保证时8位长度。数据传送时,先传高位,每个被传送的字节后面必须跟随一位应答位,在主机向从机传送数据时应答位由从机产生,主机检测从机产生的应答信号,如果从机应答时间超时,主机默认数据传输完成,本段代码就是检测从机的应答信号。

4.png

1. //等待应答信号到来
2. //返回值:1,接收应答失败
3. //        0,接收应答成功
4. uint8_t IIC_Wait_Ack(void)
5. {
6.  uint8_t ucErrTime=0;
7.  SDA_IN();      //SDA设置为输入  
8.  IIC_SDA1;delay_us(6);    
9.  IIC_SCL1;delay_us(6);  
10.   while(READ_SDA)
11.   {
12.     ucErrTime++;
13.     if(ucErrTime>250)
14.     {
15.       IIC_Stop();
16.       return 1;
17.     }
18.   }
19.   IIC_SCL0;//时钟输出0     
20.   return 0;  
21. } 
c++

(4)主机读数据产生的应答信号或非应答信号

主机读数据时有这样的规定:当主机每读一个数据主机都要回复应答信号。当最后一个字节数据读完后,主机要返回以“非应答”,并终止信号读出操作,这里的应答信号和非应答信号作用是给从机判断是否继续读取数据。由图可以看出应答信号是在SDA低电平期间,SCL由低置高再置低。非应答信号是在SDA位高电平期间SCL由低置高再置低。 5.png

1. //函 数 名:void IIC_Ack(void)
2. //功    能:IIC产生ACK应答信号
3. void IIC_Ack(void)
4. {
5.  IIC_SCL0;
6.  SDA_OUT();
7.  IIC_SDA0;
8.  delay_us(10);
9.  IIC_SCL1;
10.   delay_us(10);
11.   IIC_SCL0;
12. }
13. 
14. 
15. //函 数 名:void IIC_NAck(void)
16. //功    能:IIC产生NACK应答信号
17. void IIC_NAck(void)
18. {
19.   IIC_SCL0;
20.   SDA_OUT();
21.   IIC_SDA1;
22.   delay_us(10);
23.   IIC_SCL1;
24.   delay_us(10);
25.   IIC_SCL0;
26. }
27. 
28. ```C++
c++

(5)主机发送数据

主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。 6.jpg

主设备往从设备中写数据。数据传输格式如下: 7.jpg

1. //IIC发送一个字节
2. //返回从机有无应答
3. //1,有应答
4. //0,无应答        
5. void IIC_Send_Byte(uint8_t txd)
6. {                        
7. uint8_t t;   
8.  SDA_OUT();      
9.     IIC_SCL0;//拉低时钟开始数据传输
10. for(t=0;t<8;t++)
11.     {              
12.     if((txd&0x80)>>7)
13.     {
14.       IIC_SDA1;
15.     }
16.     else
17.       IIC_SDA0;
18. 
19.         txd<<=1;    
20.     delay_us(10);   //这三个延时都是必须的
21.     IIC_SCL1;
22.     delay_us(10); 
23.     IIC_SCL0; 
24.     delay_us(10);
25.     }  
26. }   
27. 
28. //完整IIC主机写一个字节数据
29. uint8_t IIC_Write_1Byte(uint8_t SlaveAddress, uint8_t REG_Address,uint8_t REG_data)
30. {
31.   IIC_Start();
32.   IIC_Send_Byte(SlaveAddress);
33.   if(IIC_Wait_Ack())
34.   {
35.     IIC_Stop();//释放总线
36.     return 1;//没应答则退出
37. 
38.   }
39.   IIC_Send_Byte(REG_Address);
40.   IIC_Wait_Ack(); 
41.   IIC_Send_Byte(REG_data);
42.   IIC_Wait_Ack(); 
43.   IIC_Stop();
44. 
45.   return 0;
46. }
47. 
c++

(5)主机读取数据

主设备从从设备中读数据。数据传输格式如下:

8.jpg

1. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
2. uint8_t IIC_Read_Byte(unsigned char ack)
3. {
4.  unsigned char i,receive=0;
5.  SDA_IN();//SDA设置为输入
6. for(i=0;i<8;i++ )
7.  {
8.         IIC_SCL0; 
9. delay_us(10);
10.     IIC_SCL1;
11.         receive<<=1;
12. if(READ_SDA)receive++;   
13.     delay_us(5); 
14.     }          
15. if (!ack)
16. IIC_NAck();//发送nACK
17. else
18. IIC_Ack(); //发送ACK   
19. return receive;
20. }
21. 
22. 
23. //IIC读一个字节数据
24. uint8_t IIC_Read_1Byte(uint8_t SlaveAddress, uint8_t REG_Address,uint8_t *REG_data)
25. {
26.   IIC_Start();
27.   IIC_Send_Byte(SlaveAddress);//发写命令
28.   if(IIC_Wait_Ack())
29.   {
30.      IIC_Stop();//释放总线
31.      return 1;//没应答则退出
32.   }   
33.   IIC_Send_Byte(REG_Address);
34.   IIC_Wait_Ack();
35.   IIC_Start(); 
36.   IIC_Send_Byte(SlaveAddress|0x01);//发读命令
37.   IIC_Wait_Ack();
38.   *REG_data = IIC_Read_Byte(0);
39.   IIC_Stop();
40. 
41.   return 0;
42. }
c++

结束语

以上就是一个完整的iic驱动解析,大家有问题欢迎在评论区交流,一起学习,一起进步。下面附录是写的iic驱动的代码,本人能力有限,还请各位大佬多多指教。

附录

驱动下载链接iic驱动

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
4月前
stm32f407探索者开发板(十三)——JLINK在线调试_软件调试_方法与技巧
stm32f407探索者开发板(十三)——JLINK在线调试_软件调试_方法与技巧
298 0
正点原子战舰开发板---串口调试(硬件调试的一点经验吧)
正点原子战舰开发板---串口调试(硬件调试的一点经验吧)
250 0
|
传感器 开发框架 网络协议
【毕设参考】ESP32 + HaaS Python 打造数据上云声控灯
【毕设参考】ESP32 + HaaS Python 打造数据上云声控灯
193 0
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
255 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
|
存储 Ubuntu Linux
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(下)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
229 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(下)
|
Linux 程序员 调度
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(中)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
228 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(中)
|
Linux 调度 开发工具
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(上)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石
255 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(上)
|
Linux 开发工具 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)查询方式的按键驱动程序_编写框架
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)查询方式的按键驱动程序_编写框架
175 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)查询方式的按键驱动程序_编写框架
|
Ubuntu Linux 开发工具
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十)驱动怎么学
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十)驱动怎么学
164 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十)驱动怎么学
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(上)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程
441 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十一)驱动进化之路:设备树的引入及简明教程(上)