基于ringbuffer的串口接收demo
之前写了一篇文章,实现了一个环形缓冲区
一种环形缓冲区的分析和实现,比较详细的分析了环形缓冲区的原理和实现,本文将基于环形缓冲区实现一个串口接收的demo。
一、前提准备
- GD32F103开发板(其他开发板也可,本人这里使用GD32F103)
- 串口发送接收例程
- 串口调试助手
二、实现思路
在串口发送接收例程的基础上,添加DMA接收串口数据,添加空闲中断,在中断里将DMA接收到的数据存入环形缓冲区,在主函数中读取环形缓冲区中的数据。
三、代码实现
1、准备好代码模板
代码模板主要实现了毫秒、微秒延时,LED灯控制,printf重定向到串口,中断优先级分组,关闭半主机模式等功能。
2、添加ringbuffer库
将ringbuffer库添加到工程中,这里可以在RingBufferDemo工程中到User/App
目录下找到,将array.h
、array.c
、ring_buffer.h
、ring_buffer.c
复制到工程中。
在初始化串口前,需要先初始化ringbuffer,具体代码如下:
#define RX_FIFO_POOL_SIZE 1024
static uint8_t rx_fifo_pool[RX_FIFO_POOL_SIZE];
static Array rx_array = {0};
static RingBuffer rx_ring_buffer = {0};
void usart_config(void)
{
ArrayError arr_err = array_init(&rx_array, rx_fifo_pool, RX_FIFO_POOL_SIZE, sizeof(uint8_t));
if (ARRAY_OK != arr_err)
{
while (1)
;
}
RingBufferStatus ring_err = ring_buffer_init(&rx_ring_buffer, &rx_array, NULL, NULL);
if (RB_OK != ring_err)
{
while (1)
;
}
// 串口初始化...
}
3、添加DMA接收串口数据
这里使用串口0进行发送和接收,DMA0的通道4用来接收串口0到数据,具体配置如下:
void dma_for_usart_config(void)
{
dma_parameter_struct dma_init_struct;
/* deinitialize DMA channel4 (USART0 rx) */
dma_deinit(DMA0, DMA_CH4);
dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; // 方向是 外设到内存
dma_init_struct.memory_addr = (uint32_t)rxbuffer; // DMA缓冲区到地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 内存地址自增
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; // 内存数据宽度8位
dma_init_struct.number = RX_BUFFER_SIZE; // DMA接收数据长度
dma_init_struct.periph_addr = USART0_RDATA_ADDRESS; // 外设地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 外设地址不自增
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; // 外设数据宽度8位
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; // 优先级最高
dma_init(DMA0, DMA_CH4, &dma_init_struct); // 初始化DMA通道4
/* configure DMA mode */
dma_circulation_disable(DMA0, DMA_CH4); // 环形模式关闭
/* enable DMA channel4 */
dma_channel_enable(DMA0, DMA_CH4); // 使能DMA通道4
}
其中rxbuffer
是一个全局的数组,用来存储DMA从外设中接收到的数据。
USART0_RDATA_ADDRESS
是一个宏定义,用于获取串口0接收数据的地址。
4、添加空闲中断
在初始化串口0到配置时,需要打开dma接收,然后使能空闲中断,并设置串口0的中断优先级.
static void params_usart_config(void)
{
/* USART configure */
usart_deinit(USART0);
usart_baudrate_set(USART0, 115200);
usart_word_length_set(USART0, USART_WL_8BIT);
usart_stop_bit_set(USART0, USART_STB_1BIT);
usart_parity_config(USART0, USART_PM_NONE);
usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE); // 使能DMA接收
usart_enable(USART0);
usart_interrupt_enable(USART0, USART_INT_IDLE); // 使能空闲中断
}
void nvic_for_usart_config(void)
{
nvic_irq_enable(USART0_IRQn, 0, 0); // 配置串口0中断优先级
}
5、添加串口0中断处理函数
在串口0中断处理函数中,需要判断是否是空闲中断,如果是,则表示接收完成,此时需要将接收到的数据从DMA缓冲区中拷贝到ringbuffer中,最后还需要重新配置DMA接收,以便下一次接收数据。
void USART0_IRQHandler(void)
{
uint8_t rx_count = 0;
if (RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE))
{
/* clear IDLE flag */
usart_data_receive(USART0);
/* number of data received */
rx_count = RX_BUFFER_SIZE - (dma_transfer_number_get(DMA0, DMA_CH4));
ring_buffer_write(&rx_ring_buffer, rxbuffer, rx_count);
/* disable DMA and reconfigure */
dma_channel_disable(DMA0, DMA_CH4);
dma_transfer_number_config(DMA0, DMA_CH4, RX_BUFFER_SIZE);
dma_channel_enable(DMA0, DMA_CH4);
}
}
6、实现串口接收函数
为了方便使用,我们可以实现两个串口接收函数,一个用于接收一个字节,另一个用于接收指定长度的数据。
我这里使用了一个枚举类型给上层使用,具体实现可参考文末附到具体代码。
int32_t usart_rx_byte(USART_ID usart_id, uint8_t *data)
{
return ring_buffer_read(usart_ring_buffer_handler[usart_id].ring_buffer, data, 1);
}
int32_t usart_rx(USART_ID usart_id, uint8_t *data, uint32_t len)
{
return ring_buffer_read(usart_ring_buffer_handler[usart_id].ring_buffer, data, len);
}
7、主函数测试
在主函数中初始化各硬件,循环等待接收数据,如果没有接收到数据,打印提示空;如果环形缓冲出现错误,打印错误码;
如果接收到数据,打印接收到到数据。
int main(void)
{
int32_t recv_num = 0;
uint8_t recv_data[20] = {0};
systick_config();
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
usart_config();
led_init(RESET);
while (1)
{
recv_num = usart_rx(HARD_USART0, recv_data, 20);
if (recv_num > 0)
{
printf("recv_size = %d, recv data: %s\r\n", recv_num, recv_data);
led_ctrl(LED_R, LED_ON);
delay_1ms(5000);
led_ctrl(LED_R, LED_OFF);
}
else
{
if (recv_num == -RB_EMPTY)
{
printf("recv buffer empty\r\n");
}
else
{
printf("recv error. error code: %d\r\n", recv_num);
}
}
delay_1ms(10);
}
}