2025寒假练 - 基于CrowPanel ESP32 HMI 4.3英寸显示屏开发板实现手写数字识别
该项目使用了CrowPanel ESP32 HMI 4.3英寸显示屏开发板,实现了手写数字识别的设计,它的主要功能为:使用LVGL绘制了UI,并在屏幕上实现了手写数字识别。
标签
lvgl
2025寒假练
CrowPanel ESP32
taotie
更新2025-03-17
南京信息工程大学
36

一、项目介绍

本项目主要使用了CrowPanel ESP32 HMI 4.3英寸显示屏开发板,通过Squareline Studio绘制了图形化ui界面,然后移植到到了CrowPanel ESP32 HMI 4.3英寸显示屏开发板,通过手写笔在屏幕上手写数字,然后通过串口将手写数据发送到电脑,经过matlab识别后,通过串口将结果重新发送给esp32并在屏幕和灯板上显示识别结果。


二、硬件分析

Elecrow ESP32 4.3英寸显示屏是一款功能强大的HMI触摸屏,具有480*272分辨率的LCD显示屏。它使用ESP32-S3-WROOM-1-N4R2模组作为主控处理器,具有双核32位 LX7处理器,集成WiFi和蓝牙无线功能,主频高达240MHz,提供强大的性能和多功能的应用,适用于物联网应用设备等场景。板上集成 ESP32-S3-WROOM-1-N4R2 模组,内置无线通信 2.4 GHz Wi-Fi(802.11 b/g/n)和蓝牙 5.0,支持开发环境Arduino IDE、Espressif IDF、PlatformIO、MicroPython并兼容LVGL图形库,丰富的外设接口和扩展功能使其能够满足不同领域的需求。


三、思路分析

本项目需要在屏幕上手写数字,然后识别手写数字对应0-9的哪个数字,然后显示识别结果并使灯板表现出对应的数字。如下是根据上述思路绘制的思路流程图:

c25b31e9c4540bf030790a2e066daf23.png

本项目使用Squareline Studio绘制UI界面,然后导出对应的文件,利用LVGL库在esp32上移植UI,然后定义各回调函数所需实现的功能(如:clear函数:清除手写部分的区域、清除识别结果、清除内部手写数字数据压缩矩阵;predict函数:识别和串口发送数据使能等等),然后获取手写数字数据压缩矩阵(通过触摸函数),通过串口发送手写数字数据压缩矩阵并获取识别结果,然后更改屏幕显示并用74595驱动驱动灯板。

根据上述思路绘制设计流程图如下:

5f71c645ef0a035b7093da976886c1d6.png


四、主要代码分析

1、调用了LVGL库以及Squareline Studio生成的UI文件等:

#include <Wire.h>
#include <SPI.h>
#include "matrix_num.h"

#include <lvgl.h>
#include "ui.h"

#include "gfx_conf.h"

2、定义LVGL的显示缓冲区和驱动对象,采用双缓冲机制优化显示性能

static lv_disp_draw_buf_t draw_buf;
static lv_color_t disp_draw_buf1[screenWidth * screenHeight / 10];
static lv_color_t disp_draw_buf2[screenWidth * screenHeight / 10];
static lv_disp_drv_t disp_drv;

3、通过DMA加速图像传输,调用tft.pushImageDMA将LVGL绘图缓冲区内容刷新到屏幕

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = ( area->x2 - area->x1 + 1 );
uint32_t h = ( area->y2 - area->y1 + 1 );

tft.pushImageDMA(area->x1, area->y1, w, h,(lgfx::rgb565_t*)&color_p->full);

lv_disp_flush_ready( disp );

}

4、读取触摸坐标并转换为28x28矩阵索引,记录到matrix数组中,并在屏幕上绘制白点

void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
uint16_t touchX, touchY;
uint16_t X, Y;
bool touched = tft.getTouch( &touchX, &touchY);
if( !touched )
{
data->state = LV_INDEV_STATE_REL;
}
else
{
data->state = LV_INDEV_STATE_PR;

/*Set the coordinates*/
data->point.x = touchX;
data->point.y = touchY;
lv_obj_t * current_screen = lv_scr_act();

if(current_screen == ui_Screen2)
if (touchX >= 136 && touchX <= 332 && touchY >= 54 && touchY <= 250){
X = (touchX - 136) / 7;
Y = (touchY - 54) / 7;
matrix[X][Y] = 255;
tft.fillCircle(touchX, touchY, 5, TFT_WHITE);
}
}
}

5、按钮回调函数

contain_clear函数用于充当复位按钮,重置28x28数字矩阵数据、清空识别结果显示标签

void contain_clear(lv_event_t * e)
{
ui_Container1 = lv_obj_create(ui_Screen2);
lv_obj_remove_style_all(ui_Container1);
lv_obj_set_width(ui_Container1, 196);
lv_obj_set_height(ui_Container1, 196);
lv_obj_set_x(ui_Container1, -6);
lv_obj_set_y(ui_Container1, 16);
lv_obj_set_align(ui_Container1, LV_ALIGN_CENTER);
lv_obj_clear_flag(ui_Container1, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags
lv_obj_set_style_bg_color(ui_Container1, lv_color_hex(0x020202), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(ui_Container1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
for(int i=0;i<28;i++)
for(int j=0;j<28;j++)
matrix[i][j] = 0;
lv_label_set_text(ui_Label4, "");
}

num_predict函数用于使能串口通信,当按钮按下时与上位机交换数据,完成数字识别

6、74595驱动

#include <Arduino.h>

// 引脚定义
#define DIN 17
#define SRCLK 18
#define RCLK 37

// 初始化函数
void led8x8_init() {
pinMode(DIN, OUTPUT);
pinMode(SRCLK, OUTPUT);
pinMode(RCLK, OUTPUT);
digitalWrite(DIN, LOW);
digitalWrite(SRCLK, LOW);
digitalWrite(RCLK, LOW);
}

byte reverseBits(byte b) {
// 通过三次位操作完成8位反转
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}

// 核心写入函数(私有)
void _shiftWrite(byte data) {
for(int i=7; i>=0; i--) {
digitalWrite(SRCLK, LOW);
digitalWrite(DIN, (data & (1 << i)) ? HIGH : LOW);
digitalWrite(SRCLK, HIGH);
}
}

// 显示8x8图案函数
void display8x8(int pattern[8], uint16_t duration_ms = 1000) {
unsigned long start = millis();

while(millis() - start < duration_ms) {
byte row_mask = 0x01; // 从第一行开始扫描
for(int row=0; row<8; row++) {
// 先发送列数据(阴极),再发送行数据(阳极)
_shiftWrite(reverseBits(pattern[row]));
_shiftWrite(row_mask);
// 锁存输出
digitalWrite(RCLK, HIGH);
delayMicroseconds(1);
digitalWrite(RCLK, LOW);

row_mask <<= 1; // 移到下一行
delayMicroseconds(300); // 行间延时
}
}
// 清除显示
_shiftWrite(0xFF); // 关闭所有列
_shiftWrite(0x00);
digitalWrite(RCLK, HIGH);
digitalWrite(RCLK, LOW);
}

//0-9字库
unsigned int dig_1[10][8]=

{
{0x1c,0x22,0x22,0x22,0x22,0x22,0x22,0x1c},//0
{0x1c,0x08,0x08,0x08,0x08,0x08,0x0c,0x08},//1
{0x3e,0x04,0x08,0x10,0x20,0x20,0x22,0x1c},//2
{0x1c,0x22,0x20,0x20,0x18,0x20,0x22,0x1c},//3
{0x20,0x20,0x3e,0x22,0x24,0x28,0x30,0x20},//4
{0x1c,0x22,0x20,0x20,0x1e,0x02,0x02,0x3e},//
{0x1c,0x22,0x22,0x22,0x1e,0x02,0x22,0x1c},//6
{0x04,0x04,0x04,0x08,0x10,0x20,0x20,0x3e},//7
{0x1c,0x22,0x22,0x22,0x1c,0x22,0x22,0x1c},//8
{0x1c,0x22,0x20,0x3c,0x22,0x22,0x22,0x1c},//9
};

7、串口控制部分

if(en == 1){
Serial.print("START,");
for(int i=0;i<28;i++)
for(int j=0;j<28;j++){
Serial.print(matrix[i][j]);
if(i == 27 && j == 27);
else
Serial.print(",");
}
Serial.println(",END");
unsigned long startTime = millis();
while(!Serial.available() && (millis()-startTime < 3000));
if(Serial.available()){
String response = Serial.readStringUntil('\n');
int num_1 = response.toInt();
lv_label_set_text_fmt(ui_Label4,"%d",num_1);
int mt[8];
for(int i=8;i>0;i--)
mt[i-1] = dig_1[num_1][8-i];
display8x8(mt, 2000);
response.trim();
}

en = 0;
}

当predict按键按下时,en = 1,然后打开串口,将压缩手写数字数据矩阵定义好发送格式化后发送到matlab,然后读取从串口传回的数据,并改变屏幕上预测的结果,同时驱动LED矩阵显示对应的值。

8、matlab识别部分

加载预训练模型

load('Minist_LeNet5');

串口配置

s = serialport('COM4', 9600);
configureTerminator(s, "LF");%设置终止符

监听串口数据,提取有效字段后分类并返回结果

while true
try
rawData = readline(s);
if contains(rawData, 'START') && contains(rawData, 'END')
% 提取数据部分
dataStr = extractBetween(rawData, 'START,', ',END');
numArray = str2double(strsplit(dataStr{1}, ','));
% 转换为28x28矩阵
inputData = reshape(numArray, [], 28);
% 转换为单精度
inputData = im2single(inputData);
% 执行预测
predictedLabel = classify(trainNet, inputData);
disp(predictedLabel);
% 发送结果
writeline(s, char(predictedLabel));
end
catch ME
disp(['Error: ' ME.message]);
continue; % 发生错误时继续监听
end
end

五、实物展示

e9397f38f086c980df7ce01abc7ff61c.jpg

六、活动总结

通过这次活动我学习到了有关神经网络方面的知识,希望能多多参加这样的活动。

附件下载
t2.m
matlab识别模型
crowpanel (1).rar
团队介绍
小田
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号