[APM32E0] APM32E030 IIC的使用与OLED屏幕

[复制链接]
 楼主| LIZARD925 发表于 2025-6-28 15:37 | 显示全部楼层 |阅读模式
本帖最后由 LIZARD925 于 2025-6-28 15:37 编辑

#技术资源#

APM32E030系列使用记录--软硬件IIC驱动OLED屏幕与菜单的移植
软硬件IIC驱动OLED屏幕显示:
此例程中,我们使用软件(硬件)IIC驱动0.96寸OLED屏幕,实现OLED屏幕显示字符、数字、中文等信息,由于网上有很多开源的OLED驱动了,所用我们只需要配置好底层的通信接口即可,我们使用上次移植好按键库的程序,找到江科大的开源OLED屏幕驱动,将下面4个文件复制到自己的工程,注意我们使用的是GB2312编码,需要选择对应的程序,否则需要进行编码的转换,加入文件夹后打开keil加入到工程中,如图所示:
14707685aaabd7877f.png
57031685aaaed2352a.png
加入后,我们只需要更改底层的GPIO驱动函数,实现软件模拟IIC的数据通信,即完成了软件IIC驱动OLED屏幕的移植,IIC引脚使用PB8与PB9,主要函数如下所示:
  1. /**
  2.   * 函    数:OLED写SCL高低电平
  3.   * 参    数:要写入SCL的电平值,范围:0/1
  4.   * 返 回 值:无
  5.   * 说    明:当上层函数需要写SCL时,此函数会被调用
  6.   *           用户需要根据参数传入的值,将SCL置为高电平或者低电平
  7.   *           当参数传入0时,置SCL为低电平,当参数传入1时,置SCL为高电平
  8.   */
  9. void OLED_W_SCL(uint8_t BitValue)
  10. {
  11.         /*根据BitValue的值,将SCL置高电平或者低电平*/
  12.         GPIO_WriteBitValue(GPIOB, GPIO_PIN_8, BitValue);
  13.         
  14.         /*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
  15.         //...
  16. }

  17. /**
  18.   * 函    数:OLED写SDA高低电平
  19.   * 参    数:要写入SDA的电平值,范围:0/1
  20.   * 返 回 值:无
  21.   * 说    明:当上层函数需要写SDA时,此函数会被调用
  22.   *           用户需要根据参数传入的值,将SDA置为高电平或者低电平
  23.   *           当参数传入0时,置SDA为低电平,当参数传入1时,置SDA为高电平
  24.   */
  25. void OLED_W_SDA(uint8_t BitValue)
  26. {
  27.         /*根据BitValue的值,将SDA置高电平或者低电平*/
  28.         GPIO_WriteBitValue(GPIOB, GPIO_PIN_9, BitValue);
  29.         
  30.         /*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
  31.         //...
  32. }

  33. /**
  34.   * 函    数:OLED引脚初始化
  35.   * 参    数:无
  36.   * 返 回 值:无
  37.   * 说    明:当上层函数需要初始化时,此函数会被调用
  38.   *           用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
  39.   */
  40. void OLED_GPIO_Init(void)
  41. {
  42.         uint32_t i, j;
  43.         
  44.         /*在初始化前,加入适量延时,待OLED供电稳定*/
  45.         for (i = 0; i < 1000; i ++)
  46.         {
  47.                 for (j = 0; j < 1000; j ++);
  48.         }
  49.         
  50.         /*将SCL和SDA引脚初始化为开漏模式*/
  51.         RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOB);        //打开GPIOB的时钟

  52.         GPIO_Config_T GPIO_InitStructure;   
  53.         
  54.         GPIO_InitStructure.mode = GPIO_MODE_OUT;            // 输出模式
  55.         GPIO_InitStructure.pin = GPIO_PIN_8|GPIO_PIN_9;      
  56.         GPIO_InitStructure.speed = GPIO_SPEED_50MHz;        
  57.         GPIO_InitStructure.outtype = GPIO_OUT_TYPE_OD;        //开漏输出
  58.         GPIO_Config(GPIOB,&GPIO_InitStructure);

  59.         
  60.         /*释放SCL和SDA*/
  61.         OLED_W_SCL(1);
  62.         OLED_W_SDA(1);
  63. }
修改后,如不报错,则移植基本成功,可在主函数中添加初始化,并测试屏幕显示,可看到屏幕成功驱动,显示中文,英文,数字,图片等,主要初始化函数如下所示:
  1.         /*OLED初始化*/
  2.         OLED_Init();
  3.         
  4.         /*在(0, 0)位置显示字符'A',字体大小为8*16点阵*/
  5.         OLED_ShowChar(0, 0, 'A', OLED_8X16);
  6.         
  7.         /*在(16, 0)位置显示字符串"Hello World!",字体大小为8*16点阵*/
  8.         OLED_ShowString(16, 0, "Hello World!", OLED_8X16);
  9.         
  10.         /*在(0, 18)位置显示字符'A',字体大小为6*8点阵*/
  11.         OLED_ShowChar(0, 18, 'A', OLED_6X8);
  12.         
  13.         /*在(16, 18)位置显示字符串"Hello World!",字体大小为6*8点阵*/
  14.         OLED_ShowString(16, 18, "Hello World!", OLED_6X8);
  15.         
  16.         /*在(0, 28)位置显示数字12345,长度为5,字体大小为6*8点阵*/
  17.         OLED_ShowNum(0, 28, 12345, 5, OLED_6X8);
  18.         
  19.         /*在(40, 28)位置显示有符号数字-66,长度为2,字体大小为6*8点阵*/
  20.         OLED_ShowSignedNum(40, 28, -66, 2, OLED_6X8);
  21.         
  22.         /*在(70, 28)位置显示十六进制数字0xA5A5,长度为4,字体大小为6*8点阵*/
  23.         OLED_ShowHexNum(70, 28, 0xA5A5, 4, OLED_6X8);
  24.         
  25.         /*在(0, 38)位置显示二进制数字0xA5,长度为8,字体大小为6*8点阵*/
  26.         OLED_ShowBinNum(0, 38, 0xA5, 8, OLED_6X8);
  27.         
  28.         /*在(60, 38)位置显示浮点数字123.45,整数部分长度为3,小数部分长度为2,字体大小为6*8点阵*/
  29.         OLED_ShowFloatNum(60, 38, 123.45, 3, 2, OLED_6X8);
  30.         
  31.         /*在(0, 48)位置显示汉字串"你好,世界。",字体大小为固定的16*16点阵*/
  32.         OLED_ShowChinese(0, 48, "你好,世界。");
  33.         
  34.         /*在(96, 48)位置显示图像,宽16像素,高16像素,图像数据为Diode数组*/
  35.         OLED_ShowImage(96, 48, 16, 16, Diode);
  36.         
  37.         /*在(96, 18)位置打印格式化字符串,字体大小为6*8点阵,格式化字符串为"[%02d]"*/
  38.         OLED_Printf(96, 18, OLED_6X8, "[%02d]", 6);
  39.         
  40.         /*调用OLED_Update函数,将OLED显存数组的内容更新到OLED硬件进行显示*/
  41.         OLED_Update();
  42.         
  43.         /*延时3000ms,观察现象*/
  44.         Delay_ms(3000);
  45. ..........
至此,软件IIC驱动OLED屏幕大致完成,但某些时候,可能软件IIC速度不够用,对于一些简单的屏幕显示来说可能足够,如果跑菜单等UI,软件IIC的速度很影响菜单的显示与使用体验,所以我们可以配置硬件IIC,来优化IIC驱动OLED的使用体验,我们先将简单的屏幕显示优化成硬件IIC驱动,我们需要初始化硬件IIC,并使用硬件IIC的通信接口,去适配我们的协议层,我们需要更改OLED.C 前面的引脚配置,并将软件模拟的IIC读写函数改为硬件读写命令。
  1. /**
  2.   * 函    数:OLED引脚初始化
  3.   * 参    数:无
  4.   * 返 回 值:无
  5.   * 说    明:当上层函数需要初始化时,此函数会被调用
  6.   *           用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
  7.   */
  8. void OLED_GPIO_Init(void)
  9. {
  10.      /*将SCL和SDA引脚初始化为开漏模式*/
  11.     GPIO_Config_T gpio_init_structure;
  12.     I2C_Config_T iic_init_struct;
  13.         
  14.     RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOB);
  15.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);
  16.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);

  17.         /* Connect I2C to SCL */
  18.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_PIN1);
  19.     /* Connect I2C to SDA */
  20.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_PIN1);
  21.         
  22.     gpio_init_structure.mode = GPIO_MODE_AF;
  23.     gpio_init_structure.speed = GPIO_SPEED_50MHz;
  24.     gpio_init_structure.outtype = GPIO_OUT_TYPE_OD;
  25.     gpio_init_structure.pupd = GPIO_PUPD_NO;
  26.     gpio_init_structure.pin = GPIO_PIN_8;

  27.     GPIO_Config(GPIOB, &gpio_init_structure);

  28.     gpio_init_structure.pin = GPIO_PIN_9;
  29.     GPIO_Config(GPIOB, &gpio_init_structure);
  30.         
  31.     /* Config I2C1 */
  32.     I2C_Reset(I2C1);
  33.     RCM_ConfigI2CCLK(RCM_I2C1CLK_SYSCLK);
  34.     iic_init_struct.ack = I2C_ACK_ENABLE;
  35.     iic_init_struct.ackaddress = I2C_ACK_ADDRESS_7BIT;
  36.     iic_init_struct.address1 = 0XA0;
  37.     iic_init_struct.analogfilter = I2C_ANALOG_FILTER_ENABLE;
  38.     iic_init_struct.digitalfilter = I2C_DIGITAL_FILTER_0;
  39.     iic_init_struct.mode = I2C_MODE_I2C;
  40.     iic_init_struct.timing = 0x80100103;        //0x00042121--766K  0x1042F013--130K  0x80100103--900K
  41.     I2C_Config(I2C1, &iic_init_struct);

  42.     I2C_Enable(I2C1);
  43. }

  44. /*********************引脚配置*/


  45. /*通信协议*********************/

  46. /**
  47.   * 函    数:I2C发送一个字节
  48.   * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  49.   * 返 回 值:无
  50.   */
  51. void OLED_I2C_SendByte(uint8_t addr,uint8_t Byte)
  52. {
  53.     I2C_HandlingTransfer(I2C1, EEPROM_WRITE_ADDR, 2, I2C_RELOAD_MODE_AUTOEND, I2C_GENERATE_START_WRITE);
  54.     I2C_TxData(I2C1, addr);
  55.     while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);
  56.     I2C_TxData(I2C1, Byte);
  57.     while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);

  58.     I2C_EnableGenerateStop(I2C1);

  59.     I2C_ClearIntFlag(I2C1, I2C_INT_FLAG_STOP);
  60. }

  61. /**
  62.   * 函    数:OLED写命令
  63.   * 参    数:Command 要写入的命令值,范围:0x00~0xFF
  64.   * 返 回 值:无
  65.   */
  66. void OLED_WriteCommand(uint8_t Command)
  67. {
  68.         OLED_I2C_SendByte(0x00,Command);
  69. }

  70. /**
  71.   * 函    数:OLED写数据
  72.   * 参    数:Data 要写入数据的起始地址
  73.   * 参    数:Count 要写入数据的数量
  74.   * 返 回 值:无
  75.   */
  76. void OLED_WriteData(uint8_t *Data, uint8_t Count)
  77. {
  78.     while (Count--) {
  79.         OLED_I2C_SendByte(0x40, *Data);
  80.         Data++;
  81.     }
  82. }
上面为硬件IIC的初始化函数,在硬件IIC的初始化中,其它都为通用的设置,但IIC频率的设置,我们需要查看手册进行配置,不同的配置可以使IIC的时钟工作在不同的频率,工程中,我将硬件IIC时钟配置成了1M,驱动OLED显示没问题,为后面的菜单UI刷新打下基础,由于我们OLED屏幕驱动基本为单方向的数据发送,所以主机的地址可以不用配置。
21984685aaf1d57811.png
对于I2C发送一个字节的驱动,本例程OLED默认是页地址模式,所以我们发送基本有从机地址+数据地址+数据+停止位构成,所以我们的发送配置发送2字节,其余都是默认的操作。下载验证现可以正常点亮屏幕,至此硬件IIC驱动OLED我们移植完成。
菜单的移植:
经过查看数据手册,E030的IIC硬件一次只能发送256个字节,所以要用DMA+IIC,一次发送128*8个字节实现全刷屏不好实现,我们本次只使用硬件IIC去实现OLED的屏幕驱动,根据WouoUI说明,将文件夹复制到自己的工程,并添加到工程中
68583685ab3968a5d5.png
8483685ab3b646d28.png 15055685ab3df82d43.png
在此文件下,先暂时屏蔽打印函数,并设置自己的屏幕大小,其余暂时不用更改
34017685ab46442a82.png
此时只需将原先的硬件OLED初始化函数,复制到 WouoUI_user.c中,
  1. /**
  2.   * 函    数:OLED引脚初始化
  3.   * 参    数:无
  4.   * 返 回 值:无
  5.   * 说    明:当上层函数需要初始化时,此函数会被调用
  6.   *           用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
  7.   */
  8. void OLED_GPIO_Init(void)
  9. {
  10.         /*将SCL和SDA引脚初始化为开漏模式*/
  11.     GPIO_Config_T gpio_init_structure;
  12.     I2C_Config_T iic_init_struct;
  13.         
  14.     RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOB);
  15.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);
  16.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);

  17.         /* Connect I2C to SCL */
  18.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_PIN1);
  19.     /* Connect I2C to SDA */
  20.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_PIN1);
  21.         
  22.     gpio_init_structure.mode = GPIO_MODE_AF;
  23.     gpio_init_structure.speed = GPIO_SPEED_50MHz;
  24.     gpio_init_structure.outtype = GPIO_OUT_TYPE_OD;
  25.     gpio_init_structure.pupd = GPIO_PUPD_NO;
  26.     gpio_init_structure.pin = GPIO_PIN_8;

  27.     GPIO_Config(GPIOB, &gpio_init_structure);

  28.     gpio_init_structure.pin = GPIO_PIN_9;
  29.     GPIO_Config(GPIOB, &gpio_init_structure);
  30.         
  31.         /* Config I2C1 */
  32.     I2C_Reset(I2C1);
  33.     RCM_ConfigI2CCLK(RCM_I2C1CLK_SYSCLK);
  34.     iic_init_struct.ack = I2C_ACK_ENABLE;
  35.     iic_init_struct.ackaddress = I2C_ACK_ADDRESS_7BIT;
  36.     iic_init_struct.address1 = 0XA0;
  37.     iic_init_struct.analogfilter = I2C_ANALOG_FILTER_ENABLE;
  38.     iic_init_struct.digitalfilter = I2C_DIGITAL_FILTER_0;
  39.     iic_init_struct.mode = I2C_MODE_I2C;
  40.     iic_init_struct.timing = 0x80100103;        //0xF--10K  0x00042121--766K  0x1042F013--130K  0x80100103--900K
  41.     I2C_Config(I2C1, &iic_init_struct);

  42.     I2C_Enable(I2C1);
  43. }
  44. /**
  45.   * 函    数:I2C发送一个字节
  46.   * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  47.   * 返 回 值:无
  48.   */
  49. void OLED_I2C_SendByte(uint8_t addr,uint8_t Byte)
  50. {
  51.     I2C_HandlingTransfer(I2C1, EEPROM_WRITE_ADDR, 2, I2C_RELOAD_MODE_AUTOEND, I2C_GENERATE_START_WRITE);
  52.     I2C_TxData(I2C1, addr);
  53.     while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);
  54.     I2C_TxData(I2C1, Byte);
  55.     while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);

  56.     I2C_EnableGenerateStop(I2C1);

  57.     I2C_ClearIntFlag(I2C1, I2C_INT_FLAG_STOP);
  58. }
  59. /**
  60.   * 函    数:OLED写命令
  61.   * 参    数:Command 要写入的命令值,范围:0x00~0xFF
  62.   * 返 回 值:无
  63.   */
  64. void OLED_WriteCommand(uint8_t Command)
  65. {
  66.         OLED_I2C_SendByte(0x00,Command);
  67. }

  68. /**
  69.   * 函    数:OLED写数据
  70.   * 参    数:Data 要写入数据的起始地址
  71.   * 参    数:Count 要写入数据的数量
  72.   * 返 回 值:无
  73.   */
  74. void OLED_WriteData(uint8_t *Data, uint8_t Count)
  75. {
  76.     while (Count--) {
  77.         OLED_I2C_SendByte(0x40, *Data);
  78.         Data++;
  79.     }
  80. }
  81. void OLED_SendBuff(uint8_t buff[8][128])
  82. {                    
  83.         for(uint8_t i=0;i<8;i++)  
  84.         {  
  85.                 OLED_WriteCommand (0xb0+i);    //设置页地址(0~7)(b0-b7)
  86.                 OLED_WriteCommand(0x00);//---set low column address
  87.                 OLED_WriteCommand (0x10);      //设置显示位置—列高地址   
  88.                 OLED_WriteData(buff[i],128); //写一页128个字符
  89.         }
  90. }

  91. //--------------给主函数调用的接口函数
  92. void TestUI_Init(void) {
  93.     OLED_GPIO_Init();  //硬件的初始化
在初始化函数中加入硬件IIC的初始化,并在main函数中调用即可
  1. #include "apm32e030.h"                  // Device header
  2. #include "Delay.h"
  3. #include "LED.h"
  4. #include "button_app.h"
  5. #include "WouoUI.h"
  6. #include "WouoUI_user.h"

  7. int main (void)
  8. {
  9.         LED_init();
  10.         
  11.         /*按键初始化*/
  12.         button_Init();
  13.         
  14.         WouoUI_SelectDefaultUI();
  15.     WouoUI_AttachSendBuffFun(OLED_SendBuff);
  16.     TestUI_Init();

  17.         while(1)
  18.         {
  19.                 WouoUI_Proc(30);
  20.         }
  21. }
使用硬件IIC,并将 UI的时间尺度增大到30 ,基本可以满足UI的刷新,在按键库中,定义按键1短按为向下,按键2短按为向上,按键1长按
长按为进入,按键2长按为退出,按键1双击为向左,按键2双击为向右。
  1. void BTN1_PRESS_DOWN_Handler(void* btn)
  2. {
  3.         LED1_on();
  4.         WOUOUI_MSG_QUE_SEND(msg_down);
  5. }
  6. void BTN1_PRESS_REPEAT_Handler(void* btn)
  7. {
  8.         LED1_off();
  9.         WOUOUI_MSG_QUE_SEND(msg_left);
  10. }
  11. void BTN1_LONG_RRESS_START_Handler(void* btn)
  12. {
  13.         LED1_turn();
  14.         WOUOUI_MSG_QUE_SEND(msg_click);
  15. }

  16. void BTN2_PRESS_DOWN_Handler(void* btn)
  17. {
  18.         LED2_on();
  19.         WOUOUI_MSG_QUE_SEND(msg_up);
  20. }
  21. void BTN2_PRESS_REPEAT_Handler(void* btn)
  22. {
  23.         LED2_off();
  24.         WOUOUI_MSG_QUE_SEND(msg_right);
  25. }
  26. void BTN2_LONG_RRESS_START_Handler(void* btn)
  27. {
  28.         LED2_turn();
  29.         WOUOUI_MSG_QUE_SEND(msg_return);
  30. }
这样,我们的OLED屏幕菜单框架基本就搭建完成,可以通过按键进行简单的测试,如需要更快的速度,建议使用SPI接口去实现,只需要实现接口函数即可,本次IIC的OLED屏幕驱动大致完成。
30772685f973a7f775.png










WouoUI-说明文档.zip

22.98 KB, 下载次数: 5

4-1 APM32E030-OLED显示.zip

191.6 KB, 下载次数: 6

4-2 APM32E030-OLED显示-硬件.zip

192.4 KB, 下载次数: 7

4-3 APM32E030-OLED显示-菜单-软件.zip

266.66 KB, 下载次数: 6

4-4 APM32E030-OLED-UI菜单-硬件.zip

265.84 KB, 下载次数: 6

永恒回声 发表于 2025-6-28 16:10 | 显示全部楼层
现在的I2C设备有支持1Mbps的速率的吗?
我怎么感觉支持400K的都少呢
梦之一瞥 发表于 2025-6-28 22:30 | 显示全部楼层
I2C的硬件模式下,数据通讯的效率还是挺高的。
SpiritSong 发表于 2025-6-29 17:56 | 显示全部楼层
我看楼主在I2C初始化的时候未使能中断,仍然使用的查询的方式来做的通讯。
I2C通讯的性能如何?有做过I2C中断或者DMA方式的实现吗?
霜咬回响 发表于 2025-6-30 14:53 | 显示全部楼层
E030的硬件资源还可以跑GUI吗?
显示字符串是不是基本就满了
星云狂想曲 发表于 2025-6-30 20:44 | 显示全部楼层
我去查了一下datasheet。APM32E030的I2C外设最快支持到400Kbps的速率。
这个速率下传输的吞吐率应该也能达到 40KBps吧
OceanGaze 发表于 2025-7-3 14:21 | 显示全部楼层
想想通过中断或者DMA的方式进行通讯的实际案例。
FrostShimmer 发表于 2025-7-3 19:34 | 显示全部楼层
要是驱动OLED小屏,E030的小MCU只发送的话,I2C的速率应该是很快的吧!
手里没有I2C接口的OLED屏,有机会试试
神秘低语 发表于 2025-7-9 11:50 | 显示全部楼层
楼主有试用过SMBus通讯吗?
使用SMBus来驱动OLED屏可以吗
风暴之眸 发表于 2025-7-9 17:59 | 显示全部楼层
这种I2C查询的方式,效率高吗?
感觉会对系统的整体性能有影响。
永恒回声 发表于 2025-7-21 18:01 | 显示全部楼层
对于OLED这种只写的器件,还是直接上DMA吧!
省事
您需要登录后才可以回帖 登录 | 注册

本版积分规则

17

主题

19

帖子

0

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