今天来学习一下最常用的外设之一USART。
首先是硬件的连接,我们需要至少三根线,GND,TX,RX。参阅datasheet。默认的USART1_TX和USART1_RX的引脚如下图
关于HAL drivers的文档 尽量多查阅【UM1725】 User Manual Description of STM32F4xx HAL drivers和 STM32Cube_FW_F4_V1.5.0\Drivers\STM32F4xx_HAL_Driver\STM32F439xx_User_Manual.chm。
首先我们用最简单的查询方式来使用UART
1.定义好UART_HandleTypeDef结构体。因为Polling模式,只需指定UART的实例和配置参数即可。即UART_HandleTypeDef结构体前两个变量定义好。
2.配置IO口,在stm32f4xx_hal_msp.c中定义好HAL_UART_MspInit()回调函数后,在初始化外设时,系统会自动调用它,因此我们利用这个函数来配置我们的GPIO。
///////////////////Uart.c/////////////////////// UART_HandleTypeDef UartHandle; void init_Uart(void) { UartHandle.Instance = USART1; UartHandle.Init.BaudRate = 115200; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; if(HAL_UART_Init(&UartHandle) != HAL_OK) { LCD_TEXT_STRINGLINE("error with USART Init"); } } ///////////////stm32f4xx_hal_msp.c/////////////////////// void HAL_UART_MspInit(UART_HandleTypeDef *huart) { GPIO_InitTypeDef GPIO_InitStruct; /******Enable peripherals and GPIO Clocks ***/ /* Enable GPIO TX/RX clock */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* Enable USART1 clock */ __HAL_RCC_USART1_CLK_ENABLE(); /**** Configure peripheral GPIO *****/ /* UART TX GPIO pin configuration */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FAST; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* UART RX GPIO pin configuration */ GPIO_InitStruct.Pin = GPIO_PIN_10; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA , &GPIO_InitStruct); }
然后调用HAL_UART_Init(&UartHandle) 成功返回HAL_OK,我们的UART1就可以用了
对于查询方式,UART的发送接受数据我们仅使用两个函数 HAL_UART_Transmit()、 HAL_UART_Receive() 来实现就可以了
一个例子:HAL_UART_Transmit(&UartHandle, txbuf, size, 5000);这里的参数:UartHandle就是我们刚刚定义的uart实例,txbuf为字符串,size为字符串长度。5000为等待超时时间。
OK使用就这么简单。对于大部分的应用来说,都可以满足了。
使用DMA来完成传输
如果使用DMA来传输的话,我们需要做一下修改了,首先是要配置一下DMA模块,我们使用同一个DMA通道的两个流来分别做为TX、RX的传输路径。
HAL_UART_MspInit()函数中添加如下代码
void HAL_UART_MspInit(UART_HandleTypeDef *huart) { static DMA_HandleTypeDef hdma_tx; static DMA_HandleTypeDef hdma_rx; GPIO_InitTypeDef GPIO_InitStruct; /******Enable peripherals and GPIO Clocks ***/ /* Enable GPIO TX/RX clock */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* Enable USART1 clock */ __HAL_RCC_USART1_CLK_ENABLE(); /* Enable DMA2 Clock*/ __HAL_RCC_DMA2_CLK_ENABLE(); /**** Configure peripheral GPIO *****/ /* UART TX GPIO pin configuration */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FAST; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* UART RX GPIO pin configuration */ GPIO_InitStruct.Pin = GPIO_PIN_10; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA , &GPIO_InitStruct); ////////////////////////configure the DMA/////////////// hdma_tx.Instance = DMA2_Stream7; hdma_tx.Init.Channel = DMA_CHANNEL_4; hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_tx.Init.Mode = DMA_NORMAL; hdma_tx.Init.Priority = DMA_PRIORITY_LOW; hdma_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; hdma_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; hdma_tx.Init.MemBurst = DMA_MBURST_INC4; hdma_tx.Init.PeriphBurst = DMA_PBURST_INC4; HAL_DMA_Init(&hdma_tx); /* Associate the initialized DMA handle to the the UART handle */ __HAL_LINKDMA(huart, hdmatx, hdma_tx); /* Configure the DMA handler for Transmission process */ hdma_rx.Instance = DMA2_Stream5; hdma_rx.Init.Channel = DMA_CHANNEL_4; hdma_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_rx.Init.Mode = DMA_NORMAL; hdma_rx.Init.Priority = DMA_PRIORITY_HIGH; hdma_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; hdma_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; hdma_rx.Init.MemBurst = DMA_MBURST_INC4; hdma_rx.Init.PeriphBurst = DMA_PBURST_INC4; HAL_DMA_Init(&hdma_rx); /* Associate the initialized DMA handle to the the UART handle */ __HAL_LINKDMA(huart, hdmarx, hdma_rx); /*Configure the NVIC for DMA*/ /* NVIC configuration for DMA transfer complete interrupt (USARTx_TX) */ HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 1); HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn); /* NVIC configuration for DMA transfer complete interrupt (USARTx_RX) */ HAL_NVIC_SetPriority(DMA2_Stream5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream5_IRQn); /* NVIC configuration for USART TC interrupt */ HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); }
这段代码添加了 hdma_tx,hdama_rx的数据结构来初始化分别初始化发送、接收的dma初始化,启动了dma2的时钟,连接uart1与dma,并且启动了两个DMA流和USART1的中断。
中断服务函数的文件stm32f4xx_it.c中添加相关的ISR
void DMA2_Stream5_IRQHandler(void) { HAL_DMA_IRQHandler(UartHandle.hdmarx); } void DMA2_Stream7_IRQHandler(void) { HAL_DMA_IRQHandler(UartHandle.hdmatx); } void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&UartHandle); }
这样我们就可以直接使用
HAL_UART_Transmit_DMA、HAL_UART_Recieve_DMA函数来完成uart数据传输了。
初步的尝试结果得出,HAL让我们不必关心太多底层的细节,比较适合快速的开发。但是要想熟练的使用,我们还是要学会使用固件库以及一些寄存器的配制方法,不可巧取。