基于 C++ + SDL2 + Dear ImGui 的点阵屏模拟器,用于与单片机进行串口调试。
- 点阵大屏模拟:由多个小屏拼接组成大屏,支持任意行列配置
- 多协议支持:通过抽象协议接口支持不同点阵面板协议,可在运行时切换
- 自动串口扫描:每 2 秒扫描一次 COM 口,可配置波特率
- 帧协议解析:解析来自单片机的刷屏指令(全屏/区域/单点/清屏/文本显示)
- 文本渲染:迪文屏协议支持 ASCII 文本的 5×7 点阵渲染
- 分区配置:对大屏任意像素区域配置独立滚动效果
- 配置持久化:所有配置自动读写
cfg.ini(与可执行文件同目录) - 命令窗口:串口收发数据、程序运行日志实时显示,支持 HEX 手动发送
# 安装依赖(MSYS2 ucrt64 终端中执行)
pacman -S mingw-w64-ucrt-x86_64-gcc \
mingw-w64-ucrt-x86_64-cmake \
mingw-w64-ucrt-x86_64-SDL2 \
mingw-w64-ucrt-x86_64-ninja \
curl# 在 MSYS2 ucrt64 终端中
cd /path/to/led_matrix_sim
bash build.sh debug # Debug 构建
bash build.sh release # Release 构建脚本会自动:
- 检查依赖工具
- 下载 Dear ImGui 源码到
third_party/imgui/ - CMake 配置 + 编译
- 复制 SDL2.dll 到
bin/
构建产物:bin/LedMatrixSim.exe
mkdir build && cd build
cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug
# 如果找不到编译器,显式指定:
cmake .. -G "MinGW Makefiles" -DCMAKE_CXX_COMPILER="D:/msys64/mingw64/bin/g++.exe" -DCMAKE_BUILD_TYPE=Debug
cmake --build . --parallel模拟器采用抽象协议接口(IProtocolParser),支持多种点阵屏协议,可在 UI 中随时切换。
简化的二进制帧协议,适合快速测试和通用调试。
[0xAA] [CMD] [LEN_H] [LEN_L] [DATA...] [CHECKSUM]
0xAA:帧头CMD:命令字节LEN_H/L:Data 段长度(大端,2 字节)DATA:可变长数据CHECKSUM:XOR(CMD, LEN_H, LEN_L, DATA...)
| CMD | 名称 | 数据格式 | 说明 |
|---|---|---|---|
| 0x01 | FULL_FRAME | 位图(总像素数/8 字节,行主序,MSB first) | 全屏刷新 |
| 0x02 | REGION_WRITE | X_H X_L Y_H Y_L W H + 位图 | 局部区域写 |
| 0x03 | CLEAR | (无数据) | 清屏 |
| 0x04 | SET_PIXEL | X_H X_L Y_H Y_L VAL | 单像素写 |
| 0xF0 | PING | (无数据) | 心跳,模拟器回复 PONG |
| 0xF1 | PONG | (无数据) | 心跳响应 |
#include <stdint.h>
#include <string.h>
void serial_send_frame(uint8_t cmd, const uint8_t *data, uint16_t len) {
uint8_t cs = cmd ^ (uint8_t)(len >> 8) ^ (uint8_t)(len & 0xFF);
for (uint16_t i = 0; i < len; i++) cs ^= data[i];
uint8_t header[4] = {0xAA, cmd, (uint8_t)(len>>8), (uint8_t)(len&0xFF)};
HAL_UART_Transmit(&huart1, header, 4, 100);
if (len > 0) HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 500);
HAL_UART_Transmit(&huart1, &cs, 1, 100);
}
void lcd_flush(const uint8_t *framebuf, uint16_t len) {
serial_send_frame(0x01, framebuf, len);
}
void lcd_clear(void) {
serial_send_frame(0x03, NULL, 0);
}
void lcd_set_pixel(uint16_t x, uint16_t y, uint8_t val) {
uint8_t d[5] = {(uint8_t)(x>>8),(uint8_t)x,
(uint8_t)(y>>8),(uint8_t)y, val};
serial_send_frame(0x04, d, 5);
}适用于类似 resources/lcdmatrix/DianZhengPing.c 中的点阵面板控制卡协议。
[0xAA] [0xA5] [LEN_L] [LEN_H] [DATA...] [CRC_L] [CRC_H] [0x5A] [0x55]
0xAA 0xA5:双字节帧头LEN_L/H:DATA 段长度(2 字节,不含 SF 字段)DATA:参见下方详细结构CRC_L/H:CRC16 校验(通常为 0x00 表示关闭)0x5A 0x55:帧尾
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0 | DES | 2 | 目标地址 (0xFF 0xFF = 广播) |
| 2 | SRC | 2 | 源地址 |
| 4 | TID | 2 | 通信标识 (0xB0 0xA1) |
| 6 | CMD | 2 | 命令 (0x10 0x03 = 文本显示) |
| 8 | WID | 2 | 条目类型 (2 = 文本) |
| 10 | REV | 2 | 条目标识 |
| 12 | STYLE | 4 | 条目格式 |
| 16 | INVERT | 1 | 反转标志 (0x40=反转) |
| 17 | FONT_ID | 1 | 字号 ID |
| 18 | FORMAT | 2 | 文本显示格式 |
| 20 | ENTRY | 2 | 入场效果 |
| 22 | SPENTRY | 2 | 入场速度 |
| 24 | DUENTRY | 2 | 停留时间 |
| 26 | HIGHLIGHT | 2 | 高亮效果 |
| 28 | SPHL | 2 | 高亮速度 |
| 30 | DUHL | 2 | 高亮停留 |
| 32 | EXIT | 2 | 退出效果 |
| 34 | SPEXIT | 2 | 退出速度 |
| 36 | TIMES | 2 | 重复次数 (0xFFFF = 无限循环) |
| 38 | DISP_LEN | 2 | 显示文本长度 |
| 40 | TEXT | 可变 | 文本数据 (通常为 GBK 编码) |
目前模拟器支持 ASCII 文本的 5×7 点阵渲染。GBK 中文暂直接记录到日志。
程序启动时自动读取可执行文件同目录的 cfg.ini,所有 UI 修改在退出时自动保存。
[display]
panel_width = 32 ; 单个小屏宽度(像素)
panel_height = 16 ; 单个小屏高度(像素)
panels_cols = 4 ; 小屏列数
panels_rows = 2 ; 小屏行数
pixel_size = 8 ; 每个像素点渲染尺寸(屏幕像素)
pixel_gap = 1 ; 像素间距
pixel_on_r = 255 ; 亮色 RGBA
pixel_on_g = 180
pixel_on_b = 0
pixel_on_a = 255
[protocol]
name = Simple ; Simple | DianZhengPing
[serial]
port = COM3 ; 串口(留空则不自动连接)
baud_rate = 115200
[window]
win_width = 1280
win_height = 800
[regions]
count = 1
region_0 = 0,0,64,8,1,50 ; x,y,w,h,scroll_mode,speed_ms
; scroll_mode: 0=停止 1=左滚 2=右滚 3=上滚 4=下滚led_matrix_sim/
├── CMakeLists.txt
├── build.sh # 一键构建脚本
├── cfg.ini.template # 默认配置模板
├── include/
│ ├── AppContext.h # 全局数据上下文
│ ├── ConfigManager.h # INI 读写
│ ├── IProtocolParser.h # 协议抽象接口
│ ├── ProtocolParser.h # Simple 协议解析器
│ ├── DianZhengPingParser.h # 迪文屏协议解析器
│ ├── SerialManager.h # 串口扫描/收发
│ ├── MatrixDisplay.h # 点阵渲染引擎
│ ├── Region.h # 分区滚动
│ ├── LogConsole.h # 日志缓冲
│ └── UIManager.h # ImGui UI
├── src/
│ ├── main.cpp
│ ├── AppContext.cpp
│ ├── ConfigManager.cpp
│ ├── SerialManager.cpp
│ ├── IProtocolParser.cpp # 接口基类实现
│ ├── ProtocolParser.cpp # Simple 协议
│ ├── DianZhengPingParser.cpp # 迪文屏协议 + 内嵌 5×7 字体
│ ├── MatrixDisplay.cpp
│ ├── Region.cpp
│ ├── LogConsole.cpp
│ └── UIManager.cpp
├── resources/
│ └── lcdmatrix/ # 单片机端参考代码
│ ├── DianZhengPing.c
│ └── DianZhengPing.h
├── third_party/
│ └── imgui/ # Dear ImGui(构建时自动下载)
└── bin/ # 编译输出
├── LedMatrixSim.exe
├── SDL2.dll
└── cfg.ini
- 继承
IProtocolParser(参见include/IProtocolParser.h) - 实现
feed(),name()及可选的build_test_frame() - 在
src/main.cpp的create_parser()中添加分支 - 在
src/UIManager.cpp的协议下拉列表中添加名称 - 将新文件加入
CMakeLists.txt
class IProtocolParser {
public:
virtual void feed(const uint8_t* data, size_t len) = 0; // 喂入原始字节
virtual const char* name() const = 0; // 协议名称
virtual std::vector<uint8_t> build_ack(); // 应答帧(可选)
virtual std::vector<uint8_t> build_test_frame(int id); // 测试帧(可选)
protected:
AppContext* ctx_; // 通过它访问 framebuffer / console / serial
};AppContext::framebuffer 是唯一共享的像素缓冲区(uint8_t 数组,0=灭,1=亮),行主序,左上角为原点。
Q: 找不到 SDL2
pacman -S mingw-w64-ucrt-x86_64-SDL2
Q: 串口无法打开,提示 Access Denied
检查是否有其他程序(串口调试助手)占用该端口。
Q: 运行时提示 SDL2.dll 找不到
将 /ucrt64/bin/SDL2.dll 复制到 bin/ 目录。
Q: 如何切换协议
在程序运行后,在 "Serial Port" 面板的 "Protocol" 下拉框中选择即可实时切换。
Q: DianZhengPing 协议为什么中文没有渲染
当前内嵌字体仅覆盖 ASCII(0x20–0x7F),GBK 中文会被记录到日志但不在屏上显示。后续版本可接入 SDL2_ttf 实现全字符渲染。