当前位置:首页 > 公众号精选 > 嵌入式云IOT技术圈
[导读]超详细总结,值得一看!


微信公众号:morixinguan
关注可了解更多的教程。问题或建议,请公众号留言;
如果你觉得本文对你有帮助,欢迎赞赏


▲长按图片保存可分享至朋友圈


大彩科技是专注做串口屏的厂家,网址如下:

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//防止缓冲区溢出 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 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 (;i83 { 84 TX_8(pData[i]); 85 } 86} 87 88void SendNU16(uint16 *pData,uint16 nDataLen) 89{ 90 uint16 i = 0; 91 for (;i92 { 93 TX_16(pData[i]); 94 } 95}


长期商务合作服务:



免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

串行通信需要传输的数据通过调制器(Modulator)将数据转换为模拟信号,经过信号调制(Modulation)后在传输线上传输,接收端通过解调器(Demodulator)将信号解码还原成原始数据。

关键字: 串口 串行通信 并行通信

51 单片机内部有一个全双工串行接口。什么叫全双工串口呢?一般来说,只能接受或只能发送的称为单工串行;既可接收又可发送,但不能同时进行的称为半双工;能同时接收和发送的串行口称为全双工串行口。串行通信是指数据一位一位地按顺...

关键字: 单片机 全双工 串口

串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合...

关键字: 串口 RS232 同步传输

串口作为单片机开发的一个常用的外设,应用范围非常广。大部分时候,串口需要接收处理的数据长度是不定的。那么怎么才能判断一帧数据是否结束呢,今天就以STM32单片机为例,介绍几种接收不定长数据的方法。

关键字: 单片机 串口 STM32

这是FPGA之旅设计的第十例啦,在上一例中,已经成功驱动了OLED屏幕,本例将结合上一例,以及第四例多bytes串口通信做一个有趣的例程。

关键字: FPGA OLED屏 串口

接下来测试烧写功能,本次采用串口和USB烧写方式。使用ISP串口烧写这是51单片机常用的方案,本次测试比较顺利,没有遇到什么问题。但是USB烧写没有测试成功,USB烧写方式不需要任何的驱动和硬件支持,直接将USB线和ST...

关键字: PCB控制板 USB 串口

摘要:多功能电能表在配电系统中应用广泛,其计量的准确度对企业管理和考核至关重要,因此在设计多功能电能表时需要对其进行校准,满足一定应用等级。常规的多功能电能表校准方法是以电能脉冲校准为主,现提出一种基于C#和功率校表法的...

关键字: 多功能电能表 串口 波特率

摘 要:为了能通过串口采集电能参数,完成一种基于串口的三相电能采集设备的研制,设计了电能采集设备的硬件和软件部分。其中硬件采用MCU+专用电能计量芯片的结构,结构简单;软件则用于实现输入、输出、三相电能参数的采集和串行通...

关键字: 电能采集 ATT7022B MSP430 串口

如何确定时基假如要测量的波特率为9600,则每一比特位的时间为:1/9600≈104μs,一般示波器横向上每个大格子里5个小格子,要想看清一比特位一般需要一个小格子就够了,则时基为:104μs*5=520μs,也就是说时...

关键字: 串口

VivadoML最新版2021下载方法:《安装Vivado2021.1ML版,编译时间真的会减少吗?》今天我们通过zedboard串口使用的实例来简单介绍vivado和vitis的使用步骤。1,首先打开软件,新建一个空白...

关键字: 串口 zedboard vi
关闭
关闭