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

strongerHuang 2025-10-23 08:20

关注+星标公众,不错过精彩内容

来源 | 嵌入式大杂烩

一、项目简介

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

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

https://github.com/ricmoo/QRCode

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

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 许可证:商业友好的开源协议,可以自由用于商业项目。

二、目录结构

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

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

2.1 关键数据结构

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

这个结构体设计非常精炼,只占用 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。

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

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

  1. Buffer 模式:用于存储一维比特流(如编码后的数据),bitOffsetOrWidth 表示当前写入到第几个比特
  2. Grid 模式:用于存储二维点阵,bitOffsetOrWidth 表示矩阵的宽度

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

3.2 关键代码解析

位设置函数

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

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

  1. 位移代替除法offset >> 3 等价于 offset / 8,但位移速度更快
  2. 位与代替取模offset & 0x07 等价于 offset % 8,避免了昂贵的除法运算
  3. 大端序位索引7 - (offset & 0x07) 使得位在字节内从左到右排列,方便调试时查看十六进制数据

位追加函数

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

这个函数用于将任意长度的值(如 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

输出效果:

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

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 - 11);
            }
        }
    }
    
    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));

------------ END ------------


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


关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。


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

声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
嵌入式系统
more
DigiKey 应用探索站 | 嵌入式系统和微控制器技术【第一趴】
告别内存碎片!嵌入式系统内存池完美解决方案
分享一款嵌入式系统内存泄漏检测利器:MTrace
分享一款专为嵌入式系统设计的HTTP库
嵌入式系统中的极简二维码生成方案!
一个被严重低估的嵌入式系统微内核!
嵌入式系统软件代码常见的容错设计
RISC-V 生态专区 “嵌入式系统与智能终端” Workshop圆满落幕:隼瞻科技携手战略合作伙伴共探产业新路径
基于AI的嵌入式系统的功能安全设计
专为嵌入式系统设计的轻量级框架
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号