打印
[APM32E0]

APM32E030 IIC的使用与OLED屏幕

[复制链接]
230|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 LIZARD925 于 2025-6-28 15:37 编辑

#技术资源#

APM32E030系列使用记录--软硬件IIC驱动OLED屏幕与菜单的移植
软硬件IIC驱动OLED屏幕显示:
此例程中,我们使用软件(硬件)IIC驱动0.96寸OLED屏幕,实现OLED屏幕显示字符、数字、中文等信息,由于网上有很多开源的OLED驱动了,所用我们只需要配置好底层的通信接口即可,我们使用上次移植好按键库的程序,找到江科大的开源OLED屏幕驱动,将下面4个文件复制到自己的工程,注意我们使用的是GB2312编码,需要选择对应的程序,否则需要进行编码的转换,加入文件夹后打开keil加入到工程中,如图所示:


加入后,我们只需要更改底层的GPIO驱动函数,实现软件模拟IIC的数据通信,即完成了软件IIC驱动OLED屏幕的移植,IIC引脚使用PB8与PB9,主要函数如下所示:
/**
  * 函    数:OLED写SCL高低电平
  * 参    数:要写入SCL的电平值,范围:0/1
  * 返 回 值:无
  * 说    明:当上层函数需要写SCL时,此函数会被调用
  *           用户需要根据参数传入的值,将SCL置为高电平或者低电平
  *           当参数传入0时,置SCL为低电平,当参数传入1时,置SCL为高电平
  */
void OLED_W_SCL(uint8_t BitValue)
{
        /*根据BitValue的值,将SCL置高电平或者低电平*/
        GPIO_WriteBitValue(GPIOB, GPIO_PIN_8, BitValue);
        
        /*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
        //...
}

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

/**
  * 函    数:OLED引脚初始化
  * 参    数:无
  * 返 回 值:无
  * 说    明:当上层函数需要初始化时,此函数会被调用
  *           用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
  */
void OLED_GPIO_Init(void)
{
        uint32_t i, j;
        
        /*在初始化前,加入适量延时,待OLED供电稳定*/
        for (i = 0; i < 1000; i ++)
        {
                for (j = 0; j < 1000; j ++);
        }
        
        /*将SCL和SDA引脚初始化为开漏模式*/
        RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOB);        //打开GPIOB的时钟

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

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

        /* Connect I2C to SCL */
    GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_PIN1);
    /* Connect I2C to SDA */
    GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_PIN1);
        
    gpio_init_structure.mode = GPIO_MODE_AF;
    gpio_init_structure.speed = GPIO_SPEED_50MHz;
    gpio_init_structure.outtype = GPIO_OUT_TYPE_OD;
    gpio_init_structure.pupd = GPIO_PUPD_NO;
    gpio_init_structure.pin = GPIO_PIN_8;

    GPIO_Config(GPIOB, &gpio_init_structure);

    gpio_init_structure.pin = GPIO_PIN_9;
    GPIO_Config(GPIOB, &gpio_init_structure);
        
    /* Config I2C1 */
    I2C_Reset(I2C1);
    RCM_ConfigI2CCLK(RCM_I2C1CLK_SYSCLK);
    iic_init_struct.ack = I2C_ACK_ENABLE;
    iic_init_struct.ackaddress = I2C_ACK_ADDRESS_7BIT;
    iic_init_struct.address1 = 0XA0;
    iic_init_struct.analogfilter = I2C_ANALOG_FILTER_ENABLE;
    iic_init_struct.digitalfilter = I2C_DIGITAL_FILTER_0;
    iic_init_struct.mode = I2C_MODE_I2C;
    iic_init_struct.timing = 0x80100103;        //0x00042121--766K  0x1042F013--130K  0x80100103--900K
    I2C_Config(I2C1, &iic_init_struct);

    I2C_Enable(I2C1);
}

/*********************引脚配置*/


/*通信协议*********************/

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void OLED_I2C_SendByte(uint8_t addr,uint8_t Byte)
{
    I2C_HandlingTransfer(I2C1, EEPROM_WRITE_ADDR, 2, I2C_RELOAD_MODE_AUTOEND, I2C_GENERATE_START_WRITE);
    I2C_TxData(I2C1, addr);
    while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);
    I2C_TxData(I2C1, Byte);
    while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);

    I2C_EnableGenerateStop(I2C1);

    I2C_ClearIntFlag(I2C1, I2C_INT_FLAG_STOP);
}

/**
  * 函    数:OLED写命令
  * 参    数:Command 要写入的命令值,范围:0x00~0xFF
  * 返 回 值:无
  */
void OLED_WriteCommand(uint8_t Command)
{
        OLED_I2C_SendByte(0x00,Command);
}

/**
  * 函    数:OLED写数据
  * 参    数:Data 要写入数据的起始地址
  * 参    数:Count 要写入数据的数量
  * 返 回 值:无
  */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
    while (Count--) {
        OLED_I2C_SendByte(0x40, *Data);
        Data++;
    }
}
上面为硬件IIC的初始化函数,在硬件IIC的初始化中,其它都为通用的设置,但IIC频率的设置,我们需要查看手册进行配置,不同的配置可以使IIC的时钟工作在不同的频率,工程中,我将硬件IIC时钟配置成了1M,驱动OLED显示没问题,为后面的菜单UI刷新打下基础,由于我们OLED屏幕驱动基本为单方向的数据发送,所以主机的地址可以不用配置。

对于I2C发送一个字节的驱动,本例程OLED默认是页地址模式,所以我们发送基本有从机地址+数据地址+数据+停止位构成,所以我们的发送配置发送2字节,其余都是默认的操作。下载验证现可以正常点亮屏幕,至此硬件IIC驱动OLED我们移植完成。
菜单的移植:
经过查看数据手册,E030的IIC硬件一次只能发送256个字节,所以要用DMA+IIC,一次发送128*8个字节实现全刷屏不好实现,我们本次只使用硬件IIC去实现OLED的屏幕驱动,根据WouoUI说明,将文件夹复制到自己的工程,并添加到工程中


在此文件下,先暂时屏蔽打印函数,并设置自己的屏幕大小,其余暂时不用更改

此时只需将原先的硬件OLED初始化函数,复制到 WouoUI_user.c中,
/**
  * 函    数:OLED引脚初始化
  * 参    数:无
  * 返 回 值:无
  * 说    明:当上层函数需要初始化时,此函数会被调用
  *           用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
  */
void OLED_GPIO_Init(void)
{
        /*将SCL和SDA引脚初始化为开漏模式*/
    GPIO_Config_T gpio_init_structure;
    I2C_Config_T iic_init_struct;
        
    RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOB);
    RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);

        /* Connect I2C to SCL */
    GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_PIN1);
    /* Connect I2C to SDA */
    GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_PIN1);
        
    gpio_init_structure.mode = GPIO_MODE_AF;
    gpio_init_structure.speed = GPIO_SPEED_50MHz;
    gpio_init_structure.outtype = GPIO_OUT_TYPE_OD;
    gpio_init_structure.pupd = GPIO_PUPD_NO;
    gpio_init_structure.pin = GPIO_PIN_8;

    GPIO_Config(GPIOB, &gpio_init_structure);

    gpio_init_structure.pin = GPIO_PIN_9;
    GPIO_Config(GPIOB, &gpio_init_structure);
        
        /* Config I2C1 */
    I2C_Reset(I2C1);
    RCM_ConfigI2CCLK(RCM_I2C1CLK_SYSCLK);
    iic_init_struct.ack = I2C_ACK_ENABLE;
    iic_init_struct.ackaddress = I2C_ACK_ADDRESS_7BIT;
    iic_init_struct.address1 = 0XA0;
    iic_init_struct.analogfilter = I2C_ANALOG_FILTER_ENABLE;
    iic_init_struct.digitalfilter = I2C_DIGITAL_FILTER_0;
    iic_init_struct.mode = I2C_MODE_I2C;
    iic_init_struct.timing = 0x80100103;        //0xF--10K  0x00042121--766K  0x1042F013--130K  0x80100103--900K
    I2C_Config(I2C1, &iic_init_struct);

    I2C_Enable(I2C1);
}
/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void OLED_I2C_SendByte(uint8_t addr,uint8_t Byte)
{
    I2C_HandlingTransfer(I2C1, EEPROM_WRITE_ADDR, 2, I2C_RELOAD_MODE_AUTOEND, I2C_GENERATE_START_WRITE);
    I2C_TxData(I2C1, addr);
    while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);
    I2C_TxData(I2C1, Byte);
    while (I2C_ReadStatusFlag(I2C1, I2C_FLAG_TXBE) == RESET);

    I2C_EnableGenerateStop(I2C1);

    I2C_ClearIntFlag(I2C1, I2C_INT_FLAG_STOP);
}
/**
  * 函    数:OLED写命令
  * 参    数:Command 要写入的命令值,范围:0x00~0xFF
  * 返 回 值:无
  */
void OLED_WriteCommand(uint8_t Command)
{
        OLED_I2C_SendByte(0x00,Command);
}

/**
  * 函    数:OLED写数据
  * 参    数:Data 要写入数据的起始地址
  * 参    数:Count 要写入数据的数量
  * 返 回 值:无
  */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
    while (Count--) {
        OLED_I2C_SendByte(0x40, *Data);
        Data++;
    }
}
void OLED_SendBuff(uint8_t buff[8][128])
{                    
        for(uint8_t i=0;i<8;i++)  
        {  
                OLED_WriteCommand (0xb0+i);    //设置页地址(0~7)(b0-b7)
                OLED_WriteCommand(0x00);//---set low column address
                OLED_WriteCommand (0x10);      //设置显示位置—列高地址   
                OLED_WriteData(buff[i],128); //写一页128个字符
        }
}

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

int main (void)
{
        LED_init();
        
        /*按键初始化*/
        button_Init();
        
        WouoUI_SelectDefaultUI();
    WouoUI_AttachSendBuffFun(OLED_SendBuff);
    TestUI_Init();

        while(1)
        {
                WouoUI_Proc(30);
        }
}
使用硬件IIC,并将 UI的时间尺度增大到30 ,基本可以满足UI的刷新,在按键库中,定义按键1短按为向下,按键2短按为向上,按键1长按
长按为进入,按键2长按为退出,按键1双击为向左,按键2双击为向右。
void BTN1_PRESS_DOWN_Handler(void* btn)
{
        LED1_on();
        WOUOUI_MSG_QUE_SEND(msg_down);
}
void BTN1_PRESS_REPEAT_Handler(void* btn)
{
        LED1_off();
        WOUOUI_MSG_QUE_SEND(msg_left);
}
void BTN1_LONG_RRESS_START_Handler(void* btn)
{
        LED1_turn();
        WOUOUI_MSG_QUE_SEND(msg_click);
}

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











WouoUI-说明文档.zip

22.98 KB

4-1 APM32E030-OLED显示.zip

191.6 KB

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

192.4 KB

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

266.66 KB

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

265.84 KB

使用特权

评论回复
沙发
永恒回声| | 2025-6-28 16:10 | 只看该作者
现在的I2C设备有支持1Mbps的速率的吗?
我怎么感觉支持400K的都少呢

使用特权

评论回复
板凳
梦之一瞥| | 2025-6-28 22:30 | 只看该作者
I2C的硬件模式下,数据通讯的效率还是挺高的。

使用特权

评论回复
地板
SpiritSong| | 2025-6-29 17:56 | 只看该作者
我看楼主在I2C初始化的时候未使能中断,仍然使用的查询的方式来做的通讯。
I2C通讯的性能如何?有做过I2C中断或者DMA方式的实现吗?

使用特权

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

本版积分规则

15

主题

17

帖子

0

粉丝