• 正文
  • 相关推荐
申请入驻 产业图谱

嵌入式系统中的极简二维码生成方案!

10/23 16:56
529
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

一、项目简介

嵌入式开发中,二维码的应用场景越来越多:设备配网、产品溯源、快速配置。但是,大多数QR码库要么依赖复杂的图形库,要么需要大量动态内存,在资源受限的MCU上很难用起来。

QRCode 是一个专为资源受限的嵌入式系统设计的二维码生成库:

https://github.com/ricmoo/QRCode

QRCode由 Richard Moore 基于 Project Nayuki 的 QR 码生成器改进而来。这个库用纯 C 语言实现,核心代码不到 900 行,却包含了完整的 QR 码生成逻辑,支持从 Version 1 到 Version 40 的所有规格。

物联网设备日益普及的今天,许多应用场景需要在设备端直接生成二维码,比如智能门锁生成临时访客码、工业设备输出设备信息码、智能手环显示健康数据码等。传统的二维码生成库往往需要较大的内存和计算资源,而这个项目针对嵌入式环境做了深度优化。

它只有两个文件(qrcode.hqrcode.c),不依赖任何第三方库,也不使用堆内存,生成的代码非常紧凑。更重要的是,它支持编译时裁剪——如果你的产品只需要固定版本的QR码,可以通过LOCK_VERSION宏大幅减少代码体积。

1.1 核心价值

零堆内存依赖:整个库完全基于栈内存工作,用户可以自己管理缓冲区,这对于没有 malloc/free 或者堆空间极其有限的嵌入式系统至关重要。

极低的资源占用:通过编译时配置(LOCK_VERSION 宏),可以将特定版本的查找表直接编译进代码,大幅减少运行时内存。例如,如果你的应用只需要 Version 3 的二维码,可以节省数百字节的 RAM。

高度可移植性:没有使用任何 POSIX 或平台特定的 API,只依赖标准 C 库的 string.h 和 stdint.h,可以轻松移植到 ARM Cortex-M、AVR、STM32 等各种 MCU 平台。

MIT 许可证:商业友好的开源协议,可以自由用于商业项目。

二、目录结构

核心代码高度集中:头文件只有 100 行,实现文件 877 行,整个库的核心逻辑不到 1000 行。

2.1 关键数据结构

这个结构体设计非常精炼,只占用 10 字节(32 位系统)。所有成员都使用?uint8_t?类型,避免内存对齐浪费。modules?指针指向外部提供的缓冲区,库本身不分配内存。

三、核心机制

QR 码的本质是一个二维的黑白点阵,但如何高效地存储和操作这个点阵,直接影响内存占用和性能。QRCode 库使用的 BitBucket 机制是整个项目最精彩的设计之一。

3.1 BitBucket 机制的设计思路

在嵌入式系统中,内存是最宝贵的资源。一个 Version 10 的 QR 码尺寸为 57×57,共 3249 个模块。如果用?uint8_t?数组存储(每个元素代表一个模块),需要 3249 字节。但实际上每个模块只有黑/白两种状态,只需要 1 bit 即可表示。

BitBucket 的核心思想:用位数组存储二维矩阵,将内存占用压缩到原来的 1/8。

这个结构体有两种工作模式:

Buffer 模式:用于存储一维比特流(如编码后的数据),bitOffsetOrWidth 表示当前写入到第几个比特

Grid 模式:用于存储二维点阵,bitOffsetOrWidth 表示矩阵的宽度

这种"一结构两用"的设计避免了定义多个结构体,节省代码空间。

3.2 关键代码解析

位设置函数

这段代码展示了三个优化技巧:

位移代替除法offset >> 3 等价于?offset / 8,但位移速度更快

位与代替取模offset & 0x07 等价于?offset % 8,避免了昂贵的除法运算

大端序位索引7 - (offset & 0x07) 使得位在字节内从左到右排列,方便调试时查看十六进制数据

位追加函数

这个函数用于将任意长度的值(如 10 bits 的数字、4 bits 的模式指示符)追加到比特流中。循环从最高位开始,逐位提取并写入目标位置。

四、实践

4.1 Linux 平台使用

首先创建一个测试程序:

// test_qrcode.c
#include?<stdio.h>
#include?"qrcode.h"

int?main()?{
? ??// 创建 QRCode 结构
? ? QRCode qrcode;
? ??
? ??// 分配缓冲区(Version 3 需要约 100 字节)
? ??uint8_t?qrcodeBytes[qrcode_getBufferSize(3)];
? ??
? ??// 生成二维码
? ? qrcode_initText(&qrcode, qrcodeBytes,?3, ECC_LOW,?"My name is LinuxZn, WeChat: li1459193464");
? ??
? ??printf("QR Code Version: %dn", qrcode.version);
? ??printf("Size: %dx%dn", qrcode.size, qrcode.size);
? ??printf("ECC Level: %dn", qrcode.ecc);
? ??printf("Mode: %dn", qrcode.mode);
? ??
? ??// 打印二维码
? ??for?(uint8_t?y =?0; y < qrcode.size; y++) {
? ? ? ??for?(uint8_t?x =?0; x < qrcode.size; x++) {
? ? ? ? ? ??printf(qrcode_getModule(&qrcode, x, y) ??"██"?:?" ?");
? ? ? ? }
? ? ? ??printf("n");
? ? }
? ??
? ??return0;
}

编译并运行:

gcc -I./src test_qrcode.c src/qrcode.c -o test_qrcode
./test_qrcode

输出效果:

4.2 移植到 STM32 的详细步骤

4.2.1 移植步骤

第一步:复制源文件

将?qrcode.h?和?qrcode.c?复制到 STM32 项目的?Core/Src?和?Core/Inc?目录。

第二步:配置编译选项(可选)

如果只需要特定版本的 QR 码,在?qrcode.h?中定义:

#define?LOCK_VERSION 3 ?// 锁定 Version 3,节省约 400 字节 Flash

第三步:实现 OLED 绘制函数

#include?"qrcode.h"
#include?"oled.h"??// 你的OLED驱动库

void?draw_qrcode_on_oled(QRCode *qrcode)?{
? ??uint8_t?scale =?2; ?// 每个模块占2×2像素
? ??uint8_t?offset_x = (128?- qrcode->size * scale) /?2; ?// 居中
? ??uint8_t?offset_y = (64?- qrcode->size * scale) /?2;
? ??
? ? OLED_Clear();
? ??
? ??for?(uint8_t?y =?0; y < qrcode->size; y++) {
? ? ? ??for?(uint8_t?x =?0; x < qrcode->size; x++) {
? ? ? ? ? ??if?(qrcode_getModule(qrcode, x, y)) {
? ? ? ? ? ? ? ??// 绘制一个2×2的方块
? ? ? ? ? ? ? ? OLED_Fill(offset_x + x * scale,?
? ? ? ? ? ? ? ? ? ? ? ? ? offset_y + y * scale,?
? ? ? ? ? ? ? ? ? ? ? ? ? offset_x + x * scale + scale -?1,
? ? ? ? ? ? ? ? ? ? ? ? ? offset_y + y * scale + scale -?1,?1);
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ??
? ? OLED_Refresh();
}

第四步:主程序调用

int?main(void)?{
? ? HAL_Init();
? ? SystemClock_Config();
? ??
? ??// 初始化OLED
? ? OLED_Init();
? ??
? ??// 生成二维码
? ? QRCode qrcode;
? ??uint8_t?qrcodeBytes[qrcode_getBufferSize(3)];
? ? qrcode_initText(&qrcode, qrcodeBytes,?3, ECC_LOW,?"My name is LinuxZn, WeChat: li1459193464");
? ??
? ??// 显示在OLED上
? ? draw_qrcode_on_oled(&qrcode);
? ??
? ??while?(1) {
? ? ? ??// 主循环
? ? }
}
4.2.2 使用建议
版本 尺寸 缓冲区大小 推荐使用场景
1 21×21 56 字节 短URL、设备ID
3 29×29 106 字节 中等长度文本
5 37×37 172 字节 较长文本、URL
10 57×57 407 字节 复杂数据

对于 RAM 受限的系统,建议使用 Version 1-3。如果需要更高版本,可以在生成后释放部分临时缓冲区。

4.3 常见问题与解决方案

问题 1:编译时出现 "error: variable-sized object may not be initialized"

原因:某些老版本编译器不支持变长数组(VLA)。

解决:在?performErrorCorrection?函数中,将:

uint8_t?result[data->capacityBytes];

改为:

uint8_t?result[512]; ?// 使用固定大小

问题 2:生成的二维码无法扫描

原因:可能是 Quiet Zone(静区)不足。

解决:在二维码周围预留至少 4 个模块宽度的空白区域。在 OLED 上绘制时确保有边距。

问题 3:中文字符乱码

原因:中文需要使用 UTF-8 编码。

解决:

const?char?*chinese_text =?"你好世界"; ?// UTF-8编码
qrcode_initBytes(&qrcode, qrcodeBytes,?5, ECC_MEDIUM,?
? ? ? ? ? ? ? ? ?(uint8_t*)chinese_text,?strlen(chinese_text));

 

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!