1. 项目概述
这个基于STM32F103C8T6单片机的16×16点阵广告牌项目,是我最近完成的一个嵌入式系统设计。它不仅能显示汉字和简单图形,还支持通过按键切换不同显示内容,非常适合作为入门级的嵌入式开发练手项目,也可以直接用于小型店铺的广告展示。
整个系统由三个核心部分组成:STM32F103C8T6最小系统板作为控制核心,16×16 LED点阵屏作为显示输出,外加几个按键和电源模块。系统功能虽然简单,但涵盖了嵌入式开发的几个关键环节:GPIO控制、定时器中断、按键扫描、点阵屏驱动等,对初学者来说是个很好的综合练习。
2. 硬件设计详解
2.1 核心控制器选型
我选择了STM32F103C8T6这款经典的Cortex-M3内核单片机,主要基于以下几点考虑:
- 性能足够:72MHz主频完全能满足点阵屏刷新需求
- GPIO数量:这款芯片有37个GPIO,驱动16×16点阵(需要32个IO)后还有余量
- 开发便利:STM32生态完善,调试工具丰富
- 成本优势:国产替代型号价格已降至10元以内
提示:如果项目需要驱动更大点阵屏,可以考虑STM32F103C8T6的48脚封装版本(STM32F103CBT6),或者使用IO扩展芯片如74HC595。
2.2 点阵屏驱动方案
16×16点阵屏的驱动是本项目的核心难点。我采用了最常见的行列扫描方式:
- 行驱动:使用4-16译码器74HC154
- 列驱动:直接使用STM32的16个IO口
- 刷新频率:通过定时器中断实现1kHz的刷新率(避免闪烁)
这种方案的优势是硬件简单,成本低。缺点是占用IO较多(16+4=20个),如果使用串行转并行的方案(如74HC595)可以节省IO,但软件复杂度会增加。
2.3 电源设计
系统供电采用常见的5V USB电源,通过AMS1117-3.3稳压芯片为STM32提供3.3V工作电压。点阵屏直接使用5V供电,通过限流电阻控制亮度。
3. 软件设计实现
3.1 显示驱动程序设计
点阵屏的显示驱动采用"逐行扫描"方式,主要逻辑如下:
- 初始化定时器2,配置1ms中断
- 在定时器中断中:
- 关闭当前行
- 切换到下一行
- 输出该行对应的列数据
- 打开当前行
c复制// 定时器中断服务函数示例
void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// 关闭当前行
GPIO_ResetBits(ROW_PORT, 1<<current_row);
// 切换到下一行
current_row = (current_row + 1) % 16;
// 输出列数据
GPIO_Write(COL_PORT, display_buffer[current_row]);
// 打开当前行
GPIO_SetBits(ROW_PORT, 1<<current_row);
}
}
3.2 字模数据处理
汉字显示需要预先提取字模数据。我使用PCtoLCD2003这款工具生成16×16点阵的字模,存储格式为纵向取模,字节倒序。
字模数据存储在Flash中,通过指针数组组织:
c复制const uint16_t fontLib[][16] = {
// "欢" 字模数据
{0x0000,0x7FFC,0x4004,0x5FF4,0x4114,0x4114,0x4114,0x5FF4,
0x4114,0x4114,0x4114,0x5FF4,0x4004,0x7FFC,0x0000,0x0000},
// "迎" 字模数据
{0x1000,0x13FE,0x1020,0xFC20,0x1020,0x13FC,0x1020,0x1020,
0xFD20,0x1020,0x1020,0x1020,0x1420,0x1820,0x0000,0x0000},
// 其他字模数据...
};
3.3 按键功能实现
系统使用3个独立按键实现显示内容切换:
- KEY1:切换到"广告内容"显示
- KEY2:切换到"欢迎使用"显示
- KEY3:切换到心形图案显示
按键检测采用简单的轮询方式,通过软件消抖:
c复制void Key_Scan(void) {
static uint8_t key_state = 0;
if(KEY1 == 0) {
if(key_state == 0) {
key_state = 1;
display_mode = MODE_AD;
}
} else if(KEY2 == 0) {
if(key_state == 0) {
key_state = 1;
display_mode = MODE_WELCOME;
}
} else if(KEY3 == 0) {
if(key_state == 0) {
key_state = 1;
display_mode = MODE_HEART;
}
} else {
key_state = 0;
}
}
4. 系统优化与问题解决
4.1 亮度不均匀问题
在实际调试中,我发现点阵屏存在亮度不均匀的问题,特别是当显示内容变化时更为明显。经过分析,主要原因是:
- 不同行点亮时间不一致
- LED正向压降差异
- 限流电阻取值不合理
解决方案:
- 改用恒流驱动芯片如TLC5940
- 优化扫描算法,确保每行点亮时间一致
- 精心调整限流电阻值(最终选用220Ω)
4.2 显示闪烁问题
初期版本在快速切换显示内容时会出现明显闪烁。通过逻辑分析仪抓取波形发现,问题出在显示缓冲区更新时机不当。
解决方法:
- 实现双缓冲机制:前台显示使用一个缓冲区,后台更新使用另一个缓冲区
- 在垂直消隐期间(所有行都关闭时)切换缓冲区指针
c复制// 双缓冲实现示例
uint16_t display_buffer[2][16];
volatile uint8_t current_buffer = 0;
void Switch_Buffer(void) {
// 在垂直消隐期间切换
if(current_row == 0) {
current_buffer ^= 1;
}
}
4.3 功耗优化
实测发现系统静态功耗偏高(约50mA)。通过以下措施将功耗降至30mA以内:
- 将未使用的GPIO设置为模拟输入模式
- 降低系统时钟频率(从72MHz降至48MHz)
- 优化扫描算法,减少同时点亮的LED数量
- 增加休眠模式,无操作时自动进入低功耗状态
5. 项目扩展思路
这个基础版本完成后,可以考虑以下几个方向的扩展:
- 增加无线控制:添加蓝牙或WiFi模块,实现手机APP控制
- 支持更多显示效果:滚动显示、动画效果等
- 环境光自适应:添加光敏电阻,自动调节亮度
- 多屏级联:通过SPI或I2C接口扩展更多点阵屏
以蓝牙控制为例,可以选用HC-05模块,通过串口与STM32通信:
c复制void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t ch = USART_ReceiveData(USART1);
// 解析蓝牙指令
switch(ch) {
case 'A': display_mode = MODE_AD; break;
case 'W': display_mode = MODE_WELCOME; break;
case 'H': display_mode = MODE_HEART; break;
}
}
}
6. 开发心得与建议
在实际开发过程中,我总结了以下几点经验:
- 点阵屏布线要规范:行线和列线最好分开走线,避免交叉干扰
- 注意驱动电流:计算好每行LED的总电流,确保不超过驱动芯片的额定值
- 调试先从静态显示开始:先确保能稳定显示静态内容,再实现动态效果
- 合理组织字模数据:使用结构体或联合体可以提高访问效率
对于初学者,我建议按照以下步骤进行开发:
- 先实现单行LED的点亮控制
- 扩展到多行扫描显示
- 添加简单的静态内容显示
- 实现内容切换功能
- 最后优化显示效果和功耗
这个项目虽然不大,但涵盖了嵌入式开发的多个重要环节。通过实践,我深刻理解了硬件设计与软件编程的协同关系,特别是在时序控制、中断处理等方面的经验,对后续更复杂的项目开发很有帮助。
