小记。
项目临时需要单片机进行节点控制,主要用来控制模块的开关,以串口进行通讯。
单片机几多久没玩了,选用的是C8051F920,传说中增强型51,不过看了Datesheet.
还是51而已。。无难度,项目要求主要是功耗的问题,5年内只能更换一次电池。
蛀牙用到模块是定时器,几个GPIO,smaRTClock,串口。
主要在通讯协议这部分花的时间较多,串口接收采用循环缓冲区的方式,以FIFO方式进行读写
串口的缓冲区需要两个一个接收,一个发送,通讯协议中规定数据长度在16字节,就把缓冲区
大小定为64字节
#defineUART0_BUF_SIZE 256
static uchar data UART0_Rx_head; //OUT
static uchar data UART0_Rx_tail; //IN
static uchar data UART0_Tx_head; //IN
static uchar data UART0_Tx_tail; //OUT
串口采用中断方式:
if(RI0 == 1)
{
RI0= 0; // Clear interrupt flag
tmp_data = SBUF0;
if(((UART0_Rx_In - UART0_Rx_Out) & ~(UART0_RX_BUFF_SIZE - 1)) == 0){
UART0_RxBuffer[UART0_Rx_In & (UART0_RX_BUFF_SIZE - 1)] =tmp_data;
UART0_Rx_In++;
}
}
if(TI0 == 1) // Check if transmit flag is set
{
TI0= 0; // Clear interrupt flag
if (UART0_Tx_In != UART0_Tx_Out) {
SBUF0 = UART0_TxBuffer[UART0_Tx_Out & (UART0_TX_BUFF_SIZE -1)];
UART0_Tx_Out++;
tx_restart_uart0= 0;
}
else tx_restart_uart0 = 1;
}
这里:
UART0_Rx_In&(UART0_RX_BUFF_SIZE - 1),相当于UART0_Rx_In% UART0_RX_BUFF_SIZE,对IN取模
结果相当于映射到对应的缓冲区位置,比如这里IN的值是0~255而buffer大小只有64字节,即
IN的值对应四段缓冲区,064 128 192都对应缓冲区的起始地址。
((UART0_Rx_In- UART0_Rx_Out) & ~(UART0_RX_BUFF_SIZE - 1)) == 0 //这是判断缓冲区是否满
因为IN始终在OUT的前面(当IN=OUT时缓冲区为空,所以OUT不可能在IN前面),即便是IN=5
OUT=240时,IN<OUT,但IN也在前面,IN- OUT = -235 =21,这里IN和OUT是无符号字符型,相减也
为无符号型。故此时缓冲区有21字节数据。UART0_Rx_In++;IN指向的节点内容是空的,当前值应
该在Buffer[IN- 1]
同理:
发送中断产生时,首先判断缓冲区是否为空,为空可以直接发送,否则,继续发送缓冲区数据
怎样才能产生发送中断呢,
1.可以先将数据写入发送缓冲区,然后手动置TI0= 1.产生中断,使中断函数自动发送缓冲区数据。
2.发送数据前判断串口有无数据待发送(包含正在发送和缓冲区数据),没有则直接发送,有则将
数据继续加入缓冲区
uint8put_char_uart0(uint8 Data)
{
if(UART0_TX_BUFF_LEN >= UART0_TX_BUFF_SIZE)
return(-1);
if(tx_restart_uart0)
{
tx_restart_uart0= 0;// ==0 ,串口正在发送
SBUF0= Data;
}
else
{
UART0_TxBuffer[UART0_Tx_In& (UART0_TX_BUFF_SIZE - 1)] = Data;//数据放入发送BUF
UART0_Tx_In++;
}
return(0);
}
如果要连续发送一个协议包,只需调用put_char_uart0(),一次写入一个字节
从缓冲区读写数据,这里有个策略问题,读数据是有数据就读,还是只有当有一个完成的数据包才读。
写数据有足够空间才写,还是有空间才写。
基于串口的特性,采用读数据时从OUT端点开始向后scan,浏览到数据头才开始组包。
写数据,只有当缓冲区大小能够装下数据时,才一次写入。
缓冲区的情况分为这几种:
0 size
OUT IN
BUFFER size
空闲大小为:SIZE – IN + OUT (这里为0)
数据大小: IN– OUT
0 OUT IN size
B
A B C
先来看看linux内核怎么写循环缓冲区的:
len= min(len, fifo->size - fifo->in + fifo->out);
//len是要发送的数据长度,size为缓冲区大小,这里IN/OUT始终在size范围内,这里的结果是空闲的数据区大小,结果len为一次可以写的大小
/*first put the data starting from fifo->in to buffer end */
l= min(len, fifo->size - (fifo->in & (fifo->size - 1)));
//这里的结果为本次循环能写的大小,如图C区
memcpy(fifo->buffer+ (fifo->in & (fifo->size - 1)), buffer, l);
//先写l大小,写到缓冲区的最后一个字节,
/*then put the rest (if any) at the beginning of the buffer */
memcpy(fifo->buffer,buffer + l, len - l);
//缓冲区满在写到缓冲区头部A区,如果已写满,这里写的0字节
fifo->in+= len;
//in加上已写长度,可能溢出计数
//若要使in/out的值始终小于size:
//fifo->in= (fifo->in +len) % fifo->size;这个上面指向的内容是一样的
returnlen;//这里返回上层,通知应用程序写了多少字节。
linux内核读缓冲区示例:
unsignedint l;
len= min(len, fifo->in - fifo->out);
//可读数据长度
/*first get the data from fifo->out until the end of the buffer */
l= min(len, fifo->size - (fifo->out & (fifo->size - 1)));
//结果是从OUT到BUF尾部的数据长度,教IN/OUT交换后如C区
memcpy(buffer,fifo->buffer + (fifo->out & (fifo->size - 1)), l);
//首先读取从OUT到SIZE这部分
/*then get the rest (if any) from the beginning of the buffer */
memcpy(buffer+ l, fifo->buffer, len - l);
//若未读取完,在读取前面的一部分A区
fifo->out+= len;
//OUT端点加上读取长度
returnlen;
数据的检索:
包头的搜索:包头通常位于一个有效数据包的前端,当缓冲区数据大小大于最小包长时,就开始读取包头,每读取一次包头,缓冲区指针+1,直到不可读(小于最小包长)。
由于这样,不是包头的内容就会丢弃,故包头的标识应该为唯一的!这适用与简单的数据协议,不牵涉文件的传输。
我们来看看协议的一般处理流程:
数据处理的起始条件是:缓冲区中的有效数据长度大于或者等于最小包长。
1,搜索包:如果成功,转到2。失败则丢弃一个字节,继续搜索。
2,包长度的检查:scan出长度域,通过协议中可能出现的最大和最小包长检查,如果正常,则转到3,否则丢弃一个字节,转到1。
3,命令的检查:scan出帧号,检查帧号是否为有效的帧号,有效,则转到4,否则,丢弃一个字节,转到1。
4,校验和的检查:scan出长度后的数据域和校验域,检查校验和是否正确,错误则丢弃一个字节,转到1。如果正确,则读取这一完整的命令,取出命令和数据。转到5。
5,根据命令,执行相应的操作。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)