STM32串口中斷的一些資料
SECTION 1
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
|
/*
調試STM32串口過程中發現一個奇怪的問題,初始化串口1口,使能串口發送完成中斷後,立刻就進入了發送完成中斷。
仔細的查閱了STM32手冊中的串口部分的介紹:
以下是字元發送的配置過程,注意第6點,在設置USART_CR1中的TE位時,會發送一個空閒幀作為第一次數據發送,所以即便你執行了USART_ClearFlag(USART1, USART_FLAG_TC); (這個函數肯定在空閒幀資料發送完成前執行),所以當空閒幀發送完後,就進入發送完成中斷。
配置步驟:
1. 通過在USART_CR1寄存器上置位UE位來啟動USART
2. 程式設計USART_CR1的M位來定義字長。
3. 在USART_CR2中程式設計停止位的位數。
4. 如果採用多緩衝器通信,配置USART_CR3中的DMA使能位(DMAT)。按多緩衝器通信中
的描述配置DMA寄存器。
5. 利用USART_BRR寄存器選擇要求的串列傳輸速率。
6. 設置USART_CR1中的TE位,發送一個空閒幀作為第一次數據發送。
7. 把要發送的資料寫進USART_DR寄存器(此動作清除TXE位)。在只有一個緩衝器的情況
下,對每個待發送的資料重複步驟7。
8. 在USART_DR寄存器中寫入最後一個資料字後,要等待TC=1,它表示最後一個資料幀的
傳輸結束。當需要關閉USART或需要進入停機模式之前,需要確認傳輸結束,避免破壞
最後一次傳輸。
*/
//解決的辦法:
//方法一
//在執行
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//之前,先延時一段時間,基本上比一個字元發送的時間長一點就可以了,然後再執行
USART_ClearFlag(USART1, USART_FLAG_TC);
//方法二:
//在執行
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET)
{
; //等待空閒幀發送完成後 再清零發送標誌
}
USART_ClearFlag(USART1,USART_FLAG_TC);
|
SECTION 2
先說TC。即Transmission Complete。發送一個位元組後才進入中斷,這裡稱為“發送後中斷”。和原來8051的TI方式一樣,都是發送後才進中斷,需要在發送函數中先發送一個位元組觸發中斷。發送函數如下
/*******
功能:中斷方式發送字串.採用判斷TC的方式.即 判斷 發送後中斷 位.
輸入:字串的首位址
輸出:無
*******/
void USART_SendDataString( u8 *pData )
{
pDataByte = pData;
USART_ClearFlag(USART1, USART_FLAG_TC);//清除傳輸完成標誌位元,否則可能會丟失第1個位元組的資料.網友提供.
USART_SendData(USART1, *(pDataByte++) ); //必須要++,不然會把第一個字元t發送兩次
}
中斷處理函數如下
/********
* Function Name : USART1_IRQHandler
* Description : This function handles USART1 global interrupt request.
* Input : None
* Output : None
* Return : None
*********/
void USART1_IRQHandler(void)
{
if( USART_GetITStatus(USART1, USART_IT_TC) == SET )
{
if( *pDataByte == '\0' )//TC需要 讀SR+寫DR 方可清0,當發送到最後,到'\0'的時候用個if判斷關掉
USART_ClearFlag(USART1, USART_FLAG_TC);//不然TC一直是set, TCIE也是打開的,導致會不停進入中斷. clear掉即可,不用關掉TCIE
else
USART_SendData(USART1, *pDataByte++ );
}
}
其中u8 *pDataByte;是一個外部指標變數
在中斷處理常式中,發送完該字串後,不用關閉TC的中斷使能TCIE,只需要清掉標誌位元TC;這樣就能避免TC == SET 導致反復進入中斷了。
/*******
功能:中斷方式發送字串.採用判斷TC的方式.即 判斷 發送後中斷 位.
輸入:字串的首位址
輸出:無
*******/
void USART_SendDataString( u8 *pData )
{
pDataByte = pData;
USART_ClearFlag(USART1, USART_FLAG_TC);//清除傳輸完成標誌位元,否則可能會丟失第1個位元組的資料.網友提供.
USART_SendData(USART1, *(pDataByte++) ); //必須要++,不然會把第一個字元t發送兩次
}
中斷處理函數如下
/********
* Function Name : USART1_IRQHandler
* Description : This function handles USART1 global interrupt request.
* Input : None
* Output : None
* Return : None
*********/
void USART1_IRQHandler(void)
{
if( USART_GetITStatus(USART1, USART_IT_TC) == SET )
{
if( *pDataByte == '\0' )//TC需要 讀SR+寫DR 方可清0,當發送到最後,到'\0'的時候用個if判斷關掉
USART_ClearFlag(USART1, USART_FLAG_TC);//不然TC一直是set, TCIE也是打開的,導致會不停進入中斷. clear掉即可,不用關掉TCIE
else
USART_SendData(USART1, *pDataByte++ );
}
}
其中u8 *pDataByte;是一個外部指標變數
在中斷處理常式中,發送完該字串後,不用關閉TC的中斷使能TCIE,只需要清掉標誌位元TC;這樣就能避免TC == SET 導致反復進入中斷了。
串口初始化函數如下
/*********
名稱: USART_Config
功能: 設置串口參數
輸入: 無
輸出: 無
返回: 無
**********/
void USART_Config()
{
USART_InitTypeDef USART_InitStructure;//定義一個包含串口參數的結構體
USART_InitStructure.USART_BaudRate = 9600; //串列傳輸速率9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位元數據位元
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//無校驗
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬體流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//輸入加輸出模式
USART_InitStructure.USART_Clock = USART_Clock_Disable;//時鐘關閉
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init(USART1, &USART_InitStructure);//設置到USART1
USART_ITConfig(USART1, USART_IT_TC, ENABLE);//Tramsimssion Complete後,才產生中斷. 開TC中斷必須放在這裡,否則還是會丟失第一位元組
USART_Cmd(USART1, ENABLE); //使能USART1
}
/*********
名稱: USART_Config
功能: 設置串口參數
輸入: 無
輸出: 無
返回: 無
**********/
void USART_Config()
{
USART_InitTypeDef USART_InitStructure;//定義一個包含串口參數的結構體
USART_InitStructure.USART_BaudRate = 9600; //串列傳輸速率9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位元數據位元
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//無校驗
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬體流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//輸入加輸出模式
USART_InitStructure.USART_Clock = USART_Clock_Disable;//時鐘關閉
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init(USART1, &USART_InitStructure);//設置到USART1
USART_ITConfig(USART1, USART_IT_TC, ENABLE);//Tramsimssion Complete後,才產生中斷. 開TC中斷必須放在這裡,否則還是會丟失第一位元組
USART_Cmd(USART1, ENABLE); //使能USART1
}
這裡請問一個問題:開TC中斷USART_ITConfig()如果放在我的USART_SendDataString()中再開,會丟失字串的第一位元組。必須放在串口初始化函數中才不會丟。不知道為什麼??
這裡筆者可以給出解釋,你看下SECTION1 就可以知道為什麼呢,你這樣做的原理和SECTION1講解的差不多,就相當於延時,而你後面沒有丟失資料的主要原因就是你代碼中有這麼一句 USART_ClearFlag(USART1, USART_FLAG_TC);//清除傳輸完成標誌位元,否則可能會丟失第1個位元組的資料.網友提供.
再說判斷TXE。即Tx DR Empty,發送寄存器空。當使能TXEIE後,只要Tx DR空了,就會產生中斷。所以,發送完字串後必須關掉,否則會導致重複進入中斷。這也是和TC不同之處。
發送函數如下:
/*******
功能:中斷方式發送字串.採用判斷TC的方式.即 判斷 發送後中斷 位.
輸入:字串的首位址
輸出:無
*******/
void USART_SendDataString( u8 *pData )
{
pDataByte = pData;
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//只要發送寄存器為空,就會一直有中斷,因此,要是不發送資料時,把發送中斷關閉,只在開始發送時,才打開。
}
中斷處理函數如下:
/********
* Function Name : USART1_IRQHandler
* Description : This function handles USART1 global interrupt request.
* Input : None
* Output : None
* Return : None
********/
void USART1_IRQHandler(void)
{
if( USART_GetITStatus(USART1, USART_IT_TXE) == SET )
{
if( *pDataByte == '\0' )//待發送的位元組發到末尾NULL了
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//因為是 發送寄存器空 的中斷,所以發完字串後必須關掉,否則只要空了,就會進中斷
else
USART_SendData(USART1, *pDataByte++ );
}
}
在串口初始化函數中就不用打開TXE的中斷了(是在發送函數中打開的)如下:
/************
名稱: USART_Config
功能: 設置串口參數
輸入: 無
輸出: 無
返回: 無
************/
void USART_Config()
{
USART_InitTypeDef USART_InitStructure;//定義一個包含串口參數的結構體
USART_InitStructure.USART_BaudRate = 9600; //串列傳輸速率9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位元數據位元
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//無校驗
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬體流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//輸入加輸出模式
USART_InitStructure.USART_Clock = USART_Clock_Disable;//時鐘關閉
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init(USART1, &USART_InitStructure);//設置到USART1
USART_Cmd(USART1, ENABLE); //使能USART1
發送函數如下:
/*******
功能:中斷方式發送字串.採用判斷TC的方式.即 判斷 發送後中斷 位.
輸入:字串的首位址
輸出:無
*******/
void USART_SendDataString( u8 *pData )
{
pDataByte = pData;
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//只要發送寄存器為空,就會一直有中斷,因此,要是不發送資料時,把發送中斷關閉,只在開始發送時,才打開。
}
中斷處理函數如下:
/********
* Function Name : USART1_IRQHandler
* Description : This function handles USART1 global interrupt request.
* Input : None
* Output : None
* Return : None
********/
void USART1_IRQHandler(void)
{
if( USART_GetITStatus(USART1, USART_IT_TXE) == SET )
{
if( *pDataByte == '\0' )//待發送的位元組發到末尾NULL了
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//因為是 發送寄存器空 的中斷,所以發完字串後必須關掉,否則只要空了,就會進中斷
else
USART_SendData(USART1, *pDataByte++ );
}
}
在串口初始化函數中就不用打開TXE的中斷了(是在發送函數中打開的)如下:
/************
名稱: USART_Config
功能: 設置串口參數
輸入: 無
輸出: 無
返回: 無
************/
void USART_Config()
{
USART_InitTypeDef USART_InitStructure;//定義一個包含串口參數的結構體
USART_InitStructure.USART_BaudRate = 9600; //串列傳輸速率9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位元數據位元
USART_InitStructure.USART_StopBits = USART_StopBits_1;//1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//無校驗
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬體流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//輸入加輸出模式
USART_InitStructure.USART_Clock = USART_Clock_Disable;//時鐘關閉
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init(USART1, &USART_InitStructure);//設置到USART1
USART_Cmd(USART1, ENABLE); //使能USART1
}
SECTION 3
在USART的發送端有2個寄存器,一個是程式可以看到的USART_DR寄存器(下圖中陰影部分的TDR),另一個是程式看不到的移位暫存器(下圖中陰影部分Transmit Shift Register)。
對應USART資料發送有兩個標誌,一個是TXE=發送資料寄存器空,另一個是TC=發送結束;對照下圖,當TDR中的資料傳送到移位暫存器後,TXE被設置,此時移位暫存器開始向TX信號線按位元傳輸資料,但因為TDR已經變空,程式可以把下一個要發送的位元組(操作USART_DR)寫入TDR中,而不必等到移位暫存器中所有位發送結束,所有位發送結束時(送出停止位元後)硬體會設置TC標誌。
另一方面,在剛剛初始化好USART還沒有發送任何資料時,也會有TXE標誌,因為這時發送資料寄存器是空的。
TXEIE和TCIE的意義很簡單,TXEIE允許在TXE標誌為'1'時產生中斷,而TCIE允許在TC標誌為'1'時產生中斷。
至於什麼時候使用哪個標誌,需要根據你的需要自己決定。但我認為TXE允許程式有更充裕的時間填寫TDR寄存器,保證發送的資料流程不間斷。TC可以讓程式知道發送結束的確切時間,有利於程式控制外部資料流程的時序。
對應USART資料發送有兩個標誌,一個是TXE=發送資料寄存器空,另一個是TC=發送結束;對照下圖,當TDR中的資料傳送到移位暫存器後,TXE被設置,此時移位暫存器開始向TX信號線按位元傳輸資料,但因為TDR已經變空,程式可以把下一個要發送的位元組(操作USART_DR)寫入TDR中,而不必等到移位暫存器中所有位發送結束,所有位發送結束時(送出停止位元後)硬體會設置TC標誌。
另一方面,在剛剛初始化好USART還沒有發送任何資料時,也會有TXE標誌,因為這時發送資料寄存器是空的。
TXEIE和TCIE的意義很簡單,TXEIE允許在TXE標誌為'1'時產生中斷,而TCIE允許在TC標誌為'1'時產生中斷。
至於什麼時候使用哪個標誌,需要根據你的需要自己決定。但我認為TXE允許程式有更充裕的時間填寫TDR寄存器,保證發送的資料流程不間斷。TC可以讓程式知道發送結束的確切時間,有利於程式控制外部資料流程的時序。
SECTION 4
總的來說,STM32單片機的串口還是很好理解的,程式設計也不算複雜。當然我更願意希望其中斷系統和51單片機一樣的簡單。
對於接收終端,就是RXNE了,這只在接收完成後才產生,在執行USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)代碼時不會進入ISR。但麻煩的就是發送有關的中斷了:TXE或者TC,根據資料和測試的結果,TXE在復位後就是置1的,即在執行USART_ITConfig(USART1, USART_IT_TXE, ENABLE)後會立即產生插斷要求。因此這造成一個麻煩的問題:如果沒有真正的發送資料,TXE中斷都會發生,而且沒有休止,這將佔用很大部分的CPU時間,甚至影響其他程式的運行!
因此建議的是在初始化時不好啟用TXE中斷,只在要發送資料(尤其是字串、陣列這樣的系列資料)時才啟用TXE。在發送完成後立即將其關閉,以免引起不必要的麻煩。
對於發送,需要注意TXE和TC的差別——這裡簡單描述一下,假設串口資料寄存器是DR、串口移位暫存器是SR以及TXD引腳TXDpin,其關係是DR->SR->TXDpin。當DR中的資料轉移到SR中時TXE置1,如果有資料寫入DR時就能將TXE置0;如果SR中的資料全部通過TXDpin移出並且沒有資料進入DR,則TC置1。並且需要注意TXE只能通過寫DR來置0,不能直接將其清零,而TC可以直接將其寫1清零。
對於發送單個字元可以考慮不用中斷,直接以查詢方式完成。
對於發送字串/陣列類的資料,唯一要考慮的是只在最後一個字元發送後關閉發送中斷,這裡可以分為兩種情況:對於發送可顯示的字串,其用0x00作為結尾的,因此在ISR中就用0x00作為關閉發送中斷(TXE或者TC)的條件;第二種情況就是發送二進位資料,那就是0x00~0xFF中間的任意資料,就不能用0x00來判斷結束了,這時必須知道資料的具體長度。
這裡簡單分析上面代碼的執行過程:TXE中斷產生於前一個字元從DR送入SR,執行效果是後一個字元送入DR。對於第一種情況,如果是可顯示字元,就執行USART_SendData來寫DR(也就清零了TXE),當最後一個可顯示的字元從DR送入SR之後,產生的TXE中斷發現要送入DR的是字元是0x00——這當然不行——此時就關閉TXE中斷,字串發送過程就算結束了。當然這時不能忽略一個隱含的結果:那就是最後一個可顯示字元從DR轉入SR後TXE是置1的,但關閉了TXE中斷,因此只要下次再開啟TXE中斷就會立即進入ISR。對於第二種情況,其結果和第一種的相同。
對於第一種情況,其程式可以這麼寫:其中TXS是保存了要發送資料的字串,TxCounter1是索引值:
extern __IO uint8_t TxCounter1;
extern uint8_t *TXS;
extern __IO uint8_t TxLen;
extern uint8_t *TXS;
extern __IO uint8_t TxLen;
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
{
if(TXS[TxCounter1]) //如果是可顯示字元
{ USART_SendData(USART1,TXS[TxCounter1++]);}
else //發送完成後關閉TXE中斷,
{ USART_ITConfig(USART1,USART_IT_TXE,DISABLE);}
}
}
{
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
{
if(TXS[TxCounter1]) //如果是可顯示字元
{ USART_SendData(USART1,TXS[TxCounter1++]);}
else //發送完成後關閉TXE中斷,
{ USART_ITConfig(USART1,USART_IT_TXE,DISABLE);}
}
}
對於第二種情況,和上面的大同小異,其中TXLen表示要發送的二進位資料長度:
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //對USART_DR的寫操作,將該位清零。
{
if(TxCounter1
{ USART_SendData(USART1,TXS[TxCounter1++]);}
else //發送完成後關閉TXE中斷
{ USART_ITConfig(USART1,USART_IT_TXE,DISABLE);}
}
}
{
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //對USART_DR的寫操作,將該位清零。
{
if(TxCounter1
{ USART_SendData(USART1,TXS[TxCounter1++]);}
else //發送完成後關閉TXE中斷
{ USART_ITConfig(USART1,USART_IT_TXE,DISABLE);}
}
}
事實上第一種情況是第二種的特殊形式,就是說可以用第二種情況去發送可顯示的字元——當然沒人有閒心去數一句話裡有多少個字母空格和標點符號!
在使用時,只要將TXS指向要發送的字串或者陣列,設置TxLen為要發送的資料長度,然後執行USART_ITConfig(USART1, USART_IT_TXE,ENABLE)就立即開始發送過程。使用者可以檢查TxCounter1來確定發送了多少位元組。比如以第二種情況為例:
uint32_t *TXS;
uint8_t TxBuffer1[]="0123456789ABCDEF";
uint8_t DST2[]="ASDFGHJKL";
__IO uint8_t TxLen = 0x00;
uint8_t TxBuffer1[]="0123456789ABCDEF";
uint8_t DST2[]="ASDFGHJKL";
__IO uint8_t TxLen = 0x00;
TxLen=8; //發送8個字元,最終發送的是01234567
TXS=(uint32_t *)TxBuffer1; //將TXS指向字串TxBuffer1
TxCounter1=0; //重定索引值
USART_ITConfig(USART1, USART_IT_TXE,ENABLE); //啟用TXE中斷,即開始發送過程
while(TxCounter1!=TxLen); //等待發送完成
TXS=(uint32_t *)TxBuffer2; //同上,最終發送的是ASDFGHJK
TxCounter1=0;
USART_ITConfig(USART1, USART_IT_TXE,ENABLE);
while(TxCounter1!=TxLen);
TXS=(uint32_t *)TxBuffer1; //將TXS指向字串TxBuffer1
TxCounter1=0; //重定索引值
USART_ITConfig(USART1, USART_IT_TXE,ENABLE); //啟用TXE中斷,即開始發送過程
while(TxCounter1!=TxLen); //等待發送完成
TXS=(uint32_t *)TxBuffer2; //同上,最終發送的是ASDFGHJK
TxCounter1=0;
USART_ITConfig(USART1, USART_IT_TXE,ENABLE);
while(TxCounter1!=TxLen);
以上就是我認為的最佳方案,但串口中斷方式資料有多長就中斷多少次,我認為還是佔用不少CPU時間,相比之下DMA方式就好多了,因為DMA發送字串時最多中斷兩次(半傳輸完成,全傳輸完成),並且將串口變成類似16C550的器件。
沒有留言:
張貼留言