USART發送數據

一、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 = f / (16*USARTDIV)

其中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);  
    }        
}

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章