805721366 发表于 2022-7-26 22:27

[N32L43X评测] 2.模拟I2C驱动OLED

<p><span style="font-family:宋体;">I2C总线是我们开发过程中经常使用的一种串行总线。</span></p>

<p><span style="font-family:宋体;">I2C主要功能特点包括:</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;1、只需要两条总线;</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;2、所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;3、是真正的多主设备总线,可提供仲裁和冲突检测;</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;4、最大从机数:理论上是127;</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;5、传输速度:</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;标准模式:Standard Mode = 100 Kbps</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;快速模式:Fast Mode = 400 Kbps</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;高速模式:High speed mode = 3.4 Mbps</span></p>

<p><span style="font-family:宋体;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;超快速模式:Ultra fast mode = 5 Mbps</span></p>

<p><span style="font-family:宋体;">I2C总线协议详解可参考以下资料:</span></p>

<p></p>

<p></p>

<p><span style="font-family:宋体;">此篇主要介绍GPIO软件模拟实现I2C来驱动0.96寸OLED显示屏,OLED显示屏资料:</span></p>

<p><span style="color:#f39c12;"><strong><span style="font-family:宋体;">硬件连接</span></strong></span></p>

<p><span style="font-family:宋体;">GND&nbsp; &mdash;&mdash;&nbsp; GND</span></p>

<p><span style="font-family:宋体;">VCC&nbsp; &mdash;&mdash;&nbsp; 3.3V</span></p>

<p><span style="font-family:宋体;">SCL&nbsp; &mdash;&mdash;&nbsp; PB8</span></p>

<p><span style="font-family:宋体;">SDA&nbsp; &mdash;&mdash;&nbsp; PB9</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle"><span style="color:#f39c12;"><strong><span style="font-family:宋体;">软件代码</span></strong></span></p>

<p class="imagemiddle"><span style="font-family:宋体;">I2C代码:</span></p>

<pre>
<code>//设置SDA输入模式
void SDA_IN(void)
{
    GPIO_InitType GPIO_InitStructure;

    RCC_EnableAPB2PeriphClk( SDA_GPIO_CRM_CLK, ENABLE);

    GPIO_InitStruct(&amp;GPIO_InitStructure);
    GPIO_InitStructure.Pin = SDA_PIN;
    GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
    GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
    GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Input;
    GPIO_InitPeripheral(SDA_GPIO, &amp;GPIO_InitStructure);
}

//设置SDA为输出模式
void SDA_OUT(void)
{
    GPIO_InitType GPIO_InitStructure;

    RCC_EnableAPB2PeriphClk( SDA_GPIO_CRM_CLK, ENABLE);

    GPIO_InitStruct(&amp;GPIO_InitStructure);
    GPIO_InitStructure.Pin = SDA_PIN;
    GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
    GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
    GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitPeripheral(SDA_GPIO, &amp;GPIO_InitStructure);
}

//设置SCL电平
void I2C_SCL(int n)
{
    if(n == 1)
    {
      GPIO_WriteBit(SCL_GPIO, SCL_PIN, Bit_SET); //设置SCL为高电平
    }
    else
    {
      GPIO_WriteBit(SCL_GPIO, SCL_PIN, Bit_RESET); //设置SCL为低电平
    }
}

//设置SDA电平
void I2C_SDA(int n)
{
    if(n == 1)
    {
      GPIO_WriteBit(SDA_GPIO, SDA_PIN, Bit_SET); //设置SDA为高电平
    }
    else
    {
      GPIO_WriteBit(SDA_GPIO, SDA_PIN, Bit_RESET); //设置SDA为低电平
    }
}

//读取SDA电平
unsigned char READ_SDA(void)
{
    return GPIO_ReadInputDataBit(SDA_GPIO, SDA_PIN); //读取SDA电平
}

//I2C初始化
void I2C_Initial(void)
{
    GPIO_InitType GPIO_InitStructure;

    //根据GPIO组初始化GPIO时钟
    RCC_EnableAPB2PeriphClk( SCL_GPIO_CRM_CLK, ENABLE);
    RCC_EnableAPB2PeriphClk( SDA_GPIO_CRM_CLK, ENABLE);

    //GPIO_SCL初始化设置
    GPIO_InitStruct(&amp;GPIO_InitStructure);
    GPIO_InitStructure.Pin = SCL_PIN;
    GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
    GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
    GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitPeripheral(SCL_GPIO, &amp;GPIO_InitStructure);

    //GPIO_SDA初始化设置
    GPIO_InitStruct(&amp;GPIO_InitStructure);
    GPIO_InitStructure.Pin = SDA_PIN;
    GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
    GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
    GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitPeripheral(SDA_GPIO, &amp;GPIO_InitStructure);
    //SCL、SDA的初始化均为高电平
    I2C_SCL(1);
    I2C_SDA(1);
}

//I2C Start
void I2C_Start(void)
{
    SDA_OUT();
    I2C_SDA(1);
    I2C_SCL(1);
    Delay_Us(4);
    I2C_SDA(0); //START:when CLK is high,DATA change form high to low
    Delay_Us(4);
    I2C_SCL(0); //钳住I2C总线,准备发送或接收数据
}

//I2C Stop
void I2C_Stop(void)
{
    SDA_OUT();
    I2C_SCL(0);
    I2C_SDA(0); //STOP:when CLK is high DATA change form low to high
    Delay_Us(4);
    I2C_SCL(1);
    I2C_SDA(1); //发送I2C总线结束信号
    Delay_Us(4);
}

//I2C_Wait_ack 返回HAL_OK表示wait成功,返回HAL_ERROR表示wait失败
unsigned char I2C_Wait_Ack(void) //IIC_Wait_ack,返回wait失败或是成功
{
    unsigned char ucErrTime = 0;

    SDA_IN();
    I2C_SDA(1);
    Delay_Us(1);
    I2C_SCL(1);
    Delay_Us(1);

    while(READ_SDA())
    {
      ucErrTime++;

      if(ucErrTime &gt; 250)
      {
            I2C_Stop();
            return HAL_ERROR;
      }
    }

    I2C_SCL(0);
    return HAL_OK;
}

//产生ACK应答
void I2C_Ack(void)
{
    I2C_SCL(0);
    SDA_OUT();
    I2C_SDA(0);
    Delay_Us(2);
    I2C_SCL(1);
    Delay_Us(2);
    I2C_SCL(0);
}

//产生NACK应答
void I2C_NAck(void)
{
    I2C_SCL(0);
    SDA_OUT();
    I2C_SDA(1);
    Delay_Us(2);
    I2C_SCL(1);
    Delay_Us(2);
    I2C_SCL(0);
}

//I2C_Send_Byte,入口参数为要发送的字节
void I2C_Send_Byte(unsigned char txd)
{
    unsigned char cnt = 0;

    SDA_OUT();
    I2C_SCL(0);

    for(cnt = 0; cnt &lt; 8; cnt++)
    {
      I2C_SDA((txd &amp; 0x80) &gt;&gt; 7);
      txd &lt;&lt;= 1;
      Delay_Us(2);
      I2C_SCL(1);
      Delay_Us(2);
      I2C_SCL(0);
      Delay_Us(2);
    }
}

//I2C_Read_Byte,入口参数为是否要发送ACK信号
unsigned char I2C_Read_Byte(unsigned char ack)
{
    unsigned char cnt, rec = 0;

    SDA_IN();

    for(cnt = 0; cnt &lt; 8; cnt++)
    {
      I2C_SCL(0);
      Delay_Us(2);
      I2C_SCL(1);
      rec &lt;&lt;= 1;

      if(READ_SDA())
      {
            rec++;
      }

      Delay_Us(1);
    }

    if(!ack)
    {
      I2C_NAck();
    }
    else
    {
      I2C_Ack();
    }

    return rec;
}</code></pre>

<p><span style="font-family:宋体;">OLED代码:</span></p>

<pre>
<code>//向设备写控制命令
static void OLED_Write_CMD(unsigned char cmd)
{
    I2C_Start();
    I2C_Send_Byte(0x78);
    I2C_Wait_Ack();
    I2C_Send_Byte(0x00);
    I2C_Wait_Ack();
    I2C_Send_Byte(cmd);
    I2C_Wait_Ack();
    I2C_Stop();
}

//向设备写数据
static void OLED_Write_Date(unsigned char date)
{
    I2C_Start();
    I2C_Send_Byte(0x78);
    I2C_Wait_Ack();
    I2C_Send_Byte(0x40);
    I2C_Wait_Ack();
    I2C_Send_Byte(date);
    I2C_Wait_Ack();
    I2C_Stop();
}

//坐标设置
static void OLED_Set_Pos(unsigned char x, unsigned char y)
{
    OLED_Write_CMD(0xB0 + y);
    OLED_Write_CMD(((x &amp; 0xF0) &gt;&gt; 4) | 0x10);
    OLED_Write_CMD(x &amp; 0x0F);
}

//开启OLED显示
static void OLED_Display_On(void)
{
    OLED_Write_CMD(0x8D); //SET DCDC命令
    OLED_Write_CMD(0x14); //DCDC ON
    OLED_Write_CMD(0xAF); //DISPLAY ON
}

//关闭OLED显示
static void OLED_Display_Off(void)
{
    OLED_Write_CMD(0x8D); //SET DCDC命令
    OLED_Write_CMD(0x10); //DCDC OFF
    OLED_Write_CMD(0xAE); //DISPLAY OFF
}

//OLED清屏
void OLED_Clear(void)
{
    unsigned char cnt, count;

    for(cnt = 0; cnt &lt; 8; cnt++)
    {
      OLED_Write_CMD(0xB0 + cnt);
      OLED_Write_CMD(0x00);
      OLED_Write_CMD(0x10);

      for(count = 0; count &lt; 128; count++)
      {
            OLED_Write_Date(0x00);
      }
    }
}

//OLED清行
void OLED_Clear_Row(unsigned char n)
{
    unsigned char count;

    OLED_Write_CMD(0xB0 + n);
    OLED_Write_CMD(0x00);
    OLED_Write_CMD(0x10);

    for(count = 0; count &lt; 128; count++)
    {
      OLED_Write_Date(0x00);
    }
}

//OLED填满屏幕
void OLED_Fill(void)
{
    unsigned char cnt, count;

    for(cnt = 0; cnt &lt; 8; cnt++)
    {
      OLED_Write_CMD(0xB0 + cnt); //设置页地址(0~7)
      OLED_Write_CMD(0x00); //设置显示位置—列低地址
      OLED_Write_CMD(0x10); //设置显示位置—列高地址

      for(count = 0; count &lt; 128; count++)
      {
            OLED_Write_Date(0x01);
      }
    }
}

//指定位置显示一个字符
//x:0~127
//y:0~63
//chr:字符
//size:选择字体 16/12
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr, unsigned char size)
{
    unsigned char offset = 0, cnt = 0;

    offset = chr - ' '; //计算偏移量

    if(x &gt; 128 - 1)
    {
      x = 0;
      y = y + 2;
    }

    if(size == 16)
    {
      OLED_Set_Pos(x, y);

      for(cnt = 0; cnt &lt; 8; cnt++)
      {
            OLED_Write_Date(F8x16);
      }

      OLED_Set_Pos(x, y + 1);

      for(cnt = 0; cnt &lt; 8; cnt++)
      {
            OLED_Write_Date(F8x16);
      }
    }
    else
    {
      OLED_Set_Pos(x, y);

      for(cnt = 0; cnt &lt; 6; cnt++)
      {
            OLED_Write_Date(F6x8);
      }
    }
}

unsigned int oled_pow(unsigned char m, unsigned char n)
{
    unsigned int result = 1;

    while(n--)
    {
      result *= m;
    }

    return result;
}

//指定位置显示一个数字
//x,y:起点坐标
//num:数值(0~4294967295)
//len:数字的位数
//size:字体大小
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len, unsigned char size)
{
    unsigned char cnt, temp;
    unsigned char show = 0;

    for(cnt = 0; cnt &lt; len; cnt++)
    {
      temp = (num / oled_pow(10, len - cnt - 1)) % 10;

      if(show == 0 &amp;&amp; cnt &lt; (len - 1))
      {
            if(temp == 0)
            {
                OLED_ShowChar(x + (size / 2) * cnt, y, ' ', size);
                continue;
            }
            else
            {
                show = 1;
            }
      }

      OLED_ShowChar(x + (size / 2) * cnt, y, temp + '0', size);
    }
}

//指定位置显示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr, unsigned char size)
{
    unsigned char cnt = 0;

    while(chr != '\0')
    {
      OLED_ShowChar(x, y, chr, size);
      x += 8;

      if(x &gt; 120)
      {
            x = 0;
            y += 2;
      }

      cnt++;
    }
}

//显示汉字
void OLED_ShowCHinese(unsigned char x, unsigned char y, unsigned char no)
{
    unsigned char cnt, addr = 0;

    OLED_Set_Pos(x, y);

    for(cnt = 0; cnt &lt; 16; cnt++)
    {
      OLED_Write_Date(Hzk);
      addr++;
    }

    OLED_Set_Pos(x, y + 1);

    for(cnt = 0; cnt &lt; 16; cnt++)
    {
      OLED_Write_Date(Hzk);
      addr++;
    }
}

//显示图片
/*
        @brief 显示图片
        @param                        x0:起始列地址
                                        y0:起始页地址
                                        x1:终止列地址
                                        y1:终止页地址
                                        BMP[]:存放图片代码的数组
        @retval                        无
*/
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, const unsigned char BMP[])
{
    unsigned int j = 0; //定义变量
    unsigned char x, y; //定义变量

    if(y1 % 8 == 0)
    {
      y = y1 / 8; //判断终止页是否为8的整数倍
    }
    else
    {
      y = y1 / 8 + 1;
    }

    for(y = y0; y &lt; y1; y++) //从起始页开始,画到终止页
    {
      OLED_Set_Pos(x0, y); //在页的起始列开始画

      for(x = x0; x &lt; x1; x++) //画x1 - x0 列
      {
            OLED_Write_Date(BMP); //画图片的点
      }
    }
}

//显示动图
/*
        @brief                        显示动图
        @param                        x0:起始列地址
                                y0:起始页地址
                                x1:终止列地址
                                y1:终止页地址
                                k: 帧个数
                                m: 单帧数组大小
                                BMP[]:存放动图代码的数组
        @retval                        无
*/
void OLED_DrawGIF(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char k, int m, const unsigned char GIF[])
{
    unsigned int j = 0; //定义变量
    unsigned char x, y, i; //定义变量

    if(y1 % 8 == 0)
    {
      y = y1 / 8; //判断终止页是否为8的整数倍
    }
    else
    {
      y = y1 / 8 + 1;
    }

    for (i = 0; i &lt; k; i++) //从第一帧开始画
    {
      j = 0;

      for(y = y0; y &lt; y1; y++) //从起始页开始,画到终止页
      {
            OLED_Set_Pos(x0, y); //在页的起始列开始画

            for(x = x0; x &lt; x1; x++) //画x1 - x0 列
            {
                OLED_Write_Date(GIF); //画图片的点
            }
      }

      //Delay_Ms(80);
    }
}

//OLED初始化
void OLED_Init(void)
{
    I2C_Initial();
    Delay_Ms(200);
    OLED_Write_CMD(0xAE); //display off
    OLED_Write_CMD(0x00); //set low column address
    OLED_Write_CMD(0x10); //set high column address
    OLED_Write_CMD(0x40); //set start line address
    OLED_Write_CMD(0xB0); //set page address
    OLED_Write_CMD(0x81); //contract control
    OLED_Write_CMD(0xFF); //128
    OLED_Write_CMD(0xA1); //set segment remap
    OLED_Write_CMD(0xA6); //normal / reverse
    OLED_Write_CMD(0xA8); //set multiplex ratio(1 to 64)
    OLED_Write_CMD(0x3F); //1/32 duty
    OLED_Write_CMD(0xC8); //Com scan direction
    OLED_Write_CMD(0xD3); //set display offset
    OLED_Write_CMD(0x00); //
    OLED_Write_CMD(0xD5); //set osc division
    OLED_Write_CMD(0x80); //
    OLED_Write_CMD(0xD8); //set area color mode off
    OLED_Write_CMD(0x05); //
    OLED_Write_CMD(0xD9); //Set Pre-Charge Period
    OLED_Write_CMD(0xF1); //
    OLED_Write_CMD(0xDA); //set com pin configuartion
    OLED_Write_CMD(0x12); //
    OLED_Write_CMD(0xDB); //set Vcomh
    OLED_Write_CMD(0x30); //
    OLED_Write_CMD(0x8D); //set charge pump enable
    OLED_Write_CMD(0x14); //
    OLED_Write_CMD(0xAF); //turn on oled panel
}</code></pre>

<p style="text-align: center;">&nbsp;</p>

<p><span style="color:#f39c12;"><strong><span style="font-family:宋体;">运行测试</span></strong></span></p>

<p>76782c3660d0897299f3b7439a38cce9</p>

<p><span style="color:#f39c12;"><strong><span style="font-family:宋体;">测试代码</span></strong></span><br />
</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

freebsder 发表于 2022-7-27 18:44

<p>谢谢分享,期待后续。</p>

lugl4313820 发表于 2022-8-14 22:07

<p>其实他的硬件I2C速度会提高很多,这样刷新率就会好很多,有机会试一下。</p>

805721366 发表于 2022-8-14 23:20

lugl4313820 发表于 2022-8-14 22:07
其实他的硬件I2C速度会提高很多,这样刷新率就会好很多,有机会试一下。

<p>硬件I2C速度确实快,有测试过https://bbs.eeworld.com.cn/thread-1212208-1-1.html#pid3164870</p>
页: [1]
查看完整版本: [N32L43X评测] 2.模拟I2C驱动OLED