STM32 串口中斷總結


原文:https://blog.csdn.net/weixin_42480952/article/details/82981409

最近在學習使用dma傳輸方式進行串口通訊,感覺這個很詳細,存一下

.定義了三種傳輸方式:阻塞傳輸,中斷傳輸、DMA傳輸

HAL_UART_Transmit;  HAL_UART_Receive

HAL_UART_Transmit_IT;    HAL_UART_Receive_IT

HAL_UART_Transmit_DMA;    HAL_UART_Receive_DMA

此外還定義了兩個中斷回調函數,供中斷和DMA使用,分別在數據傳輸一半和完成時使用

voidHAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef*huart);

voidHAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

voidHAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);

2.阻塞傳輸

阻塞傳輸是調用這個函數並在等待時間內一直等待操作完成。

uint8_t aTxbuffer[]="enter 10 characters:\n";
uint8_t aRxBuffer;
uint8_t Usart1_RxBuff[10];
uint8_t Usart1_Rx_Cnt = 0;
int main(void)
{
HAL_Init();
Sysclk_config();
USART1_UART_Init(19200);
printf("input your string:\n");
HAL_UART_Transmit(&huart1 ,(uint8_t*)aTxbuffer,sizeof(aTxbuffer),0xFFF);
HAL_UART_Receive(&huart1,(uint8_t*)Usart1_RxBuff,10,10);
HAL_UART_Transmit(&huart1 ,(uint8_t*)Usart1_RxBuff,10,10);
}
可以添加循環語句,循環輸入輸出。

3.中斷傳輸

配置串口,開啟中斷,在中斷處理函數中進行輸入語句的輸出。

通過查看源代碼,可以看到HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)這個函數只是用來開啟中斷用的,並不能真正接收數據。開啟中斷后,在中斷處理函數HAL_UART_IRQHandler(&huart1)中,會先調用UART_Receive_IT(huart)函數進行數據輸入的接收,此為靜態全局函數,代碼如下:

static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint16_t* tmp;
uint16_t uhMask = huart->Mask;

/* Check that a Rx process is ongoing */
if(huart->RxState == HAL_UART_STATE_BUSY_RX)
{

if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
tmp = (uint16_t*) huart->pRxBuffPtr ;
*tmp = (uint16_t)(huart->Instance->RDR & uhMask);
huart->pRxBuffPtr +=2;
}
else
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->RDR & (uint8_t)uhMask);
}

if(--huart->RxXferCount == 0)
{
/* Disable the UART Parity Error Interrupt and RXNE interrupt*/
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));

/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;

HAL_UART_RxCpltCallback(huart);

return HAL_OK;
}

return HAL_OK;
}
else
{
/* Clear RXNE interrupt flag */
__HAL_UART_SEND_REQ(huart, UART_RXDATA_FLUSH_REQUEST);

return HAL_BUSY;
}
}
可以看到該函數的作用是將接收到的數據存入結構體huart內的pRxBuffPtr指針中,當待傳輸數據長度RxXferCount為0時,便調用回調函數HAL_UART_RxCpltCallback(huart);因此可以考慮更改此函數的源代碼,當待傳輸數據的字符為空格或回車時,判斷為輸入結束,將RxXferCount置為0,然后調用回調函數進行數據處理。

a.單字節循環接收數據

HAL_UART_Receive_IT通過設置接收緩沖區和需要接收的數據個數。當數據接收達到設定個數后引發一次中斷調用回調函數HAL_UART_RxCpltCallback。由於只引發一次中斷,如果需要連續接收,則需要在HAL_UART_RxCpltCallback再調用HAL_UART_Receive_IT。這種定長的接收可能並不是想要的,往往傳輸的數據都是不定長的,我想這需要將HAL_UART_Receive_IT長度設置為1,然后自己根據接收的數據判斷。此外由於回調函數沒有指明是哪個串口引發的中斷,因此有必要在回調函數中做判斷,如if(huart==&huart1){ }。

int main(void)
{
HAL_Init();
Sysclk_config();
USART1_UART_Init(19200);
//HAL_UART_Transmit(&huart1,aTxBuffer,sizeof(aTxBuffer),0xFFF);
HAL_UART_Receive_IT(&huart1,&aRxBuffer,1);//開啟接收中斷
while(1)
{
if(flag==1)
{
HAL_UART_Transmit(&huart1,RxBuff,count,0xFF);
printf("\n");
for(uint8_t i=0;i<LENTH;i++)
{
RxBuff[i]=0;
}//清空數組
flag=0;
count=0;
HAL_UART_Receive_IT(&huart1,&aRxBuffer,1);//重新開啟接收中斷
}
}
}


void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(count<LENTH-1)
{
if(aRxBuffer!=0x0d)//輸入非回車
{
flag=0;
RxBuff[count]=aRxBuffer;
count++;
HAL_UART_Transmit(&huart1,&aRxBuffer,count,0x20);
printf("\n");
HAL_UART_Receive_IT(&huart1,&aRxBuffer,1);//重新開啟接收中斷
}
else flag=1;
}
else
flag=1;
}
//接收中斷回調函數
b.長字節數據循環輸入

設置輸入緩沖數組(長度可設置100),利用單次中斷,將接收到的數據元素輪流存入數組中,數據元素為0或回車時判斷數據輸入完成,進行數據輸入完成標志位置位。

void Buffer_reset(void);
int fputc(int ch, FILE *f);


uint8_t aTxBuffer[]="this is a test message!\n";
uint16_t flag=0;
uint8_t RxBuff[LENTH]; //接收緩沖數組
uint16_t count = 0; //接收緩沖計數
uint8_t aRxBuffer[LENTH]={0}; //USART接收Buffer

int main(void)
{
HAL_Init();
Sysclk_config();
Buffer_reset();
USART1_UART_Init(19200);
HAL_UART_Receive_IT(&huart1,aRxBuffer,LENTH);//開啟接收中斷
while(1)
{
if(flag==1)
{
HAL_UART_Transmit(&huart1,RxBuff,count,0xFF);
printf("\n");
Buffer_reset();
flag=0;
count=0;
HAL_UART_Receive_IT(&huart1,aRxBuffer,LENTH);//重新開啟接收中斷
}
}
}


void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
do
{
RxBuff[count]=aRxBuffer[count];
printf("aRxBuffer[%d]=%d\n",count,aRxBuffer[count]);
count++;
}
while((aRxBuffer[count]!=0)&&(count<LENTH)); //將接收到的字節存進數組,直到元素為0或溢出為止。
flag=1;
}


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

}
//接收中斷回調函數

void Buffer_reset(void){
for(uint8_t i=0;i<LENTH;i++)
{
RxBuff[i]=0;
aRxBuffer[i]=0;
}//清空數組
}
此代碼暫時有問題,調試中。

 

4.DMA傳輸

DMA可以解放CPU,同時可以利用DMA+空閑中斷實現任意字節的串口輸入輸出。

空閑中斷:接收到一條完整的數據,就會產生空閑中斷,同時空閑標志位置位。

串口接收中斷:每接收到一個字符,就會產生一個串口接收中斷。

原理:利用DMA配置,將串口讀入的數據存儲在DMA緩沖區的接收數組中。當檢測到一幀數據(即數據輸入完成)時,產生空閑中斷,此時可以將所接收的數據進行處理或輸出。

步驟:

a.串口配置(時鍾使能,引腳配置,串口配置,中斷配置,使能空閑中斷,串口全局中斷,開啟DMA接收數據)

b.DMA配置(時鍾使能,usart_tx和usart_rx通道配置,中斷配置,關聯usart和DMA通道)

c.重寫串口中斷函數(檢測到空閑中斷時,清除空閑中斷標志位,停止DMA傳輸,獲取輸入數據的長度,置位輸入完成標志位)

d.主函數處理(檢測到輸入完成標志位時,進行數據處理或輸入,然后清空數組,清除數據長度和輸入完成標志位)

DMA配置:

void usart_dma_init(void){

__HAL_RCC_DMA2_CLK_ENABLE();

huart1_dma_rx.Instance=DMA2_Stream2;
huart1_dma_rx.Init.Channel=DMA_CHANNEL_4;
huart1_dma_rx.Init.Direction=DMA_PERIPH_TO_MEMORY;
huart1_dma_rx.Init.PeriphInc=DMA_PINC_DISABLE;
huart1_dma_rx.Init.MemInc=DMA_MINC_ENABLE;
huart1_dma_rx.Init.MemDataAlignment= DMA_MDATAALIGN_BYTE;
huart1_dma_rx.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;
huart1_dma_rx.Init.Mode=DMA_NORMAL;
huart1_dma_rx.Init.Priority=DMA_PRIORITY_LOW;
huart1_dma_rx.Init.FIFOMode=DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&huart1_dma_rx);
//RX_DMA_config

huart1_dma_tx.Instance=DMA2_Stream7;
huart1_dma_tx.Init.Channel=DMA_CHANNEL_4;
huart1_dma_tx.Init.Direction=DMA_MEMORY_TO_PERIPH;
huart1_dma_tx.Init.PeriphInc=DMA_PINC_DISABLE;
huart1_dma_tx.Init.MemInc=DMA_MINC_ENABLE;
huart1_dma_tx.Init.MemDataAlignment= DMA_MDATAALIGN_BYTE;
huart1_dma_tx.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;
huart1_dma_tx.Init.Mode=DMA_NORMAL;
huart1_dma_tx.Init.Priority=DMA_PRIORITY_HIGH;
huart1_dma_tx.Init.FIFOMode=DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&huart1_dma_rx);
//TX_DMA_config

__HAL_LINKDMA(&huart1, hdmarx, huart1_dma_rx);
__HAL_LINKDMA(&huart1, hdmatx, huart1_dma_tx);
//關聯USART1和DMA

HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
//配置DMA通道的中斷
}
中斷處理函數:

void USART1_IRQHandler(void)
{
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag= __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
if(tmp_flag==1)//當產生空閑中斷時(及接收到一幀數據)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空閑中斷標志位
HAL_UART_DMAStop(&huart1); //停止串口的DMA傳輸
temp = __HAL_DMA_GET_COUNTER(&huart1_dma_rx);// 獲取DMA中未傳輸的數據個數
rx_len = BUFFER_SIZE - temp; //總計數減去未傳輸的數據個數,即得到已經接收的數據個數
flag=1;
}
HAL_UART_IRQHandler(&huart1);
}
5.利用定時器實現串口的不定長字節輸入

當接收到第一個字符時,打開定時器。經過延時后,進入定時器中斷回調函數,在該回調函數中進行數據的處理和輸出。

int main(void)
{
HAL_Init();
Sysclk_config();
USART1_UART_Init(19200);
Basic_Tim_Config();
printf("input strings:\n");
num_reset();
HAL_UART_Receive(&huart1,RxBuff,LENTH,0xFFF);
if(RxBuff[0])
{
HAL_TIM_Base_Start_IT(&Basic_Tim6);//有數據輸入的時候就開啟定時器
}
while(1)
{
if(flag)
{
printf("input strings again:\n");
flag=0;
count=0;
num_reset();
HAL_UART_Receive(&huart1,RxBuff,LENTH,0xFFF); //開啟串口輸入
if(RxBuff[0])
{
HAL_TIM_Base_Start_IT(&Basic_Tim6);//重新開啟定時器
}
}
}
}

void TIM6_DAC_IRQHandler(TIM_HandleTypeDef *htim)
{
HAL_TIM_IRQHandler(&Basic_Tim6);
}


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_Base_Stop(&Basic_Tim6);
huart1.RxState = HAL_UART_STATE_READY;
huart1.Lock=HAL_UNLOCKED;
printf("your input:");
do
{
aRxBuff[count]=RxBuff[count];
count++;
}
while(RxBuff[count]);
if((HAL_UART_Transmit(&huart1,aRxBuff,count+1,0xFFF))==HAL_OK)//串口輸出
{
flag=1;
}
}

void num_reset(void)
{
for(uint8_t i=0;i<LENTH;i++)
{
RxBuff[i]=0;
aRxBuff[i]=0;
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM