news 2026/3/26 18:37:07

STM32下LED阵列汉字显示的矩阵扫描技术详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32下LED阵列汉字显示的矩阵扫描技术详解

STM32驱动LED阵列显示汉字:从原理到实战的完整实现

你有没有遇到过这样的场景?工业设备上只用几个数码管显示“E01”、“RUN”这种代码,操作人员一头雾水;或者想做个滚动字幕屏,却发现OLED太贵、TFT又太复杂。其实,一块十几块钱的16×16 LED点阵 + 一颗STM32芯片,就能搞定清晰可见的汉字显示。

这不仅是个酷炫的小项目,更是嵌入式开发中“软硬协同”的经典范例。今天我们就来拆解这个看似简单、实则暗藏玄机的技术——如何在STM32上通过矩阵扫描技术,让LED阵列稳稳地打出一个“汉”字


为什么不用直接驱动?先说清楚这个坑

如果你试图给每个LED单独配一个MCU引脚……那恭喜你,256个灯就得256个IO口——现实吗?STM32F103C8T6才37个可用GPIO,连16×16点阵的一半都接不下。

所以必须换思路:用时间换空间

这就是矩阵扫描的核心思想。把16行和16列交叉排布,总共只需要32个引脚,就能控制256个LED。听起来像魔术?其实原理非常朴素:

每次只点亮一行,快速轮询所有行,利用人眼的视觉暂留效应,看起来就像全屏同时亮着。

但问题来了:怎么保证不闪、不重影、亮度还够?别急,我们一步步来看。


矩阵扫描的本质:不是静态图,而是高速动画

我们以最常见的16×16共阴极LED点阵模块为例。它的结构是这样的:

  • 行线(ROW0~ROW15):连接到LED的负极(共阴)
  • 列线(COL0~COL15):连接到LED的正极

要让某个位置的LED亮起来,就得满足两个条件:
1. 对应的行被拉高(选中该行);
2. 对应的列被拉低(提供电流通路)。

比如我想点亮第3行第5列的那个灯:

GPIO_SetBits(ROW_PORT, 1 << 2); // ROW3 = 高 GPIO_ResetBits(COL_PORT, 1 << 4); // COL5 = 低

但如果我一直这么保持,其他行也会跟着乱亮!因为一旦某列被拉低,只要对应行被选中,就会导通。

解决办法只有一个:

我们采用“逐行扫描 + 高速刷新”的策略:

  1. 打开第0行,根据“汉”字的第一行数据设置列输出;
  2. 延时约0.5ms;
  3. 关闭第0行,打开第1行,加载第二行数据;
  4. 如此循环至第15行;
  5. 回到第0行重新开始。

只要整个周期小于10ms(即刷新率 > 100Hz),人眼就看不出闪烁。

关键参数你得心里有数

参数数值说明
整屏刷新率≥100Hz肉眼无感闪烁的底线
单行显示时间~62.5μs16行均分1/100秒
占空比1/16 ≈ 6.25%每个LED实际只亮1/16的时间
峰值电流建议15~20mA弥补低占空比带来的亮度损失

⚠️ 注意:由于每行只亮1/16的时间,为了维持整体亮度,我们必须提高单次点亮时的电流。但也不能超过LED最大耐受值(通常20mA),否则烧坏或衰减加快。


STM32是怎么精准控制这一切的?

主控选型上,STM32F103系列简直是为此类应用量身定做的:高性能、低成本、生态成熟。我们拿最常用的STM32F103C8T6来说事。

它有几个关键优势:
- 主频高达72MHz,足够处理高速IO切换;
- GPIO翻转速度可达纳秒级(配合BSRR寄存器);
- 定时器资源丰富,可精确触发中断;
- Flash空间充足,能存下上百个汉字字模。

我们的任务分解如下:

  1. 定时器中断:每62.5μs触发一次,用于切换行;
  2. 快速IO操作:在中断里完成“关旧行 → 写列数据 → 开新行”;
  3. 字模读取:从Flash中取出当前行对应的16位数据;
  4. 循环调度:扫描完16行后自动归零,持续刷新。

下面这段代码就是核心所在:

#include "stm32f10x.h" #define LED_ROWS 16 #define SCAN_FREQ 100 // 全屏刷新率(Hz) // 行选端口 PA0~PA15,列数据端口 PB0~PB15 #define ROW_PORT GPIOA #define COL_PORT GPIOB volatile uint8_t current_row = 0; extern const uint16_t hanzi_han[16]; // “汉”字字模 void Timer_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseInitTypeDef timer; TIM_TimeBaseStructInit(&timer); timer.TIM_Prescaler = 72 - 1; // 1MHz计数频率 (72MHz / 72) timer.TIM_Period = 1000000 / (SCAN_FREQ * LED_ROWS) - 1; // 每行62.5μs TIM_TimeBaseInit(TIM3, &timer); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); NVIC_EnableIRQ(TIM3_IRQn); TIM_Cmd(TIM3, ENABLE); } void GPIO_Init_Config(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef gpio; gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; gpio.GPIO_Pin = 0xFFFF; GPIO_Init(GPIOA, &gpio); // 行输出 GPIO_Init(GPIOB, &gpio); // 列输出 GPIO_ResetBits(ROW_PORT, 0xFFFF); GPIO_SetBits(COL_PORT, 0xFFFF); // 初始灭态(共阴,列高为灭) } void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 原子操作:先关闭当前行 ROW_PORT->BRR = 0xFFFF; // 输出当前行的列数据(共阴需低电平点亮,故取反) uint16_t col_data = ~hanzi_han[current_row]; COL_PORT->ODR = col_data; // 开启当前行(BSRR高位为置位) ROW_PORT->BSRR = (1 << current_row); // 更新行索引(循环) current_row = (current_row + 1) % LED_ROWS; } } int main(void) { SystemInit(); GPIO_Init_Config(); Timer_Init(); while (1) { // 可在此处理按键、串口命令等后台任务 } }

这段代码的精妙之处在哪?

  1. 使用BSRR/BRR寄存器
    BSRR是位设置寄存器,BRR是位清除寄存器,它们的操作是原子的,不会被中断打断,避免了IO状态错乱。

  2. ODR直接赋值列数据
    一次性写入16位列数据,比逐位操作快得多,确保时序紧凑。

  3. 中断间隔严格对齐
    定时器配置为精确的62.5μs,保证每一行显示时间一致,防止画面抖动。

  4. 主循环空闲可做别的事
    显示由中断自动完成,CPU可以去响应用户输入、通信协议等任务,真正体现嵌入式系统的多任务潜力。


汉字是怎么变成一堆数字的?聊聊字模那些事

你说“我要显示‘汉’字”,可MCU不认识汉字。它只认二进制。所以我们需要先把汉字转换成点阵数据。

字模生成流程

  1. 选择字体与大小:比如黑体16×16像素;
  2. 渲染成黑白图像:工具如 [PCtoLCD2002] 或在线生成器;
  3. 按行提取位数据:每行16像素 → 一个uint16_t
  4. 导出为C数组格式

例如,“汉”字可能长这样:

const uint16_t hanzi_han[16] = { 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0x0420, 0xFFFE, 0x0000 };

这些十六进制数代表什么?举个例子:

  • 0x0420的二进制是0000 0100 0010 0000
  • 对应一行中有两个点被点亮:第6位和第10位(从右往左数)

你可以用Excel画出来验证一下,是不是有点“汉”字的感觉了?

存储优化建议

  • 小项目:直接把常用字模放在.c文件里,编译进Flash;
  • 中大型系统:构建字库索引表,支持动态查字;
  • 极致省空间:使用RLE压缩、字形共享等算法;
  • 扩展性需求:外挂SPI Flash存放GBK或Unicode子集。

💡 提示:务必确认你的字模方向和扫描顺序匹配!如果字模是“横向取模、高位在前”,而你代码里却是低位先行输出,结果就是镜像或旋转90度。


实际搭建时容易踩的坑,我都替你试过了

你以为代码跑通就万事大吉?硬件上的细节往往决定成败。

1. 电源带不动,一亮就重启

算笔账:
- 单LED工作电流:假设限流电阻220Ω,VF=2V → I ≈ (3.3V - 2V)/220 ≈ 5.9mA;
- 最坏情况:某一行全部16个LED都亮 → 总电流 ≈ 94mA;
- 如果你是多个点阵级联,瞬态电流轻松突破200mA。

后果是什么?MCU供电电压跌落,导致复位或死机

✅ 解决方案:
- 使用独立LDO或DC-DC给LED供电;
- 加大电源滤波电容(10μF + 0.1μF组合);
- 在PCB布局上,电源走线尽量宽,远离敏感信号线。

2. 出现“鬼影”或拖尾现象

这是典型的列信号串扰或锁存延迟问题。

原因可能是:
- 列数据未在换行前稳定;
- GPIO切换太快导致毛刺;
- 没有加入锁存机制(如74HC573)。

✅ 改进方法:
- 在关闭旧行之前,先清空列数据;
- 使用外部锁存器隔离数据变化过程;
- 或者在软件中增加微小延时(几纳秒),确保时序裕量。

3. 亮度不均匀,中间亮两边暗

常见于使用普通电阻限流的情况。由于走线阻抗差异,边缘LED压降略大,电流偏小。

✅ 更优方案:
- 使用恒流驱动芯片(如 TLC5916、IS31FL3731);
- 或统一使用IC内部调节,提升一致性。


能不能更进一步?当然可以!

你现在掌握了基础能力,接下来就可以玩出花来了:

✅ 双缓冲机制防撕裂

当前做法是在中断中直接读字模。但如果你在主循环中修改字模数组,可能会出现“上半部分是旧字,下半部分是新字”的撕裂现象。

引入双缓冲:

uint16_t display_buffer[16]; // 修改内容时不改原字模,而是复制到buffer后再切换指针

✅ DMA搬运列数据

目前列数据由CPU在中断中写入。换成DMA传输,可以让CPU彻底解放,尤其适合多任务系统。

✅ 添加PWM调光

通过调整整体亮度(比如夜间自动调暗),可以用另一个定时器产生PWM,控制使能信号的开启时间。

✅ 支持滚动显示

将字模缓冲区设计为横向滑动窗口,每隔若干帧移动一位,实现左右滚动效果。

✅ 接入Wi-Fi远程更新文字

结合ESP-01S模块,接收手机APP发来的文本指令,实时更新显示内容,变身智能公告牌。


这项技术到底有什么用?

别以为这只是个学生实验。它已经在很多真实场景中落地:

  • 🏭工厂设备状态提示:不再是“ERR01”,而是直接显示“电机过热,请停机检查”;
  • 🚇公交站台简易信息屏:成本远低于LCD,阳光下依然清晰;
  • 🧑‍🏫教学实训平台:帮助学生理解中断、定时器、GPIO、内存管理等核心概念;
  • 🎮DIY创意作品:生日祝福屏、歌词滚动灯、游戏积分板……

更重要的是,它教会我们一种思维方式:如何在资源受限的环境下,用软件弥补硬件不足


写在最后

当你第一次看到那个歪歪扭扭的“汉”字出现在LED阵列上时,也许会觉得没什么了不起。但你知道背后有多少工程细节吗?

  • 精确到微秒的定时中断;
  • 原子级的IO操作;
  • 从字符到像素的映射逻辑;
  • 电源、布局、抗干扰的设计考量……

这不仅仅是一个“点灯”实验,它是嵌入式系统软硬协同设计的缩影。

下次有人问你:“你会做LED显示吗?”
你可以笑着说:“我会用STM32扫出一个会呼吸的汉字。”

如果你正在做类似项目,欢迎留言交流经验。也别忘了点赞分享,让更多人看到这项低调却实用的技术。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/25 11:22:48

3分钟搭建永久有效的网易云音乐直链解析API

3分钟搭建永久有效的网易云音乐直链解析API 【免费下载链接】netease-cloud-music-api 网易云音乐直链解析 API 项目地址: https://gitcode.com/gh_mirrors/ne/netease-cloud-music-api 还在为网易云音乐分享链接频繁失效而烦恼吗&#xff1f;网易云音乐直链解析API为您…

作者头像 李华
网站建设 2026/3/23 1:14:07

《原神》帧率解锁全攻略:告别60fps限制,体验极致流畅游戏

《原神》帧率解锁全攻略&#xff1a;告别60fps限制&#xff0c;体验极致流畅游戏 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 还在为《原神》的60fps限制而烦恼吗&#xff1f;&#x…

作者头像 李华
网站建设 2026/3/24 14:46:50

Boss直聘批量投递:打造高效自动化招聘消息推送系统

Boss直聘批量投递&#xff1a;打造高效自动化招聘消息推送系统 【免费下载链接】boss_batch_push Boss直聘批量投简历&#xff0c;解放双手 项目地址: https://gitcode.com/gh_mirrors/bo/boss_batch_push 在当今竞争激烈的招聘市场中&#xff0c;如何快速高效地完成大量…

作者头像 李华
网站建设 2026/3/25 3:26:45

Windows右键菜单管理完全指南:轻松打造高效桌面环境

还在为混乱的右键菜单而烦恼吗&#xff1f;每次安装新软件后&#xff0c;右键菜单就变得更加臃肿&#xff0c;想要的功能找不到&#xff0c;不需要的选项却占满了屏幕。今天我要向你推荐一款完全免费的Windows右键菜单管理工具——ContextMenuManager&#xff0c;它将彻底改变你…

作者头像 李华
网站建设 2026/3/25 3:26:44

10分钟玩转空洞骑士模组:Scarab模组管理器的完整使用攻略

10分钟玩转空洞骑士模组&#xff1a;Scarab模组管理器的完整使用攻略 【免费下载链接】Scarab An installer for Hollow Knight mods written in Avalonia. 项目地址: https://gitcode.com/gh_mirrors/sc/Scarab 还在为空洞骑士模组安装的繁琐步骤而头疼吗&#xff1f;&…

作者头像 李华
网站建设 2026/3/25 3:26:42

飞书文档批量导出实战指南:700份文档25分钟极速迁移方案

飞书文档批量导出实战指南&#xff1a;700份文档25分钟极速迁移方案 【免费下载链接】feishu-doc-export 项目地址: https://gitcode.com/gh_mirrors/fe/feishu-doc-export 还在为飞书文档迁移而发愁吗&#xff1f;feishu-doc-export这款开源神器为你提供了完美的解决方…

作者头像 李华