[STM32F4] STM32F407 —— 硬件 I2C 驱动的步骤与应用

[复制链接]
 楼主| 漫天星yl 发表于 2023-7-31 15:17 | 显示全部楼层 |阅读模式
此篇文章将从理论到实践,对 I2C 通信方式进行理论介绍与实际应用。理论部分主要讲述 I2C 总线的物理结构与其协议中的相关规定及几种不同的通信过程。实践部分将以我们各种开发场合常用到的一个显示模块——4线 0.96寸 OLED 显示屏和本人正在进行的大创项目中用到的 MS5837 压力传感器为例,对在 STM32F407 上如何用 I2C 协议驱动模块进行介绍。这是本人(仅仅是一位刚入门嵌入式的平平无奇的大学生)在 CSDN 的第一篇博文,旨在自己总结相关知识,如有错误,欢迎各位技术大牛批评指正!本人VX:Cyy15880234628。如果能帮到他人,那再好不过啦!
一、I2C相关知识
1、物理层
(1) 定义
IIC 是 Inter-Integrated Circuit 的缩写,即两线式串行总线,有数据线 SDA 和时钟线 SCL 构成的串行总线,可进行数据的收发。总线结构如图所示:
9335164c75ff5ce9fb.png

 楼主| 漫天星yl 发表于 2023-7-31 15:17 | 显示全部楼层
(2) 通信方式
由定义可知,其数据线只有一条,所以 I2C 通信采用的是半双工通信,即数据在数据线上可以进行双向传输,但是不能同时收发。(可以想象成一根水管,水可以从左端流向右端,也可以从右端流向左端,但不能同时即从左流向右,又从右流向左,不可兼得哦!)
 楼主| 漫天星yl 发表于 2023-7-31 15:17 | 显示全部楼层
2、协议层
在协议层中,我们先对通信过程中所用到的几个名词所对应的相关规定做个了解。
(我也只懂这么多,哈哈哈哈,毕竟 I2C 协议不是一天两天读得完的。)

(1) 空闲状态
SDA 与 SCL 两条信号线同时处于高电平,这时规定总线处于空闲状态。
 楼主| 漫天星yl 发表于 2023-7-31 15:18 | 显示全部楼层
(2) 起始信号与停止信号的定义
起始信号: SCL 为高电平时,SDA 由高电平跳变为低电平;
停止信号: SCL 为高电平时,SDA 由低电平跳变为高电平。
光看文字解释似乎很抽象,来,咱们直接上图(本人自己画的丑图)!
1755364c7602fc5030.png
 楼主| 漫天星yl 发表于 2023-7-31 15:18 | 显示全部楼层
start 表示起始信号,stop 表示停止信号,其实起始信号与停止信号的定义就是用 SDA 与 SCL 的不同组成来表示罢了。(当然,我对这俩规定的理解是,可以把SDA 想象成一个开关的刀,下,即合上,开始传输;上,即断开,停止传输。)
 楼主| 漫天星yl 发表于 2023-7-31 15:18 | 显示全部楼层
(3) 应答信号
发送器发生一个字节,在第九个时钟脉冲期间释放总线,接收器反馈一个应答信号。而应答信号又可分为两种,①有效应答(ACK):应答信号为低电平;②无效应答(NACK):应答信号为高电平。
 楼主| 漫天星yl 发表于 2023-7-31 15:19 | 显示全部楼层
(4) 数据传输
每位数据都有一个时钟脉冲来同步控制,即在 SCL 串行时钟配合下,SDA 逐位地串行传送每一位数据,传输是边沿出发的。
 楼主| 漫天星yl 发表于 2023-7-31 15:19 | 显示全部楼层
(5) 数据有效性
怎样的数据在传输过程中才算有效呢?在时钟信号高电平期间,数据线上的数据保持稳定。(或许又不好理解,那再上图!)
 楼主| 漫天星yl 发表于 2023-7-31 15:19 | 显示全部楼层
 楼主| 漫天星yl 发表于 2023-7-31 15:19 | 显示全部楼层
3、I2C 基本读写过程
主要包括三种通信过程:主机写数据到从机、主机读取从机数据、复合通信(既读又写)。在应用中,主要是前两种通信过程,复合通信很少见,这里我也只介绍前两种,后面驱动实战部分也是对这两种分别举例展示。
 楼主| 漫天星yl 发表于 2023-7-31 15:20 | 显示全部楼层
(1) 主机写数据到从机
通信过程如下图所示:
6638564c760a34a381.png
(有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。)
首先,主机产生一个起始信号,开始传输数据。接着,主机向从机发送地址位和读写位(0),确定是哪个从机要接收主机发送的数据。之后就开始从主机向从机发送有效数据,从机根据接收到的数据返回有效应答或无效应答。最后主机产生一个停止信号,表示此次传输结束。
整个过程就是这样的,我不知道自己是否表述清楚,其实大家也可以类比送快递的一个过程,要知道送去谁家,送什么东西,签收时给一个反馈,在淘宝确认收货表示派送成功,过程终止。这样可能形象一点。
 楼主| 漫天星yl 发表于 2023-7-31 15:22 | 显示全部楼层
(2) 主机读从机数据
通信过程如下图所示:
5323064c76143e73a0.png
(有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。)
大体过程与主机写数据到从机上差不多,大家自行类比理解哈,要注意的点就是数据和应答信号是谁发给谁。
 楼主| 漫天星yl 发表于 2023-7-31 15:26 | 显示全部楼层
二、实战应用
1、I2C 驱动 4线 0.96寸 OLED 显示屏
这个模块相信大家并不陌生,小型而又低功耗,运用范围十分广泛。现在关于这个模块在STM32F1系列上的实现源码很多,但在STM32F4系列上的实现源码很少,还是有很多人不懂得移植或者移植不成功,也当作是一个半开源了吧。
话不多说,开始讲思路!
 楼主| 漫天星yl 发表于 2023-7-31 15:26 | 显示全部楼层
其实就是按照前面所讲的对应的通信过程和相关协议规定来配置,再配合OLED的数据手册,编写移植显示的函数即可。
驱动该模块的流程图就是主机写数据到从机的通信过程,再放一遍图吧(把良心打在公屏上) 4252664c7621fcfe03.png
 楼主| 漫天星yl 发表于 2023-7-31 15:26 | 显示全部楼层
通信过程的配置就按照下面这部分代码配置即可,注释个人觉得挺清楚的了。
  1. /*********************OLED写数据************************************/
  2. void OLED_WrDat(unsigned char IIC_Data)
  3. {
  4.         IIC_Start();   //起始信号
  5.   IIC_Send_Byte(0x78);                        //确定从机地址
  6.         IIC_Wait_Ack();       
  7.   IIC_Send_Byte(0x40);                        //确定读写位,写为0
  8.         IIC_Wait_Ack();                  //从机应答
  9.   IIC_Send_Byte(IIC_Data);  //开始写数据到从机上
  10.         IIC_Wait_Ack();           //从机应答
  11.         IIC_Stop();               //停止信号
  12. }
 楼主| 漫天星yl 发表于 2023-7-31 15:26 | 显示全部楼层
起始信号的定义:
  1. void IIC_Start(void)
  2. {
  3.         SDA_OUT();     //sda线输出
  4.         IIC_SDA=1;                    
  5.         IIC_SCL=1;  
  6.         delay_us(2);
  7.         IIC_SDA=0;     //START:when CLK is high,DATA change form high to low
  8.         delay_us(2);
  9.         IIC_SCL=0;     //钳住I2C总线,准备发送或接收数据
  10. }          
 楼主| 漫天星yl 发表于 2023-7-31 15:27 | 显示全部楼层
停止信号的定义:
  1. void IIC_Stop(void)
  2. {
  3.         SDA_OUT();//sda线输出
  4.         IIC_SCL=0;
  5.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
  6.         delay_us(2);
  7.         IIC_SCL=1;
  8.         IIC_SDA=1;//发送I2C总线结束信号
  9.         delay_us(2);                                                                                                             
  10. }
 楼主| 漫天星yl 发表于 2023-7-31 15:27 | 显示全部楼层
等待应答信号:
  1. //等待应答信号到来
  2. //返回值:1,接收应答失败
  3. //        0,接收应答成功

  4. u8 IIC_Wait_Ack(void)
  5. {
  6.         u8 ucErrTime=0;
  7.         SDA_IN();      //SDA设置为输入  
  8.         IIC_SDA=1;delay_us(1);   
  9.         IIC_SCL=1;delay_us(1);       
  10.   
  11.         while(READ_SDA)
  12.         {
  13.                 ucErrTime++;
  14.                 if(ucErrTime>250)
  15.                 {
  16.                         IIC_Stop();
  17.                         return 1;
  18.                 }
  19.         }
  20.         IIC_SCL=0;//时钟输出0           
  21.         return 0;  
  22. }
 楼主| 漫天星yl 发表于 2023-7-31 15:27 | 显示全部楼层
有效应答与无效应答的定义:
  1. //产生ACK应答
  2. void IIC_Ack(void)
  3. {
  4.         IIC_SCL=0;
  5.         SDA_OUT();
  6.         IIC_SDA=0;
  7.         delay_us(4);
  8.         IIC_SCL=1;
  9.         delay_us(4);
  10.         IIC_SCL=0;
  11. }

  12. //不产生ACK应答                    
  13. void IIC_NAck(void)
  14. {
  15.         IIC_SCL=0;
  16.         SDA_OUT();
  17.         IIC_SDA=1;
  18.         delay_us(4);
  19.         IIC_SCL=1;
  20.         delay_us(4);
  21.         IIC_SCL=0;
  22. }                                                        
 楼主| 漫天星yl 发表于 2023-7-31 15:28 | 显示全部楼层
根据数据手册又可编写一些显示函数,如下:
  1. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
  2. {             
  3.         unsigned char c=0,i=0;       
  4.                 c=chr-' ';//得到偏移后的值                       
  5.                 if(x>128-1){x=0;y=y+2;}
  6.                 if(Char_Size ==16)
  7.                         {
  8.                         OLED_Set_Pos(x,y);       
  9.                         for(i=0;i<8;i++)
  10.                         OLED_WrDat(F8X16[c*16+i]);
  11.                         OLED_Set_Pos(x,y+1);
  12.                         for(i=0;i<8;i++)
  13.                         OLED_WrDat(F8X16[c*16+i+8]);
  14.                         }
  15.                         else {       
  16.                                 OLED_Set_Pos(x,y);
  17.                                 for(i=0;i<6;i++)
  18.                                 OLED_WrDat(F6x8[c][i]);
  19.                                
  20.                         }
  21. }
  22. //m^n函数
  23. u32 oled_pow(u8 m,u8 n)
  24. {
  25.         u32 result=1;         
  26.         while(n--)result*=m;   
  27.         return result;
  28. }                                  
  29. //显示2个数字
  30. //x,y :起点坐标         
  31. //len :数字的位数
  32. //size:字体大小
  33. //mode:模式        0,填充模式;1,叠加模式
  34. //num:数值(0~4294967295);                           
  35. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
  36. {                
  37.         u8 t,temp;
  38.         u8 enshow=0;                                                  
  39.         for(t=0;t<len;t++)
  40.         {
  41.                 temp=(num/oled_pow(10,len-t-1))%10;
  42.                 if(enshow==0&&t<(len-1))
  43.                 {
  44.                         if(temp==0)
  45.                         {
  46.                                 OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
  47.                                 continue;
  48.                         }else enshow=1;
  49.                           
  50.                 }
  51.                  OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
  52.         }
  53. }
  54. //显示一个字符号串
  55. void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
  56. {
  57.         unsigned char j=0;
  58.         while (chr[j]!='\0')
  59.         {                OLED_ShowChar(x,y,chr[j],Char_Size);
  60.                         x+=8;
  61.                 if(x>120){x=0;y+=2;}
  62.                         j++;
  63.         }
  64. }

  65. //------------显示中文------------------
  66. void OLED_ShowCHinese(u8 x,u8 y,u8 no)
  67. {                                  
  68.         u8 t,adder=0;
  69.         OLED_Set_Pos(x,y);       
  70.     for(t=0;t<16;t++)
  71.                 {
  72.                                 OLED_WrDat(Hzk[2*no][t]);
  73.                                 adder+=1;
  74.      }       
  75.                 OLED_Set_Pos(x,y+1);       
  76.     for(t=0;t<16;t++)
  77.                         {       
  78.                                 OLED_WrDat(Hzk[2*no+1][t]);
  79.                                 adder+=1;
  80.       }                                       
  81. }
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

350

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部