关注+星标公众号,不错过精彩内容
来源 | 嵌入式大杂烩
一、项目简介
在嵌入式开发中,二维码的应用场景越来越多:设备配网、产品溯源、快速配置。但是,大多数QR码库要么依赖复杂的图形库,要么需要大量动态内存,在资源受限的MCU上很难用起来。
QRCode 是一个专为资源受限的嵌入式系统设计的二维码生成库:
https://github.com/ricmoo/QRCode

QRCode由 Richard Moore 基于 Project Nayuki 的 QR 码生成器改进而来。这个库用纯 C 语言实现,核心代码不到 900 行,却包含了完整的 QR 码生成逻辑,支持从 Version 1 到 Version 40 的所有规格。
在物联网设备日益普及的今天,许多应用场景需要在设备端直接生成二维码,比如智能门锁生成临时访客码、工业设备输出设备信息码、智能手环显示健康数据码等。传统的二维码生成库往往需要较大的内存和计算资源,而这个项目针对嵌入式环境做了深度优化。
它只有两个文件(qrcode.h和qrcode.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: %d\n", qrcode.version);
printf("Size: %dx%d\n", qrcode.size, qrcode.size);
printf("ECC Level: %d\n", qrcode.ecc);
printf("Mode: %d\n", 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 使用建议
对于 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));
------------ END ------------

点击“阅读原文”查看更多分享。