使用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驱动

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
1月前
|
IDE 开发工具 流计算
保姆级Arduino开发环境搭建
保姆级Arduino开发环境搭建
28 1
|
10月前
|
C语言
野火F1开发板STM32案例-MultiButton移植
野火F1开发板STM32案例-MultiButton移植
129 0
|
Ubuntu 编译器 Linux
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(九)第一个程序背后的C语言知识
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(九)第一个程序背后的C语言知识
139 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(九)第一个程序背后的C语言知识
|
Ubuntu Linux 开发工具
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十)驱动怎么学
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十)驱动怎么学
130 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十)驱动怎么学
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
202 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)具体单板的按键驱动程序(查询方式)
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(中)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程
177 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(中)
|
Ubuntu Linux API
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(上)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程
232 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(上)
|
Ubuntu Linux 测试技术
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(下)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程
299 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(下)
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十六)LED驱动程序框架
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十六)LED驱动程序框架
152 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十六)LED驱动程序框架
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十五)最简单的LED驱动程序
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十五)最简单的LED驱动程序
120 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十五)最简单的LED驱动程序