文章目录
- 1 前言
- 2 STM32H7实现
- 2.1 关键步骤
- 2.2 注意事项
- 3 代码仓库
1 前言
关于串口DMA收发实现,不同CPU其套路都是类似的,不同之处在于寄存器配置、依赖BSP库等差异。串口DMA收发详细实现技巧、流程、方法,参考文章“一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制”。
2 STM32H7实现
H7已经不支持标准库,只支持HAL库,虽然HAL库臃肿,但借助CubeMX生成配置代码还是很香的,特别对于复杂的外设,如USB、Etherent、SDIO。某些外设支持LL库,在CubeMX生成工程时可以选择;LL库的套路比较像以前的标准库,效率比较高,个人建议首选LL库,虽然ST没有主推。这里的串口DMA收发,用LL库实现!
测试平台:
- CPU:STM32H743XI
- 主频:400 MHz
- BSP库:HAL&LL
- UART:UART5,1500000/8/n/1
- DMA:DMA1 Stream0、DMA1 Stream1
2.1 关键步骤
基于一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制里面工程,这里只需实现BSP初始和相关接口提供给“dev_uart”即可。
- CubeMX配置
cpu时钟配置
uart5配置
uart5选择LL库
- 补充初始代码
CubeMX生成配置代码只是配置部分,并不能满足正常工作,需根据具体情况完善,如使能uart空闲中断、指定DMA收发内存、启动外设等。
- 中断回调函数实体实现
串口DMA收发需使用到串口空闲中断、DMA接收半满中断、DMA接收溢出中断、DMA发送完成中断。
/**
* \brief DMA1 stream0 interrupt handler for UART5 RX
*/
void DMA1_Stream0_IRQHandler(void)
{
if (LL_DMA_IsEnabledIT_HT(DMA1, LL_DMA_STREAM_0) && LL_DMA_IsActiveFlag_HT0(DMA1))
{
uart_dmarx_half_done_isr(DEV_UART1);
LL_DMA_ClearFlag_HT0(DMA1);
}
if (LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_STREAM_0) && LL_DMA_IsActiveFlag_TC0(DMA1))
{
uart_dmarx_done_isr(DEV_UART1);
LL_DMA_ClearFlag_TC0(DMA1);
}
}
/**
* \brief DMA1 stream1 interrupt handler for UART5 TX
*/
void DMA1_Stream1_IRQHandler(void)
{
if (LL_DMA_IsEnabledIT_TC(DMA1, LL_DMA_STREAM_1) && LL_DMA_IsActiveFlag_TC1(DMA1))
{
uart_dmatx_done_isr(DEV_UART1);
LL_DMA_ClearFlag_TC1(DMA1);
}
}
/**
* @brief This function handles UART5 global interrupt.
*/
void UART5_IRQHandler(void)
{
/* USER CODE BEGIN UART5_IRQn 0 */
if (LL_USART_IsEnabledIT_IDLE(UART5) && LL_USART_IsActiveFlag_IDLE(UART5))
{
uart_dmarx_idle_isr(DEV_UART1);
LL_USART_ClearFlag_IDLE(UART5);
}
}
- BSP接口支持
Bsp初始化
DMA发送使能接口
DMA接收使能接口
DMA接收buf剩余空间
void uart5_dma_init(uint8_t *mem_addr, uint32_t mem_size);
void bsp_uart5_dmatx_config(uint8_t *mem_addr, uint32_t mem_size);
void bsp_uart5_dmarx_config(uint8_t *mem_addr, uint32_t mem_size);
uint16_t bsp_uart5_get_dmarx_buf_remain_size(void);
2.2 注意事项
H7与F1、F0系列稍有不同,以下注意事项避免踩坑。
- 初始化过程
DMA初始化必须置于uart初始化之前,否则通信异常
***************
bsp_uart5_dmarx_config(mem_addr, mem_size);
NVIC_SetPriority(DMA1_Stream0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 1, 0));
NVIC_EnableIRQ(DMA1_Stream0_IRQn);
NVIC_SetPriority(DMA1_Stream1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 1, 1));
NVIC_EnableIRQ(DMA1_Stream1_IRQn);
NVIC_SetPriority(UART5_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0));
NVIC_EnableIRQ(UART5_IRQn);
UART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1;
UART_InitStruct.BaudRate = 1500000;
UART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
UART_InitStruct.StopBits = LL_USART_STOPBITS_1;
UART_InitStruct.Parity = LL_USART_PARITY_NONE;
UART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
UART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
UART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;
LL_USART_Init(UART5, &UART_InitStruct);
***************
- DMA可访问内存
H7内存区域分为几大块,TCM区、AXI SRAM区、SRAM1 SRAM2 SRAM3区、SRAM4区,不同内存区域的特性和使用者不同,如TCM区域用于CPU指令执行;SRAM3主要用于USB和Ethernet,详细内存分区特性自行查阅手册,各外设可访问内存区域如下图。对于DMA来说,只需知道TCM区不可访问,只能访问后三块区域,因此在使用DAM时,需指定内存区域。
H7 各外设可访问内存区域
对于Keil来说,在编译器勾选RAM2即可。
定义DMA buf时指定内存绝对地址,推荐后者方法,可以适用于不同编译器。因为,不指定内存地址,将由编译时自由分配内存空间,如分配在TCM区域,DMA功能不能正常工作;如开启了DMA错误中断,则触发错误中断,可以看到错误码。
static uint8_t s_uart1_dmarx_buf[UART1_DMA_RX_BUF_SIZE] __attribute__((section(".ARM.__at_0x24000000")));
static uint8_t s_uart1_dmatx_buf[UART1_DMA_TX_BUF_SIZE] __attribute__((section(".ARM.__at_0x24000080")));
3 代码仓库
GitHub:https://github.com/Prry/stm32-uart-dma