1710 views|3 replies

20

Posts

0

Resources
The OP
 

[N32L43X Review] 2. Simulating I2C to drive OLED [Copy link]

 

The I2C bus is a serial bus that we often use in our development process.

The main features of I2C include:

1. Only two buses are needed;

2. There is a simple master/slave relationship between all components, and each device connected to the bus can be software-addressed by a unique address;

3. It is a true multi-master bus that can provide arbitration and conflict detection;

4. Maximum number of slaves: theoretically 127;

5. Transmission speed:

Standard Mode: Standard Mode = 100 Kbps

Fast Mode: Fast Mode = 400 Kbps

High speed mode: High speed mode = 3.4 Mbps

Ultra fast mode: Ultra fast mode = 5 Mbps

For detailed explanation of I2C bus protocol, please refer to the following materials:

UM10204_I2C-bus specification and user manual-Rev.7.0.pdf (733.36 KB, downloads: 1)

I2C总线规范.pdf (918.13 KB, downloads: 2)

This article mainly introduces GPIO software simulation to realize I2C to drive 0.96-inch OLED display. OLED display information: 0.96寸OLED使用文档V3.0.pdf (1.68 MB, downloads: 2)

Hardware Hookup

GND —— GND

VCC —— 3.3V

SCL —— PB8

SDA —— PB9

Software Code

I2C Code:

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

    RCC_EnableAPB2PeriphClk( SDA_GPIO_CRM_CLK, ENABLE);

    GPIO_InitStruct(&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, &GPIO_InitStructure);
}

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

    RCC_EnableAPB2PeriphClk( SDA_GPIO_CRM_CLK, ENABLE);

    GPIO_InitStruct(&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, &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(&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, &GPIO_InitStructure);

    //GPIO_SDA初始化设置
    GPIO_InitStruct(&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, &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 > 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 < 8; cnt++)
    {
        I2C_SDA((txd & 0x80) >> 7);
        txd <<= 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 < 8; cnt++)
    {
        I2C_SCL(0);
        Delay_Us(2);
        I2C_SCL(1);
        rec <<= 1;

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

        Delay_Us(1);
    }

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

    return rec;
}

OLED 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 & 0xF0) >> 4) | 0x10);
    OLED_Write_CMD(x & 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 < 8; cnt++)
    {
        OLED_Write_CMD(0xB0 + cnt);
        OLED_Write_CMD(0x00);
        OLED_Write_CMD(0x10);

        for(count = 0; count < 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 < 128; count++)
    {
        OLED_Write_Date(0x00);
    }
}

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

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

        for(count = 0; count < 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 > 128 - 1)
    {
        x = 0;
        y = y + 2;
    }

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

        for(cnt = 0; cnt < 8; cnt++)
        {
            OLED_Write_Date(F8x16[offset * 16 + cnt]);
        }

        OLED_Set_Pos(x, y + 1);

        for(cnt = 0; cnt < 8; cnt++)
        {
            OLED_Write_Date(F8x16[offset * 16 + cnt + 8]);
        }
    }
    else
    {
        OLED_Set_Pos(x, y);

        for(cnt = 0; cnt < 6; cnt++)
        {
            OLED_Write_Date(F6x8[offset][cnt]);
        }
    }
}

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 < len; cnt++)
    {
        temp = (num / oled_pow(10, len - cnt - 1)) % 10;

        if(show == 0 && cnt < (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[cnt] != '\0')
    {
        OLED_ShowChar(x, y, chr[cnt], size);
        x += 8;

        if(x > 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 < 16; cnt++)
    {
        OLED_Write_Date(Hzk[2 * no][cnt]);
        addr++;
    }

    OLED_Set_Pos(x, y + 1);

    for(cnt = 0; cnt < 16; cnt++)
    {
        OLED_Write_Date(Hzk[2 * no + 1][cnt]);
        addr++;
    }
}

//显示图片
/*
	[url=home.php?mod=space&uid=159083]@brief[/url] 显示图片
	@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 < y1; y++) //从起始页开始,画到终止页
    {
        OLED_Set_Pos(x0, y); //在页的起始列开始画

        for(x = x0; x < x1; x++) //画x1 - x0 列
        {
            OLED_Write_Date(BMP[j++]); //画图片的点
        }
    }
}

//显示动图
/*
	@brief			显示动图
	@param			x0:起始列地址
				y0:起始页地址
				x1:终止列地址
				y1:终止页地址
				k: 帧个数
				m: 单帧数组大小
				BMP[][m]:存放动图代码的数组
	@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[][m])
{
    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 < k; i++) //从第一帧开始画
    {
        j = 0;

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

            for(x = x0; x < x1; x++) //画x1 - x0 列
            {
                OLED_Write_Date(GIF[i][j++]); //画图片的点
            }
        }

        //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
}

Running Tests

SW_I2C_OLED

Test code
N32L43x_SW_I2C_OLED.zip (516.95 KB, downloads: 30)

This post is from Domestic Chip Exchange

Latest reply

In fact, its hardware I2C speed will be much higher, so the refresh rate will be much better. You can try it when you get a chance.   Details Published on 2022-8-14 22:07
 
 

7422

Posts

2

Resources
2
 

Thanks for sharing, looking forward to the follow-up.

This post is from Domestic Chip Exchange
Personal signature

默认摸鱼,再摸鱼。2022、9、28

 
 
 

6818

Posts

11

Resources
3
 

In fact, its hardware I2C speed will be much higher, so the refresh rate will be much better. You can try it when you get a chance.

This post is from Domestic Chip Exchange

Comments

Hardware I2C is indeed fast, I have tested it https://en.eeworld.com/bbs/thread-1212208-1-1.html#pid3164870  Details Published on 2022-8-14 23:20
 
 
 

20

Posts

0

Resources
4
 
lugl4313820 posted on 2022-8-14 22:07 In fact, its hardware I2C speed will be much higher, so the refresh rate will be much better. I will try it if I have a chance.

Hardware I2C is indeed fast, I have tested it https://en.eeworld.com/bbs/thread-1212208-1-1.html#pid3164870

This post is from Domestic Chip Exchange
 
 
 

Guess Your Favourite
Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list