STM32F103C8T6上玩转U8G2库:手把手教你驱动0.96寸OLED显示中文和动画
在嵌入式开发中,OLED显示屏因其高对比度、低功耗和快速响应等特性,成为许多项目的首选显示方案。而U8G2库作为一款功能强大的图形库,能够为这些小型显示屏带来丰富的图形和文本显示能力。本文将深入探讨如何在STM32F103C8T6这款经典的Cortex-M3微控制器上,通过U8G2库实现0.96寸OLED屏的中文显示和动画效果。
1. 环境准备与基础配置
在开始之前,我们需要确保开发环境已经准备就绪。对于STM32F103C8T6这款芯片,Keil MDK或者STM32CubeIDE都是不错的选择。这里我们以Keil MDK为例,介绍必要的配置步骤。
首先,创建一个新的工程,选择STM32F103C8T6作为目标设备。在工程设置中,有几个关键点需要注意:
- 确保勾选C99 Mode选项,这是U8G2库正常运行的前提
- 设置合适的优化等级,通常选择-O2可以获得较好的性能与代码大小的平衡
- 在Include Paths中添加U8G2库的路径
接下来是硬件连接部分。0.96寸OLED屏通常采用I2C接口,接线方式如下:
| OLED引脚 | STM32引脚 | 功能 |
|---|---|---|
| GND | GND | 地线 |
| VCC | 3.3V | 电源 |
| SCL | PB8 | 时钟线 |
| SDA | PB9 | 数据线 |
2. U8G2库的裁剪与优化
U8G2库功能强大但体积较大,直接使用可能会导致编译错误或占用过多Flash空间。因此,我们需要对库进行适当的裁剪。
首先从GitHub获取U8G2源码:
git clone https://github.com/olikraus/u8g2.git在csrc目录中,我们只需要保留以下几个关键文件:
- u8x8_d_ssd1306_128x64_noname.c (OLED驱动)
- u8g2_d_setup.c (保留i2c相关函数)
- u8g2_d_memory.c (保留u8g2_m_16_8_f函数)
对于中文显示,还需要特别注意字库的处理。U8G2提供了多种中文字体,但全量包含会导致编译失败。推荐只保留需要的字体,例如:
// 在u8g2_fonts.c中只保留以下字体 const uint8_t u8g2_font_wqy16_t_chinese2[] U8G2_FONT_SECTION("u8g2_font_wqy16_t_chinese2");3. I2C接口驱动实现
U8G2库需要开发者提供底层的GPIO和延时函数。下面是一个完整的I2C接口实现示例:
#include "u8g2.h" #include "stm32f10x.h" #define OLED_I2C_SCL_PIN GPIO_Pin_8 #define OLED_I2C_SCL_PORT GPIOB #define OLED_I2C_SDA_PIN GPIO_Pin_9 #define OLED_I2C_SDA_PORT GPIOB void Delay_us(uint32_t us) { // 实现微秒级延时函数 us *= 72 / 5; while(us--) { __NOP(); } } void Delay_ms(uint32_t ms) { while(ms--) { Delay_us(1000); } } uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { case U8X8_MSG_DELAY_MILLI: Delay_ms(arg_int); break; case U8X8_MSG_GPIO_I2C_CLOCK: if(arg_int) GPIO_SetBits(OLED_I2C_SCL_PORT, OLED_I2C_SCL_PIN); else GPIO_ResetBits(OLED_I2C_SCL_PORT, OLED_I2C_SCL_PIN); break; case U8X8_MSG_GPIO_I2C_DATA: if(arg_int) GPIO_SetBits(OLED_I2C_SDA_PORT, OLED_I2C_SDA_PIN); else GPIO_ResetBits(OLED_I2C_SDA_PORT, OLED_I2C_SDA_PIN); break; default: return 0; } return 1; }4. 中文显示与动画效果实现
4.1 中文显示基础
要在OLED上显示中文,首先需要选择合适的字体。U8G2提供了多种中文字体,如u8g2_font_wqy16_t_chinese2。使用前需要确保:
- 字体已包含在工程中
- 源代码文件保存为UTF-8编码
- Keil中设置了正确的编译选项
基本的中文显示代码如下:
void ShowChinese(u8g2_t *u8g2) { u8g2_ClearBuffer(u8g2); u8g2_SetFont(u8g2, u8g2_font_wqy16_t_chinese2); u8g2_DrawUTF8(u8g2, 0, 16, "嵌入式开发"); u8g2_DrawUTF8(u8g2, 0, 32, "中文显示测试"); u8g2_SendBuffer(u8g2); }4.2 文字动画效果
U8G2库支持多种文字动画效果,下面实现一个简单的文字滚动效果:
void ScrollText(u8g2_t *u8g2, const char *text) { int width = u8g2_GetUTF8Width(u8g2, text); for(int x = 128; x > -width; x--) { u8g2_ClearBuffer(u8g2); u8g2_SetFont(u8g2, u8g2_font_wqy16_t_chinese2); u8g2_DrawUTF8(u8g2, x, 32, text); u8g2_SendBuffer(u8g2); Delay_ms(30); } }4.3 图形动画实现
除了文字,我们还可以创建简单的图形动画。下面是一个跳动的小球示例:
void BouncingBall(u8g2_t *u8g2) { int x = 64, y = 32; int dx = 2, dy = 2; while(1) { u8g2_ClearBuffer(u8g2); u8g2_DrawDisc(u8g2, x, y, 5, U8G2_DRAW_ALL); u8g2_SendBuffer(u8g2); x += dx; y += dy; if(x <= 5 || x >= 123) dx = -dx; if(y <= 5 || y >= 59) dy = -dy; Delay_ms(20); } }5. 性能优化与常见问题解决
5.1 显示刷新优化
OLED的刷新速度直接影响动画的流畅度。以下是几个优化建议:
- 局部刷新:只更新变化的部分,而不是整个屏幕
- 双缓冲:使用U8G2的双缓冲功能减少闪烁
- 简化图形:减少复杂图形的使用
5.2 常见问题及解决方案
问题1:中文显示乱码
- 确保源代码文件保存为UTF-8编码
- 在Keil的Misc Controls中添加:--no-multibyte-chars
- 检查是否包含了正确的中文字体
问题2:编译时Flash空间不足
- 裁剪不需要的字体和功能
- 提高优化等级
- 使用更小的字体
问题3:动画卡顿
- 减少每帧的绘制内容
- 优化延时函数精度
- 检查I2C时钟速度是否设置合理
6. 高级应用示例
6.1 多页面菜单系统
利用U8G2可以构建简单的菜单界面。下面是一个基础实现框架:
typedef struct { const char *title; void (*action)(u8g2_t*); } MenuItem; void ShowMenu(u8g2_t *u8g2, MenuItem *items, int count, int selected) { u8g2_ClearBuffer(u8g2); u8g2_SetFont(u8g2, u8g2_font_wqy16_t_chinese2); for(int i = 0; i < count; i++) { if(i == selected) { u8g2_DrawBox(u8g2, 0, i*16, 128, 16); u8g2_SetDrawColor(u8g2, 0); } u8g2_DrawUTF8(u8g2, 10, i*16 + 13, items[i].title); if(i == selected) { u8g2_SetDrawColor(u8g2, 1); } } u8g2_SendBuffer(u8g2); }6.2 实时数据可视化
对于需要显示传感器数据的应用,可以创建简单的图表:
void DrawGraph(u8g2_t *u8g2, int *data, int count) { u8g2_ClearBuffer(u8g2); // 绘制坐标轴 u8g2_DrawHLine(u8g2, 10, 60, 100); u8g2_DrawVLine(u8g2, 10, 10, 50); // 绘制数据点 for(int i = 0; i < count-1; i++) { int y1 = 60 - data[i]/2; int y2 = 60 - data[i+1]/2; u8g2_DrawLine(u8g2, 15+i*5, y1, 15+(i+1)*5, y2); } u8g2_SendBuffer(u8g2); }在实际项目中,我发现合理使用U8G2的绘图函数可以大大提升用户体验。例如,通过组合基本图形创建自定义图标,或者利用帧动画原理实现复杂的界面过渡效果。对于资源有限的STM32F103C8T6,关键在于找到功能丰富性和性能消耗之间的平衡点。