问题描述
在串口接收中断中调用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
为实际数据长度。