1. 写在前面:一块屏幕的“自我介绍”
你有没有想过,一块小小的液晶屏,除了显示文字、图片,还能做些什么?
在这个万物互联的时代,屏幕早已不只是“看”的工具,它开始学会“表达”——比如,把一串字符变成一张黑白相间的二维码,让手机轻轻一扫,就能读懂它想说的话。

这次实验,我们在一款基于 LiteOS 的嵌入式开发板上,让一块 TFT 液晶屏“开口说话”:它把一串字符编码成二维码,稳稳地显示在屏幕上。整个过程没有云端、没有服务器,全靠板子自己完成。听起来很酷,对吧?
这篇文章不打算堆砌晦涩的技术术语,而是想用更轻松的方式,带你走一遍这个“屏幕开口说话”的实验,看看它背后到底发生了什么。

2. 实验初体验:它到底做了什么?
简单来说,这个实验做了三件事:
整个过程就像一场小型交响乐,每个模块各司其职,最后由屏幕这个“指挥家”把结果呈现出来。
3. 一张图看懂整体流程
在深入代码之前,先用一张流程图感受一下整个系统的“呼吸节奏”:
#mermaid-svg-igtpgagB3MqIuuCk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-igtpgagB3MqIuuCk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-igtpgagB3MqIuuCk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-igtpgagB3MqIuuCk .error-icon{fill:#552222;}#mermaid-svg-igtpgagB3MqIuuCk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-igtpgagB3MqIuuCk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-igtpgagB3MqIuuCk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-igtpgagB3MqIuuCk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-igtpgagB3MqIuuCk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-igtpgagB3MqIuuCk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-igtpgagB3MqIuuCk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-igtpgagB3MqIuuCk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-igtpgagB3MqIuuCk .marker.cross{stroke:#333333;}#mermaid-svg-igtpgagB3MqIuuCk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-igtpgagB3MqIuuCk p{margin:0;}#mermaid-svg-igtpgagB3MqIuuCk .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-igtpgagB3MqIuuCk .cluster-label text{fill:#333;}#mermaid-svg-igtpgagB3MqIuuCk .cluster-label span{color:#333;}#mermaid-svg-igtpgagB3MqIuuCk .cluster-label span p{background-color:transparent;}#mermaid-svg-igtpgagB3MqIuuCk .label text,#mermaid-svg-igtpgagB3MqIuuCk span{fill:#333;color:#333;}#mermaid-svg-igtpgagB3MqIuuCk .node rect,#mermaid-svg-igtpgagB3MqIuuCk .node circle,#mermaid-svg-igtpgagB3MqIuuCk .node ellipse,#mermaid-svg-igtpgagB3MqIuuCk .node polygon,#mermaid-svg-igtpgagB3MqIuuCk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-igtpgagB3MqIuuCk .rough-node .label text,#mermaid-svg-igtpgagB3MqIuuCk .node .label text,#mermaid-svg-igtpgagB3MqIuuCk .image-shape .label,#mermaid-svg-igtpgagB3MqIuuCk .icon-shape .label{text-anchor:middle;}#mermaid-svg-igtpgagB3MqIuuCk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-igtpgagB3MqIuuCk .rough-node .label,#mermaid-svg-igtpgagB3MqIuuCk .node .label,#mermaid-svg-igtpgagB3MqIuuCk .image-shape .label,#mermaid-svg-igtpgagB3MqIuuCk .icon-shape .label{text-align:center;}#mermaid-svg-igtpgagB3MqIuuCk .node.clickable{cursor:pointer;}#mermaid-svg-igtpgagB3MqIuuCk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-igtpgagB3MqIuuCk .arrowheadPath{fill:#333333;}#mermaid-svg-igtpgagB3MqIuuCk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-igtpgagB3MqIuuCk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-igtpgagB3MqIuuCk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-igtpgagB3MqIuuCk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-igtpgagB3MqIuuCk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-igtpgagB3MqIuuCk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-igtpgagB3MqIuuCk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-igtpgagB3MqIuuCk .cluster text{fill:#333;}#mermaid-svg-igtpgagB3MqIuuCk .cluster span{color:#333;}#mermaid-svg-igtpgagB3MqIuuCk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-igtpgagB3MqIuuCk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-igtpgagB3MqIuuCk rect.text{fill:none;stroke-width:0;}#mermaid-svg-igtpgagB3MqIuuCk .icon-shape,#mermaid-svg-igtpgagB3MqIuuCk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-igtpgagB3MqIuuCk .icon-shape p,#mermaid-svg-igtpgagB3MqIuuCk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-igtpgagB3MqIuuCk .icon-shape .label rect,#mermaid-svg-igtpgagB3MqIuuCk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-igtpgagB3MqIuuCk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-igtpgagB3MqIuuCk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-igtpgagB3MqIuuCk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
上电启动
HAL_Init 硬件初始化
sysclkinit 系统时钟初始化
task_view_init 显示任务初始化
LOS_KernelInit 操作系统初始化
creat_view_task 创建显示任务
LOS_Start 启动操作系统
view_task 循环显示二维码
QR_CODE_Display 生成并绘制二维码
可以看到,整个系统是一个“初始化 → 启动 → 循环”的经典嵌入式结构。操作系统(LiteOS)在这里扮演了“调度员”的角色,让显示任务可以独立、循环地运行。
4. 核心代码:让屏幕“开口”的关键
4.1 主函数:一切从这里开始
int main(void)
{
UINT32 uwRet = LOS_OK;
/*初始化*/
HAL_Init(); //硬件初始化
sysclkinit(); //系统时钟初始化
task_view_init(); //显示任务初始化
task_control_init(); //控制任务初始化
printf("\\r\\n 二维码识别实验 \\r\\n");
printf("———————————-\\r\\n");
printf("开始本实验:\\r\\n\\r\\n");
uwRet = LOS_KernelInit(); //操作系统初始化
if (uwRet != LOS_OK)
{
return LOS_NOK;
}
uwRet = creat_view_task(); //创建显示任务
if (uwRet != LOS_OK)
{
return LOS_NOK;
}
LOS_Start(); //启动操作系统
while (1)
{
}
}
主函数像一位“总导演”,按顺序把各个模块唤醒,最后启动操作系统,让一切开始运转。
4.2 显示任务:屏幕的“专属管家”
VOID view_task(VOID)
{
UINT32 uwRet = LOS_OK;
char strbuffer[sizeof(QR_STR_HEAD) + 20] = {"LiYuanYuan"};
while(1)
{
QR_CODE_Display((uint8_t *)strbuffer);
keytask();
uwRet = LOS_TaskDelay(10);
if(uwRet != LOS_OK)
{
return;
}
}
}
这个任务就像一个不知疲倦的“播报员”,循环不断地把 LiYuanYuan 这串字符变成二维码显示在屏幕上,同时还会检查按键输入,让用户可以和系统交互。
4.3 二维码生成:从字符到图案的魔法
void QR_CODE_Display(uint8_t *qrcode_data)
{
uint8_t i, j;
uint16_t x, y, p;
uint16_t old_point_color = POINT_COLOR;
uint16_t old_back_color = BACK_COLOR;
BACK_COLOR = WHITE;
POINT_COLOR = BLACK;
#define QR_CODE_SIZE 120 //二维码图片大小
#define QR_CODE_X 45 //设置x轴二维码显示位置
#define QR_CODE_Y 65 //设置Y轴二维码显示位置
EncodeData((char *)qrcode_data);
if(m_nSymbleSize*2 > QR_CODE_SIZE)
{
return;
}
for(i=0;i<10;i++)
{
if((m_nSymbleSize*i*2) > QR_CODE_SIZE) break;
}
p=(i–1)*3; //点大小
x=QR_CODE_X;
y=QR_CODE_Y;
for(i=0;i<m_nSymbleSize;i++)
{
for(j=0;j<m_nSymbleSize;j++)
{
if(m_byModuleData[i][j]==0)
LCD_Fill(x+p*i, y+p*j, x+p*(i+1)–1, y+p*(j+1)–1, WHITE);
else
LCD_Fill(x+p*i, y+p*j, x+p*(i+1)–1, y+p*(j+1)–1, BLACK);
}
}
Gui_StrCenter(0, 240, BLACK, WHITE, "欢迎来到万物互联的智能世界", 16, 0);
BACK_COLOR = old_back_color;
POINT_COLOR = old_point_color;
}
这段代码是“魔法”的核心:它调用 EncodeData 把字符编码成二维码的模块数据,然后遍历每一个模块,用黑白方块在屏幕上“画”出二维码。屏幕上的每一个黑白格子,都是数据的一种可视化表达。
5. 一张表看懂各模块分工
| 主函数 | 系统初始化与任务创建 | main() |
| 显示任务 | 循环显示二维码、处理按键 | view_task() |
| 屏幕驱动 | 控制液晶屏显示 | LCD_Init()、LCD_Fill() |
| 二维码编码 | 将字符编码为二维码模块数据 | EncodeData() |
| 图形界面 | 绘制文字、图形、二维码 | Gui_StrCenter()、QR_CODE_Display() |
| 操作系统 | 任务调度与延时管理 | LOS_KernelInit()、LOS_TaskDelay() |
6. 实验中的小细节
- 字符内容:实验中显示的字符串是 LiYuanYuan,你可以随意改成自己的名字或任何想展示的内容。
- 二维码大小:代码中定义了 QR_CODE_SIZE 为 120 像素,如果内容太长导致二维码过大,程序会直接返回,避免显示异常。
- 屏幕坐标:屏幕的坐标系是 x: 0~240、y: 0~320,二维码被放置在 (45, 65) 的位置,居中偏上,下方还显示了一句欢迎语。
- ⚠️ 中文内容扫不出来:这个实验生成的二维码只能编码英文和数字,不能放中文。如果你把字符串改成中文(比如 张三),用微信扫码会直接失败、扫不出来;但用支付宝扫码却能正常识别。这是因为微信和支付宝对二维码编码的容错与解码策略不同——微信对非标准编码(如 GBK 编码的中文)支持较差,而支付宝的容错性更强,能兼容更多编码方式。所以做实验时,建议统一使用英文或数字内容,避免踩坑。
- 编码方式差异:二维码默认按 UTF-8 编码,但部分嵌入式二维码库(如本实验用的 QR_Encode)对中文支持不完整,生成的中文二维码可能不符合标准规范,导致不同 App 识别结果不一致。
- 扫码前检查:实验前建议先用手机分别测试微信和支付宝扫码,确认内容能被正常识别,再固定显示内容。
- 屏幕亮度与距离:扫码时保持屏幕亮度适中、手机与屏幕距离合适,避免反光或过近导致识别失败。
7. 实验背后的知识点
这个实验虽然看起来简单,背后却藏着不少值得深挖的知识点:
7.1 二维码的原理
二维码(QR Code)本质上是一种矩阵式二维条码,它通过黑白方块的排列组合来存储信息。它的核心优势在于:
- 信息容量大:可以存储数字、字母、汉字甚至二进制数据。
- 容错能力强:即使部分图案被遮挡或损坏,依然可以正确识别。
- 识别速度快:手机摄像头可以快速定位并解码。
- ⚠️ 编码方式影响兼容性:二维码的编码方式直接影响不同 App 的识别效果。标准二维码通常使用 UTF-8 编码,但很多嵌入式二维码库(如本实验的 QR_Encode)对中文支持不完整,生成的中文二维码可能不符合标准规范。这就导致同一个二维码,微信扫不出来、支付宝却能扫出来——因为微信对非标准编码的兼容性较差,而支付宝的容错性更强。因此,在嵌入式设备上生成二维码时,尽量使用英文和数字,避免中文带来的兼容性问题。二维码的编码过程大致包括:数据分析 → 数据编码 → 纠错编码 → 构造最终矩阵 → 掩码 → 格式与版本信息。
7.2 嵌入式实时操作系统(RTOS)
实验中使用的 LiteOS 是一个轻量级的实时操作系统。它负责:
- 任务调度:让多个任务(如显示任务、控制任务)并发运行。
- 延时管理:通过 LOS_TaskDelay 让任务按节奏执行。
- 资源管理:合理分配 CPU、内存等资源。
RTOS 是嵌入式开发的重要基石,它让复杂的系统变得井井有条。
7.3 液晶屏驱动
TFT 液晶屏的驱动涉及:
- 初始化:设置屏幕的工作模式、分辨率等。
- 画点/画线:通过控制像素点来绘制图形。
- 显示字符:通过字库将字符映射为像素点阵。
实验中用到的 LCD_Fill、Gui_StrCenter 等函数,都是屏幕驱动的基础操作。
7.4 字符编码与显示
在嵌入式系统中,中文字符的显示通常依赖点阵字库。每个汉字被拆分成一个个像素点,存储在字库中,显示时再逐点绘制。实验中 GUI_DrawFont16、GUI_DrawFont24 等函数就是干这个活的。
8. 结语:屏幕的“语言”
一块屏幕,从只能显示固定内容,到能够“说出”一串可被识别的二维码,这背后是硬件、操作系统、编码算法和图形驱动的协同工作。
这个实验最迷人的地方在于:它把一串看不见的字符,变成了一张看得见的、可以被手机“听懂”的图案。这大概就是物联网的魅力——让万物都能“说话”,让信息在设备之间自由流动。
下次当你用手机扫一个二维码时,不妨想一想:这黑白相间的小方块背后,藏着多少工程师的巧思与代码的魔法。

网硕互联帮助中心




评论前必须登录!
注册