一、USART簡介
USART即爲通用同步異步收發器,用於串行通信,例如其可以用於打印程序輸出信息,以便於調試程序。
USART框圖
圖10-1
這裏簡單介紹下USART框圖。
TX爲發送數據的輸出引腳,RX爲接收數據的輸入引腳,SCLK爲發送器時鐘輸出引腳(同步模式下會用到)。其中SCLK來源於APB1總線時鐘(36MHz)和APB2總線時鐘(72MHz)。
這裏涉及到USART數據寄存器(USART_DR)。如圖10-2。
圖10-2
數據的發送和接收
從圖10-2的寄存器描述我們知道,USART_DR實際上包含了一個發送用的TDR寄存器,一個接收用的RDR寄存器。發送時,把TDR內容轉移到發送移位寄存器,由發送移位寄存器一位一位發出;接收時,把收到的每一位保存到接收移位寄存器然後再轉移到RDR。
USART有專門的發送器和接收器,在使用USART前需要先使能USART,將USART_CR1寄存器的UE位置1即可。而發送或接收的數據字長可選8位或9位,由USART_CR1的M位控制。
要啓動數據發送,需要先使能USART_CR1的TE位,則發送移位寄存器的數據會在TX引腳輸出,從低位開始發送,如果是同步模式,則SCLK也會輸出時鐘信號。在異步模式中,一個字符幀包含三部分:起始位+數據幀+停止位。中間部分的數據幀則是我們要發送的8位或9位數據。當使能TE位後,發送器開始會先發送一個空閒幀,然後往USART_DR寫入要發送的數據。發送完成後,等待狀態寄存器(USART_SR)的TC位置1後,則代表數據傳輸完成,同時如果USART_CR1的TCIE位置1,將產生中斷。
同理,在接收時,需要置位USART_CR1的RE位,使能接收。接收完成後,會把USART_SR的RXNE位置1,同時如果USART_CR1的RXNEIE位置1,可以產生中斷。
USART_DR、USART_SR和USART_CR1~3需要結合使用,相關寄存器描述可自行查閱參考手冊。
波特率相關
USART中,波特率和比特率的值相等,所以一般不區分這兩個概念。波特率越大,傳輸速率越快。USART的發送器和接收器使用相同的波特率,公式如下:
boud =
其中boud爲波特率的值,f爲USART時鐘頻率,USARTDIV是USART分頻器除法因子,如圖10-3的寄存器描述。
圖10-3
由描述可知,DIV_Mantissa爲USARTDIV的整數部分,DIV_Fraction爲USARTDIV的小數部分。那麼,
USARTDIV = DIV_Mantissa + DIV_Fraction / 16
波特率的常用值有2400、9600、19200、115200。
例如,掛載在APB2總線的USART1,其有72MHz的時鐘頻率,即f=72MHz,假設我們需要115200的波特率,則由上面的公式可得:
115200 = 72000000 / (16*USARTDIV)
我們能得到USARTDIV=39.0625,那麼
DIV_Mantissa=39=0x17,
DIV_Fraction=0.625*16=1=0x01
這時我們應該設置USART_BRR的值爲0x171。
校驗控制
USART還支持奇偶校驗。當使用校驗位時,數據幀長度爲8位數據幀加上1位校驗位,共9位,此時USART_CR1的M位需要置1。將USART_CR1的PCE位置1可以使能校驗控制。奇偶校驗由硬件自動完成,在發送數據時會自動添加校驗位,接收數據時會自動驗證校驗位。接收數據驗證校驗位時如果校驗失敗,USART_SR的PE位將會置1,同時如果USART_CR1的PEIE位置1,便能產生奇偶校驗中斷。
使能校驗控制後,每個字符幀組成將變爲:起始位+數據幀+校驗位+停止位。
二、使用USART發送數據
我們在寫單片機程序的時候,在Debug時,往往要用到串口輸出信息,這是會使用printf打印出我們想要的信息來,但是printf有一個弊端,就是輸出打印時間較長。這樣在一些對時間精度要求非常高的場合,使用printf將會帶來一系列問題,這時,如果使用單片機的USART自定義一個協議,直接發送數據到上位機,將會得到我們想要的效果。下面對怎樣使用USART發送數據做一個整理。
1、發送單個字符
void USART1_PutChar(USART_TypeDef * USARTx,u8 ch){
USART_SendData8(USARTx,(u8)ch);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);
}
2、發送固定長度的字符串
void USART1_PutStrLen(USART_TypeDef * USARTx,u8 *buf,u16 len){
for(;len > 0 ; len--) {
USART_SendData8(USARTx,*buf++);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);
}
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);
}
3、發送任意長度的字符串
void USART1_PutStr(USART_TypeDef * USARTx,u8 *buf){
while(*buf){
USART_SendData8(USARTx,*buf++);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);
// 這句話有必要加,他是用於檢查串口是否發送完成的標誌,如果不加這句話會發生數據丟失的情況。
}
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);
// 這句話有必要加,他是用於檢查串口是否發送完成的標誌,如果不加這句話會發生數據丟失的情況。
}
4、 實時操作系統(考慮函數重入)
該函數就可以像printf使用可變參數。通過觀察函數但這個函數只支持了%d,%s的參數,想要支持更多,可以仿照printf的函數寫法加以補充。
void USART_printf ( USART_TypeDef * USARTx, char * Data, ... ){
const char *s;
int d;
char buf[16];
va_list ap;
va_start(ap, Data);
while ( * Data != 0 ){ // 判斷是否到達字符串結束符
if ( * Data == 0x5c ){ //'\'
switch ( *++Data ){
case 'r': //回車符
USART_SendData(USARTx, 0x0d);
Data ++;
break;
case 'n': //換行符
USART_SendData(USARTx, 0x0a);
Data ++;
break;
default:
Data ++;
break;
}
}
else if ( * Data == '%'){
switch ( *++Data )
{
case 's'://字符串
s = va_arg(ap, const char *);
for ( ; *s; s++) {
USART_SendData(USARTx,*s);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
}
Data++;
break;
case 'd'://十進制
d = va_arg(ap, int);
itoa(d, buf, 10);
for (s = buf; *s; s++){
USART_SendData(USARTx,*s);
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
}
Data++;
break;
default:
Data++;
break;
}
}
else
USART_SendData(USARTx, *Data++);
while ( USART_GetFlagStatus ( USARTx, USART_FLAG_TXE ) == RESET );
}
}
二、使用USART接收數據
數據的頭標識爲“\n”既換行符,尾標識爲“+”。該函數將串口接收的數據存放在USART_Buffer數組中,然後先判斷當前字符是不是尾標識,如果是說明接收完畢,然後再來判斷頭標識是不是“+”號,如果還是那麼就是我們想要的數據,接下來就可以進行相應數據的處理了。但如果不是那麼就讓Usart2_Rx=0重新接收數據。這樣做的有以下好處:
- 可以接受不定長度的數據,最大接收長度可以通過Max_BUFF_Len來更改
- 可以接受指定的數據
- 防止接收的數據使數組越界
這裏我的把接收正確數據直接打印出來,也可以通過設置標識位,然後在主函數裏面輪詢再操作。
1、正常接收
void USART2_IRQHandler(){
if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET){ //中斷產生
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中斷標誌
Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2); //接收串口1數據到buff緩衝區
Uart2_Rx++;
if(Uart2_Buffer[Uart2_Rx-1] == 0x0a || Uart2_Rx == Max_BUFF_Len){ //如果接收到尾標識是換行符(或者等於最大接受數就清空重新接收)
if(Uart2_Buffer[0] == '+'){ //檢測到頭標識是我們需要的
printf("%s\r\n",Uart2_Buffer); //這裏我做打印數據處理
Uart2_Rx=0;
}
else{
Uart2_Rx=0;//不是我們需要的數據或者達到最大接收數則開始重新接收
}
}
}
}
2、DMA接收
串口空閒中斷,一幀數據過來中斷進入一次且接收的數據時候是DMA來搬運到指定緩衝區(程序中是USART1_RECEIVE_DMABuffer數組),不佔用CPU時間。
#define DMA_USART1_RECEIVE_LEN 18 //根據需要設置
void USART1_IRQHandler(void){
u32 temp = 0;
uint16_t i = 0;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){
USART1->SR;
USART1->DR; //這裏我們通過先讀SR(狀態寄存器)和DR(數據寄存器)來清USART_IT_IDLE標誌
DMA_Cmd(DMA1_Channel5,DISABLE);
temp = DMA_USART1_RECEIVE_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); //接收的字符串長度=設置的接收長度-剩餘DMA緩存大小
for (i = 0;i < temp;i++){
Uart2_Buffer[i] = USART1_RECEIVE_DMABuffer[i];
}
//設置傳輸數據長度
DMA_SetCurrDataCounter(DMA1_Channel5,DMA_USART1_RECEIVE_LEN);
//打開DMA
DMA_Cmd(DMA1_Channel5,ENABLE);
}
}