一、概述
在使用大彩TFT串口屏时,发现指令解析的封装不太方便,对其进行的优化。
二、应用
1、指令结构体
cpp
#pragma pack(push)
#pragma pack(1) //按字节对齐
typedef struct
{
uint8_t cmd_head; //帧头
uint8_t cmd_type; //命令类型(UPDATE_CONTROL)
uint8_t ctrl_msg; //CtrlMsgType-指示消息的类型
uint16_t screen_id; //产生消息的画面ID
uint16_t control_id; //产生消息的控件ID
uint8_t control_type; //控件类型
uint8_t param[256]; //可变长度参数,最多256个字节
uint8_t cmd_tail[4]; //帧尾
}CTRL_MSG,*PCTRL_MSG;
volatile typedef union
{
CTRL_MSG S;
uint8_t A[sizeof(CTRL_MSG)];
}Command_Union;
#pragma pack(pop)
在之前结构体的基础上增加了一个共用体,通过不同的方式访问同一块内存地址:一种是结构体的方式一种是以数组的方式;主要是因为串口接收到的数据有一些不是按照结构体协议格式传输的如RTC和记录控件。
由于使用了#pragma pack(1),所有成员都按1字节对齐,所以不会有额外的填充字节。例如,screen_id虽然是uint16_t(通常2字节),但在默认情况下可能需要对齐到偶数地址,但现在强制1字节对齐,因此直接跟在前一字段后面。
另外,param数组的大小固定为256字节,即使实际参数可能更短,这里也预留了足够的空间。而cmd_tail作为帧尾,占用4个字节,可能是用于校验或其他用途。
最后,通过#pragma pack(pop)恢复之前的打包设置,确保后续代码不受影响。这种技巧常用于嵌入式系统编程中,需要精确控制数据布局的情况。
🔹 CTRL_MSG: 结构体类型名
🔹 PCTRL_MSG: 指向该结构体的指针类型别名
2、指令解析
cpp
/*********************************************************
函数功能:指令解析函数
入口参数:
返 回:无。
备 注:。
作 者:Roy
时 间:2025-03-05
*********************************************************/
char ShowStr[20] = {0};
char AlarmString[30] = {0};
char USB_State = 0;
extern uint8_t Command[300];
char IDStr[20] = {0};
uint16_t TempValue;
uint16_t Record_UserID_SelectNum = 0;
uint16_t Record_EndoID_SelectNum = 0;
#include"User.h"
void Command_Parser(Command_Union* msg, uint16_t size)
{
uint8_t cmd_type = msg->S.cmd_type; //指令类型
uint8_t ctrl_msg = msg->S.ctrl_msg; //消息的类型
uint8_t control_type = msg->S.control_type; //控件类型
uint16_t screen_id = PTR2U16( &msg->S.screen_id); //画面ID
uint16_t control_id = PTR2U16( &msg->S.control_id); //控件ID
uint32_t value = PTR2U32(msg->S.param); //数值
switch (cmd_type)
{
case NOTIFY_TOUCH_PRESS: //触摸屏按下
case NOTIFY_TOUCH_RELEASE: //触摸屏松开
// NotifyTouchXY(cmd_buffer[1],PTR2U16(cmd_buffer+2),PTR2U16(cmd_buffer+4));
break;
case NOTIFY_WRITE_FLASH_OK: //写FLASH成功
// NotifyWriteFlash(1);
break;
case NOTIFY_WRITE_FLASH_FAILD: //写FLASH失败
// NotifyWriteFlash(0);
break;
case NOTIFY_READ_FLASH_OK: //读取FLASH成功
// NotifyReadFlash(1,cmd_buffer+2,size-6); //去除帧头帧尾
break;
case NOTIFY_READ_FLASH_FAILD: //读取FLASH失败
// NotifyReadFlash(0,0,0);
break;
case NOTIFY_READ_RTC: //读取RTC时间
NotifyReadRTC(msg->A[2], msg->A[3], msg->A[4], msg->A[5], msg->A[6], msg->A[7], msg->A[8]);
break;
case NOTIFY_CONTROL:
{
if (ctrl_msg == MSG_GET_CURRENT_SCREEN) //画面ID变化通知
{
NotifyScreen(screen_id); //画面切换调动的函数
}
else
{
switch (control_type)
{
case kCtrlButton: //按钮控件
// printf("按钮控件\r\n");
NotifyButton(screen_id, control_id, msg->S.param[1]);
break;
case kCtrlText: //文本控件
NotifyText(screen_id, control_id, msg->S.param);
break;
case kCtrlProgress: //进度条控件
// NotifyProgress(screen_id,control_id,value);
break;
case kCtrlSlider: //滑动条控件
// NotifySlider(screen_id,control_id,value);
break;
// case kCtrlMeter: //仪表控件
//// NotifyMeter(screen_id,control_id,value);
//// break;
// case kCtrlMenu: //菜单控件
// NotifyMenu(screen_id,control_id,msg->param[0],msg->param[1]);
// break;
// case kCtrlSelector: //选择控件
// NotifySelector(screen_id,control_id,msg->param[0]);
// break;
case kCtrlRTC: //倒计时控件
NotifyTimer(screen_id, control_id);
break;
case 0X1D:
printf("记录控件\r\n");
NotifyRecord(ctrl_msg, screen_id, control_id, msg->A[8], msg->A[9], msg->S.param);
break;
default:
break;
}
}
break;
}
case NOTIFY_HandShake: //握手通知
// NOTIFYHandShake();
break;
default:
break;
}
}
3、提取指令
cpp
/*********************************************************
函数功能:提取指令
入口参数:
返 回:无。
备 注:目前是提取一条指令后续可考虑实现多条指令的提取。
作 者:Roy
时 间:2026-06-28
*********************************************************/
uint8_t HMI_FindCommand(RingBuffer *R_Buffer,uint8_t*Cmd,uint8_t *length )
{
char Data;
while(RingBuffer_GetSize(R_Buffer)>0)
{
//取一个数据
RingBuffer_Pop(R_Buffer,&Data);
if((CMD_Pos==0)&&((unsigned char)Data!=CMD_HEAD)) //指令第一个字节必须是帧头,否则跳过
{
continue;
}
// if(CMD_Pos<buf_len) //防止缓冲区溢出
Cmd[CMD_Pos++] = Data;
CMD_End = ((CMD_End<<8)|Data); //拼接最后4个字节,组成一个32位整数
printf("%02X ",Data); // 打印接收到的数据
//最后4个字节与帧尾匹配,得到完整帧
if(CMD_End==CMD_TAIL)
{
*length = CMD_Pos; //指令字节长度
CMD_End = 0; //重新检测帧尾巴
CMD_Pos = 0; //复位指令指针
printf("得到完整一条指令长度;%d;",*length);
#if(CRC16_ENABLE)
//去掉指令头尾EE,尾FFFCFFFF共计5个字节,只计算数据部分CRC
if(!CheckCRC16(Cmd+1,*length-5)) //CRC校验
return 0;
*length -= 2; //去掉CRC16(2字节)
#endif
return *length;
}
}
}
4、解析指令
cpp
void HMI_Run_Command()
{
static uint8_t CMD_len;
static uint16_t Rx_Cur;
static uint16_t Rx_Pre;
#ifdef TANK_A
while ( RingBuffer_GetSize(&TankA_HMIBuffer)> 0) //如果接收缓存区有数据就判断提取指令
{
Rx_Pre=RingBuffer_GetSize(&TankA_HMIBuffer);
delay_ms(20); // 等待串口数据更新
Rx_Cur=RingBuffer_GetSize(&TankA_HMIBuffer);
if (Rx_Pre&& Rx_Cur) /* 接收到了数据,且接收完成了 */
{
if (HMI_FindCommand(&TankA_HMIBuffer,Command, &CMD_len) != 0) //判断是否接收到有效指令,每次提取一条指令
{
printf("解析指令:");
Command_Parser((Command_Union*)Command, CMD_len); //指令解析
//memset(Data,0,Data_len*sizeof(uint8_t));
memset(Command, 0, CMD_len *sizeof(uint8_t));
printf("\r\n");
}
}
else
{
break;
}
}
#endif
#ifdef TANK_B
while (RingBuffer_GetSize(&TankB_HMIBuffer) > 0) //如果接收缓存区有数据就判断提取指令
{
Rx_Pre=RingBuffer_GetSize(&TankB_HMIBuffer);
delay_ms(20); // 等待串口数据更新
Rx_Cur=RingBuffer_GetSize(&TankB_HMIBuffer);
if (Rx_Pre&& Rx_Cur) /* 接收到了数据,且接收完成了 */
{
if (HMI_FindCommand(&TankB_HMIBuffer,Command, &CMD_len) != 0) //判断是否接收到有效指令,每次提取一条指令
{
printf("解析指令:");
Command_Parser((Command_Union*)Command, CMD_len); //指令解析
//memset(Data,0,Data_len*sizeof(uint8_t));
memset(Command, 0, CMD_len *sizeof(uint8_t));
printf("\r\n");
}
}
}
#endif
}
三、测试

测试指令解析正常