大彩科技是专注做串口屏的厂家,网址如下:
http://www.gz-dc.com/
指令格式如下:
一般情况下,采用的是CRC格式校验的指令。
处理指令方面,大彩提供了一个例程,主要用一个队列来维护。
数据结构:
#define QUEUE_MAX_SIZE 128 /*!< 指令接收缓冲区大小,根据需要调整,尽量设置大一些*/
typedef struct _QUEUE
{
qsize _head; //队列头
qsize _tail; //队列尾
qdata _data[QUEUE_MAX_SIZE]; //队列数据缓存区
}QUEUE;
static QUEUE que = {0,0,0}; //指令队列
static uint32 cmd_state = 0; //队列帧尾检测状态
static qsize cmd_pos = 0; //当前指令指针位置
操作队列的接口有:
/*!
* \brief 清空指令数据
*/
extern void queue_reset(void);
/*!
* \brief 添加指令数据
* \detial 串口接收的数据,通过此函数放入指令队列
* \param _data 指令数据
*/
extern void queue_push(qdata _data);
/*!
* \brief 从指令队列中取出一条完整的指令
* \param cmd 指令接收缓存区
* \param buf_len 指令接收缓存区大小
* \return 指令长度,0表示队列中无完整指令
*/
extern qsize queue_find_cmd(qdata *cmd,qsize buf_len);
队列清空的实现很简单,只要把队列头和队队列尾检查状态、当前指针的位置置为0即可,实现如下:
void queue_reset()
{
que._head = que._tail = 0;
cmd_pos = cmd_state = 0;
}
添加指令数据操作,其实就是入队的操作,也就是把数据源源不断的放到队列的缓存区中去:
void queue_push(qdata _data)
{
qsize pos = (que._head+1)%QUEUE_MAX_SIZE;
if(pos!=que._tail)//非满状态
{
que._data[que._head] = _data;
que._head = pos;
}
}
从指令队列中取出一条完整的指令其实就是出队操作,先将数据出队,然后根据指令格式帧进行分割处理。
//从队列中取一个数据
static void queue_pop(qdata* _data)
{
if(que._tail!=que._head)//非空状态
{
*_data = que._data[que._tail];
que._tail = (que._tail+1)%QUEUE_MAX_SIZE;
}
}
qsize queue_find_cmd(qdata *buffer,qsize buf_len)
{
qsize cmd_size = 0;
qdata _data = 0;
while(queue_size()>0)
{
//取一个数据
queue_pop(&_data);
if(cmd_pos==0&&_data!=CMD_HEAD)//指令第一个字节必须是帧头,否则跳过
continue;
if(cmd_pos
那么具体在哪里入队呢?在大彩提供的例程中,入队操作是在串口中断服务函数中进行的:
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
uint8_t data = USART_ReceiveData(USART1);
queue_push(data);
}
}
在这期间主要发生两个操作:
1、串口通过中断接收一个字节
2、将接收到的每一个字节放入队列缓存区中
那么又具体怎么知道串口屏给我回复的指令呢,然后发生一系列动作呢?
这时候,程序里需要有一个while(1),源源不断的等待queue_find_cmd函数给我们做取数据,完成拼接指令的过程。
....
while(1)
{
size = queue_find_cmd(cmd_buffer,CMD_MAX_SIZE); //从缓冲区中获取一条指令
if(size>0)//接收到指令
{
ProcessMessage((PCTRL_MSG)cmd_buffer, size);//指令处理
}
}
....
cmd_buffer在这里就是一条完整的指令,再将这条完整的指令传入ProcessMessage函数,对指令进行处理,其中将数据强转为PCTRL_MSG这个数据结构,主要为:
typedef struct
{
uint8 cmd_head; //帧头
uint8 cmd_type; //命令类型(UPDATE_CONTROL)
uint8 ctrl_msg; //CtrlMsgType-指示消息的类型
uint8 screen_id_high; //产生消息的画面ID
uint8 screen_id_low;
uint8 control_id_high; //产生消息的控件ID
uint8 control_id_low;
uint8 control_type; //控件类型
uint8 param[256];//可变长度参数,最多256个字节
uint8 cmd_tail[4]; //帧尾
}CTRL_MSG,*PCTRL_MSG;
在这里接收到的cmd_buffer里的指令是把头尾去掉的,这时候我们明白了,接收过来的指令需要赋给它一定的含义,于是看ProcessMessage函数的实现:
/*!
* \brief 消息处理流程,此处一般不需要更改
* \param msg 待处理消息
* \param size 消息长度
*/
void ProcessMessage( PCTRL_MSG msg, uint16 size )
{
uint8 cmd_type = msg->cmd_type;//指令类型
uint8 ctrl_msg = msg->ctrl_msg; //消息的类型
uint8 control_type = msg->control_type;//控件类型
uint16 screen_id = PTR2U16(&msg->screen_id_high);//画面ID
uint16 control_id = PTR2U16(&msg->control_id_high);//控件ID
uint32 value = PTR2U32(msg->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(cmd_buffer[1],cmd_buffer[2],cmd_buffer[3],cmd_buffer[4],cmd_buffer[5],cmd_buffer[6],cmd_buffer[7]);
break;
case NOTIFY_CONTROL:
{
if(ctrl_msg==MSG_GET_CURRENT_SCREEN)//画面ID变化通知
{
NotifyScreen(screen_id);
}
else
{
switch(control_type)
{
case kCtrlButton: //按钮控件
NotifyButton(screen_id,control_id,msg->param[1]);
break;
case kCtrlText://文本控件
NotifyText(screen_id,control_id,msg->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;
default:
break;
}
}
}
break;
default:
break;
}
}
这里学习到了一个编程的小技巧,将数据强转为一个结构体,再利用结构体的偏移特性来获得数据。
这个函数的作用就显而易见了,通过一条指令得知当前使用的是什么控件等等。。。
发送指令就很简单了,其实就是直接给串口发数据:
#define TX_8(P1) SEND_DATA((P1)&0xFF) //发送单个字节
#define TX_8N(P,N) SendNU8((uint8 *)P,N) //发送N个字节
#define TX_16(P1) TX_8((P1)>>8);TX_8(P1) //发送16位整数
#define TX_16N(P,N) SendNU16((uint16 *)P,N) //发送N个16位整数
#define TX_32(P1) TX_16((P1)>>16);TX_16((P1)&0xFFFF) //发送32位整数
#if(CRC16_ENABLE)
static uint16 _crc16 = 0xffff;
static void AddCRC16(uint8 *buffer,uint16 n,uint16 *pcrc)
{
uint16 i,j,carry_flag,a;
for (i=0; i>1;
if (carry_flag==1)
*pcrc=*pcrc^0xa001;
}
}
}
uint16 CheckCRC16(uint8 *buffer,uint16 n)
{
uint16 crc0 = 0x0;
uint16 crc1 = 0xffff;
if(n>=2)
{
crc0 = ((buffer[n-2]<<8)|buffer[n-1]);
AddCRC16(buffer,n-2,&crc1);
}
return (crc0==crc1);
}
void SEND_DATA(uint8 c)
{
AddCRC16(&c,1,&_crc16);
SendChar(c);
}
void BEGIN_CMD()
{
TX_8(0XEE);
_crc16 = 0XFFFF;//开始计算CRC16
}
void END_CMD()
{
uint16 crc16 = _crc16;
TX_16(crc16);//发送CRC16
TX_32(0XFFFCFFFF);
}
#else//NO CRC16
#define SEND_DATA(P) SendChar(P)
#define BEGIN_CMD() TX_8(0XEE)
#define END_CMD() TX_32(0XFFFCFFFF)
#endif
void DelayMS(unsigned int n)
{
int i,j;
for(i = n;i>0;i--)
for(j=1000;j>0;j--) ;
}
void SendStrings(uchar *str)
{
while(*str)
{
TX_8(*str);
str++;
}
}
void SendNU8(uint8 *pData,uint16 nDataLen)
{
uint16 i = 0;
for (;i
具体发送的指令(参大彩提供的串口屏指令手册)
void SetProgressValue(uint16 screen_id,uint16 control_id,uint32 value)
{
BEGIN_CMD();
TX_8(0xB1);
TX_8(0x10);
TX_16(screen_id);
TX_16(control_id);
TX_32(value);
END_CMD();
}
void SetMeterValue(uint16 screen_id,uint16 control_id,uint32 value)
{
BEGIN_CMD();
TX_8(0xB1);
TX_8(0x10);
TX_16(screen_id);
TX_16(control_id);
TX_32(value);
END_CMD();
}
void SetSliderValue(uint16 screen_id,uint16 control_id,uint32 value)
{
BEGIN_CMD();
TX_8(0xB1);
TX_8(0x10);
TX_16(screen_id);
TX_16(control_id);
TX_32(value);
END_CMD();
}
void SetSelectorValue(uint16 screen_id,uint16 control_id,uint8 item)
{
BEGIN_CMD();
TX_8(0xB1);
TX_8(0x10);
TX_16(screen_id);
TX_16(control_id);
TX_8(item);
END_CMD();
}