本文以STM32F1xx_HAL_Driver驱动库做讲解,实验以stm32f103c8芯片做示例,工程采用makefile进行编译。
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; // 指向串口寄存器基地址
UART_InitTypeDef Init; // 串口通信相关的基本配置
uint8_t *pTxBuffPtr; // 指向串口发送缓冲区
uint16_t TxXferSize; // 串口发送缓冲区大小
__IO uint16_t TxXferCount; // 串口剩余需要发送的数目
uint8_t *pRxBuffPtr; // 指向串口接收缓冲区
uint16_t RxXferSize; // 串口接收缓冲区大小
__IO uint16_t RxXferCount; // 串口剩余需要接收的数目
__IO HAL_UART_RxTypeTypeDef ReceptionType; // 接收类型
DMA_HandleTypeDef *hdmatx; // 指向串口发送DMA句柄
DMA_HandleTypeDef *hdmarx; // 指向串口接收DMA句柄
HAL_LockTypeDef Lock; // 锁对象
__IO HAL_UART_StateTypeDef gState; // 串口全局句柄管理、串口发送的状态
__IO HAL_UART_StateTypeDef RxState; // 串口接收的状态
__IO uint32_t ErrorCode; // 错误码
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
void (*TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口发送过半回调函数
void (*TxCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口发送完成回调函数
void (*RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口接收过半回调函数
void (*RxCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口接收完成回调函数
void (*ErrorCallback)(struct __UART_HandleTypeDef *huart); // 串口错误回调函数
void (*AbortCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口中止完成回调函数
void (*AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口中止发送完成回调函数
void (*AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); // 串口中止接收完成回调函数
void (*WakeupCallback)(struct __UART_HandleTypeDef *huart); // 串口唤醒回调函数
void (*RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos); // 串口接收时间回调函数
void (*MspInitCallback)(struct __UART_HandleTypeDef *huart); // 串口初始化回调函数
void (*MspDeInitCallback)(struct __UART_HandleTypeDef *huart); // 串口释放回调函数
#endif
} UART_HandleTypeDef;
typedef struct
{
uint32_t BaudRate; // 波特率
uint32_t WordLength; // 数据位比特数
uint32_t StopBits; // 停止位
uint32_t Parity; // 奇偶校验模式
uint32_t Mode; // 串口模式
uint32_t HwFlowCtl; // 硬件流控
uint32_t OverSampling; // 过采样
} UART_InitTypeDef;
下面使用STM32CubeMX进行工程模板搭建,关键的配置信息如下图。
1、打开串口DMA发送接收中断。
2、打开串口全局中断。
在main函数中,我们使能jlink swd模式。先进入UART_TestCase串口用例,然后在while(1) --> UART_TestCaseTask串口用例Task进行应用的处理。
__IO ITStatus g_uartTxReady = RESET;
__IO ITStatus g_uartRxReady = RESET;
#define UART_TEST_BUFF_SIZE 256
uint8_t aTxBuffer[UART_TEST_BUFF_SIZE] = "---------- This is usart dma test demo ----------\n";
uint8_t aRxBuffer[UART_TEST_BUFF_SIZE] = {0};
uint8_t tempBuffer[UART_TEST_BUFF_SIZE] = {0};
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_AFIO_REMAP_SWJ_ENABLE();
UART_TestCase();
while (1)
{
UART_TestCaseTask();
HAL_Delay(200);
}
}
在printf中,我们并没有采用DMA进行传输。DMA一般用在数据量大的场景,所以单独在UART_TestCase测试用例进行实现。
__attribute__((used)) int _write(int fd, char *ptr, int len)
{
(void)HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 0xFFFF);
return len;
}
HAL_UART_TxCpltCallback是串口发送完成中断回调函数,HAL_UARTEx_RxEventCallback是串口空闲中断函数。
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
if(UartHandle->Instance == USART1) {
g_uartTxReady = SET;
}
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1) {
g_uartRxReady = SET;
}
}
在UART_TestCase测试用例中,我们将看到uart dma发送和接收的实例,同时计算了uart dma传输的时间。
void UART_TestCase(void)
{
uint32_t tickStart = 0;
printf("dma transmit test case\n");
printf("gState:%#x, RxState:%#x\n", huart1.gState, huart1.RxState);
if(HAL_UART_Transmit_DMA(&huart1, (uint8_t*)aTxBuffer, strlen((const char *)aTxBuffer)) != HAL_OK) {
printf("HAL_UART_Transmit_DMA err!!!\n");
return;
}
sprintf((char *)tempBuffer, "wait before gState:%#x, RxState:%#x\n", huart1.gState, huart1.RxState);
tickStart = HAL_GetTick();
while(!g_uartTxReady);
g_uartTxReady = RESET;
tickStart = HAL_GetTick() - tickStart;
printf("%s", tempBuffer);
printf("wait after gState:%#x, RxState:%#x\n", huart1.gState, huart1.RxState);
printf("dma transmit cost tick:%ld\n", tickStart);
printf("dma receive test case\n");
if(HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *)aRxBuffer, sizeof(aRxBuffer)) != HAL_OK) {
printf("HAL_UARTEx_ReceiveToIdle_DMA err!!!\n");
return;
}
while(!g_uartRxReady)
{
printf("wait while gState:%#x, RxState:%#x\n", huart1.gState, huart1.RxState);
HAL_Delay(500);
}
printf("wait after gState:%#x, RxState:%#x\n", huart1.gState, huart1.RxState);
printf("dma receive data success\n");
printf("aRxBuffer:%s\n", aRxBuffer);
g_uartRxReady = RESET;
memset(aRxBuffer, 0, sizeof(aRxBuffer));
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *)aRxBuffer, sizeof(aRxBuffer));
}
UART_TestCaseTask函数主要实现的功能是将串口DMA接收到的数据进行打印。
void UART_TestCaseTask(void)
{
if(g_uartRxReady) {
printf("aRxBuffer:%s\n", aRxBuffer);
g_uartRxReady = RESET;
memset(aRxBuffer, 0, sizeof(aRxBuffer));
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *)aRxBuffer, sizeof(aRxBuffer));
}
}
使用Visual Studio Code进行代码的编辑、编译、下载、调试,本部分不做详细介绍,有兴趣的可到附件下载相关资源。
在控制台(git bush)执行make install命令,将代码下载至开发板。串口工具输出如下。
dma transmit test case
gState:0x20, RxState:0x20
---------- This is usart dma test demo ----------
wait before gState:0x21, RxState:0x20
wait after gState:0x20, RxState:0x20
dma transmit cost tick:4
dma receive test case
wait while gState:0x20, RxState:0x22
wait while gState:0x20, RxState:0x22
wait while gState:0x20, RxState:0x22
wait while gState:0x20, RxState:0x22
wait after gState:0x20, RxState:0x20
dma receive data success
aRxBuffer: ****UART_TwoBoards communication based on DMA****
aRxBuffer: ****UART_TwoBoards communication based on DMA****
aRxBuffer: ****UART_TwoBoards communication based on DMA****
aRxBuffer: ****UART_TwoBoards communication based on DMA****
aRxBuffer: ****UART_TwoBoards communication based on DMA****
可以看到:在执行完HAL_UART_Transmit_DMA函数后,gState由ready态HAL_UART_STATE_READY(0x20)变为tx busy态HAL_UART_STATE_BUSY_TX(0x21),UART DMA发送完成后,再变为ready态HAL_UART_STATE_READY(0x20)。
在调用HAL_UARTEx_ReceiveToIdle_DMA函数后,RxState由ready态HAL_UART_STATE_READY(0x20)变为rx busy态HAL_UART_STATE_BUSY_RX(0x22),如果没有收到串口工具发来的数据,会一直打印。当收到串口工具发来的字符串“ UART_TwoBoards communication based on DMA”时,printf回显到串口工具。
进入到UART_TestCaseTask函数后,如果有接收到字符串,将printf回显到串口工具。可以看到,串口工具后面又发来了4次字符串“ UART_TwoBoards communication based on DMA”。
[1] 为什么UART串口通信要16倍过采样数据
https://blog.csdn.net/wordwarwordwar/article/details/80178708
[2] STM32-CubeMx-HAL库-串口空闲中断+DMA——利用HAL_UARTEx_ReceiveToIdle_DMA实现不定长数据接收
https://blog.csdn.net/weixin_43864631/article/details/125855290
stm32f103c8_uart_dma_20230604代码实例。
https://download.csdn.net/download/qq_24629659/87862907