大彩科技是专注做串口屏的厂家,网址如下:
http://www.gz-dc.com/
指令格式如下:
一般情况下,采用的是CRC格式校验的指令。
处理指令方面,大彩提供了一个例程,主要用一个队列来维护。
数据结构:
1#define QUEUE_MAX_SIZE 128 /*!< 指令接收缓冲区大小,根据需要调整,尽量设置大一些*/
2typedef struct _QUEUE
3{
4 qsize _head; //队列头
5 qsize _tail; //队列尾
6 qdata _data[QUEUE_MAX_SIZE]; //队列数据缓存区
7}QUEUE;
8
9static QUEUE que = {0,0,0}; //指令队列
10static uint32 cmd_state = 0; //队列帧尾检测状态
11static qsize cmd_pos = 0; //当前指令指针位置
操作队列的接口有:
1/*!
2 * \brief 清空指令数据
3 */
4extern void queue_reset(void);
5
6/*!
7 * \brief 添加指令数据
8 * \detial 串口接收的数据,通过此函数放入指令队列
9 * \param _data 指令数据
10 */
11extern void queue_push(qdata _data);
12
13/*!
14 * \brief 从指令队列中取出一条完整的指令
15 * \param cmd 指令接收缓存区
16 * \param buf_len 指令接收缓存区大小
17 * \return 指令长度,0表示队列中无完整指令
18 */
19extern qsize queue_find_cmd(qdata *cmd,qsize buf_len);
队列清空的实现很简单,只要把队列头和队队列尾检查状态、当前指针的位置置为0即可,实现如下:
1void queue_reset()
2{
3 que._head = que._tail = 0;
4 cmd_pos = cmd_state = 0;
5}
添加指令数据操作,其实就是入队的操作,也就是把数据源源不断的放到队列的缓存区中去:
1void queue_push(qdata _data)
2{
3 qsize pos = (que._head+1)%QUEUE_MAX_SIZE;
4 if(pos!=que._tail)//非满状态
5 {
6 que._data[que._head] = _data;
7 que._head = pos;
8 }
9}
从指令队列中取出一条完整的指令其实就是出队操作,先将数据出队,然后根据指令格式帧进行分割处理。
1//从队列中取一个数据
2static void queue_pop(qdata* _data)
3{
4 if(que._tail!=que._head)//非空状态
5 {
6 *_data = que._data[que._tail];
7 que._tail = (que._tail+1)%QUEUE_MAX_SIZE;
8 }
9}
10
11qsize queue_find_cmd(qdata *buffer,qsize buf_len)
12{
13 qsize cmd_size = 0;
14 qdata _data = 0;
15 while(queue_size()>0)
16 {
17 //取一个数据
18 queue_pop(&_data);
19
20 if(cmd_pos==0&&_data!=CMD_HEAD)//指令第一个字节必须是帧头,否则跳过
21 continue;
22
23 if(cmd_pos<buf_len)//防止缓冲区溢出
24 buffer[cmd_pos++] = _data;
25
26 cmd_state = ((cmd_state<<8)|_data);//拼接最后4个字节,组成一个32位整数
27
28 //最后4个字节与帧尾匹配,得到完整帧
29 if(cmd_state==CMD_TAIL)
30 {
31 cmd_size = cmd_pos; //指令字节长度
32 cmd_state = 0; //重新检测帧尾巴
33 cmd_pos = 0; //复位指令指针
34
35#if(CRC16_ENABLE)
36 //去掉指令头尾EE,尾FFFCFFFF共计5个字节,只计算数据部分CRC
37 if(!CheckCRC16(buffer+1,cmd_size-5))//CRC校验
38 return 0;
39
40 cmd_size -= 2;//去掉CRC16(2字节)
41#endif
42
43 return cmd_size;
44 }
45 }
46
47 return 0;//没有形成完整的一帧
48}
那么具体在哪里入队呢?在大彩提供的例程中,入队操作是在串口中断服务函数中进行的:
1void USART1_IRQHandler(void)
2{
3 if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
4 {
5 uint8_t data = USART_ReceiveData(USART1);
6 queue_push(data);
7 }
8}
在这期间主要发生两个操作:
1、串口通过中断接收一个字节
2、将接收到的每一个字节放入队列缓存区中
那么又具体怎么知道串口屏给我回复的指令呢,然后发生一系列动作呢?
这时候,程序里需要有一个while(1),源源不断的等待queue_find_cmd函数给我们做取数据,完成拼接指令的过程。
1....
2while(1)
3{
4 size = queue_find_cmd(cmd_buffer,CMD_MAX_SIZE); //从缓冲区中获取一条指令
5 if(size>0)//接收到指令
6 {
7 ProcessMessage((PCTRL_MSG)cmd_buffer, size);//指令处理
8 }
9}
10....
cmd_buffer在这里就是一条完整的指令,再将这条完整的指令传入ProcessMessage函数,对指令进行处理,其中将数据强转为PCTRL_MSG这个数据结构,主要为:
1typedef struct
2{
3 uint8 cmd_head; //帧头
4
5 uint8 cmd_type; //命令类型(UPDATE_CONTROL)
6 uint8 ctrl_msg; //CtrlMsgType-指示消息的类型
7 uint8 screen_id_high; //产生消息的画面ID
8 uint8 screen_id_low;
9 uint8 control_id_high; //产生消息的控件ID
10 uint8 control_id_low;
11 uint8 control_type; //控件类型
12
13 uint8 param[256];//可变长度参数,最多256个字节
14
15 uint8 cmd_tail[4]; //帧尾
16}CTRL_MSG,*PCTRL_MSG;
在这里接收到的cmd_buffer里的指令是把头尾去掉的,这时候我们明白了,接收过来的指令需要赋给它一定的含义,于是看ProcessMessage函数的实现:
1/*!
2 * \brief 消息处理流程,此处一般不需要更改
3 * \param msg 待处理消息
4 * \param size 消息长度
5 */
6void ProcessMessage( PCTRL_MSG msg, uint16 size )
7{
8 uint8 cmd_type = msg->cmd_type;//指令类型
9 uint8 ctrl_msg = msg->ctrl_msg; //消息的类型
10 uint8 control_type = msg->control_type;//控件类型
11 uint16 screen_id = PTR2U16(&msg->screen_id_high);//画面ID
12 uint16 control_id = PTR2U16(&msg->control_id_high);//控件ID
13 uint32 value = PTR2U32(msg->param);//数值
14
15 switch(cmd_type)
16 {
17 case NOTIFY_TOUCH_PRESS://触摸屏按下
18 case NOTIFY_TOUCH_RELEASE://触摸屏松开
19 NotifyTouchXY(cmd_buffer[1],PTR2U16(cmd_buffer+2),PTR2U16(cmd_buffer+4));
20 break;
21 case NOTIFY_WRITE_FLASH_OK://写FLASH成功
22 NotifyWriteFlash(1);
23 break;
24 case NOTIFY_WRITE_FLASH_FAILD://写FLASH失败
25 NotifyWriteFlash(0);
26 break;
27 case NOTIFY_READ_FLASH_OK://读取FLASH成功
28 NotifyReadFlash(1,cmd_buffer+2,size-6);//去除帧头帧尾
29 break;
30 case NOTIFY_READ_FLASH_FAILD://读取FLASH失败
31 NotifyReadFlash(0,0,0);
32 break;
33 case NOTIFY_READ_RTC://读取RTC时间
34 NotifyReadRTC(cmd_buffer[1],cmd_buffer[2],cmd_buffer[3],cmd_buffer[4],cmd_buffer[5],cmd_buffer[6],cmd_buffer[7]);
35 break;
36 case NOTIFY_CONTROL:
37 {
38 if(ctrl_msg==MSG_GET_CURRENT_SCREEN)//画面ID变化通知
39 {
40 NotifyScreen(screen_id);
41 }
42 else
43 {
44 switch(control_type)
45 {
46 case kCtrlButton: //按钮控件
47 NotifyButton(screen_id,control_id,msg->param[1]);
48 break;
49 case kCtrlText://文本控件
50 NotifyText(screen_id,control_id,msg->param);
51 break;
52 case kCtrlProgress: //进度条控件
53 NotifyProgress(screen_id,control_id,value);
54 break;
55 case kCtrlSlider: //滑动条控件
56 NotifySlider(screen_id,control_id,value);
57 break;
58 case kCtrlMeter: //仪表控件
59 NotifyMeter(screen_id,control_id,value);
60 break;
61 case kCtrlMenu://菜单控件
62 NotifyMenu(screen_id,control_id,msg->param[0],msg->param[1]);
63 break;
64 case kCtrlSelector://选择控件
65 NotifySelector(screen_id,control_id,msg->param[0]);
66 break;
67 case kCtrlRTC://倒计时控件
68 NotifyTimer(screen_id,control_id);
69 break;
70 default:
71 break;
72 }
73 }
74 }
75 break;
76 default:
77 break;
78 }
79}
这里学习到了一个编程的小技巧,将数据强转为一个结构体,再利用结构体的偏移特性来获得数据。 这个函数的作用就显而易见了,通过一条指令得知当前使用的是什么控件等等。。。
发送指令就很简单了,其实就是直接给串口发数据,这里是实现如何发送数据给串口的定义:
1#define TX_8(P1) SEND_DATA((P1)&0xFF) //发送单个字节
2#define TX_8N(P,N) SendNU8((uint8 *)P,N) //发送N个字节
3#define TX_16(P1) TX_8((P1)>>8);TX_8(P1) //发送16位整数
4#define TX_16N(P,N) SendNU16((uint16 *)P,N) //发送N个16位整数
5#define TX_32(P1) TX_16((P1)>>16);TX_16((P1)&0xFFFF) //发送32位整数
这里是参考手册发送的指令:
1#if(CRC16_ENABLE)
2
3static uint16 _crc16 = 0xffff;
4static void AddCRC16(uint8 *buffer,uint16 n,uint16 *pcrc)
5{
6 uint16 i,j,carry_flag,a;
7
8 for (i=0; i<n; i++)
9 {
10 *pcrc=*pcrc^buffer[i];
11 for (j=0; j<8; j++)
12 {
13 a=*pcrc;
14 carry_flag=a&0x0001;
15 *pcrc=*pcrc>>1;
16 if (carry_flag==1)
17 *pcrc=*pcrc^0xa001;
18 }
19 }
20}
21
22uint16 CheckCRC16(uint8 *buffer,uint16 n)
23{
24 uint16 crc0 = 0x0;
25 uint16 crc1 = 0xffff;
26
27 if(n>=2)
28 {
29 crc0 = ((buffer[n-2]<<8)|buffer[n-1]);
30 AddCRC16(buffer,n-2,&crc1);
31 }
32
33 return (crc0==crc1);
34}
35
36void SEND_DATA(uint8 c)
37{
38 AddCRC16(&c,1,&_crc16);
39 SendChar(c);
40}
41
42void BEGIN_CMD()
43{
44 TX_8(0XEE);
45 _crc16 = 0XFFFF;//开始计算CRC16
46}
47
48void END_CMD()
49{
50 uint16 crc16 = _crc16;
51 TX_16(crc16);//发送CRC16
52 TX_32(0XFFFCFFFF);
53}
54
55#else//NO CRC16
56
57#define SEND_DATA(P) SendChar(P)
58#define BEGIN_CMD() TX_8(0XEE)
59#define END_CMD() TX_32(0XFFFCFFFF)
60
61#endif
62
63void DelayMS(unsigned int n)
64{
65 int i,j;
66 for(i = n;i>0;i--)
67 for(j=1000;j>0;j--) ;
68}
69
70void SendStrings(uchar *str)
71{
72 while(*str)
73 {
74 TX_8(*str);
75 str++;
76 }
77}
78
79void SendNU8(uint8 *pData,uint16 nDataLen)
80{
81 uint16 i = 0;
82 for (;i<nDataLen;++i)
83 {
84 TX_8(pData[i]);
85 }
86}
87
88void SendNU16(uint16 *pData,uint16 nDataLen)
89{
90 uint16 i = 0;
91 for (;i<nDataLen;++i)
92 {
93 TX_16(pData[i]);
94 }
95}