×

ESP32------关于软件I2C的移植

zxjy辉 zxjy辉 发表于2023-07-14 14:39:52 浏览371 评论0

抢沙发发表评论


漂亮的彩虹.jpg

这个可真的挖了一个坑,把我埋进去了,原本是用硬件I2C的,毕竟esp32 自带了两个I2C控制器,这样比较方便,但是我发现我手中,有一款oled,使用esp32,无法查找到对应设备地址,但是,使用商家给的stm32代码,是可以直接驱动的,说明屏幕没有问题;于是我就用另一款oled,接入esp32,是可以查找到对应的地址,这就很尴尬了,(这难道就是硬件I2C的缺点? 因为有的设备可以通过写,然后查找到对用的设备地址,有的需要先读再写,才能读到设备地址。参考链接:https://blog.csdn.net/lovemengx/article/details/127252061),所以打算开始写软件的I2C驱动


  1. I2C协议的编写

    正常I2C总线的数据是:Start + I2C devece id + R/W + ACK + Data(first byte)+ ACK + ... + Data(n)+ ACK + Stop

    Ps:数据在传输的时候,SCL为高电平,此时对应的SDA的值,即为传输的数据,在SCL为低电平的时候,SDA值才能改变

    图片.png

    1. 起始信号

      在SCL为高电平,SDA由高电平转低电平

      图片.png

    2. 停止信号

      在SCL为高电平的时候,SDA由低电平转高电平

      图片.png

    3. 响应(从机应答)

      在主机发送一个字节的数据,都会等待从机应答,也就是一个字节结束后的第一个SCL高电平对应的SDA电平即应答,应答有效:SDA = 0 ,无效则:SDA = 1;

      此时发送的是  从机地址 0x78,

      图片.png

    4. 完整过程

      图片.png

      图片.png

      图片.png

    1. 这个在网上就有现成的,可以不用轮子,但是,在移植的时候需要注意。没想到,是真没想到,esp32在发送地址的时候,做了一个移位操作,我在使用硬件I2C的时候,又看到这个东西,但是,我想着,这个可能是硬件做的操作,软件I2C不需要做移位操作,结果,搞了老半天,都不行,我都emo了,最后用逻辑分析仪才发现,传输出来的地址,是有问题的,(原本硬件I2C是直接在传入地址的时候,我还做过向右的移位操作,结果初始化失败,就直接传入地址,反而可以用的)

      图片.png


  2. 参考代码

    1. #include "I2C_SOFT.H"
      /*===========================================================================
                          这个是关于eps32 软件I2C 的学习             
                          SDA:18
                          SCL:19
      ============================================================================*/
      
      
      #define OLED_CMD 0
      #define OLED_DATA 1
      #define u8 uint8_t
      #define mydelay_us 10
      #define I2C_SDA (gpio_num_t)18
      #define I2C_SCL (gpio_num_t)19
      #define SDA_Clear() gpio_set_level(I2C_SDA,0)
      #define SCL_Clear() gpio_set_level(I2C_SCL,0)
      #define SDA_Set() gpio_set_level(I2C_SDA,1)
      #define SCL_Set() gpio_set_level(I2C_SCL,1)
      
      u8 OLED_GRAM[144][8];
      int usleep(useconds_t us);
      void oled_init()
      {
          gpio_config_t my_gpio;
          my_gpio.intr_type = GPIO_INTR_DISABLE;                    //下降沿触发
          my_gpio.mode      = GPIO_MODE_OUTPUT;               //设置引脚为输入输出模式,如果是检测外部电平,则需要选择带有输入的模式
          my_gpio.pin_bit_mask = ( 1ull << GPIO_NUM_19);           //注意:引脚掩码是64位
          my_gpio.pull_up_en   = 1;                                 //使能上拉,注意:使用软件的上拉,其带负载能力非常的差,这个上拉和下拉是针对输出高电平时,如果外部没有上拉电阻,采用内部的上拉电阻,那么同样是输出3.3v,内部的供电电流小,外部上拉采用的是外部的3.3v,所以电流比较大,带负载能力比较强
          my_gpio.pull_down_en = 0;                                 //下拉,应该是没什么区别的
          gpio_config( &my_gpio );  
      
          my_gpio.intr_type = GPIO_INTR_DISABLE;                    //下降沿触发
          my_gpio.mode      = GPIO_MODE_OUTPUT;               //设置引脚为输入输出模式,如果是检测外部电平,则需要选择带有输入的模式
          my_gpio.pin_bit_mask = ( 1ull << GPIO_NUM_18 );           //注意:引脚掩码是64位
          my_gpio.pull_up_en   = 1;                                 //使能上拉,注意:使用软件的上拉,其带负载能力非常的差,这个上拉和下拉是针对输出高电平时,如果外部没有上拉电阻,采用内部的上拉电阻,那么同样是输出3.3v,内部的供电电流小,外部上拉采用的是外部的3.3v,所以电流比较大,带负载能力比较强
          my_gpio.pull_down_en = 0;                                 //下拉,应该是没什么区别的
          gpio_config( &my_gpio );  
      
      }
      void IIC_delay(void)
      {
          usleep(5);
      }
      //起始信号
      void I2C_Start(void)
      {
          SDA_Set();
          SCL_Set();
          IIC_delay();
          SDA_Clear();
          IIC_delay();
          SCL_Clear();
          IIC_delay();
      }
      //结束信号
      void I2C_Stop(void)
      {
          SDA_Clear();
          SCL_Set();
          IIC_delay();
          SDA_Set();
      }
      //等待信号响应 :传输完一个字节的数据就要等待应答
      void I2C_WaitAck(void) //测数据信号的电平
      {                      //也就是在时钟线在高电平的时候,将硬件间高电平的SDA拉低
          SDA_Set();
          IIC_delay();
          SCL_Set();
          IIC_delay();
          SCL_Clear();   //时钟线只有在拉低的时候,才能切换数据线,也就是换一个数据
          IIC_delay();
      }
      //写入一个字节
      void Send_Byte(u8 dat)
      {
          u8 i;
          for(i=0;i<8;i++)
          {
              if(dat&0x80)//将dat的8位从最高位依次写入
              {
                  SDA_Set();
          }
              else
              {
                  SDA_Clear();
          }
              IIC_delay();
              SCL_Set();      //在数据传输的时候,必须要保证时钟脉冲在高电平期间的稳定(保持高电平)
              IIC_delay();
              SCL_Clear();      //将时钟信号设置为低电平
              dat<<=1;
        }
      }
      //发送一个字节
      //mode:数据/命令标志 0,表示命令;1,表示数据;
      void OLED_WR_Byte(u8 dat,u8 mode)
      {
          I2C_Start();
          Send_Byte(0x78);
          I2C_WaitAck();
          if(mode){Send_Byte(0x40);} //写入寄存器的值
        else{Send_Byte(0x00);}
          I2C_WaitAck();             //每次写完一个字节,就要等待确认信号
          Send_Byte(dat);
          I2C_WaitAck();
          I2C_Stop();
      }
      void oled_soft_init()
      {
          oled_init();
      
          OLED_WR_Byte(0xAE, OLED_CMD); //--display off
          OLED_WR_Byte(0x00, OLED_CMD); //---set low column address
          OLED_WR_Byte(0x10, OLED_CMD); //---set high column address
          OLED_WR_Byte(0x40, OLED_CMD); //--set start line address
          OLED_WR_Byte(0xB0, OLED_CMD); //--set page address
          OLED_WR_Byte(0x81, OLED_CMD); // contract control
          OLED_WR_Byte(0xFF, OLED_CMD); //--128
          OLED_WR_Byte(0xA1, OLED_CMD); // set segment remap
          OLED_WR_Byte(0xA6, OLED_CMD); //--normal / reverse
          OLED_WR_Byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64)
          OLED_WR_Byte(0x3F, OLED_CMD); //--1/32 duty
          OLED_WR_Byte(0xC8, OLED_CMD); // Com scan direction
          OLED_WR_Byte(0xD3, OLED_CMD); //-set display offset
          OLED_WR_Byte(0x00, OLED_CMD); //
       
          OLED_WR_Byte(0xD5, OLED_CMD); // set osc division
          OLED_WR_Byte(0x80, OLED_CMD); //
       
          OLED_WR_Byte(0xD8, OLED_CMD); // set area color mode off
          OLED_WR_Byte(0x05, OLED_CMD); //
       
          OLED_WR_Byte(0xD9, OLED_CMD); // Set Pre-Charge Period
          OLED_WR_Byte(0xF1, OLED_CMD); //
       
          OLED_WR_Byte(0xDA, OLED_CMD); // set com pin configuartion
          OLED_WR_Byte(0x12, OLED_CMD); //
       
          OLED_WR_Byte(0xDB, OLED_CMD); // set Vcomh
          OLED_WR_Byte(0x30, OLED_CMD); //
       
          OLED_WR_Byte(0x8D, OLED_CMD); // set charge pump enable
          OLED_WR_Byte(0x14, OLED_CMD); //
       
          OLED_WR_Byte(0xAF, OLED_CMD); //--turn on oled panel
      
      }
      //更新显存到OLED 
      void OLED_Refresh(void)
      {
          u8 i,n;
          for(i=0;i<8;i++)
          {
              OLED_WR_Byte(0xb0+i,OLED_CMD); //设置行起始地址      也就是第几页 ,分成了8页(8行)
              OLED_WR_Byte(0x00,OLED_CMD);   //设置低列起始地址    也就是每一页中都是有8行 128列的像素点 8位数据高位为0代表列(0~127),高位为1代表行
              OLED_WR_Byte(0x10,OLED_CMD);   //设置高列起始地址
              I2C_Start();
              Send_Byte(0x3c);               //设置从机地址,最低为为1 为读模式;为0,为写模式
              I2C_WaitAck();
              Send_Byte(0x40);               //定义写入的是数据还是指令 ,位6为1 为数据,为0为数据,
              I2C_WaitAck();
              for(n=0;n<128;n++)
              {
                  Send_Byte(OLED_GRAM[n][i]);
                  I2C_WaitAck();
              }
              I2C_Stop();
        }
      }
      void OLED_Soft_On(void)
      {
          // u8 i, n;
          // for (i = 0; i < 8; i++)
          // {
          //  OLED_WR_Byte(0xb0 + i, OLED_CMD); // 设置页地址(0~7)
          //  OLED_WR_Byte(0x00, OLED_CMD);     // 设置显示位置—列低地址
          //  OLED_WR_Byte(0x10, OLED_CMD);     // 设置显示位置—列高地址
          //  for (n = 0; n < 128; n++)
          //      OLED_WR_Byte(1, OLED_DATA);
          // } 
          u8 i,n;
          for(i=0;i<8;i++)
          {
             for(n=0;n<128;n++)
                  {
                   OLED_GRAM[n][i]=1;  
                  }
          }
          // 更新显示
          OLED_Refresh();
      }
      // 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
      void OLED_Soft_Clear(void)
      {
          // u8 i, n;
          // for (i = 0; i < 8; i++)
          // {
          //  OLED_WR_Byte(0xb0 + i, OLED_CMD); // 设置页地址(0~7)
          //  OLED_WR_Byte(0x00, OLED_CMD);     // 设置显示位置—列低地址
          //  OLED_WR_Byte(0x10, OLED_CMD);     // 设置显示位置—列高地址
          //  for (n = 0; n < 128; n++)
          //      OLED_WR_Byte(0, OLED_DATA);
          // }
          u8 i,n;
          for(i=0;i<8;i++)
          {
             for(n=0;n<128;n++)
                  {
                   OLED_GRAM[n][i]=0;//清除所有数据
                  }
          }
           // 更新显示
          OLED_Refresh();
      }
  3. 参考资料

    逻辑分析仪分析时序

    I2C协议详解

#好好学习!

群贤毕至

访客