问题描述
在串口接收中断中调用ring\_buf\_push()函数向环形数组ring\_buf写入数据,在while(1)循环中调用ring\_buf\_pop()从环形数组ring\_buf读取数据。通过串口每隔10ms大量写入数据时,然后停止发送数据,此时环形数组ring\_buf中in\_point和out\_point成员不相等,out\_point总是在in\_point成员的后面一个数据单元内(即in\_point总是指向out_point的前面一个数据单元)。
代码
头文件ring\_buf.h和源文件ring\_buf.c代码如下
/**
* @file ring_buf.h
*/
#ifndef RING_BUF_H
#define RING_BUF_H
#ifdef __cplusplus
extern "C"
{
#endif
#define RINGBUF_SIZE 200
typedef struct
{
uint8_t buf[RINGBUF_SIZE];
uint32_t in_point;
uint32_t out_point;
uint32_t length;
uint8_t full_flag;
}RING_BUF_T;
void ring_buf_init(void);
uint16_t get_ring_buf_len(void);
uint8_t ring_buf_pop(void);
uint8_t ring_buf_push(uint8_t byte);
uint8_t read_ring_buf(uint8_t * dst,uint32_t size);
uint8_t write_ring_buf(uint8_t * buf,uint8_t size);
#ifdef __cplusplus
}
#endif
#endif // RING_BUF_H
/**
* @file ring_buf.c
*/
#include "type_define.h"
#include "ring_buf.h"
#include "stm32l0xx_hal.h"
RING_BUF_T ring_buf;
/**
* @brief
*
*/
void ring_buf_init(void)
{
uint32_t loop = 0;
ring_buf.in_point = 0;
ring_buf.out_point = 0;
ring_buf.length = 0;
ring_buf.full_flag = 0;
for(loop = 0;loop < RINGBUF_SIZE;loop ++)
{
ring_buf.buf[loop] = 0;
}
}
/**
* @brief Get the uart buf len object
*
* @return uint16_t
*/
uint16_t get_ring_buf_len(void)
{
return ring_buf.length;
}
uint8_t ring_buf_push(uint8_t byte)
{
if(1 == ring_buf.full_flag)
{
return RET_FAILED;
}
else
{
ring_buf.buf[ring_buf.in_point] = byte;
ring_buf.in_point ++;
ring_buf.length ++;
if(ring_buf.in_point == RINGBUF_SIZE)
{
ring_buf.in_point = 0;
}
if(ring_buf.length == RINGBUF_SIZE)
{
ring_buf.full_flag = 1;
}
}
return 1;
}
/**
* @brief
*
* @return uint8_t
*/
uint8_t ring_buf_pop(void)
{
uint8_t data = 0;
if(ring_buf.length)
{
data = ring_buf.buf[ring_buf.out_point ++];
ring_buf.length --;
if(ring_buf.out_point == RINGBUF_SIZE)
{
ring_buf.out_point = 0;
}
ring_buf.full_flag = 0;
}
return data;
}
问题分析
由上述代码段可以看到ring\_buf\_pop()在执行的过程中未屏蔽中断,故在该函数执行时可被中断打断,即在ring\_buf\_pop()函数的任何位置ring\_buf\_push()函数都有可能执行(由于ring\_buf\_push()函数在中断接收函数中执行)。
图1为ring\_buf\_pop()函数中ring\_buf.length --;操作的汇编代码,图2为ring\_buf\_push()函数中ring\_buf.length ++;操作的汇编代码。
图1 ring\_buf\_pop函数执行汇编

图2 ring\_buf\_push函数执行汇编
由图,假设ring_buf.length值为2。
在图1ring_buf_pop()执行完LDR R5,[R3]串口产生了中断,则R5寄存器和的值为2,由于还未执行减操作,故ring_buf.length值仍为2。中断后程序会调到ring_buf_push()函数处执行,当执行ring_buf.length ++;时,由于ring_buf.length的值仍为2,故执行后ring\_buf.length为3,该语句执行结束后退到SUBS R5,R5,#1语句处继续执行。此时R5寄存器的值为1,然后将1写入到ring_buf.length地址处,此时ring\_buf.length的值变为1。
若不产生中断,正常的执行流程,先执行ring_buf.length -- 然后执行ring_buf.length ++,则最终的结果应为2,故若在ring_buf.length --执行过程中产生中断会导致长度数据异常。
而读数据时,写入数据时in\_point等于实际的写入数量即3,而读数据时采用ring\_buf.length来判断实际需要读取的数据量,故out\_point由于被中断打断实际能读取到的数据只有2个,故out\_point会永远读取不到最后一个数据单元的数据。
解决方法
- 可以在执行
ring_buf_pop()时关闭串口中断,此处可能会导致串口接收数据丢失。 - 读取数据时,实际数据长度不以ring_buf.length为计数量,而采用
((in_point+RINGBUF_SIZE) - out_point) % RINGBUF_SIZE为实际数据长度。