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

[复制链接]
2700|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,详见下图:
3075eaf5d65510f573240f83cd65ca06
在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)函数。
  1. void II2CGpioInit(void)
  2. {
  3.         #if 0
  4.         gpio_init_type gpio_init_struct;
  5.         /* enable gpioc periph clock */
  6.   crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
  7.         
  8.         
  9.   gpio_default_para_init(&gpio_init_struct);

  10.   /* gpio output config */
  11.   gpio_bits_write(GPIOC, IO_SCL_PIN | IO_SDA_PIN, TRUE);
  12.         
  13.          gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
  14.   gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
  15.   gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
  16.   gpio_init_struct.gpio_pins = IO_SCL_PIN | IO_SDA_PIN;
  17.   gpio_init_struct.gpio_pull = GPIO_PULL_UP;
  18.   gpio_init(GPIOC, &gpio_init_struct);
  19.         #else
  20.                 gpio_init_type gpio_init_structure;
  21.           crm_periph_clock_enable(I2Cx_CLK, TRUE);
  22.     crm_periph_clock_enable(I2Cx_SCL_GPIO_CLK, TRUE);
  23.     crm_periph_clock_enable(I2Cx_SDA_GPIO_CLK, TRUE);

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

  26.     gpio_pin_mux_config(I2Cx_SDA_GPIO_PORT, I2Cx_SDA_GPIO_PinsSource, I2Cx_SDA_GPIO_MUX);

  27.     /* configure i2c pins: scl */
  28.     gpio_init_structure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
  29.     gpio_init_structure.gpio_mode           = GPIO_MODE_MUX;
  30.     gpio_init_structure.gpio_out_type       = GPIO_OUTPUT_OPEN_DRAIN;
  31.     gpio_init_structure.gpio_pull           = GPIO_PULL_UP;

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

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

  39.   /* i2c config */
  40.   i2c_config(&hi2cx);
  41.         #endif
  42. }

【i2c发送数据】
在AT32F423中的固件库中,官方给我们提供了一个i2c_application函数,函数封装了常用的数据发送函数。这个函数位于\middlewares\i2c_application_library目录下面。
69700636c72086140ede66bd290fed8f
这个函数中定义了i2c事件,传输数据的宽度,i2c的传输模式,i2c的传输反馈状态,i2c的事件状态等的枚举。
文件中还封装了i2c所有的发送、状态获取、接收、DMA发送、中断发送、阻寒式发送的函数,大家可以详细的去阅读他的源码,对掌握i2c非常有用:
dc5a1c1b7f73ecafd3d02ed513c65bc7
我这里也是采用了官方封装的发送函数来传输一个byte的函数:i2c_master_transmit。用这个函数,实现了向oled写一个字节,写命令、写数据的三个功能函数:
  1. /*****************************************************************************
  2. * [url=home.php?mod=space&uid=139335]@name[/url]       :void Write_IIC_Byte(uint8_t IIC_Byte)
  3. * [url=home.php?mod=space&uid=212281]@date[/url]       :2018-09-13
  4. * [url=home.php?mod=space&uid=42490]@function[/url]   :Write a byte of content with iic bus
  5. * [url=home.php?mod=space&uid=2814924]@parameters[/url] :IIC_Byte
  6. * @retvalue   :None
  7. ******************************************************************************/
  8. void Write_IIC_Byte(uint8_t IIC_Byte) {
  9.         i2c_status_type i2c_status;
  10.         #if 0
  11.         uint8_t i;
  12.         uint8_t m,da;
  13.         da=IIC_Byte;
  14.         OLED_SCL_CLR();
  15.         for(i=0;i<8;i++) {
  16.                 m=da;
  17.                 m=m&0x80;
  18.                 if(m==0x80) {
  19.                         OLED_SDA_SET();
  20.                 } else {
  21.                         OLED_SDA_CLR();
  22.                 }
  23.                 da=da<<1;
  24.                 OLED_SCL_SET();
  25.                 OLED_SCL_CLR();
  26.         }
  27.         #else
  28.         uint8_t buff[2] = {0};
  29.         buff[0] = IIC_Byte;
  30.         i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 1, 1000);
  31.         if(i2c_status != I2C_OK)
  32.         {
  33.                 printf("erro send %d",i2c_status);
  34.         }
  35.         #endif
  36. }

  37. /*****************************************************************************
  38. * [url=home.php?mod=space&uid=139335]@name[/url]       :void Write_IIC_Command(uint8_t IIC_Command)
  39. * [url=home.php?mod=space&uid=212281]@date[/url]       :2018-09-13
  40. * [url=home.php?mod=space&uid=42490]@function[/url]   :Write a byte of command to oled screen
  41. * [url=home.php?mod=space&uid=2814924]@parameters[/url] :IIC_Command:command to be written
  42. * @retvalue   :None
  43. ******************************************************************************/
  44. void Write_IIC_Command(uint8_t IIC_Command) {
  45.         i2c_status_type i2c_status;
  46.         #if 0
  47.         IIC_Start();
  48.         Write_IIC_Byte(IIC_SLAVE_ADDR);            //Slave address,SA0=0
  49.         IIC_Wait_Ack();        
  50.         Write_IIC_Byte(0x00);                        //write command
  51.         IIC_Wait_Ack();        
  52.         Write_IIC_Byte(IIC_Command);
  53.         IIC_Wait_Ack();        
  54.         IIC_Stop();
  55.         #else
  56.         uint8_t buff[2] = {0};
  57.         buff[0] = 0x00;
  58.         buff[1] = IIC_Command;
  59.         i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 2, 1000);
  60.         if(i2c_status != I2C_OK)
  61.         {
  62.                 printf("erro send %d",i2c_status);
  63.         }
  64.         #endif
  65. }

  66. /*****************************************************************************
  67. * @name       :void Write_IIC_Data(uint8_t IIC_Data)
  68. * @date       :2018-09-13
  69. * @function   :Write a byte of data to oled screen
  70. * @parameters :IIC_Data:data to be written
  71. * @retvalue   :None
  72. ******************************************************************************/
  73. void Write_IIC_Data(uint8_t IIC_Data) {
  74.         i2c_status_type i2c_status;
  75.         #if 0
  76.         IIC_Start();
  77.         Write_IIC_Byte(IIC_SLAVE_ADDR);                        //D/C#=0; R/W#=0
  78.         IIC_Wait_Ack();        
  79.         Write_IIC_Byte(0x40);                        //write data
  80.         IIC_Wait_Ack();        
  81.         Write_IIC_Byte(IIC_Data);
  82.         IIC_Wait_Ack();        
  83.         IIC_Stop();
  84.         #else
  85.         uint8_t buff[2] = {0};
  86.         buff[0] = 0x40;
  87.         buff[1] = IIC_Data;
  88.         i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 2, 1000);
  89.         if(i2c_status != I2C_OK)
  90.         {
  91.                 printf("erro send %d",i2c_status);
  92.         }
  93.         #endif
  94. }

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

  11. /**************初始化SSD1306*****************/        
  12. //        OLED_Display_Off(); //power off
  13.         OLED_WR_Byte(0xAE,OLED_CMD);//--display off        
  14.         OLED_WR_Byte(0x20,OLED_CMD);//---set low column address
  15.         OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
  16.         OLED_WR_Byte(0xb0,OLED_CMD);
  17.         OLED_WR_Byte(0xC8,OLED_CMD);//-not offset
  18.         OLED_WR_Byte(0x00,OLED_CMD);// contract control
  19.         OLED_WR_Byte(0x10,OLED_CMD);
  20.         OLED_WR_Byte(0x40,OLED_CMD);//--128
  21.         OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
  22.         OLED_WR_Byte(0xFF,OLED_CMD);
  23.         OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
  24.         OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse        
  25.         OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
  26.         OLED_WR_Byte(0x3F,OLED_CMD);        
  27.         OLED_WR_Byte(0xa4,OLED_CMD);//-set display offset
  28.         OLED_WR_Byte(0xd3,OLED_CMD);
  29.         OLED_WR_Byte(0x00,OLED_CMD);
  30.         OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
  31.         OLED_WR_Byte(0xF0,OLED_CMD);        
  32.         OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
  33.         OLED_WR_Byte(0x22,OLED_CMD);        
  34.         OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
  35.         OLED_WR_Byte(0x12,OLED_CMD);        
  36.         OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
  37.         OLED_WR_Byte(0x20,OLED_CMD);        
  38.         OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
  39.         OLED_WR_Byte(0x14,OLED_CMD);        
  40.         OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
  41.         //OLED_Display_On(); // power on
  42. }  

【字库】
字库我这里采用的是网友提供的,如有侵权请及时通知我,其中的我这里特别要说明一下如何生成我的汉字字库,我这里使用的工具是PCtolCD2002。汉字取模采用阴码、逐行式,顺向、C51格式,配置如下图:
0c8778ca3757f8746b6be8f7497c36bd
字宽与高采用16*16,宋体:
92e86dfa0797172e2b5c855d80ac4d68
生成字模后加入到字库函数中:
f4e36fd1595a35614a0d3192f975ed4d
【测试代码】
在主函数中,我先初始化oled,清屏,然后编写测试函数:
2bd765108abd27ddf26165b3797d7104
【实现的效果】
694643aac95f785f7e4adada584cb88d
【试用心得】
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驱动包,如需要移植,可以把包下载后,导入工程就可以了。
7baa21f38f145016bcf0ecf443eb4e14
ssd1306.zip (19.16 KB, 下载次数: 65)

回忆酱 发表于 2023-11-4 14:58 | 显示全部楼层
您需要登录后才可以回帖 登录 | 注册

本版积分规则

188

主题

844

帖子

12

粉丝
快速回复 在线客服 返回列表 返回顶部