打印
[STM32F4]

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

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


使用特权

评论回复
沙发
漫天星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 由低电平跳变为高电平。
光看文字解释似乎很抽象,来,咱们直接上图(本人自己画的丑图)!

使用特权

评论回复
5
漫天星yl|  楼主 | 2023-7-31 15:18 | 只看该作者
start 表示起始信号,stop 表示停止信号,其实起始信号与停止信号的定义就是用 SDA 与 SCL 的不同组成来表示罢了。(当然,我对这俩规定的理解是,可以把SDA 想象成一个开关的刀,下,即合上,开始传输;上,即断开,停止传输。)

使用特权

评论回复
6
漫天星yl|  楼主 | 2023-7-31 15:18 | 只看该作者
(3) 应答信号
发送器发生一个字节,在第九个时钟脉冲期间释放总线,接收器反馈一个应答信号。而应答信号又可分为两种,①有效应答(ACK):应答信号为低电平;②无效应答(NACK):应答信号为高电平。

使用特权

评论回复
7
漫天星yl|  楼主 | 2023-7-31 15:19 | 只看该作者
(4) 数据传输
每位数据都有一个时钟脉冲来同步控制,即在 SCL 串行时钟配合下,SDA 逐位地串行传送每一位数据,传输是边沿出发的。

使用特权

评论回复
8
漫天星yl|  楼主 | 2023-7-31 15:19 | 只看该作者
(5) 数据有效性
怎样的数据在传输过程中才算有效呢?在时钟信号高电平期间,数据线上的数据保持稳定。(或许又不好理解,那再上图!)

使用特权

评论回复
9
漫天星yl|  楼主 | 2023-7-31 15:19 | 只看该作者

使用特权

评论回复
10
漫天星yl|  楼主 | 2023-7-31 15:19 | 只看该作者
3、I2C 基本读写过程
主要包括三种通信过程:主机写数据到从机、主机读取从机数据、复合通信(既读又写)。在应用中,主要是前两种通信过程,复合通信很少见,这里我也只介绍前两种,后面驱动实战部分也是对这两种分别举例展示。

使用特权

评论回复
11
漫天星yl|  楼主 | 2023-7-31 15:20 | 只看该作者
(1) 主机写数据到从机
通信过程如下图所示:

(有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。)
首先,主机产生一个起始信号,开始传输数据。接着,主机向从机发送地址位和读写位(0),确定是哪个从机要接收主机发送的数据。之后就开始从主机向从机发送有效数据,从机根据接收到的数据返回有效应答或无效应答。最后主机产生一个停止信号,表示此次传输结束。
整个过程就是这样的,我不知道自己是否表述清楚,其实大家也可以类比送快递的一个过程,要知道送去谁家,送什么东西,签收时给一个反馈,在淘宝确认收货表示派送成功,过程终止。这样可能形象一点。

使用特权

评论回复
12
漫天星yl|  楼主 | 2023-7-31 15:22 | 只看该作者
(2) 主机读从机数据
通信过程如下图所示:

(有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。)
大体过程与主机写数据到从机上差不多,大家自行类比理解哈,要注意的点就是数据和应答信号是谁发给谁。

使用特权

评论回复
13
漫天星yl|  楼主 | 2023-7-31 15:26 | 只看该作者
二、实战应用
1、I2C 驱动 4线 0.96寸 OLED 显示屏
这个模块相信大家并不陌生,小型而又低功耗,运用范围十分广泛。现在关于这个模块在STM32F1系列上的实现源码很多,但在STM32F4系列上的实现源码很少,还是有很多人不懂得移植或者移植不成功,也当作是一个半开源了吧。
话不多说,开始讲思路!

使用特权

评论回复
14
漫天星yl|  楼主 | 2023-7-31 15:26 | 只看该作者
其实就是按照前面所讲的对应的通信过程和相关协议规定来配置,再配合OLED的数据手册,编写移植显示的函数即可。
驱动该模块的流程图就是主机写数据到从机的通信过程,再放一遍图吧(把良心打在公屏上)

使用特权

评论回复
15
漫天星yl|  楼主 | 2023-7-31 15:26 | 只看该作者
通信过程的配置就按照下面这部分代码配置即可,注释个人觉得挺清楚的了。
/*********************OLED写数据************************************/ 
void OLED_WrDat(unsigned char IIC_Data)
{
        IIC_Start();   //起始信号
  IIC_Send_Byte(0x78);                        //确定从机地址
        IIC_Wait_Ack();       
  IIC_Send_Byte(0x40);                        //确定读写位,写为0
        IIC_Wait_Ack();                  //从机应答
  IIC_Send_Byte(IIC_Data);  //开始写数据到从机上
        IIC_Wait_Ack();           //从机应答
        IIC_Stop();               //停止信号
}

使用特权

评论回复
16
漫天星yl|  楼主 | 2023-7-31 15:26 | 只看该作者
起始信号的定义:
void IIC_Start(void)
{
        SDA_OUT();     //sda线输出
        IIC_SDA=1;                    
        IIC_SCL=1;  
        delay_us(2);
        IIC_SDA=0;     //START:when CLK is high,DATA change form high to low
        delay_us(2);
        IIC_SCL=0;     //钳住I2C总线,准备发送或接收数据
}          

使用特权

评论回复
17
漫天星yl|  楼主 | 2023-7-31 15:27 | 只看该作者
停止信号的定义:
void IIC_Stop(void)
{
        SDA_OUT();//sda线输出
        IIC_SCL=0;
        IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
        delay_us(2);
        IIC_SCL=1;
        IIC_SDA=1;//发送I2C总线结束信号
        delay_us(2);                                                                                                             
}

使用特权

评论回复
18
漫天星yl|  楼主 | 2023-7-31 15:27 | 只看该作者
等待应答信号:
//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功

u8 IIC_Wait_Ack(void)
{
        u8 ucErrTime=0;
        SDA_IN();      //SDA设置为输入  
        IIC_SDA=1;delay_us(1);   
        IIC_SCL=1;delay_us(1);       
  
        while(READ_SDA)
        {
                ucErrTime++;
                if(ucErrTime>250)
                {
                        IIC_Stop();
                        return 1;
                }
        }
        IIC_SCL=0;//时钟输出0           
        return 0;  
}

使用特权

评论回复
19
漫天星yl|  楼主 | 2023-7-31 15:27 | 只看该作者
有效应答与无效应答的定义:
//产生ACK应答
void IIC_Ack(void)
{
        IIC_SCL=0;
        SDA_OUT();
        IIC_SDA=0;
        delay_us(4);
        IIC_SCL=1;
        delay_us(4);
        IIC_SCL=0;
}

//不产生ACK应答                    
void IIC_NAck(void)
{
        IIC_SCL=0;
        SDA_OUT();
        IIC_SDA=1;
        delay_us(4);
        IIC_SCL=1;
        delay_us(4);
        IIC_SCL=0;
}                                                        

使用特权

评论回复
20
漫天星yl|  楼主 | 2023-7-31 15:28 | 只看该作者
根据数据手册又可编写一些显示函数,如下:
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{             
        unsigned char c=0,i=0;       
                c=chr-' ';//得到偏移后的值                       
                if(x>128-1){x=0;y=y+2;}
                if(Char_Size ==16)
                        {
                        OLED_Set_Pos(x,y);       
                        for(i=0;i<8;i++)
                        OLED_WrDat(F8X16[c*16+i]);
                        OLED_Set_Pos(x,y+1);
                        for(i=0;i<8;i++)
                        OLED_WrDat(F8X16[c*16+i+8]);
                        }
                        else {       
                                OLED_Set_Pos(x,y);
                                for(i=0;i<6;i++)
                                OLED_WrDat(F6x8[c][i]);
                               
                        }
}
//m^n函数
u32 oled_pow(u8 m,u8 n)
{
        u32 result=1;         
        while(n--)result*=m;   
        return result;
}                                  
//显示2个数字
//x,y :起点坐标         
//len :数字的位数
//size:字体大小
//mode:模式        0,填充模式;1,叠加模式
//num:数值(0~4294967295);                           
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{                
        u8 t,temp;
        u8 enshow=0;                                                  
        for(t=0;t<len;t++)
        {
                temp=(num/oled_pow(10,len-t-1))%10;
                if(enshow==0&&t<(len-1))
                {
                        if(temp==0)
                        {
                                OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
                                continue;
                        }else enshow=1;
                          
                }
                 OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
        }
}
//显示一个字符号串
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{
        unsigned char j=0;
        while (chr[j]!='\0')
        {                OLED_ShowChar(x,y,chr[j],Char_Size);
                        x+=8;
                if(x>120){x=0;y+=2;}
                        j++;
        }
}

//------------显示中文------------------
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
{                                  
        u8 t,adder=0;
        OLED_Set_Pos(x,y);       
    for(t=0;t<16;t++)
                {
                                OLED_WrDat(Hzk[2*no][t]);
                                adder+=1;
     }       
                OLED_Set_Pos(x,y+1);       
    for(t=0;t<16;t++)
                        {       
                                OLED_WrDat(Hzk[2*no+1][t]);
                                adder+=1;
      }                                       
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

30

主题

346

帖子

0

粉丝