1.CIU32L051 DMA的通道映射
? ? ? ? 由于華大CIU32L051的DMA外設資源有限,DMA只有兩個通道可供使用,對應的通道映射圖如下:
2.UART對應的引腳分布及其復用映射
? ? ? ??CIU32L051對應的UART對應的引腳映射圖如下,這里博主為了各位方便查找,就直接全拿進來了:
3.USART1作為無阻塞性收發的串口
? ? ? ? 根據第二章的圖片可以看到,串口1對應的IO口為PA1,PA2,PA11,PA12等等,這里為了方便,博主直接拿usart的例程中的PA11,PA12分別作為USART1_TX,USART1_RX。
? ? ? ? 對應串口的配置程序如下:
//串口1 GPIO的配置
static void UART1_GPIO_Configure(void)
{/* GPIO外設時鐘使能 */ std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);std_gpio_init_t usart_gpio_init={0};/* GPIO引腳配置 PA12 ------> RX PA11 ------> TX */ usart_gpio_init.pin = GPIO_PIN_11|GPIO_PIN_12;usart_gpio_init.mode = GPIO_MODE_ALTERNATE;//io復用模式usart_gpio_init.output_type = GPIO_OUTPUT_PUSHPULL;//復用推挽輸出usart_gpio_init.pull = GPIO_PULLUP;//上拉輸入usart_gpio_init.alternate = GPIO_AF1_USART1;//復用串口1std_gpio_init(GPIOA, &usart_gpio_init);NVIC_SetPriority(USART1_IRQn, 0);//這里配置串口1的中斷配置器,這里可以不需要使用NVIC_EnableIRQ(USART1_IRQn);
}//串口1 結構體的配置
static void usart1_init(uint32_t baud)
{/* USART1時鐘使能 */std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_USART1);std_usart_init_t usart_init={0};usart_init.direction = USART_DIRECTION_SEND_RECEIVE;//這里是使能了串口的發送和接收usart_init.baudrate = baud;//波特率usart_init.wordlength = USART_WORDLENGTH_8BITS;//數據長度usart_init.stopbits = USART_STOPBITS_1;//停止位usart_init.parity = USART_PARITY_NONE;//奇偶校驗usart_init.hardware_flow = USART_FLOWCONTROL_NONE;//無硬件流使能/* USART初始化 */ if(STD_OK != std_usart_init(USART1,&usart_init)){/* 波特率配置不正確處理代碼 */while(1);}std_usart_enable(USART1);
}
4.串口1無阻塞性發送的實現及代碼實現
? ? ? ? 根據第一章的內容可以得知,串口1的TX和RX分別對應的是DMA的通道1和通道0。具體如下:
4.1配置串口1 DMA無阻塞性發送的實現
? ? ? ? 配置代碼具體如下:
? ? ? ? 此處有配置DMA的傳輸模式為DMA_BLOCK_TRANSFER,具體的解釋如下:
/**
* @brief DMA通道1初始化
* @retval 無
*/
static void dma_init(void)
{std_dma_init_t dma_init_param={0};/* DMA外設時鐘使能 */std_rcc_ahb_clk_enable(RCC_PERIPH_CLK_DMA);/* dma_init_param 結構體初始化 */dma_init_param.dma_channel = DMA_CHANNEL_1;dma_init_param.dma_req_id = DMA_REQUEST_USART1_TX;//這里是指DMA請求的觸發條件dma_init_param.transfer_type = DMA_BLOCK_TRANSFER;//配置DMA 的傳輸模式dma_init_param.src_addr_inc = DMA_SRC_INC_ENABLE;//使能源地址遞增dma_init_param.dst_addr_inc = DMA_DST_INC_DISABLE;//DMA目的地址自增使能或禁止dma_init_param.data_size = DMA_DATA_SIZE_BYTE;//DMA傳輸數據寬度,字節、半字或字dma_init_param.mode = DMA_MODE_NORMAL;//DMA工作模式為單次傳輸/* DMA初始化 */std_dma_init(&dma_init_param);/* 使能傳輸完成中斷 */std_dma_interrupt_enable(dma_init_param.dma_channel,DMA_INTERRUPT_TF);//這里使能的傳輸完成中斷,當DMA將對應目標的數據搬運完成后會將傳輸完成的標記置為1/* NVIC初始化 */NVIC_SetPriority(DMA_Channel1_IRQn, 0);NVIC_EnableIRQ(DMA_Channel1_IRQn);
}/**
* @brief DMA配置函數 目的是更新DMA傳輸的數據源和目標地址
* @param source DMA 傳輸源地址 指的是發送緩沖區的地址
* @param number DMA 傳輸字符數 這個參數就是指的是你發送緩沖區對應的大小
* @retval 無
*/
void bsp_usart_dma_config(uint8_t *source,uint32_t number)
{std_dma_config_t dma_config = {0};/* 配置DMA 源地址、目的地址和傳輸數據大小,并使能DMA */dma_config.src_addr = (uint32_t)source;dma_config.dst_addr = (uint32_t)&USART1->TDR;//目標地址為串口1的發送數據寄存器dma_config.data_number = number;dma_config.dma_channel = DMA_CHANNEL_1;std_dma_start_transmit(&dma_config);
}void En_Dma_Channel(void)//這里在進行對DMA通道的使能
{std_dma_enable(DMA_CHANNEL_1);
}void Dis_Dma_Channel(void)//這里在進行對DMA通道的失能
{std_dma_disable(DMA_CHANNEL_1);
}
? ? ? ? 上述代碼中,DMA的通道1觸發傳輸的條件是觸發了串口1的發送,即當發送緩沖區的數據非空時,DMA將會把發送緩沖區的數據搬運至串口1的發送數據寄存器中,串口則會通過發送數據寄存器將數據轉發。
? ? ? ? 由于,DMA的工作模式設置的是單次傳輸的模式,由此當DMA傳輸第一次完成后,需要對對DMA的數據源和目的地址進行更新,并重新使能對應的DMA通道,具體示例如下:
//使用DMA通道1 實現串口非阻塞性發送 這個函數的構造,就相當于是無阻塞性的UART_SENDDATA("DEBUG",strlen("DEBUG"))這種發送函數,只不過原本串口的發送函數是阻塞性的
void UART1_DMA_Send_Buf(uint8_t *buf,size_t len)
{Dis_Dma_Channel();//更新數據源前需要先關閉DMA對應的通道bsp_usart_dma_config(buf,len);//此函數則是更新了DMA對應通道的數據源,此函數在之前的配置代碼中有對應的詳細內容std_dma_interrupt_enable(DMA_CHANNEL_1,DMA_INTERRUPT_TF); //這里開啟對應的傳輸完成中斷,若是對應的中斷服務函數中沒有關閉傳輸完成中斷,此處內容可以省略En_Dma_Channel();//重新開啟DMA對應的通道
}//DMA通道1對應的中斷服務函數
/*-------------------------------------------functions------------------------------------------*/
/**
* @brief DMA通道中斷服務函數
* @retval 無
*/
//傳輸完成中斷
void DMA_Channel1_IRQHandler(void)
{if(std_dma_get_flag(DMA_FLAG_TF1)){std_dma_interrupt_disable(DMA_CHANNEL_1,DMA_INTERRUPT_TF); //這個可以不用關閉SEGGER_RTT_printf(0, "DMA DATA SEND FINISH\r\n");//這就是RRT的輸出的一個調試信息std_dma_clear_flag(DMA_FLAG_TF1);//清除傳輸完成的標記}
}
? ? ? ? 上述內容是對應的串口 DMA無阻塞性的發送的實現。
4.2串口1 無阻塞性接收的實現
? ? ? ? 無阻塞性接收數據的實現,需要用到DMA雙緩沖區備份。為什么要用雙緩沖區備份呢?
理由如下:
? ? ? ? 由于串口接收到的數據是不定長的數據,使用的DMA傳輸數據時容易造成可能由于數據過長導致緩沖區溢出的情況,以及在接收完成數據后由于數據處理花費的時間導致數據丟失。
? ? ? ? DMA提供了一個傳輸完成一半的中斷提示,由此可以通過數據傳輸完成一半的時,在中斷服務函數中更換DMA的接收緩沖區,將其余的數據轉存至到備份區域,這就避免了傳輸過程中造成的數據丟失問題。
? ? ? ? 此方式花費了原本雙倍的內存換來了更高性能的傳輸,也避免了傳輸過程中的數據丟失問題。
? ? ? ? 具體配置如下:
/**
* @brief DMA配置函數 目的是更新串口1RX對應DMA的數據源和目的地址
* @param distination DMA傳輸目的地址
* @param number DMA傳輸字符數
* @retval 無
*/
void USART1_DMA_RX_config(uint8_t *distination,uint32_t number)
{std_dma_config_t dma_config = {0};if(distination == buf_hu1)//此處內容用于中斷服務函數中更新對應的緩沖區{Set_USART1_Buf_Flag();}else if(distination == buf_hu2){Clear_USART1_Buf_Flag();}/* 配置DMA 源地址、目的地址和傳輸數據大小,并使能DMA */dma_config.src_addr = (uint32_t)&USART1->RDR;//串口數據接收寄存器dma_config.dst_addr = (uint32_t)distination;dma_config.data_number = number;dma_config.dma_channel = DMA_CHANNEL_0; std_dma_start_transmit(&dma_config);
}/**
* @brief DMA通道0初始化
* @retval 無
*/
void UART1_Dma_RX_init(void)
{std_dma_init_t dma_init_param = {0};/* DMA外設時鐘使能 */std_rcc_ahb_clk_enable(RCC_PERIPH_CLK_DMA);/* dma_init_param 結構體初始化 */dma_init_param.dma_channel = DMA_CHANNEL_0;//通道0對應串口1的RXdma_init_param.dma_req_id = DMA_REQUEST_USART1_RX;//dma請求是串口1的接收,意思是當串口1 的RDR寄存器不為空時將RDR寄存器的數據搬運置,緩沖區中dma_init_param.transfer_type = DMA_BLOCK_TRANSFER;dma_init_param.src_addr_inc = DMA_SRC_INC_DISABLE;//使能數據源地址遞增dma_init_param.dst_addr_inc = DMA_DST_INC_ENABLE;//使能目標地址遞增dma_init_param.data_size = DMA_DATA_SIZE_BYTE;//一次傳輸1bytedma_init_param.mode = DMA_MODE_CIRCULAR;//循環接收,不需要對DMA進行重新配置/* DMA初始化 */std_dma_init(&dma_init_param);USART1_DMA_RX_config(buf_hu1,256);//這設置了串口1對應DMA通道0的緩沖區/* 使能傳輸完成中斷 */std_dma_interrupt_enable(DMA_CHANNEL_0,DMA_INTERRUPT_TF);//使能dma通道0對應的接收完成中斷 std_dma_interrupt_enable(DMA_CHANNEL_0,DMA_INTERRUPT_TH); //使能dma通道0對應的接收一半中斷 /* NVIC初始化 */NVIC_SetPriority(DMA_Channel0_IRQn, 0); NVIC_EnableIRQ(DMA_Channel0_IRQn);
}void Set_USART1_Buf_Flag(void)
{ATdevs.uart1_buf_Flag = 1;
}uint8_t Get_USART1_Buf_Flag(void)
{return ATdevs.uart1_buf_Flag;
}void Clear_USART1_Buf_Flag(void)
{ATdevs.uart1_buf_Flag = 0;
}/*DMA 雙緩沖區實現串口DMA的無阻塞性接收數據配合DMA的傳輸一半的中斷進行使用當觸發傳輸一半的中斷后,此時需要更換對應的緩沖區,再下次傳輸一半數據前,是我們處理原本數據的有效時間要求是MCU的處理速度要比DMA的傳輸速度要快一半
*/
void DMA_USART_RX_CallBlack(void)
{std_dma_disable(DMA_CHANNEL_0);if(Get_USART1_Buf_Flag()){memset(buf_hu2,0,USART1_BUF_SIZE);//更換緩沖區前清除需要更換的緩沖區數據USART1_DMA_RX_config(buf_hu2,USART1_BUF_SIZE);//更新數據源SEGGER_RTT_printf(0,"dma buf is modify buf_hu2 success\r\n");}else{memset(buf_hu1,0,USART1_BUF_SIZE);USART1_DMA_RX_config(buf_hu1,USART1_BUF_SIZE);SEGGER_RTT_printf(0,"dma buf is modify buf_hu1 success\r\n");}std_dma_enable(DMA_CHANNEL_0);
}
? ? ? ? ?上述內容是配置了USART1_RX對應的DMA通道,以及更換數據源的實現。
? ? ? ? 關于DMA通道0對應的中斷服務函數如下:
/*-------------------------------------------functions------------------------------------------*/
/**
* @brief DMA通道中斷服務函數
* @retval 無
*/
//傳輸完成中斷
void DMA_Channel0_IRQHandler(void)
{if(std_dma_get_flag(DMA_FLAG_TF0)){std_dma_clear_flag(DMA_FLAG_TF0); }if(std_dma_get_flag(DMA_INTERRUPT_TH))//DMA傳輸一半完成的中斷{std_dma_clear_flag(DMA_INTERRUPT_TH);DMA_USART_RX_CallBlack();//傳輸一半后更換目的地址}
}
USART1對應的無阻塞性收發初始化內容具體如下:
void Configure_UART1_DMA0_TX_AND_RX(void)
{dma_init();//這是DMA結構體相關的初始化UART1_Dma_RX_init();//此處是關于DMA實現的無阻塞性接收的配置UART1_GPIO_Configure();usart1_init(115200);std_usart_dma_tx_enable(USART1);//這里必須要使能std_usart_dma_rx_enable(USART1);//這里必須要使能
}
5.測試效果
? ? ? ? 測試環境:
? ? ? ? ? ? ? ? RT-thread NANO系統下,開辟了一個周期為2秒的周期定時器,在周期定時器的回調中進行無阻塞性的發送。
? ? ? ? ? ? ? ? 另外,通過STM32F103的最小系統板一直給CIU32L051發送數據。
? ? ? ? 效果圖如下:
? ? ? ? 由此可見,基于DMA實現的串口無阻塞性收發已經實現。