串口通訊是串行通訊裏面的異步方式。串行通訊是相對於並行通訊來講的。串口是一個事實存在的東西,好比DB9接口。 串口通信裏面的波特率,其實是比特率。若是這兩點你還不是很清楚地話,好好往下看。編程
(1)、同步和異步的區別:簡單來講就是發送方和接收方按照同一個時鐘節拍工做就叫同步,發送方和接收方沒有統一的時鐘節拍、而各自按照本身的節拍工做就叫異步。 (2)、同步通訊中,通訊雙方按照統一節拍工做,因此配合很好;通常須要發送方給接收方發送信息同時發送時鐘信號,接收方根據發送方給它的時鐘信號來安排本身的節奏。同步通訊用在通訊雙方信息交換頻率固定,或者常常通訊時。帶時鐘同步信號傳輸。如-SPI,IIC通訊。 (3)、異步通訊又叫異步通知。在雙方通訊的頻率不固定時(有時 3ms 收發一次,有時 3 天才收發一次)不適合使用同步通訊,而適合異步通訊。異步通訊時接收方沒必要一直在乎發送方,發送方須要發送信息時會首先給接收方一個信息開始的起始信號,接收方接收到起始信號後就認爲後面緊跟着的就是有效信息,纔會開始注意接收信息,直到收到發送方發過來的結束標誌。異步通訊:不帶時鐘同步信號。如·UART(通用異步收發器),單總線。markdown
(1)、電平信號和差分信號是用來描述通訊線路傳輸方式的。也就是說如何在通訊線路上表達 1 和 0. (2)、電平信號的傳輸線中有一個參考電平線(通常是 GND),而後信號線上的信號值是由信號線電平和參考電平線的電壓差決定。 (3)、差分信號的傳輸線中沒有參考電平,全部都是信號線。而後 1 和 0 的表達靠信號線之間的電壓差。 總結:電平信號的 2 根通訊線之間的電平差別容易受到干擾,傳輸容易失敗;差分信號不容易受到干擾所以傳輸質量比較穩定,現代通訊通常都使用差分信號,電平信號幾乎沒有了。總結 2:看起來彷佛相同根數的通訊線下,電平信號要比差分信號要快;可是實際仍是差分信號快,由於差分信號抗干擾能力強,所以 1 個發送週期更短。網絡
(1)、串行、並行主要是考慮通訊線的根數,就是發送方和接收方同時能夠傳遞的信息量的多少 (2)、譬如在電平信號下,1 根參考電平線+1 根信號線能夠傳遞 1 位二進制;若是咱們有 3根線(2 根信號線+1 根參考線)就能夠同時發送 2 位二進制;若是想同時發送 8 位二進制就須要 9 根線。 (3)、在差分信號下,2 根線(彼此差分)能夠同時發送 1 位二進制;若是須要同時發送 8 位二進制,須要 16 根線。 總結:聽起來彷佛並行接口比串行接口要快(串行接口一次只能發送 1 位二進制,而並行接口一次能夠發送多位二進制)要更優秀;可是實際上串行接口才是王道,用的比較廣。由於更省信號線,並且對傳輸線的要求更低、成本更低;並且串行時能夠經過提升通訊速度來提升整體通訊性能,不必定非得要並行。 總結:異步、串行、差分,譬如 USB 和網絡通訊更勝一籌。 異步
(1)、異步:串口通訊的發送方和接收方之間是沒有統一的時鐘信號的。 (2)、電平信號:串口通訊出現的時間較早,速率較低,傳輸的距離較近,因此干擾還不太明顯,所以當時使用了電平信號傳輸。後期出現的傳輸協議都改爲差分信號傳輸了。 (3)、串行通訊:串口通訊每次同時只能傳輸 1 個二進制位函數
(1)電平信號是用信號線電平減去參考線電平獲得電壓差,這個電壓差決定了傳輸值是 1 仍是 0. (2)在電平信號時多少 V 表明 1,多少 V 表明 0 不是固定的,取決於電平標準。譬如 RS232電平中-3V~-15V 表示 1;+3~+15V 表示 0;TTL 電平則是+5V 表示 1,0V 表示 0. (3)無論哪一種電平都是爲了在傳輸線上表示 1 和 0.區別在於適用的環境和條件不一樣。RS232的電平定義比較大,適合干擾大、距離遠的狀況;TTL 電平電壓範圍小,適合距離近且干擾小的狀況。 (4)咱們臺式電腦後面的串口插座就是 RS232 接口的,在工業上用串口時都用這個,傳輸距離小於 15 米;TTL 電平通常用在電路板內部兩個芯片之間。 (5)對編程來講,RS232 電平傳輸仍是 TTL 電平是沒有差別的。因此電平標準對硬件工程師更有意義,而軟件工程師只要略懂便可。(把 TTL 電平和 RS232 電平混接是不能夠的) 性能
(1)衡量通信性能的一個很是重要的參數就是通信速率,一般以比特率(Bitrate)來表示,即每秒鐘傳輸的二進制位數,單位爲比特每秒(bit/s)。容易與比特率混淆的概念是「波特率」。(Baudrate),它表示每秒鐘傳輸了多少個碼元。而碼元是通信信號調製的概念,通信中經常使用時間間隔相同的符號來表示一個二進制數字,這樣的信號稱爲碼元。如常見的通信傳輸中,用 0V表示數字 0,5V 表示數字 1,那麼一個碼元能夠表示兩種狀態 0 和 1,因此一個碼元等於一個二進制比特位,此時波特率的大小與比特率一致;若是在通信傳輸中,有 0V、2V、4V以及 6V分別表示二進制數 00、0一、十、11,那麼每一個碼元能夠表示四種狀態,即兩個二進制比特位,因此碼元數是二進制比特位數的一半,這個時候的波特率爲比特率的一半。由於不少常見的通信中一個碼元都是表示兩種狀態,人們經常直接以波特率來表示比特率。譬如每秒種能夠傳輸 9600 個二進制位(傳輸一個二進制位須要的時間是 1/9600秒,也就是 104us),比特率就是 9600.但由於一個碼元都是表示兩種狀態,因此比特率=波特率。也一般說波特率就是 9600 (2)串口通訊的波特率不能隨意設定,而應該在一些值中去選擇。通常最多見的波特率是 9600或者 115200.爲何波特率不能夠隨便指定?主要是由於:第一,通訊雙方必須事先設定相同的波特率這樣才能成功通訊,若是發送方和接收方按照不一樣的波特率通訊則根本收不到,所以波特率最好是你們熟知的而不是隨意指定的。第二,經常使用的波特率通過長久發展,就造成了共識,你們經常使用就是 9600 或者 115200.優化
(1)串口通訊時,收發是一個週期一個週期進行的,沒週期傳輸 n 個二進制位。這一個週期就叫作一個通訊單元,一個通訊單元是由:起始位+數據位+奇偶校驗位+中止位組成的。 (2)起始位表示發送方要開始發送一個通訊單元;數據位是一個通訊單元中發送的有效信息位;奇偶校驗位是用來校驗數據位,以防止數據位出錯的;中止位是發送方用來表示本通訊單元結束標誌的。 (3)起始位的定義是串口通訊標準事先指定的,是由通訊線上的電平變化來反映的。 (4)數據位是本次通訊真正要發送的有效數據,串口通訊一次發送多少位有效數據是能夠設定的(通常可選的有 六、七、八、9,99%狀況下咱們都是選擇 8 位數據位。由於咱們通常經過串口發送的文字信息都是 ASCII 碼編碼的,而 ASCII 碼中一個字符恰好編碼爲 8 位。) (5)奇偶校驗位是用來給數據位進行奇偶校驗(把待校驗的有效數據逐個位的加起來,總和爲奇數奇偶校驗位就爲 1,總和爲偶數奇偶校驗位就爲 0)的,能夠在必定程度上防止位反轉。ui
(6)中止位的定義是串口通訊標準事先指定的,是由通訊線上的電平變化來反映的。常見的有 1 位中止位,1.5 位中止位,2 位中止位等。99%狀況下都是用 1 位中止位。 總結:串口通訊時由於是異步通訊,因此通訊雙方必須事先約定好通訊參數,這些通訊參數包括:波特率、數據位、奇偶校驗位、中止位(串口通訊中起始位定義是惟一的,因此通常不用選擇) 編碼
(1)單工就是單方向,雙工就是雙方同時收發,同時只能單方向可是方向能夠改變叫半雙工 (2)若是隻能 A 發 B 收則單工,A 發 B 收或者 B 發 A 收(兩個方向不能同時)叫半雙工,A發 B 收同時 B 發 A 收叫全雙工。 spa
(1)任何通訊都要有信息傳輸載體,或者是有線的或者是無線的。 (2)串口通訊是有線通訊,是經過串口線來通訊的。 (3)串口通訊線最少須要 2 根(GND 和信號線),能夠實現單工通訊,也可使用 3 根通訊線(Tx、Rx、GND)來實現全雙工。
(1)串口通訊屬於基層基本性的通訊規約,它本身自己不會去協商通訊參數,須要通訊前通訊雙方事先約定好通訊參數(波特率、數據位、奇偶校驗位、中止位等) (2)串口通訊的任何一個關鍵參數設置錯誤,都會致使通訊失敗。譬如波特率調錯了,發送方發送沒問題,接收方也能接收,可是接收到全是亂碼···
(1)、串口通訊的發送方每隔必定時間(時間固定爲 1/波特率,單位是秒)將有效信息(1或者 0)放到通訊線上去,逐個二進制位的進行發送。 (2)接收方經過定時(起始時間由讀到起始位標誌開始,間隔時間由波特率決定)讀取通訊線上的電平高低來區分發送給個人是 1 仍是 0。依次讀取數據位、奇偶校驗位、中止位,中止位就表示這一個通訊單元(幀)結束,而後中間是不定長短的非通訊時間(發送方有可能緊接着就發送第二幀,也可能半天都不發第二幀,這就叫異步通訊),下來就是第二幀····· 總結:第一,波特率很是重要,波特率錯了整個通訊就亂套了;數據位、奇偶校驗位、中止位也很重要,不然可能認不清數據。 第三,經過串口無論發數字、仍是文本仍是命令仍是什麼,都要先對發送內容進行編碼,編碼成二進制再進行逐個位的發送。 (3)串口發送的通常都是字符,通常都是 ASCII 碼編碼後的字符,因此通常設置數據位都是 8,方便恰好一幀發送 1 個字節。
串口通信的物理層有不少標準及變種,主要講解 RS-232 標準 ,RS-232標準主要規定了信號的用途、通信接口以及信號的電平標準。由於咱們常見的市面上的開發板在串口通信那一講都是關於RS-232標準的協議。 咱們這裏就不講啥結構體了,這些直接去看數據手冊就行了,講一下配置過程步驟,作到成竹在胸、心中有數。
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
複製代碼
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
複製代碼
/*鏈接 PA10 複用到 USART1_Rx*/
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
/*鏈接 PA9 複用到 USART1__Tx*/
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
複製代碼
STM32有不少的內置外設,這些外設的外部引腳都是與GPIO複用的。也就是說,一個GPIO若是能夠複用爲內置外設的功能引腳,那麼當這個GPIO做爲內置外設使用的時候,就叫作複用。例如串口1的發送接收引腳是PA9,PA10,當咱們把PA9,PA10不用做GPIO,而用作複用功能串口1的發送接收引腳的時候,叫端口複用。
/* GPIO初始化 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 配置Tx引腳爲複用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置Rx引腳爲複用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure);
複製代碼
串口初始化是經過 USART_Init()函數實現的。
/* 波特率設置:115200 */
USART_InitStructure.USART_BaudRate = 115200;
/* 字長(數據位+校驗位):8 */
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/* 中止位:1箇中止位 */
USART_InitStructure.USART_StopBits = USART_StopBits_1;
/* 校驗位選擇:偶校驗 */
USART_InitStructure.USART_Parity = USART_Parity_No;
/* 硬件流控制:不使用硬件流 */
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
/* USART模式控制:同時使能接收和發送 */
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* 完成USART初始化配置 */
USART_Init(USART1, &USART_InitStructure);
/* 使能串口 */
USART_Cmd(USART1, ENABLE);
/*開啓中斷 接收到數據產生中斷 進入中斷服務函數 */
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
複製代碼
初始化須要設置的參數爲:波特率,字長,中止位,奇偶校驗位,硬件數據流控制,模式(收,發)。 這裏面的USART_Cmd();函數很好理解,就是是能串口。USART_ITConfig();就是開啓中斷響應了,這裏面的第二個入口參數咱們通常寫的是USART_IT_RXNE
.也就是打開接收中斷,即程序在發送數據結束的時候要產生中斷,調到中斷服務函數中。
咱們前一章說了,只要你的程序裏面用了中斷,就必須配置串口優先級分組。可是咱們咱們有時候會發現咱們在一些廠家的串口例程中沒有配置串口中斷優先級分組,這是由於他程序只有一個串口中斷就沒有所謂的優先級,配不配置,程序都只有箇中斷。
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;//搶佔優先級3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子優先級0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
複製代碼
這個函數很重要,既然有中斷就要執行串口中斷服務函數中去,執行相應的指令。
void USART1_IRQHandler(void)
{
if(USART_GetITStatus( USART1, USART_IT_RXNE ) != RESET)//獲取接收中斷標誌位(接收到的數據必須是0x0d 0x0a結尾)
{
Res = USART_ReceiveData( USART1 );
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,從新開始
else USART_RX_STA|=0x8000;//接收完成了
}
else //還沒收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>( USART_Rec_Len-1))USART_RX_STA=0;//接收數據錯誤,從新開始接收
}
}
}
}
複製代碼
7.1 中斷服務函數名不是隨便起的,USART1_IRQHandler表示串口一箇中斷服務函數,函數名字在啓動文件startup_stm32f10x_hd.s文件中能夠找到。 7.2 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 見名知意,獲得串口中斷的標誌位,判斷是否發生串口1接收中斷,若是是串口接收中斷,則讀取串口接受到的數據。 7.3 Res =USART_ReceiveData(USART1);將從串口1讀取接收到的數據賦值給變量Res。 7.4 簡單的 接 收 協 議。通 if語句 , 配 合 一 個 數 組USART_RX_BUF[],一個接收狀態寄存器 USART_RX_STA(此寄存器其實就是一個全局變量,由讀者自行添加。因爲它起到相似寄存器的功能,這裏暫且稱之爲寄存器)實現對串口數據的接收管理。USART_RX_BUF 的大小由 USART_Rec_Len 定義,也就是一次接收的數據最大不能超過 USART_Rec_Len 個字節。USART_RX_STA 是一個接收狀態寄存器。 接收到從電腦串口調試助手發過來的數據,把接收到的數據保存在 USART_RX_BUF 中,同時在接收狀態寄存器(USART_RX_STA)中計數接收到的有效數據個數,當收到回車(回車的表示由 2 個字節組成:回車符的ASCII碼是0X0D 和換行符的ASCII碼是 0X0A)的第一個字節 0X0D 時,計數器將再也不增長,等待0X0A 的到來,而若是 0X0A 沒有來到,則認爲此次接收失敗,從新開始下一次接收。若是順利接收到 0X0A,則標記 USART_RX_STA 的第 15 位,這樣完成一次接收,並等待該位被其餘程序清除,從而開始下一次的接收,而若是遲遲沒有收到 0X0D,那麼在接收數據超過 USART_Rec_Len 的時候,則會丟棄前面的數據,從新接收。
這個自定義的發送函數是咱們直接在程序中發送數據到串口調試助手,這個自定義的函數和中斷服務函數無關,由於中斷服務函數是用來接收數據的,就是咱們經過串口調試助向單片機發送數據,須要用到中斷服務函數。換句話說,若是你只想經過單片機將數據發送到串口調試助手的話,就不須要寫中斷服務函數了。可是這樣作法沒有意義,咱們用串口是主要是接收數據的,單單發送一個數據意義不大。
/***************** 發送一個字符 **********************/
static void Uart_SendByte(uint8_t ch)
{
/* 發送一個字節數據到USART1 */
USART_SendData(USART1,ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//獲取發送狀態
}
/***************** 發送字符串 **********************/
void Uart_SendString(uint8_t *str)
{
uint8_t k=0;
do
{
Uart_SendByte(*(str + k) );
k++;
} while(*(str + k)!='\0');
}
/***************** 指定長度的發送字符串 **********************/
void Uart_SendStr_length(uint8_t *str,uint32_t strlen )
{
uint8_t k=0;
do
{
Uart_SendByte( *(str + k) );
k++;
} while(k < strlen);
}
複製代碼
咱們這裏編寫了三個函數主要是功能是:向單片機發送一個字符、向單片機發送一個字符串、向單片機發送指定長度的字符串。 代碼很短,很少解釋。在發送字節的時候用到了獲取狀態函數,獲取發送數據的寄存器第7位的狀態,當爲1是,數據傳送到移位寄存器。傳送完了就能夠發送了。
int main(void)
{
char ch;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART1_Init();
Uart_SendString( (uint8_t *)"這條數據是來自單片機發送的數據\n" );
Uart_SendString( (uint8_t *)"輸入數據並以回車鍵結束\n" );
while(1)
{
}
}
複製代碼
首先咱們須要調用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);完成中斷優先級的分組設置。再調用USART1_Init函數完成 USART 初始化配置,包括 GPIO配置,USART配置,接收中斷使用等等信息。接下來就能夠調用字符發送函數把數據發送給串口調試助手了。最後主函數什麼都不作,只是靜靜地等待 USART接收中斷的產生,並在中斷服務函數把數據回傳。
保證開發板相關硬件鏈接正確,用 USB 線鏈接開發板的串口調試usb接口跟電腦,在電腦端打開串口調試助手,配置到波特率、校驗位、數據位、中止位。把編譯好的程序下載到開發板,此時串口調試助手便可收到開發板發過來的數據。咱們在串口調試助手發送區域輸入任意字符,點擊發送按鈕,立刻在串口調試助手接收區便可看到相同的字符。由於我在家裏手上沒有串口線,因此不能演示截圖了。
單片機及收到後,執行相應的指令。咱們不只僅能夠將數據發送到串口調試助手,咱們還能夠在串口調試助手發送數據給控制器,控制器程序根據接收到的數據進行下一步工做。首先,咱們來編寫一個程序實現開發板與電腦通訊,在開發板上電時經過 USART發送一串字符串給電腦,而後開發板進入中斷接收等待狀態,若是電腦有發送數據過來,開發板就會產生中斷,咱們在中斷服務函數接收數據,並立刻把數據返回發送給電腦。
//重定向c庫函數printf到串口,重定向後可以使用printf函數
int fputc(int ch, FILE *f)
{
/* 發送一個字節數據到串口 */
USART_SendData(USART1, (uint8_t) ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向c庫函數scanf到串口,重寫向後可以使用scanf、getchar等函數
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
複製代碼
在 C 語言標準庫中,fputc函數是 printf 函數內部的一個函數,功能是將字符 ch寫入到文件指針 f所指向文件的當前寫指針位置,簡單理解就是把字符寫入到特定文件中。咱們使用 USART 函數從新修改 fputc函數內容,達到相似「寫入」的功能。fgetc 函數與 fputc 函數很是類似,實現字符讀取功能。在使用 scanf函數時須要注意字符輸入格式。 還有一點須要注意的,使用 fput和 fgetc函數達到重定向 C語言標準庫輸入輸出函數必須在 MDK的工程選項把「Use MicroLIB」勾選上,MicoroLIB 是缺省 C庫的備選庫,它對標準 C 庫進行了高度優化使代碼更少,佔用更少資源。爲使用 printf、scanf 函數須要在文件中包含 stdio.h頭文件。
int main(void)
{
char ch;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
USART1_Init();
Uart_SendString( (uint8_t *)"這條數據是來自串口發送的\n" );
Uart_SendString( (uint8_t *)"輸入數據並以回車鍵結束\n" );
while(1)
{
ch=getchar();
printf("接收到字符:%c\n",ch);
switch(ch)
{
case '1':
printf("LED燈亮");
GPIO_ResetBits(GPIOH,GPIO_Pin_12);//PH12接了一個LED燈
break;
default:
break;
}
}
}
複製代碼
至此串口通信的編程詳解就結束了,如今你會了嗎?