在 LCD 上直接画一个二维码:STM32L4 + QR_Encode 实现"扫码登录"(鸿蒙智联设备接入)

关键词:STM32L4、二维码、QR_Encode、LCD、SPI、扫码登录、鸿蒙智联、LiteOS

一、需求与方案

物联网设备没有键盘输入,怎么把设备接入信息告诉用户?在屏幕上直接显示一个二维码,用户拿手机一扫就能绑定/登录,是目前最常见的方案。

本文实现的是:STM32L4 主控把"平台地址 + 设备 nodeId"拼成字符串 → 用 QR_Encode 库编码成二维码点阵 → 通过 SPI 驱动的 TFT-LCD 把二维码逐点画出来 → 屏幕下方显示"扫码登录"。

整体流程:

平台地址前缀 + 设备ID
        │
        ▼
QR_Encode 编码(EncodeData)
        │
        ▼
二维码点阵(m_byModuleData[][], m_nSymbleSize)
        │
        ▼
按放大倍数逐点绘制到 LCD(LCD_Fill 填色块)
        │
        ▼
屏幕显示二维码 + "扫码登录"提示

二、硬件准备

器件说明
STM32L4 核心板主控
TFT-LCD 屏SPI 接口,用于显示二维码
ESP8266 等联网模块获取设备 nodeId,接入 IoT 平台
手机扫码登录验证

LCD 挂在 SPI1 上,引脚分配:

引脚功能
PE13SPI1_SCK
PE14SPI1_MISO
PE15SPI1_MOSI

三、LCD 初始化(SPI + 寄存器时序)

LCD 驱动初始化分两步:先开 SPI,再按 ILI9341 等常见驱动 IC 的初始化序列写寄存器。

spi_open(spi1,SPI_BAUDRATEPRESCALER_2,Rising)

spi_open 内部会调用 MX_SPI1_Init() 并使能 SPI1,对应引脚配置:

/**SPI1 GPIO Configuration    
PE13     ------> SPI1_SCK
PE14     ------> SPI1_MISO
PE15     ------> SPI1_MOSI 
*/
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

然后 LCD_Init() 里用 LCD_WR_REG() / LCD_WR_DATA() 一条条写初始化寄存器(下面是开头片段,完整序列按屏厂 datasheet 来):

void LCD_Init(void)
{
    SPI_GPIO_Init();
    LCD_GPIO_init();
    LCD_RESET();
    LCD_WR_REG(0xCF);
    LCD_WR_DATA(0x00);
    LCD_WR_DATA(0xC1);
    LCD_WR_DATA(0X30);
    LCD_WR_REG(0xED);
    LCD_WR_DATA(0x64);
    LCD_WR_DATA(0x03);
    LCD_WR_DATA(0X12);
    LCD_WR_DATA(0X81);
    LCD_WR_REG(0xE8);
    LCD_WR_DATA(0x85);
    LCD_WR_DATA(0x10);
    LCD_WR_DATA(0x7A);
    LCD_WR_REG(0xCB);
    LCD_WR_DATA(0x39);
    LCD_WR_DATA(0x2C);
    LCD_WR_DATA(0x00);
    LCD_WR_DATA(0x34);
    LCD_WR_DATA(0x02);
    LCD_WR_REG(0xF7);
    LCD_WR_DATA(0x20);
    /* …… 后续寄存器配置省略 …… */
}

四、二维码内容是怎么拼出来的

二维码里存的内容不是随便定的,前缀是平台固定的接入地址,后面跟设备唯一标识 nodeId:

#define QR_STR_HEAD       "https://iot.zj-huawei.com/wx/#/device?nodeId="   //二维码生成数据

拼字符串的代码在视图任务里:

static void QRCodeview(void)
{
    if(qrcodeflag == 1)
    {
        return;
    }
    char strbuffer[sizeof(QR_STR_HEAD) + 20] = {0};

    qrcodeflag = 1;
    strcat(strbuffer, QR_STR_HEAD);
    strcat(strbuffer, (char *)Get_Net_Module_ID());
    QR_CODE_Display((uint8_t *)strbuffer);
}

要点:

  1. qrcodeflag 是绘制标志,画过一次就置 1,避免每帧重复刷新二维码
  2. Get_Net_Module_ID() 从联网模块(ESP8266)读取设备 nodeId
  3. 最终二维码内容形如:https://iot.zj-huawei.com/wx/#/device?nodeId=xxxxx,手机扫码后即可跳转到设备管理页面完成绑定/登录

五、核心:QR_CODE_Display 逐点绘制

这是本次实验的灵魂函数。EncodeData() 把字符串编码成二维码点阵,存到全局数组 m_byModuleData[m_nSymbleSize][m_nSymbleSize];接下来就是把这个点阵放大后画到屏幕上。

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     85       //二维码图片大小  
#define   QR_CODE_X        150      //设置x轴二维码显示位置
#define   QR_CODE_Y        55       //设置Y轴二维码显示位置    
    EncodeData((char *)qrcode_data);
    if(m_nSymbleSize * 2 > QR_CODE_SIZE)
    {
//        Show_Str(0,0,BLUE,YELLOW,"The QR Code is too large!",12,1);
        return;
    }
    for(i = 0; i < 10; i++)
    {
        if((m_nSymbleSize * i * 2) > QR_CODE_SIZE)
        {
            break;
        }
    }
    p = (i - 1) * 3; //点大小
    x = QR_CODE_X; //设置x轴二维码显示位置
    y = QR_CODE_Y; //设置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);
            }
        }
    }
    Show_Str((QR_CODE_SIZE - 64) / 2 + QR_CODE_X, QR_CODE_Y + QR_CODE_SIZE+5, BLACK, YELLOW, "扫码登录",
             16, 1);   //修改此处的前两个参数调节字体显示位置
    BACK_COLOR = old_back_color;
    POINT_COLOR = old_point_color;
}

这段代码的 3 个关键设计

1. 自动计算放大倍数

二维码原始点阵通常只有几十像素,直接画太小扫不出来,要放大。算法:

for(i = 0; i < 10; i++)
{
    if((m_nSymbleSize * i * 2) > QR_CODE_SIZE)   // 放大后尺寸超过 QR_CODE_SIZE(85)
    {
        break;
    }
}
p = (i - 1) * 3; //点大小

先用循环试探放大多少倍不超过 85 像素,再乘 3 作为最终点大小 p。p 越大,每个码点占的屏幕像素越多,扫码越容易识别。

2. 白底黑点

  • 码点数据 m_byModuleData[i][j] == 0 → 填白色(背景)
  • 否则 → 填黑色(码点)

二维码识别需要高对比度,白底黑点是标准做法。

3. 底部提示文字

Show_Str((QR_CODE_SIZE - 64) / 2 + QR_CODE_X, QR_CODE_Y + QR_CODE_SIZE+5, BLACK, YELLOW, "扫码登录", 16, 1);

"扫码登录"四个字水平居中((QR_CODE_SIZE-64)/2 就是为了居中),放在二维码正下方。

六、显示效果与验证

  • 上电 → 联网模块获取 nodeId → 屏幕自动显示二维码 + “扫码登录”
  • 手机扫码 → 跳转 https://iot.zj-huawei.com/wx/#/device?nodeId=... → 完成设备绑定/登录
  • 串口可打印二维码内容,方便核对

这里插入实际显示效果图(LCD 上的二维码 + 手机扫码结果)

七、踩坑与经验

  1. 放大倍数要够:二维码过小(比如 p=3 以下)手机经常识别失败;本实验点大小按 (i-1)*3 计算,实测识别稳定。
  2. 别在循环里反复整屏刷新:LCD_Fill 是按区域填色,画二维码只改码点所在区域;加上 qrcodeflag 标志保证只画一次,避免闪烁和卡顿。
  3. 内容长度:QR_STR_HEAD + nodeId 总长度不要超过二维码容量,内容越长码越密,越难扫;buffer 用 sizeof(QR_STR_HEAD) + 20 预留空间。
  4. 对比度:一定要白底黑点,别用反色,很多识别失败都是对比度问题。

八、扩展思路

  • 二维码不止能放 URL:把设备 IP、配网信息(SSID+密码)、产品序列号编码进去,就是"一码配网"
  • 复用 QR_CODE_Display 的思路,把点阵换成图片数组,就能在 LCD 上显示任意黑白图片
  • 道闸场景里车牌号也能编成二维码(QR_CODE_Display_Carid),扫码核验后开闸,后面可以单独写一篇
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐