• 21.[STM32]I2C协议弄不懂,深挖时序图带你编写底层驱动


      🍌
    🍌🍌
    作者简介:大家好啊,我叫DW,每天分享一些我新学到的知识,期待和大家一起进步
       🍋
     🍋🍋
     系列专栏:STM32
     开发板:STM32F103

    🍊如有写得不好的地方欢迎大家指正🍊
    创作时间:🍊🍊🍊2022年7月3日🍊🍊🍊 

            I2C(Inter-Integrated Circuit BUS) 为集成电路总线,该总线由NXP公司设计,多用于主控制器和从器件间的主从通信。IIC和SPI接口严格的说他们都是人们所定义的软硬结合体,分为物理层和协议层。

    SDA(Serial data)是数据线,它是用来传输数据的。
    SCL(Serial clock line)是时钟线,它是控制数据发送的时序的 。
        

     I2C最重要的三个点为:

    1.起始与结束条件

    2.应答和非应答 

    3.数据的有效性

            下面,我将一一介绍如何使用这三个重要的知识点,由于涉及到SDA的输出和输入模式选择,故先配置它的输出和输入两种模式。 

    1. //模式配置 out input
    2. void I2C_Mode(u8 addr){
    3. GPIO_InitTypeDef GPIO_InitStructure;
    4. if(addr){ //out
    5. GPIO_InitStructure.GPIO_Pin = SDA;//PB0
    6. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速率
    7. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
    8. }
    9. else{ //Input
    10. GPIO_InitStructure.GPIO_Pin = SCL;//PB1
    11. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//推挽输出
    12. }
    13. GPIO_Init(I2C_PROT,&GPIO_InitStructure);//初始化引脚
    14. }

    1: 输出模式

    0 :输入模式

    起始和结束条件

    1.当SCL为高电平的时候,SDA线上由高到低的跳变被定义为起始条件

    2.当SCL为高电平的时候,SDA线上由低到高的跳变被定义为停止条件

     

    起始条件

            由时序图可以知道,SCL和SDA在默认状态下都为高电平,同时需要延迟4.7us以上,我给它5us的延迟,之后把SDA拉低,再延迟5us,同时把SCL拉低,那么就完成了起始条件时序代码的编写。

    这里要注意,SDA选择输出模式

    1. //起始
    2. void I2C_Start(void){
    3. I2C_Mode(Out);
    4. SCL_High;
    5. SDA_High;
    6. delay_us(5);
    7. SDA_Low;
    8. delay_us(5);
    9. SCL_Low;
    10. }

     结束条件

            由时序图可以知道,SCL默认状态下为高电平,SDA默认状态下为低电平,同时需要延迟4.7us以上,我给它5us的延迟,把SDA拉高,再延迟5us,那么就完成了结束条件时序代码的编写。

    这里要注意,SDA选择输出模式

    向SDA总线写:输出模式

    向SDA总线读:输入模式

    1. //结束
    2. void I2C_Stop(void){
    3. I2C_Mode(Out);
    4. SDA_Low;
    5. delay_us(5);
    6. SCL_High;
    7. delay_us(5);
    8. SDA_High;
    9. }

    应答与非应答

            每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。

            由时序图可以知道,不管是应答状态下,还是非应答状态下,SCL都为高电平,那么先把SCL拉高,之后延时4us,再判断SDA的状态;

            定义一个Time变量,如果一直不应答,既读取到SDA数据位为1,就发送一个停止信号,表示器件不存在,防止程序停止卡死在这个位置,之后返回一个非应答信号1;

            如果读取到SDA数据位为0,表示应答,那么再把SCL拉低,延时4us,最后返回0,就完成了一次应答的操作。

    1. //应答非应答判断
    2. u8 I2C_Write_Ack(void){
    3. u8 Time;
    4. I2C_Mode(Input);
    5. SCL_High;
    6. delay_us(4);
    7. while(GPIO_ReadInputDataBit(I2C_PROT,SDA)){
    8. if(++Time>250){
    9. I2C_Stop();
    10. return 1;//1 非应答
    11. }
    12. }
    13. SDA_Low;//0 应答
    14. delay_us(4);
    15. return 0;
    16. }

    数据的有效性 

     

            在写数据时,当SCL为低电平时,允许数据发生变化,此时可以写数据。那么,该如何操作呢?首先需要把SCL拉低,然后保持4us;再选择输出模式,之后从高位开始一个bit一个bit写数据。

     注意:I2C从高位开始写数据。

    1. //写字节
    2. void I2C_Write_Byte(u8 data){
    3. SCL_Low;
    4. delay_us(4);
    5. for(u8 i=0;i<8;i++){
    6. I2C_Mode(Out);
    7. if((data<<i)&0x80) SDA_High;
    8. else
    9. SDA_Low;
    10. SCL_High;
    11. delay_us(4);
    12. SCL_Low;
    13. delay_us(4);
    14. }
    15. }

            在读数据时,选择输入模式, 我们需要将SCL总线拉高,因为此时数据是稳定有效的,之后读取SDA的数据,如果SDA为高电平,data或上0x01,读完数据后,将SCL拉低,最后返回data。

    data<<=1;//从低位开始读数据,不断左移,低位将变成高位。

    1. //读数据
    2. u8 I2C_Read_Data(void){
    3. u8 data;
    4. for(u8 i=0;i<8;i++){
    5. I2C_Mode(Input);
    6. SCL_High;
    7. delay_us(4);
    8. data<<=1;
    9. if(GPIO_ReadInputDataBit(I2C_PROT,SDA) == SET){
    10. data |= 0x01;
    11. }
    12. SCL_Low;
    13. delay_us(4);
    14. }
    15. return data;
    16. }

            自此,三个部分的代码全部编写完毕,我们了解了这三张时序的原理和使用方法之后,接下来将告诉大家如何在这个基础上驱动具有I2C接口的OLED。

    OLED简介

     

    1.工作电压: 3.3V/5V
    2.通信接口: 3-wire SPI, 4-wire SPI, I2C
    3.屏幕类型: OLED
    4.控制芯片: SSD1306
    5.分辨率: 128*64(Pixel)
    6.外形尺寸: 128*64(Pixel)
    7.显示颜色: 黄蓝(双色块屏)
    8.工作温度: -20°C ~ 70°C
    9.存储温度: -30°C ~ 80°C
    10.视角: >160
    注意事项:
    OLED 显示屏不同于 LCD,OLED 上电是没有反应的,需要程序驱动才会有显示!

    接线方式:

    SDA --- PC0

    SCL --- PC1

    VCC --- 5V

    GND --- 地

    OLED通讯地址和寄存器地址 

            所有的I2C器件都会有硬件地址,即芯片的地址,由手册可以知道,b7~b2为固定不变的,b1(SA0)一般选择0,bo(R/W)用于确定I2C总接口的操作模式,R/w # = 1,它处于读模式。R/w # = 0,它处于写模式一般只向OLED写数据,因此其地址为:0111 1000(0x78) ,故我们定义的OLED器件地址为

    #define OLED 0X78
    
    0x78:写器件地址

    总线时序图

      

    由总线时序图可以知道, 要想进行发数据或者命令的流程如下: 

     

     依据上述步骤,我们编写的代码如下:

     

    1. void OLED_Write_Cmd_Data(u8 cmd,u8 data){
    2. I2C_Start();
    3. I2C_Write_Byte(OLED);
    4. I2C_Write_Ack();
    5. if(!cmd){
    6. I2C_Write_Byte(0X00);
    7. I2C_Write_Ack();
    8. I2C_Write_Byte(data);
    9. }
    10. else{
    11. I2C_Write_Byte(0X40);
    12. I2C_Write_Ack();
    13. I2C_Write_Byte(data);
    14. }
    15. I2C_Write_Ack();
    16. I2C_Stop();
    17. }

    0:写命令

    1:写数据 

     至此,最重要的部分的代码已经编写完毕,其他关于OLED的说明在第九篇文章中已有详细清楚:

    9.[STM32]0.96寸OLED难理解?不妨来看看这个。好了一起来看看效果吧!

     

     

     为了方便下次查找,记得点点关注哦。

    🌜🌜🌜本章结束,我们下一章见🌜🌜🌜


    参考资料:

    1.STM32固件库手册

    2.正点原子STM32不完全手册_库函数版本

    3.参考视频  参考文章  9.[STM32]0.96寸OLED难理解?不妨来看看这个

    资料已上传,需要自取

  • 相关阅读:
    基于thinkphp校园二手交易网站#毕业设计
    【数据结构】七种排序方法,一篇文章掌握
    怎么用JMeter写性能测试脚本
    基于PHP+MySQL学院信息发布系统的设计与实现
    单例模式——数据库连接池设计Java代码实现
    MySQL 视图View的SQL语法和更新(视图篇 二)
    【高并发基础】Spring 事务传播级别及造成死锁的隐患分析
    Linux多线程(线程的创建,等待,终止,分离)
    HTML5简明教程系列之HTML5 表格与表单(二)
    vue3中实现放大镜效果,使用ref获取节点元素
  • 原文地址:https://blog.csdn.net/qq_48796593/article/details/125523250