STM32 hal库串口空闲中断最新用法 - STM32团队 ST意法半导体中文论坛
(非常详细)【STM32】 DMA原理,步骤超细详解,一文看懂DMA-CSDN博客
STM32CubeIDE HAL库DMA与UART不定长数据接收实现:空闲中断详解-物联沃-IOTWORD物联网
STM32 hal库串口空闲中断最新用法_stm32hal库串口空闲中断-CSDN博客
HAL库 串口空闲中断+DMA接收不定长数据 详解及踩坑_hal库串口空闲中断-CSDN博客
(写的非常好,非常有助于提升底层理解)(stm32之HAL库)UART工作在DMA模式要打开串口中断吗?_hal uart dma-CSDN博客
DMA
DMA,全称为Direct Memory Access
,直接内存访问,本质是将传输数据从一个内存空间搬运至另一个内存空间,可以用来提供外设和内存,内存和外设之间的高速数据传输
在HAL
库中直接使用的HAL_UART_Transmit_DMA
,HAL_UART_Receive_DMA
,HAL_UARTEx_ReceiveToIdle_DMA
等都有中断的参与,需要在Cube
中开启中断

对具体底层原理和DMA模式实现底层与中断模式底层实现区别感兴趣可参考以下博客
(stm32之HAL库)UART工作在DMA模式要打开串口中断吗?_hal uart dma-CSDN博客
USART发送
在普通的轮询USART
中,CPU一直在等待外设发送数据,外设每发送一帧数据,CPU就从内存中移动一帧数据到外设的寄存器
在中断的USART
中,外设每从寄存器中发送一帧数据,就会触发一次发送数据寄存器空中断,使CPU回来将一帧数据从内存搬运至外设的寄存器中
USART的DMA发送
没有DMA的话,CPU会作为数据发送的中转站,而使用DMA的话,整个数据传输过程不需要CPU的参与,由DMA代理执行,DMA负责将内存数据搬运至外设的传输寄存器
在整个数据发送过半时会触发一个传输过半中断,所有数据传输完成后会调用传输完成中断回调函数(HAL_UART_TxCpltCallback
)
1
|
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
|
HAL_UART_Transmit_DMA
的传输完成中断回调函数HAL_UART_TxCpltCallback
以及传输过半中断回调函数HAL_UART_TxHalfCpltCallback
(这两个回调函数在HAL_UART_Transmit_IT
中同样有)
DMA发送注意事项
在传输的过程中,当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位 (TCIE)时,将产生一个中断请求
会触发的三个中断,传输完成中断,传输过半中断,错误中断
在stm32f1xx_hal_uart.c
的HAL_UART_Transmit_DMA
函数中可以查看到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
{
.
.
.
/* Set the UART DMA transfer complete callback */
huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;
/* Set the UART DMA Half transfer complete callback */
huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt;
/* Set the DMA error callback */
huart->hdmatx->XferErrorCallback = UART_DMAError;
.
.
.
}
}
|
USART接收
在普通的轮询USART
中,CPU一直在询问外设是否接收完数据,外设接收一帧数据,该位数据由CPU从外设的数据接收寄存器运进内存,CPU再次询问外设是否接收完数据,再运,直至整个数据接收完成
而在中断USART
中,外设每接收一帧数据,触发一次数据接收寄存器非空中断,CPU过来将数据从寄存器搬运至内存,所有数据接收完成后,会调用接收完成中断回调函数(HAL_UART_RxCpltCallback
)
USART的DMA接收
没有DMA的话,CPU会作为数据转运的中转站,而使用DMA的话,整个数据转运过程不需要CPU的参与,由DMA代理执行
在整个数据接收过半时会触发一个传输过半中断,所有数据接收完成后会调用传输完成中断回调函数(HAL_UART_RxCpltCallback
)
1
|
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
|
HAL_UART_Receive_DMA
的接收完成中断回调函数HAL_UART_RxCpltCallback
以及接收过半中断回调函数HAL_UART_RxHalfCpltCallback
(这两个回调函数在HAL_UART_Receive_IT
中同样有)
DMA接收注意事项
同DMA发送,类比即可
HAL库USART使用DMA
如果USART
串口的DMA出现错误,会调用HAL_UART_ErrorCallback
DMA发送
1
|
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
|
会触发中断,对应回调函数为
HAL_UART_TxCpltCallback
HAL_UART_TxHalfCpltCallback
DMA接收
1
|
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
|
会触发的中断,对应的回调函数为
HAL_UART_RxCpltCallback
HAL_UART_RxHalfCpltCallback
DMA模式
normal模式
以接收为例
单次接收,接收至数据上限,完成整个固定数据接收后停止,再次接收需要再次开启HAL_UART_Receive_DMA
,需要软件显式重启,触发接收过半和接收完成中断
summary:单次传输,完成后停止,需要软件显式重启,接收过半和接收完成中断
circular模式
以接收为例
无限循环,接收至数据上限一半,触发接收过半中断,接受至数据上限,触发接收完成中断,同时会硬件自动重启,接收完成中断会调用对应的接收完成中断回调函数
summary:无限循环,硬件自动重启,接收过半和接收完成中断
空闲中断
空闲中断定义
空闲中断(IDLE Interrupt) 是UART通信中的一种特殊中断类型,它在检测到UART接收线路连续空闲超过1个字节传输时间时触发的中断。“空闲"指线路保持逻辑1状态(高电平),线上无数据传输
HAL库自带的空闲中断实现
使用的是HAL
库提供的拓展函数,同时都需要在Cube
中开启中断
1
2
|
//在阻塞模式下接收一定数量的数据,直到接收到预期数量的数据或发生空闲事件。
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint16_t *RxLen, uint32_t Timeout);
|
1
2
|
//在中断模式下接收一定数量的数据,直到接收到预期数量的数据或发生空闲事件。
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
|
1
2
|
//在DMA模式下接收一定数量的数据,直到接收到预期数量的数据或发生空闲事件。
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
|
1
2
|
//使用空闲中断时的接收回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);
|
在触发空闲中断,接收完成中断都会调用HAL_UARTEx_RxEventCallback
回调函数,需要注意的是在F103c8t6
单片机以及其他st
系列单片机上,使用HAL_UARTEx_ReceiveToIdle_DMA
时,接收过半中断也会去调用该回调函数
接收过半中断的处理
STM32CubeIDE HAL库DMA与UART不定长数据接收实现:空闲中断详解-物联沃-IOTWORD物联网
HAL_UARTEx_RecceiveToIdle_DMA
会调用UART_Start_Receive_DMA(huart, pData, Size)
函数,该函数会把DMA中断传输完成,半传输,传输错误全部开启
1
|
__HAL_DMA_ENABLE_IT(DMA_IT_TC | DMA_IT_HC | DMA_IT_TE)
|
UART_Start_Receive_DMA(huart, pData, Size)
设置接收数组缓存到达数组一半时会执行下面的回调函数
1
2
|
/* Set the UART DMA Half transfer complete callback */
huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;
|
在DMA
的中断处理函数中,会调用huart->hdmarx->XferHalfCpltCallback
,此处即UART_DMARxHalfCplt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
uint32_t flag_it = hdma->DmaBaseAddress->ISR;
uint32_t source_it = hdma->Instance->CCR;
/* Half Transfer Complete Interrupt management ******************************/
if (((flag_it & (DMA_FLAG_HT1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_HT) != RESET))
{
......
if(hdma->XferHalfCpltCallback != NULL)
{
/* Half transfer callback */
hdma->XferHalfCpltCallback(hdma);
}
......
}
......
}
|
而UART_DMARxHalfCplt
会调用HAL_UARTEx_RxEventCallback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
static void UART_DMARxHalfCplt(DMA_HandleTypeDef *hdma)
{
UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
/* Initialize type of RxEvent that correspond to RxEvent callback execution;
In this case, Rx Event type is Half Transfer */
huart->RxEventType = HAL_UART_RXEVENT_HT;
/* Check current reception Mode :
If Reception till IDLE event has been selected : use Rx Event callback */ if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx Event callback*/
huart->RxEventCallback(huart, huart->RxXferSize / 2U);
#else
/*Call legacy weak Rx Event callback*/
HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize / 2U);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
else
{
/* In other cases : use Rx Half Complete callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx Half complete callback*/
huart->RxHalfCpltCallback(huart);
#else
/*Call legacy weak Rx Half complete callback*/
HAL_UART_RxHalfCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
|
为了避免接收过半中断的调用,需要使用下面的语句
1
|
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);//关闭dma接收半满中断函数,这样我们在接收一组数据时就不会触发半满中断,dma就可以正常接收一组数据
|
另外在UART_DMAReceiveCplt
函数中也调用了HAL_UARTEx_RxEventCallback
函数
1
|
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
|
DMA+空闲中断的实现接收不定长数据
方式1
自定义DMA+空闲中断
前置内容
需要自己写的宏定义
1
2
|
#define __HAL_DMA_SET_COUNTERT(__HANDLE__,__COUNTER__) ((__HANDLE__)->Instance->CNDTR = (uint16_t)(__COUNTER__))
//重新设定计数值
|
1
2
|
//用法
__HAL_DMA_SET_COUNTERT(huart->hdmarx,rxBufferLen);
|
用到的宏定义
1
2
|
__HAL_DMA_GET_COUNTER(huart->hdmarx)
//获取剩余未传输的计数值
|
用到的关于中断的操作
1
2
3
4
5
6
7
8
9
10
|
__HAL_DMA_DISABLE(huart->hdmarx)//关闭DMA
__HAL_DMA_ENABLE(huart->hdmarx)//开启DMA
//清除空闲标志位
__HAL_UART_CLEAR_IDLEFLAG(&huart1)
//开启空闲中断
__HAL_UART_ENABLE_IT(&huart1 , UART_IT_IDLE )
//关闭空闲中断
__HAL_UART_DISABLE_IT(&huart1, UART_IT_IDLE )
|
代码实现
1
2
3
|
#define rxBufferLen 10
uint8_t rxBuffer[rxBufferLen];
uint8_t txBuffer[rxBufferLen];
|
1
2
3
4
5
|
void UART_InitDMAReceive() {
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲中断标志位
__HAL_UART_ENABLE_IT(&huart1 , UART_IT_IDLE );//使能空闲中断
HAL_UART_Receive_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);//开启DMA接收
}
|
需要关闭DMA才能重设DMA的COUNTER
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//自定义的DMA空闲中断回调函数
void UART_DMAIdleCallback(UART_HandleTypeDef *huart) {
if (huart == &huart1 ) {
__HAL_DMA_DISABLE(huart->hdmarx);//失能DMA
int datalen = rxBufferLen - __HAL_DMA_GET_COUNTER(huart->hdmarx);
for(int i=0;i<datalen;i++) {
char temp = rxBuffer[i];
txBuffer[i] = (temp >= 'a' && temp <= 'z') ? temp-32:temp;
}
HAL_UART_Transmit_DMA(&huart1,(uint8_t*)txBuffer,datalen);
__HAL_DMA_SET_COUNTERT(huart->hdmarx,rxBufferLen);//重置DMA计数
__HAL_DMA_ENABLE(huart->hdmarx);//使能DMA
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
//触发空闲中断后,会进入中断请求处理函数
if (__HAL_UART_GET_FLAG(&huart1 , UART_FLAG_IDLE) != RESET ) {//在此判断是否为空闲中断
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲中断标志位
__HAL_UART_DISABLE_IT(&huart1, UART_IT_IDLE);//失能空闲中断
UART_DMAIdleCallback(&huart1);//调用自定义中断回调函数
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能空闲中断
}
/* USER CODE END USART1_IRQn 1 */
}
|
关于normal和circular在方式1的空闲中断实现的区别
normal模式
在上述空闲中断实现中,如果使用normal
模式,需要注意接收的数据一定不能超过rxBufferLen
,一旦超过,DMA
接收就会终止
执行流程是,触发空闲中断,进入USART1_IRQHandler
,再进入自定义空闲中断,在里面重新设置了DMA
的计数值(指针也会重新设置,重新指向rxBuffer[0]
),最开始开启的HAL_UART_Receive_DMA
始终没有接收完 完整的数据,就不会关闭,一直接收
但如果接收的数据超过rxBufferLen
,会触发接收过半和接收完成中断(对应回调函数没使用,约等于空函数),同时HAL_UART_Receive_DMA
会结束,需要软件显式重启
例如:rxBufferlen=10
,发送12345678910
,会回复1234567891
,随后DMA
终止,再发送数据无回复
circular模式
在上述空闲中断实现中,如果使用circular
模式,则接收的数据可以超过rxBufferLen
,超过后,DMA
接收会自动重启
执行流程是,触发空闲中断,进入USART1_IRQHandler
,再进入自定义空闲中断,在里面重新设置了DMA
的计数值(指针也会重新设置,重新指向rxBuffer[0]
),就算接收数据超过rxBufferLen
,HAL_UART_Receive_DMA
也会重新启动
但是需要注意,如果达到rxBufferLen
,会触发接收过半和接收完成中断,然后硬件重启,重新回到rxBufferLen[0]
的位置读数据,直至发生空闲中断
例如:rxBufferlen=10
,发送12345678910
,会回复0
,随后DMA
仍然正常工作
方式2
使用HAL
库提供的拓展空闲中断,使用normal
模式
该方式下的空闲中断回调函数为HAL_UARTEx_RxEventCallback
,需要注意接收过半中断也会调用HAL_UARTEx_RxEventCallback
的问题,在每次开启HAL_UARTEx_ReceiveToIdle_DMA
后都需要关闭接收过半中断
1
|
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);//关闭dma接收半满中断函数,这样我们在接收一组数据时就不会触发半满中断,dma就可以正常接收一组数据
|
注意需要添加
1
2
3
|
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
|
当接收数据超过rxBufferLen
时,接收完成中断函数会调用HAL_UARTEx_RxEventCallback
回调函数,然后重新开启接收
例如:rxBufferLen=10
,接收12345678910
,发送1234567891
(就是因为接收完成中断导致),后面DMA
空闲中断接收正常开启
代码实现
1
2
3
4
5
6
7
|
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
#define rxBufferLen 512
uint8_t rxBuffer[rxBufferLen];
uint8_t txBuffer[rxBufferLen];
|
1
2
3
4
|
void Init_IdleDMA(void) {//初始化,开启DMA空闲中断接收
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭f103板子DMA的接收过半中断
}
|
1
2
3
4
5
6
7
8
9
|
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {//接收完成和接收过半,空闲中断都会调用该中断回调函数
if (huart == &huart1 ) {
memcpy(txBuffer, rxBuffer, Size);
HAL_UART_Transmit(&huart1, (uint8_t*)txBuffer, Size,10);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);//normal模式,接受一次后需要重新开启
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭f103板子DMA的接收过半中断
}
//Init_IdldeDMA();
}
|
关于normal和circular在方式2下的空闲中断实现的区别
normal模式
方式2中,在数据接收过半,接收完成,空闲时会触发对应中断,最后调用HAL_UARTEx_RxEventCallback
回调函数,在回调函数中软件显式重启HAL_UARTEx_ReceiveToIdle_DMA
circular模式
方式2中,若使用circular
模式,则数据会一直积累,到达rxBufferLen
时,会硬件重启,同时也会有数据接收过半,接收完成,空闲时会触发对应中断,最后调用HAL_UARTEx_RxEventCallback
回调函数
例如:
(1)关闭接收过半中断
rxBufferLen=10
,接收123
,发送123
,
再接收456
,发送123456
,
再接收78910
,会先发送1234567891
(接收完成),再发送0
(空闲中断),
再接收111213
,发送0111213
(2)未关闭接收过半中断
rxBufferLen=10
,接收123
,发送123
,
再接收123
,会先发送12312
(接收过半中断),再发送123123
空闲中断,
再接收12345
,会先发送1231231234
(接收完成中断),再发送5
空闲中断
方式3
使用HAL
库提供的空闲中断,但是使用circular
模式实现
在circuar
要想实现一帧一帧不定长数据的接收主要需要考虑的就是如何实现在接收完一帧后让DMA
的计数值重置
实现原理
关闭
要实现DMA
计数值的重置,需要使用HAL_UART_DMAStop(&huart1)
函数将DMA关闭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
{
uint32_t dmarequest = 0x00U;
/* The Lock is not implemented on this API to allow the user application
to call the HAL UART API under callbacks HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback(): when calling HAL_DMA_Abort() API the DMA TX/RX Transfer complete interrupt is generated and the correspond call back is executed HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback() */
/* Stop UART DMA Tx request if ongoing */
dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT);
if ((huart->gState == HAL_UART_STATE_BUSY_TX) && dmarequest)
{
ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
/* Abort the UART DMA Tx channel */
if (huart->hdmatx != NULL)
{
HAL_DMA_Abort(huart->hdmatx);
}
UART_EndTxTransfer(huart);
}
/* Stop UART DMA Rx request if ongoing */
dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && dmarequest)
{
ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
HAL_DMA_Abort(huart->hdmarx);
}
UART_EndRxTransfer(huart);
}
return HAL_OK;
}
|
其中会调用HAL_DMA_Abort(huart->hdmatx)
,但都不会重置DMA的计数值
开启
在关闭DMA
后,需要HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen)
重新开启DMA
以及空闲中断
在HAL_UARTEx_ReceiveToIdle_DMA
中,会进行判断if (huart->RxState == HAL_UART_STATE_READY)
,判断RxState
是否为HAL_UART_STATE_READY
,如果是,则说明uart
没有开启,但是是在预备状态,随后会去调用UART_Start_Receive_DMA(huart, pData, Size)
开启uart
以及DMA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
HAL_StatusTypeDef status;
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Set Reception type to reception till IDLE Event*/
huart->ReceptionType = HAL_UART_RECEPTION_TOIDLE;
huart->RxEventType = HAL_UART_RXEVENT_TC;
status = UART_Start_Receive_DMA(huart, pData, Size);
/* Check Rx process has been successfully started */
if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
}
else
{
/* In case of errors already pending when reception is started,
Interrupts may have already been raised and lead to reception abortion. (Overrun error for instance). In such case Reception Type has been reset to HAL_UART_RECEPTION_STANDARD. */ status = HAL_ERROR;
}
return status;
}
else
{
return HAL_BUSY;
}
}
|
在UART_Start_Receive_DMA
中会将uart
的RxState
设置为HAL_UART_STATE_BUSY_RX
,中间还会去调用HAL_DMA_Start_IT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
HAL_StatusTypeDef UART_Start_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
uint32_t *tmp;
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
......
HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);
......
}
|
在HAL_DMA_Start_IT
会调用DMA_SetConfig
去对DMA
计数值等进行重置
1
2
3
4
5
6
7
|
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
......
/* Configure the source, destination address and the data length & clear flags*/
DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
......
}
|
原理总结
1.HAL_UARTEx_ReceiveToIdle_DMA
如果判断出huart->RxState == HAL_UART_STATE_READY
会调用UART_Start_Receive_DMA
2.UART_Start_Receive_DMA
会调用HAL_DMA_Start_IT
,同时会将RxState
修改为BUSY
huart->RxState = HAL_UART_STATE_BUSY_RX
3.HAL_DMA_Start_IT
会调用DMA_SetConfig
进行DMA
计数值等的重置
而使用HAL_UART_DMAStop
可以保证在回调函数处理数据时停止DMA
接收,保证数据不会被覆盖,同时可以将huart->RxState
状态设置为HAL_UART_STATE_READY
,随后再次开启DMA
空闲中断接收就能重置DMA
计数值
从而实现非定长数据的接收
代码实现
同样注意,需要关闭接收过半中断
1
2
3
4
5
6
7
8
|
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
#define rxBufferLen 512
uint8_t rxBuffer[rxBufferLen];
uint8_t txBuffer[rxBufferLen];
|
1
2
3
4
5
|
//初始化
void Init_IdleDMA(void) {
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭DMA的接收过半中断
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
//回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {//接收完成和接收过半,空闲中断都会调用该中断回调函数
if (huart == &huart1 ) {
HAL_UART_DMAStop(&huart1);//不会改变DMA的计数值
memcpy(txBuffer, rxBuffer, Size);
HAL_UART_Transmit(&huart1, (uint8_t*)txBuffer, Size,10);
memset(txBuffer, 0, Size);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);//如果是normal模式,接受一次后需要重新开启,如果关闭了DMA,也需要重新启动
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭f103板子DMA的接收过半中断
}
}
|
实验检验
我们设置cnt_i
去记录AL_UART_DMAStop(&huart1)
前后以及HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen)
前后的DMA
计数值变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#define rxBufferLen 512
uint8_t rxBuffer[rxBufferLen];
uint8_t txBuffer[rxBufferLen];
void Init_IdleDMA(void) {
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭DMA的接收过半中断
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {//接收完成和接收过半,空闲中断都会调用该中断回调函数
if (huart == &huart1 ) {
uint32_t cnt_1 = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
HAL_UART_DMAStop(&huart1);//不会改变DMA的计数值
uint32_t cnt_2 = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
memcpy(txBuffer, rxBuffer, Size);
HAL_UART_Transmit(&huart1, (uint8_t*)txBuffer, Size,10);
memset(txBuffer, 0, Size);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);//如果是normal模式,接受一次后需要重新开启,如果关闭了DMA,也需要重新启动
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭f103板子DMA的接收过半中断
uint32_t cnt_3 = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
sprintf(txBuffer, "\ncnt_1=%d cnt_2=%d cnt_3=%d\n", cnt_1,cnt_2,cnt_3);
HAL_UART_Transmit(&huart1, (uint8_t*)txBuffer, sizeof(txBuffer), 10);
}
}
|
实验结果
1
2
3
4
|
输入:123456789101112131415161718192021
输出:123456789101112131415161718192021
cnt_1=479 cnt_2=479 cnt_3=512
|
证明我们在重启DMA
空闲中断时实现了DMA
计数值重置
方式4
再仔细想一想,在circuar
要想实现一帧一帧不定长数据的接收主要需要考虑的就是如何实现在接收完一帧后让DMA
的计数值重置,也就是说没有必要为此使用HAL_UART_DMAStop
再重启DMA
空闲中断接收
如何优化呢,结合方式1的实现就可以得到答案
1
2
3
|
__HAL_DMA_DISABLE(huart->hdmarx);//需要关闭DMA才能重设DMA的COUNTER
__HAL_DMA_SET_COUNTERT(huart->hdmarx,rxBufferLen);
__HAL_DMA_ENABLE(huart->hdmarx);//需要关闭DMA才能重设DMA的COUNTER
|
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#define __HAL_DMA_SET_COUNTERT(__HANDLE__,__COUNTER__) ((__HANDLE__)->Instance->CNDTR = (uint16_t)(__COUNTER__))
//重新设定计数值
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
#define rxBufferLen 512
uint8_t rxBuffer[rxBufferLen];
uint8_t txBuffer[rxBufferLen];
void Init_IdleDMA(void) {
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rxBuffer, rxBufferLen);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);//关闭DMA的接收过半中断
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if (huart == &huart1 ) {
__HAL_DMA_DISABLE(huart->hdmarx);//需要关闭DMA才能重设DMA的COUNTER
__HAL_DMA_SET_COUNTERT(huart->hdmarx,rxBufferLen);
memcpy(txBuffer, rxBuffer, Size);
HAL_UART_Transmit(&huart1, (uint8_t*)txBuffer, Size,10);
__HAL_DMA_ENABLE(huart->hdmarx);//需要关闭DMA才能重设DMA的COUNTER
}
}
|