作为一名长期从事工业HMI开发的工程师,最近在项目中使用了DCAN(大彩)品牌的串口屏与STM32F407进行通信。与常见的TFT屏不同,这种基于串口协议的智能屏将GUI渲染工作放在屏幕端处理,大大减轻了MCU的负担。下面我将完整记录从环境搭建到功能实现的全部过程,重点分享官方库移植过程中的关键修改点和避坑经验。
大彩DMT80480T070_15WT这款7寸屏采用USART HMI协议,通过异步串口与主控通信。其核心优势在于:
通信物理层采用标准RS232电平,实际接线时需要注意:
重要提示:虽然屏幕支持5V供电,但STM32的IO口是3.3V电平,建议通过MAX3232等电平转换芯片连接,避免长期工作损坏IO口。
从大彩官网下载最新HMI开发包,关键文件包括:
cmd.c/h:协议解析核心代码driver.h:硬件抽象层接口hmi_user_uart.c:默认串口驱动实现在CubeMX中针对USART3进行如下配置(以115200bps为例):
| 参数项 | 配置值 |
|---|---|
| Mode | Asynchronous |
| Baud Rate | 115200 |
| Word Length | 8 bits |
| Parity | None |
| Stop Bits | 1 |
| Over Sampling | 16 Samples |
生成代码后需检查以下几点:
huart3实例已正确初始化将官方SDK中的以下文件复制到项目/HMI目录:
code复制HMI/
├── cmd.c
├── cmd.h
├── driver.h
└── hmi_user_uart.c
在Keil工程中添加对应分组,特别注意:
/HMI目录hmi_user_uart.c的编译(我们将自定义实现)driver.h适配:
c复制// 修改前
#include "hmi_user_uart.h"
// 修改后
#include "stm32f4xx_hal.h"
extern UART_HandleTypeDef huart3;
自定义串口发送:
c复制// 在usart.c末尾添加
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE {
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 1000);
return ch;
}
cmd.c精简:
删除原文件中的串口初始化部分(约第50-80行),因为我们使用HAL库管理外设。
大彩屏采用文本指令协议,例如:
page 1t0.txt="Hello World"封装基础发送函数:
c复制void HMI_SendCmd(const char* cmd) {
uint8_t endmark[] = {0xFF, 0xFF, 0xFF};
HAL_UART_Transmit(&huart3, (uint8_t*)cmd, strlen(cmd), 100);
HAL_UART_Transmit(&huart3, endmark, sizeof(endmark), 100);
}
在stm32f4xx_it.c中实现中断回调:
c复制uint8_t hmi_rxbuf[256];
uint16_t hmi_rxpos = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART3) {
uint8_t byte;
HAL_UART_Receive_IT(&huart3, &byte, 1);
if(hmi_rxpos < sizeof(hmi_rxbuf)-1) {
hmi_rxbuf[hmi_rxpos++] = byte;
// 检测结束符
if(hmi_rxpos >=3 &&
hmi_rxbuf[hmi_rxpos-3] == 0xFF &&
hmi_rxbuf[hmi_rxpos-2] == 0xFF &&
hmi_rxbuf[hmi_rxpos-1] == 0xFF) {
hmi_rxbuf[hmi_rxpos-3] = '\0';
HMI_Parse((char*)hmi_rxbuf);
hmi_rxpos = 0;
}
} else {
hmi_rxpos = 0; // 防止溢出
}
}
}
当屏幕按钮被点击时,会发送形如btn 1 1的指令(第一个1表示按钮ID,第二个1表示按下动作)。解析示例:
c复制void HMI_Parse(const char* cmd) {
if(strncmp(cmd, "btn", 3) == 0) {
int id, state;
sscanf(cmd+4, "%d %d", &id, &state);
switch(id) {
case 1: // 启动按钮
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, state?GPIO_PIN_SET:GPIO_PIN_RESET);
break;
}
}
}
使用定时器定期刷新屏幕数据:
c复制void UpdateDisplay(void) {
char buffer[32];
// 更新电压显示
float voltage = ReadVoltage();
sprintf(buffer, "n0.val=%d", (int)(voltage*100));
HMI_SendCmd(buffer);
// 更新状态图标
sprintf(buffer, "vis j0,%d", voltage>3.3?1:0);
HMI_SendCmd(buffer);
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 屏幕无反应 | 接线错误/波特率不匹配 | 检查TX/RX交叉连接,确认波特率 |
| 显示乱码 | 电平不兼容 | 添加电平转换芯片 |
| 指令执行不完整 | 未发送结束符 | 确保每条指令后跟0xFF 0xFF 0xFF |
| 触摸坐标不准 | 屏幕校准失效 | 在Visual TFT中重新校准 |
auto_update属性替代定时刷新在Visual TFT中创建LUA脚本:
lua复制-- 按钮按下事件
function on_btn_click(id, val)
if id == 1 then
write_reg(0, 1) -- 向MCU寄存器0写入1
end
end
STM32端解析寄存器写入请求:
c复制void HMI_Parse(const char* cmd) {
if(strncmp(cmd, "write", 5) == 0) {
int addr, val;
sscanf(cmd+6, "%d,%d", &addr, &val);
if(addr == 0) {
// 执行对应操作
}
}
}
在Visual TFT中设置多语言文本:
lang_en和lang_cn属性通过指令切换语言:
c复制// 切换到英文
HMI_SendCmd("language en");
通过三年工业HMI项目实践,我发现大彩屏在响应速度和稳定性上表现优异,但需要注意:
最后分享一个实用技巧:在Visual TFT中启用"指令监视"功能,可以实时查看MCU与屏幕的通信数据,极大提升调试效率。对于需要快速原型开发的项目,这套方案相比传统GUI开发可以节省至少40%的开发时间。