STM32使用大彩串口屏程序框架使用总结

 

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

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<buf_len)//防止缓冲区溢出
			buffer[cmd_pos++] = _data;

		cmd_state = ((cmd_state<<8)|_data);//拼接最后4个字节,组成一个32位整数

		//最后4个字节与帧尾匹配,得到完整帧
		if(cmd_state==CMD_TAIL)
		{
			cmd_size = cmd_pos; //指令字节长度
			cmd_state = 0;  //重新检测帧尾巴
			cmd_pos = 0; //复位指令指针

#if(CRC16_ENABLE)
			//去掉指令头尾EE,尾FFFCFFFF共计5个字节,只计算数据部分CRC
			if(!CheckCRC16(buffer+1,cmd_size-5))//CRC校验
				return 0;

			cmd_size -= 2;//去掉CRC16(2字节)
#endif

			return cmd_size;
		}
	}

	return 0;//没有形成完整的一帧
}

那么具体在哪里入队呢?在大彩提供的例程中,入队操作是在串口中断服务函数中进行的:

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<n; i++)
	{
		*pcrc=*pcrc^buffer[i];
		for (j=0; j<8; j++)
		{
			a=*pcrc;
			carry_flag=a&0x0001;
			*pcrc=*pcrc>>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<nDataLen;++i)
	{
		TX_8(pData[i]);
	}
}

void SendNU16(uint16 *pData,uint16 nDataLen)
{
	uint16 i = 0;
	for (;i<nDataLen;++i)
	{
		TX_16(pData[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();
}

 

Engineer-Bruce_Yang CSDN认证博客专家 嵌入式硬件 单片机 arm开发
本科毕业于华南理工大学,现美国卡罗尔工商管理硕士研究生在读,曾就职于世界名企伟易达、联发科技等,多年嵌入式产品开发经验,在智能玩具、安防产品、平板电脑、手机开发有丰富的实战开发经验,现任深圳市云之手科技有限公司副总经理、研发总工程师。
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页