1. 从“点亮”到“交互”串口屏项目开发的必经之路如果你已经跟着前两篇教程成功让迪文串口屏亮起来并且能显示一些静态的图片和文字那么恭喜你你已经迈出了坚实的第一步。但屏幕亮起来只是开始真正的挑战在于如何让这块屏幕“活”起来让它能响应用户的触摸能根据我们单片机的指令动态更新数据能实现复杂的界面切换和逻辑控制。这就是串口屏开发的核心——通信与交互。很多新手朋友在完成基础显示后会卡在交互这一步。屏幕上的按钮按了没反应单片机发送的数据屏幕收不到或者屏幕反馈的数据单片机解析不了。这些问题本质上都是对迪文屏那套独特的“0x5A A5”帧协议以及其配套的DGUS开发工具理解不够深入导致的。本篇教程我们就来彻底攻克这个难关。我将以一个基于STM32单片机与迪文串口屏的温湿度监控系统作为贯穿始终的案例手把手带你完成从界面设计、变量关联、串口通信协议解析到最终实现双向数据交互的全过程。你会发现一旦掌握了这套方法无论是陶晶驰还是其他品牌的串口屏其交互逻辑都是相通的。2. 案例蓝图定义一个清晰的温湿度监控界面在动手写代码和配置屏幕之前我们必须先想清楚我们要做一个什么东西。清晰的蓝图能避免后续开发中的反复修改。我们的目标是制作一个温湿度监控界面包含以下功能实时数据显示在屏幕指定位置动态显示从STM32读取到的温度值和湿度值。历史曲线在屏幕的一个区域绘制温湿度随时间变化的曲线图。参数设置通过触摸屏可以设置温湿度的报警上限和下限。状态指示与控制根据当前值是否超限用LED图标显示报警状态并能通过屏幕按钮控制一个继电器的开关比如控制加湿器或风扇。界面切换从一个主监控界面可以切换到参数设置界面。基于这个蓝图我们可以在DGUS Tool软件里规划两个页面.icl文件页面00.icl主监控界面。包含温湿度数值显示框、曲线图区域、报警LED图标、继电器控制按钮、一个用于切换到设置页面的“设置”按钮。页面11.icl参数设置界面。包含温度上限、温度下限、湿度上限、湿度下限四个数值输入框以及一个“保存并返回”按钮。接下来最关键的一步来了为所有需要和单片机“对话”的屏幕元素分配唯一的“通讯地址”也就是变量VP地址。在迪文屏的体系里屏幕上的每个可交互元素显示数据、触控都绑定到一个VP地址上单片机通过读写这个地址就能控制对应的元素。我们需要规划一张地址分配表VP地址 (HEX)页面控件类型功能描述数据长度0x10000数据变量显示温度当前值 (单位0.1°C)2字节0x10020数据变量显示湿度当前值 (单位0.1%RH)2字节0x10040曲线图温度曲线数据通道02字节/点0x10060曲线图湿度曲线数据通道12字节/点0x10100图标显示温度超限报警LED (0:正常绿, 1:报警红)1字节0x10110图标显示湿度超限报警LED1字节0x10200按键返回继电器控制按钮 (0:关, 1:开)1字节0x10300按键返回切换到设置页面(1)1字节0x11001数据变量显示/输入温度上限设置值2字节0x11021数据变量显示/输入温度下限设置值2字节0x11041数据变量显示/输入湿度上限设置值2字节0x11061数据变量显示/输入湿度下限设置值2字节0x11301按键返回保存参数并返回主页面(0)1字节注意地址规划是良好习惯的起点。建议按功能模块划分地址段并预留一些空间以备后续增加功能。地址用16进制表示在DGUS和单片机代码中都需要保持一致。3. DGUS Tool深度配置让控件“绑定”到地址有了地址规划我们回到DGUS Tool软件进行具体配置。这里我重点讲几个核心且容易出错的控件配置。3.1 数据变量显示控件用于显示温湿度当前值在页面0拖入一个“数据变量显示”控件。右键点击它选择“属性”变量地址填入我们规划的0x1000温度。数据变量类型选择“有符号数”或“无符号数”。假设我们的温度传感器精度是0.1°C传输的整数是实际值的10倍如25.6°C对应256。这里选“有符号数”以防零下温度。数据长度2字节即1个Word。迪文屏中2字节是常见长度。显示方式勾选“数据自动上传”。这个选项非常关键不勾选时单片机需要主动去“读”这个地址屏幕才会上传数据。勾选后只要这个地址对应的显示值发生变化无论是单片机写的还是屏幕内部触发的屏幕都会自动将新值通过串口发送给单片机。对于需要单片机实时知晓屏幕显示内容的场景虽然不常见或者做数据回读校验时有用。在我们的案例中温度值是由单片机定时写入屏幕的屏幕不需要主动上报这个值所以这里我们不勾选“自动上传”。整数位数/小数位数根据你的数据格式设置。例如数据是256代表25.6整数位设2小数位设1屏幕就会显示“25.6”。3.2 触控控件配置按键返回与数据变量输入继电器控制按钮页面0地址0x1020使用“按键返回”控件。在“基本属性”中设置“触发地址”为0x1020。在“上传数据”栏设置“键值”为0x01按下时上传的值。通常我们设置按下上传0x01弹起上传0x00这样单片机就能知道按钮的状态变化。更简单的做法是使用“位按钮”控件它自带状态切换和图标绑定功能。切换到设置页面按钮页面0地址0x1030同样用“按键返回”控件。除了设置触发地址最关键的是在“控件属性”的“页面切换”选项卡中勾选“切换页面”并设置目标页面为1。这样点击按钮屏幕会自动跳转到页面1无需单片机干预。参数输入框页面1地址0x1100等使用“数据变量显示”控件但这次我们要允许用户输入。在“基本属性”中勾选“启用数据输入”。在“输入设置”中可以设置键盘类型数字键盘、最小值、最大值等。当用户点击这个输入框屏幕会弹出键盘输入完成后屏幕会自动将输入值写入它绑定的VP地址0x1100并且如果该控件勾选了“数据自动上传”它还会把这个新值通过串口发送给单片机。这是我们实现屏幕设置参数并通知单片机的关键。3.3 曲线图控件配置曲线图控件相对复杂。首先需要在“图片库”中生成一个曲线图背景图坐标网格。然后在页面0插入“曲线图”控件绑定背景图。曲线通道我们有两个通道温度、湿度。在“曲线设置”中为通道0分配VP地址0x1004颜色为红色为通道1分配VP地址0x1006颜色为蓝色。工作原理单片机需要定时比如每10秒将当前的温度值处理后的2字节数据写入0x1004地址将湿度值写入0x1006地址。屏幕的曲线图控件会自动从这两个地址读取数据并将其作为一个新的点绘制到曲线上同时曲线会向左滚动。这里有一个巨大的坑曲线图控件读取VP地址数据的行为是屏幕内部自动完成的与“数据自动上传”无关。单片机只需要负责“写”屏幕负责“读”和“画”。配置完成后将工程编译、下载到SD卡再导入屏幕中。至此屏幕端的“静态”配置就完成了。接下来我们将进入单片机的世界让两者“对话”。4. 协议解析引擎STM32侧的通信代码实现迪文屏的通信协议帧格式是固定的帧头5A A5 数据长度 指令 数据 帧尾。数据长度是整个帧从指令开始到帧尾的字节数。我们需要在STM32上编写一个健壮的解析引擎。4.1 定义核心数据结构与指令首先定义一些常用的指令码和我们的地址映射。// 迪文屏协议指令定义 #define DGUS_CMD_WRITE_REG 0x80 // 写寄存器VP地址 #define DGUS_CMD_READ_REG 0x81 // 读寄存器 #define DGUS_CMD_WRITE_VP 0x82 // 写变量存储器我们最常用的写数据指令 #define DGUS_CMD_READ_VP 0x83 // 读变量存储器 // 我们规划的VP地址定义 #define VP_TEMP_CURRENT 0x1000 #define VP_HUMI_CURRENT 0x1002 #define VP_TEMP_CURVE 0x1004 #define VP_HUMI_CURVE 0x1006 #define VP_ALARM_TEMP 0x1010 #define VP_ALARM_HUMI 0x1011 #define VP_RELAY_CTRL 0x1020 #define VP_PAGE_SETTING 0x1030 // ... 设置页面的地址类似定义 // 协议帧结构体用于组帧和解析 typedef struct { uint8_t header[2]; // 5A A5 uint8_t data_len; // 数据长度指令数据校验 uint8_t cmd; // 指令 uint8_t data[256]; // 数据域可变长 // 注意实际帧尾是两个字节的CRC校验但很多应用为简化使用固定的0x00 0x00或省略。迪文屏通常用0x00 0x00。 } DgusFrame_t;4.2 组帧发送函数这是单片机主动向屏幕发送数据如更新显示、切换页面的核心函数。/** * brief 向迪文屏写入VP地址数据 * param vp_addr: VP地址 (16位) * param data: 要写入的数据指针 * param data_len: 数据字节数 * retval 无 */ void DGUS_WriteVP(uint16_t vp_addr, uint8_t *data, uint16_t data_len) { uint8_t frame[10 data_len]; // 预留足够空间帧头2 长度1 指令1 地址2 数据N 帧尾2 uint8_t frame_len 0; uint16_t i; // 帧头 frame[frame_len] 0x5A; frame[frame_len] 0xA5; // 数据长度 指令(1) 地址(2) 数据(N) 帧尾(2) 5 data_len frame[frame_len] 5 data_len; // 指令写VP frame[frame_len] DGUS_CMD_WRITE_VP; // VP地址高位在前 frame[frame_len] (vp_addr 8) 0xFF; frame[frame_len] vp_addr 0xFF; // 数据 for(i 0; i data_len; i) { frame[frame_len] data[i]; } // 帧尾简化处理使用0x00 0x00。严格应用需计算CRC16 frame[frame_len] 0x00; frame[frame_len] 0x00; // 通过串口发送假设使用USART1 HAL_UART_Transmit(huart1, frame, frame_len, 1000); }使用示例更新当前温度显示为25.6°C数据值为256。uint16_t temp_value 256; // 0x0100 uint8_t temp_data[2]; temp_data[0] (temp_value 8) 0xFF; // 高位字节 0x01 temp_data[1] temp_value 0xFF; // 低位字节 0x00 DGUS_WriteVP(VP_TEMP_CURRENT, temp_data, 2);4.3 数据接收与解析引擎屏幕主动上传的数据如按键按下、输入框完成输入需要单片机接收并解析。这是一个状态机解析的经典实现可以有效处理粘包、断帧。// 解析状态 typedef enum { DGUS_RX_IDLE, DGUS_RX_HEADER1, DGUS_RX_HEADER2, DGUS_RX_LEN, DGUS_RX_CMD, DGUS_RX_DATA, DGUS_RX_CHECK } DgusRxState_t; DgusRxState_t rx_state DGUS_RX_IDLE; uint8_t rx_buffer[300]; uint8_t rx_index 0; uint8_t expected_len 0; uint8_t data_len_remain 0; /** * brief 串口接收中断回调函数或在主循环中轮询调用此解析函数 * param byte: 接收到的单个字节 * retval 无 */ void DGUS_UART_RxByte(uint8_t byte) { static uint8_t cmd_saved; switch(rx_state) { case DGUS_RX_IDLE: if(byte 0x5A) { rx_state DGUS_RX_HEADER1; rx_buffer[rx_index] byte; } break; case DGUS_RX_HEADER1: if(byte 0xA5) { rx_state DGUS_RX_HEADER2; rx_buffer[rx_index] byte; } else { // 同步头错误重置状态机 rx_state DGUS_RX_IDLE; rx_index 0; } break; case DGUS_RX_HEADER2: // 接收数据长度字节 expected_len byte; // 这个长度是“指令数据校验”的总长 rx_buffer[rx_index] byte; if(expected_len 3) { // 最小帧指令1 校验2 data_len_remain expected_len; rx_state DGUS_RX_CMD; } else { // 长度非法重置 rx_state DGUS_RX_IDLE; rx_index 0; } break; case DGUS_RX_CMD: cmd_saved byte; rx_buffer[rx_index] byte; data_len_remain--; if(data_len_remain 0) { rx_state DGUS_RX_DATA; } else { rx_state DGUS_RX_CHECK; // 没有数据域直接到校验 } break; case DGUS_RX_DATA: rx_buffer[rx_index] byte; data_len_remain--; if(data_len_remain 0) { rx_state DGUS_RX_CHECK; } break; case DGUS_RX_CHECK: // 接收校验字节这里简化处理连续接收两字节 rx_buffer[rx_index] byte; if(rx_index (3 expected_len)) { // 3 帧头2 长度1 // 一帧接收完成 DGUS_ProcessFrame(cmd_saved, rx_buffer, rx_index); // 重置状态机准备接收下一帧 rx_state DGUS_RX_IDLE; rx_index 0; } break; default: rx_state DGUS_RX_IDLE; rx_index 0; break; } }4.4 帧处理函数解析出一帧完整数据后根据指令进行相应处理。/** * brief 处理解析完成的迪文屏协议帧 * param cmd: 指令 * param buffer: 完整的帧数据包含帧头 * param len: 帧总长度 * retval 无 */ void DGUS_ProcessFrame(uint8_t cmd, uint8_t *buffer, uint8_t len) { uint16_t vp_addr; uint8_t data; switch(cmd) { // 通常屏幕上传的数据是“写寄存器”指令0x80或“按键返回”等触发的数据 // 我们需要根据数据内容的前两个字节判断是哪个VP地址 case 0x80: // 写寄存器指令常用于按键返回、数据输入上传 if(len 7) { // 帧头2长度1指令1地址2数据1校验2 vp_addr (buffer[4] 8) | buffer[5]; // 第4、5字节是VP地址 data buffer[6]; // 第6字节是数据 switch(vp_addr) { case VP_RELAY_CTRL: if(data 0x01) { // 继电器按钮被按下执行开启继电器操作 HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET); // 可以反馈一个状态到屏幕的图标 uint8_t relay_state 1; DGUS_WriteVP(VP_RELAY_CTRL, relay_state, 1); } else if(data 0x00) { // 按钮释放或关闭继电器取决于你的逻辑 // HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_RESET); } break; case VP_PAGE_SETTING: // 切换到设置页面单片机通常无需处理因为屏幕已自动切换 // 但可以记录当前页面号用于逻辑判断 current_page 1; break; case 0x1100: // 温度上限设置值改变 { uint16_t new_temp_high (buffer[6] 8) | buffer[7]; // 注意2字节数据 // 保存到单片机的EEPROM或变量中 sys_params.temp_high new_temp_high; } break; // ... 处理其他设置地址 default: break; } } break; // 如果屏幕配置了“数据自动上传”可能会收到0x82写VP指令 case DGUS_CMD_WRITE_VP: // 处理逻辑类似解析VP地址和数据 break; default: // 未知指令可忽略或用于调试 break; } }5. 系统联调与实战避坑指南当屏幕配置和单片机代码都准备好后就到了最激动人心也最容易出问题的联调阶段。以下是我在实际项目中总结出的几个关键避坑点。5.1 通信波特率与接线波特率必须一致DGUS Tool中工程设置的波特率如115200、屏幕本身配置的波特率通过配置文件或初始化指令、STM32串口初始化的波特率三者必须完全一致。建议从115200开始调试。接线要交叉STM32的TX接屏幕的RXSTM32的RX接屏幕的TX。GND一定要共地。这是最基础也最容易被忽略的问题。电平匹配迪文屏通常是3.3V或5V TTL电平确保与你的STM32开发板电平匹配。3.3V屏接5V MCU可能需要电平转换否则可能损坏屏幕或通信不稳定。5.2 数据格式与字节序大小端问题迪文屏协议是大端模式高字节在前。在STM32小端架构中将一个16位整数0x1234发送给屏幕时需要先发送0x12再发送0x34。前面的DGUS_WriteVP函数中(vp_addr 8) 0xFF就是处理这个。数据变量类型匹配在DGUS Tool中设置的数据类型有符号/无符号、数据长度必须与单片机发送的数据格式严格匹配。如果你发送的是uint16_t屏幕端却配置为“长整数4字节”显示就会错乱。5.3 “数据自动上传”的误用与理解这是新手最容易困惑的地方。再次强调“数据自动上传”勾选当该VP地址的数据因任何原因发生变化时屏幕会主动将新数据发送给单片机。变化原因包括1) 单片机写入2) 用户通过输入框修改3) 屏幕内部逻辑如RTC控件更新。何时使用需要单片机同步屏幕状态时例如屏幕上有一个通过触控切换的开关图标单片机需要知道当前图标状态以控制硬件。此时需要勾选。参数设置输入框当用户完成输入你需要立即获取新值时勾选后屏幕会上传新值。何时不用单片机单向更新的显示数据如温度、湿度实时显示。单片机定时写入新值屏幕只负责显示无需反馈。勾选反而会导致串口不必要的流量。简单的页面切换按钮如果按钮只用于切换页面且页面切换由屏幕自动完成单片机无需知道则不必勾选。5.4 曲线图控件数据写入的时机与格式写入地址单片机写入的是曲线图控件配置的“通道VP地址”如我们例子中的0x1004而不是曲线图控件本身的ID地址。数据格式写入的是绝对坐标值。假设曲线图Y轴范围是0-100对应像素高度0-200。那么当温度值50需要显示在中间时你需要写入100如果范围是0-100或进行相应的线性映射。务必在DGUS Tool中设置好曲线图的最大最小值使其与单片机发送的数据范围匹配。写入频率不宜过快。通常1-10秒写入一个点即可。过快写入会导致曲线刷新太快看不清趋势也增加通信负担。5.5 调试技巧串口助手是你的眼睛监听通信将STM32的串口TX/RX同时接到电脑的USB转TTL工具用串口助手同时监听收发数据。你可以清晰地看到单片机发了什么屏幕回了什么。模拟单片机在调试屏幕触控功能时可以用串口助手模拟单片机手动发送写VP指令如5A A5 05 82 10 20 01 00 00来改变屏幕显示测试控件是否配置正确。模拟屏幕在调试单片机解析逻辑时可以用串口助手模拟屏幕发送预设的按键数据帧如5A A5 05 80 10 20 01 00 00给单片机看单片机能否正确解析并执行相应动作。5.6 页面切换与变量初始化当屏幕切换页面时新页面上的所有控件都会从它们绑定的VP地址读取初始值。如果单片机没有预先在这些地址写入数据屏幕可能显示乱码或默认值。最佳实践在单片机程序初始化时或者在监测到页面切换后主动向新页面上所有需要显示初始值的VP地址写入数据。例如进入设置页面时单片机应该将保存在EEPROM中的温度上限、下限值写入0x1100,0x1102等地址这样屏幕一打开就能显示当前设置值。通过以上五个步骤从项目规划、屏幕配置、单片机协议驱动编写到深度联调你已经掌握了迪文串口屏交互开发的完整链条。核心在于理解“VP地址”作为通信桥梁的核心作用以及“写指令”与“自动上传”两种数据流方向。剩下的就是将这套方法应用到你的具体项目中去不断实践和调试。当你第一次通过触摸屏按钮点亮一个真实的LED或者看到传感器数据在屏幕上平滑地绘制出曲线时那种成就感就是学习嵌入式GUI交互最好的回报。