打印
[AT32F423]

【AT-START-F423测评】硬件I2c驱动OLED屏

[复制链接]
1973|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lulugl|  楼主 | 2023-11-2 21:29 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 lulugl 于 2023-11-2 21:33 编辑

#申请原创# @21小跑堂
【前言】
OLED为常用的显示外设之一,他可以使用spi或者i2c接口来实现驱动。而以I2C为常见,它只需要两个IO就可以实现驱动,对于IO紧的MCU来说更为方便,而且可以与其它的I2C设备共同一个总线。AT32F423拥用3个I2C总线,可以搭载多个i2c设备。同时AT32F423也可以通过IO的复用,更换不同的IO来实现i2c。I2c驱动也可以用普通GPIO,软件摸拟时序来实现,这样使得可以接入更多的I2C的总线设备。本次实验使用i2c硬件、阻塞式的实现驱动。AT32F423的总线也可以用中断、DMA的方式来驱动,这样占用CPU的资源更小,传输速度更快。
【接线图】
这次i2c我选择i2c1,IO选择PB6为SCL,PB7为SDA。与OLED的接线图如下表:
OLED          开发板
SCL—————PB6
SDA—————PB7
GND-----------GND
VCC------------3.3V
【I2C总线初始化】
根据数据手册PB6、PB7的引脚复用GPIO_MUX_4,详见下图:

在IO初始化中,我们使用i2c的复用配置函数gpio_pin_mux_config来定义:
    /* gpio configuration */
    gpio_pin_mux_config(I2Cx_SCL_GPIO_PORT, I2Cx_SCL_GPIO_PinsSource, I2Cx_SCL_GPIO_MUX);
gpio_pin_mux_config(I2Cx_SDA_GPIO_PORT, I2Cx_SDA_GPIO_PinsSource, I2Cx_SDA_GPIO_MUX);
然后开启i2c的时序,以及gpio的时钟:
/* i2c periph clock enable */
    crm_periph_clock_enable(I2Cx_CLK, TRUE);
    crm_periph_clock_enable(I2Cx_SCL_GPIO_CLK, TRUE);
    crm_periph_clock_enable(I2Cx_SDA_GPIO_CLK, TRUE);
初始化i2c的引脚,由于i2c需要上拉电平,我们这里配置其为GPIO_PULL_UP模式。
最后我们配置i2c的总线频率为200K
具体的代码见void II2CGpioInit(void)函数。
void II2CGpioInit(void)
{
        #if 0
        gpio_init_type gpio_init_struct;
        /* enable gpioc periph clock */
  crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
        
        
  gpio_default_para_init(&gpio_init_struct);

  /* gpio output config */
  gpio_bits_write(GPIOC, IO_SCL_PIN | IO_SDA_PIN, TRUE);
        
         gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
  gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
  gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
  gpio_init_struct.gpio_pins = IO_SCL_PIN | IO_SDA_PIN;
  gpio_init_struct.gpio_pull = GPIO_PULL_UP;
  gpio_init(GPIOC, &gpio_init_struct);
        #else
                gpio_init_type gpio_init_structure;
          crm_periph_clock_enable(I2Cx_CLK, TRUE);
    crm_periph_clock_enable(I2Cx_SCL_GPIO_CLK, TRUE);
    crm_periph_clock_enable(I2Cx_SDA_GPIO_CLK, TRUE);

    /* gpio configuration */
    gpio_pin_mux_config(I2Cx_SCL_GPIO_PORT, I2Cx_SCL_GPIO_PinsSource, I2Cx_SCL_GPIO_MUX);

    gpio_pin_mux_config(I2Cx_SDA_GPIO_PORT, I2Cx_SDA_GPIO_PinsSource, I2Cx_SDA_GPIO_MUX);

    /* configure i2c pins: scl */
    gpio_init_structure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_structure.gpio_mode           = GPIO_MODE_MUX;
    gpio_init_structure.gpio_out_type       = GPIO_OUTPUT_OPEN_DRAIN;
    gpio_init_structure.gpio_pull           = GPIO_PULL_UP;

    gpio_init_structure.gpio_pins           = I2Cx_SCL_GPIO_PIN;
    gpio_init(I2Cx_SCL_GPIO_PORT, &gpio_init_structure);

    /* configure i2c pins: sda */
    gpio_init_structure.gpio_pins           = I2Cx_SDA_GPIO_PIN;
    gpio_init(I2Cx_SDA_GPIO_PORT, &gpio_init_structure);
               
                hi2cx.i2cx = I2Cx_PORT;

  /* i2c config */
  i2c_config(&hi2cx);
        #endif
}

【i2c发送数据】
在AT32F423中的固件库中,官方给我们提供了一个i2c_application函数,函数封装了常用的数据发送函数。这个函数位于\middlewares\i2c_application_library目录下面。

这个函数中定义了i2c事件,传输数据的宽度,i2c的传输模式,i2c的传输反馈状态,i2c的事件状态等的枚举。
文件中还封装了i2c所有的发送、状态获取、接收、DMA发送、中断发送、阻寒式发送的函数,大家可以详细的去阅读他的源码,对掌握i2c非常有用:

我这里也是采用了官方封装的发送函数来传输一个byte的函数:i2c_master_transmit。用这个函数,实现了向oled写一个字节,写命令、写数据的三个功能函数:
/*****************************************************************************
* [url=home.php?mod=space&uid=139335]@name[/url]       :void Write_IIC_Byte(uint8_t IIC_Byte)
* [url=home.php?mod=space&uid=212281]@date[/url]       :2018-09-13
* [url=home.php?mod=space&uid=42490]@function[/url]   :Write a byte of content with iic bus
* [url=home.php?mod=space&uid=2814924]@parameters[/url] :IIC_Byte
* @retvalue   :None
******************************************************************************/
void Write_IIC_Byte(uint8_t IIC_Byte) {
        i2c_status_type i2c_status;
        #if 0
        uint8_t i;
        uint8_t m,da;
        da=IIC_Byte;
        OLED_SCL_CLR();
        for(i=0;i<8;i++) {
                m=da;
                m=m&0x80;
                if(m==0x80) {
                        OLED_SDA_SET();
                } else {
                        OLED_SDA_CLR();
                }
                da=da<<1;
                OLED_SCL_SET();
                OLED_SCL_CLR();
        }
        #else
        uint8_t buff[2] = {0};
        buff[0] = IIC_Byte;
        i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 1, 1000);
        if(i2c_status != I2C_OK)
        {
                printf("erro send %d",i2c_status);
        }
        #endif
}

/*****************************************************************************
* [url=home.php?mod=space&uid=139335]@name[/url]       :void Write_IIC_Command(uint8_t IIC_Command)
* [url=home.php?mod=space&uid=212281]@date[/url]       :2018-09-13
* [url=home.php?mod=space&uid=42490]@function[/url]   :Write a byte of command to oled screen
* [url=home.php?mod=space&uid=2814924]@parameters[/url] :IIC_Command:command to be written
* @retvalue   :None
******************************************************************************/
void Write_IIC_Command(uint8_t IIC_Command) {
        i2c_status_type i2c_status;
        #if 0
        IIC_Start();
        Write_IIC_Byte(IIC_SLAVE_ADDR);            //Slave address,SA0=0
        IIC_Wait_Ack();        
        Write_IIC_Byte(0x00);                        //write command
        IIC_Wait_Ack();        
        Write_IIC_Byte(IIC_Command);
        IIC_Wait_Ack();        
        IIC_Stop();
        #else
        uint8_t buff[2] = {0};
        buff[0] = 0x00;
        buff[1] = IIC_Command;
        i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 2, 1000);
        if(i2c_status != I2C_OK)
        {
                printf("erro send %d",i2c_status);
        }
        #endif
}

/*****************************************************************************
* @name       :void Write_IIC_Data(uint8_t IIC_Data)
* @date       :2018-09-13
* @function   :Write a byte of data to oled screen
* @parameters :IIC_Data:data to be written
* @retvalue   :None
******************************************************************************/
void Write_IIC_Data(uint8_t IIC_Data) {
        i2c_status_type i2c_status;
        #if 0
        IIC_Start();
        Write_IIC_Byte(IIC_SLAVE_ADDR);                        //D/C#=0; R/W#=0
        IIC_Wait_Ack();        
        Write_IIC_Byte(0x40);                        //write data
        IIC_Wait_Ack();        
        Write_IIC_Byte(IIC_Data);
        IIC_Wait_Ack();        
        IIC_Stop();
        #else
        uint8_t buff[2] = {0};
        buff[0] = 0x40;
        buff[1] = IIC_Data;
        i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 2, 1000);
        if(i2c_status != I2C_OK)
        {
                printf("erro send %d",i2c_status);
        }
        #endif
}

【OLED初始化】
我的oled的芯片是ssd1306,其他的oled大多数是通用的初始化函数,我这里采用的页写入模式为分页式,配置函数如下:
/*******************************************************************
* @name       :void OLED_Init(void)
* @date       :2018-08-27
* @function   :initialise OLED SSD1306 control IC
* @parameters :None
* @retvalue   :None
********************************************************************/                                    
void OLED_Init(void) {
        II2CGpioInit();
         delay_ms(200);

/**************初始化SSD1306*****************/        
//        OLED_Display_Off(); //power off
        OLED_WR_Byte(0xAE,OLED_CMD);//--display off        
        OLED_WR_Byte(0x20,OLED_CMD);//---set low column address
        OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
        OLED_WR_Byte(0xb0,OLED_CMD);
        OLED_WR_Byte(0xC8,OLED_CMD);//-not offset
        OLED_WR_Byte(0x00,OLED_CMD);// contract control
        OLED_WR_Byte(0x10,OLED_CMD);
        OLED_WR_Byte(0x40,OLED_CMD);//--128
        OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
        OLED_WR_Byte(0xFF,OLED_CMD);
        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);        
        OLED_WR_Byte(0xa4,OLED_CMD);//-set display offset
        OLED_WR_Byte(0xd3,OLED_CMD);
        OLED_WR_Byte(0x00,OLED_CMD);
        OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
        OLED_WR_Byte(0xF0,OLED_CMD);        
        OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
        OLED_WR_Byte(0x22,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(0x20,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_Display_On(); // power on
}  

【字库】
字库我这里采用的是网友提供的,如有侵权请及时通知我,其中的我这里特别要说明一下如何生成我的汉字字库,我这里使用的工具是PCtolCD2002。汉字取模采用阴码、逐行式,顺向、C51格式,配置如下图:

字宽与高采用16*16,宋体:

生成字模后加入到字库函数中:

【测试代码】
在主函数中,我先初始化oled,清屏,然后编写测试函数:

【实现的效果】

【试用心得】
1、网上驱动OLED的例程非常多,网友@suncat0504,也提前写了oled的软件驱动例程: https://bbs.21ic.com/icview-3336944-1-1.html。我所用的库也是在他的提供的例程上修改过来的。但是他的驱动例程好象在我的这里运行不了。我在他的基础上修改了一些参数,比如页的大小、修改为硬件驱动等。"
2、在测试中需要用逻辑分析仪来查看一下时序。
3、其实要驱动AT32F423的I2C,最好是去看看官方的一些例程,例程中详细的列出了多种方式的i2c的通信方式。
4、当然这次的驱动只是实现驱动而已,速度只用到了200K。如果需要用到高的刷新率,最好使用dma的方式来实现数据的传输,同时采用双缓存来实时更新数据,以实现高频的数据刷新。当然也可以移植u8g2等第三方库来实现更多的显示功能等。
5、同时也**有其他的大佬们能把dma等高效率的驱动奉献出来。
【附件】:OLED驱动包,如需要移植,可以把包下载后,导入工程就可以了。

ssd1306.zip (19.16 KB)

使用特权

评论回复
沙发
回忆酱| | 2023-11-4 14:58 | 只看该作者

使用特权

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

本版积分规则

156

主题

744

帖子

10

粉丝