本帖最后由 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屏幕驱动大致完成。
|
|