串口实用的循环缓冲区

转载连接: http://blog.youkuaiyun.com/xz_wang/article/details/8703491


小记。

项目临时需要单片机进行节点控制,主要用来控制模块的开关,以串口进行通讯。

单片机几多久没玩了,选用的是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,根据命令,执行相应的操作。


HAL库中的串口循环缓冲区(Circular Buffer)是一种用于高效管理数据输入输出的技术,广泛应用于嵌入式系统中。通过使用这种技术可以避免频繁的数据拷贝操作,并有效提高系统的实时性和效率。 ### 1. **工作原理** - 循环缓冲区是一个固定大小的数组,在读取和写入过程中指针会不断向前移动并在达到末端时自动回到起始位置。 - 它包含两个重要指针:`Head(头)` 和 `Tail(尾)`: - 当向缓冲区添加新元素时更新头部指针; - 当从缓冲区移除旧元素时则调整尾部指针。 - 如果头赶上尾巴,则表示缓存已满;若两者相等,则意味着此时为空状态。 ### 2. **在STM32 HAL库的应用** 使用HAL库配置串口通信时,默认情况下不会开启循环接收功能。为了启用这一特性,您需要手动设置相应的中断服务函数以及初始化好对应的硬件资源。以下是基本步骤: - 配置NVIC优先级组并使能USARTx全局中断(例如EXTI线、RXNE事件触发等) - 初始化DMA通道以支持非阻塞式的连续传输模式 - 编写自定义回调处理程序来同步访问共享变量 ```c // 示例代码片段 UART_HandleTypeDef huart1; uint8_t aRxBuffer[CIRCULAR_BUFFER_SIZE]; // 声明一个足够大的静态存储空间作为我们的缓冲池 void MX_USART1_UART_Init(void) { /* USART1 init function */ huart1.Instance = USART1; huart1.Init.BaudRate = 115200; // 波特率设定... huart1.Init.WordLength = UART_WORDLENGTH_8B; // 字长选择... huart1.Init.StopBits = UART_STOPBITS_1; // 停止位数量... huart1.Init.Parity = UART_PARITY_NONE; // 校验方式... huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_DeInit(&huart1) != HAL_OK){ Error_Handler(); } if(HAL_UART_Init(&huart1)!= HAL_OK){ Error_Handler(); } __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); // 开启接收中断 } ``` ### 3. **优点及应用场景** - **减少CPU占用**:由于采用的是异步机制,因此能够极大限度地减轻主控任务负担。 - **防止丢包现象发生**:特别是在高频率通讯场景下尤为明显。 - **简化编程模型**:对于开发者来说只需关注于业务逻辑本身而无需过多考虑底层细节。 --
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值