串口DMA + 环形缓冲,共用缓冲区
串口DMA + 环形缓冲,最直接的做法是DMA 一个buffer, ringbuffer再搞个buffer, 接收和发送时再把数据倒来倒去。但这样会有个问题,两个缓冲本质上是重复的,如果都很大,那就相当于浪费了一半内存!!!DMA应该是可以和ringbuffer共用一个缓冲的...
- 串口发送时,不管DMA有没有在工作,都先将数据写入环形缓冲(避免阻塞主程序,在中断程序里面也能打印!)。
- 如果DMA空闲就用DMA发送,DMA发送完成产生中断后,如果缓冲区还有数据则继续启动DMA发送,直到发完数据。
- 只要缓冲区合理,就可以一次性写入大量数据,DMA再自己慢慢的发送出去。
- 串口接收时,如果有串口空闲中断就更新接收计数,如果有DMA完成中断就重新开始接收
- DMA会自己一直往缓冲区写入数据,只要在串口空闲中断和DMA完成中断中更新接收计数即可。
- 发送和接收过程全部由DMA完成,不占用CPU时间
/** @Author: LVGRAPE* @LastEditors: LVGRAPE*/
#include "drv_serial.h"
#include <string.h>
#include <stdbool.h>
#include "drv_pin.h"
#include "drv_led.h"#define USART1_TX_BUFFER_SIZE 1024
#define USART1_RX_BUFFER_SIZE 64uint16_t tx1_size;
volatile bool tx1_busy = 0;uint8_t usart1_tx_buffer[USART1_TX_BUFFER_SIZE];//DMA, ringbuffer 共用缓冲。 DMA 读出,ringbuffer 写入
uint8_t usart1_rx_buffer[USART1_RX_BUFFER_SIZE];//DMA, ringbuffer 共用缓冲。 DMA 写入,ringbuffer 读出
struct rt_ringbuffer rx1_ringbuffer = {.buffer_ptr = usart1_rx_buffer,.read_mirror = 0,.read_index = 0,.read_mirror = 0,.write_index = 0,.buffer_size = USART1_RX_BUFFER_SIZE
};
struct rt_ringbuffer tx1_ringbuffer = {.buffer_ptr = usart1_tx_buffer,.read_mirror = 0,.read_index = 0,.write_mirror = 0,.write_index = 0,.buffer_size = USART1_TX_BUFFER_SIZE
};/*** @brief 计算缓冲区中没有发送的数据,如果缓冲区中有数据,则启动DMA发送** @return int*/
int tx1_send_buffer(void)
{if (tx1_ringbuffer.read_index <= tx1_ringbuffer.write_index){/**缓冲未满,全部发送 *//** |---------Read----[data]-----Write-----------| */tx1_size = tx1_ringbuffer.write_index - tx1_ringbuffer.read_index;}else{/**缓冲已满,先发一部分,DMA中断接手剩余部分 *//**发送完成后,Read就回到Write左边了,也就是未满状态!环形缓冲转起来了!*//** |----[data]-----Write-----Read----[data]-----| */tx1_size = tx1_ringbuffer.buffer_size - tx1_ringbuffer.read_index;}if (tx1_size > 0){/**DMA发送 */tx1_busy = 1;DMA1_CHANNEL1->maddr = (uint32_t)tx1_ringbuffer.buffer_ptr + tx1_ringbuffer.read_index;//start address + read indexDMA1_CHANNEL1->dtcnt = tx1_size;dma_channel_enable(DMA1_CHANNEL1, TRUE);}return tx1_size;
}
/*** @brief 把数据写入到缓冲区,如果DMA没有在忙,则启动DMA发送,* DMA会连续在发送,直到缓冲区为空** @param buf* @param size* @return int*/
int uart1_write(uint8_t *buf, rt_size_t size)
{/**先把数据写入到缓冲区 */rt_ringbuffer_put(&tx1_ringbuffer, buf, size);/**如果DMA没有在忙,则启动DMA发送 */if (!tx1_busy){return tx1_send_buffer();}return 0;
}
/*** @brief 从缓冲区中读取数据** @param buf* @param size* @return int*/
int uart1_read(uint8_t *buf, rt_size_t size)
{return rt_ringbuffer_get(&rx1_ringbuffer, buf, size);
}
/*** @brief 缓冲区中有多少数据未读取** @return int16_t*/
int16_t usart1_get_rx_data_len(void)
{return rt_ringbuffer_data_len(&rx1_ringbuffer);
}/*** @brief DMA发送完成中断。* @param none* @retval none*/
void DMA1_Channel1_IRQHandler(void)
{rt_enter_critical();if (dma_interrupt_flag_get(DMA1_FDT1_FLAG)){tx1_busy = 0;dma_flag_clear(DMA1_FDT1_FLAG);dma_channel_enable(DMA1_CHANNEL1, FALSE);tx1_ringbuffer.read_index += tx1_size;if (tx1_ringbuffer.read_index >= tx1_ringbuffer.buffer_size){//下一圈tx1_ringbuffer.read_mirror = ~tx1_ringbuffer.read_mirror;tx1_ringbuffer.read_index -= tx1_ringbuffer.buffer_size;}/**NOTE 如果缓冲区中还有数据,继续发送 */tx1_send_buffer();}rt_exit_critical();
}/*** @brief DMA接收完成中断* @param none* @retval none*/
void DMA1_Channel2_IRQHandler(void)
{rt_enter_critical();if (dma_interrupt_flag_get(DMA1_FDT2_FLAG)){dma_flag_clear(DMA1_FDT2_FLAG);dma_channel_enable(DMA1_CHANNEL2, false);/**NOTE DMA不重置的话,计数就不会重置,接收内存地址也会继续增加 *//**NOTE DMA传输完成中断,说明缓冲已满,从头开始 */rx1_ringbuffer.write_index = 0;rx1_ringbuffer.write_mirror = ~rx1_ringbuffer.write_mirror;/**NOTE 重新接收 */DMA1_CHANNEL2->maddr = (uint32_t)usart1_rx_buffer;dma_data_number_set(DMA1_CHANNEL2, USART1_RX_BUFFER_SIZE);dma_channel_enable(DMA1_CHANNEL2, true);}rt_exit_critical();
}
/*** @brief USART1 空闲中断,主要是为了避免DMA完全接收完的时间太久,* 所以使用空闲中断来及时更新接收到的数据长度**/
void USART1_IRQHandler(void)
{rt_enter_critical();if (usart_interrupt_flag_get(USART1, USART_IDLEF_FLAG)){usart_flag_clear(USART1, USART_IDLEF_FLAG);usart_data_receive(USART1);/**NOTE 使用IDLE中断来更新接收到的数据长度 *//**NOTE DMA向下计数,剩余多少即是接收了多少 *//**NOTE DMA完成中断会必须要优先于空闲中断,并先将DMA计数重置 *//**NOTE |MAX------[DATA]---DMA_CNT-------0| *//**NOTE |0--------[DATA]---Write-------MAX| */uint16_t rxCnt = USART1_RX_BUFFER_SIZE - dma_data_number_get(DMA1_CHANNEL2);rx1_ringbuffer.write_index = rxCnt;}rt_exit_critical();
}int rt_hw_console_init(void)
{rt_hw_uart1_init();return 0;
}void rt_hw_console_output(const char *str)
{uint16_t len = rt_strlen(str);uart1_write((uint8_t *)str, len);
}
int rt_hw_console_getchar(void)
{rt_uint8_t ch = -1;if (rt_ringbuffer_getchar(&rx1_ringbuffer, &ch) == 1)return ch;return -1;
}
/*** @brief config usart* @param none* @retval none*/
void usart_configuration(void)
{gpio_init_type gpio_init_struct;/* enable the usart1 and gpio clock */crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE);crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);gpio_default_para_init(&gpio_init_struct);/* configure the usart2 tx pin */gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;gpio_init_struct.gpio_mode = GPIO_MODE_MUX;gpio_init_struct.gpio_pins = GPIO_PINS_9;gpio_init_struct.gpio_pull = GPIO_PULL_NONE;gpio_init(GPIOA, &gpio_init_struct);/* configure the usart2 rx pin */gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;gpio_init_struct.gpio_pins = GPIO_PINS_10;gpio_init_struct.gpio_pull = GPIO_PULL_UP;gpio_init(GPIOA, &gpio_init_struct);/* configure usart2 param */usart_init(USART1, 921600, USART_DATA_8BITS, USART_STOP_1_BIT);usart_transmitter_enable(USART1, TRUE);usart_receiver_enable(USART1, TRUE);usart_dma_transmitter_enable(USART1, TRUE);usart_dma_receiver_enable(USART1, TRUE);usart_interrupt_enable(USART1, USART_IDLE_INT, TRUE);nvic_irq_enable(USART1_IRQn, 1, 1);usart_enable(USART1, TRUE);
}/*** @brief config dma for usart2 and usart3* @param none* @retval none*/
void dma_configuration(void)
{dma_init_type dma_init_struct;/* enable dma1 clock */crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);/* dma1 channel1 for usart1 tx configuration */dma_reset(DMA1_CHANNEL1);dma_default_para_init(&dma_init_struct);dma_init_struct.buffer_size = USART1_TX_BUFFER_SIZE;dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;dma_init_struct.memory_base_addr = (uint32_t)usart1_tx_buffer;dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;dma_init_struct.memory_inc_enable = TRUE;dma_init_struct.peripheral_base_addr = (uint32_t)&USART1->dt;dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;dma_init_struct.peripheral_inc_enable = FALSE;dma_init_struct.priority = DMA_PRIORITY_MEDIUM;dma_init_struct.loop_mode_enable = FALSE;dma_init(DMA1_CHANNEL1, &dma_init_struct);/* enable transfer full data interrupt */dma_interrupt_enable(DMA1_CHANNEL1, DMA_FDT_INT, TRUE);/* dma1 channel1 interrupt nvic init */nvic_irq_enable(DMA1_Channel1_IRQn, 0, 0);/* config flexible dma for usart2 tx */dma_flexible_config(DMA1, FLEX_CHANNEL1, DMA_FLEXIBLE_UART1_TX);/* dma1 channel2 for usart1 rx configuration */dma_reset(DMA1_CHANNEL2);dma_default_para_init(&dma_init_struct);dma_init_struct.buffer_size = USART1_RX_BUFFER_SIZE;dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;dma_init_struct.memory_base_addr = (uint32_t)usart1_rx_buffer;dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;dma_init_struct.memory_inc_enable = TRUE;dma_init_struct.peripheral_base_addr = (uint32_t)&USART1->dt;dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;dma_init_struct.peripheral_inc_enable = FALSE;dma_init_struct.priority = DMA_PRIORITY_MEDIUM;dma_init_struct.loop_mode_enable = FALSE;dma_init(DMA1_CHANNEL2, &dma_init_struct);/* enable transfer full data interrupt */dma_interrupt_enable(DMA1_CHANNEL2, DMA_FDT_INT, TRUE);/* dma1 channel2 interrupt nvic init */nvic_irq_enable(DMA1_Channel2_IRQn, 0, 0);/* config flexible dma for usart2 rx */dma_flexible_config(DMA1, FLEX_CHANNEL2, DMA_FLEXIBLE_UART1_RX);dma_channel_enable(DMA1_CHANNEL2, TRUE); /* usart1 rx begin dma receiving */
}
void rt_hw_uart1_init(void)
{usart_configuration();dma_configuration();led_pins_init();
}