1. 程式人生 > 其它 >2021/10/15 智慧傢俱 嵌入式實訓 第五天 串列埠通訊 (1)

2021/10/15 智慧傢俱 嵌入式實訓 第五天 串列埠通訊 (1)

通訊的兩種方式:

並行通訊

  -傳輸原理:資料各個位同時傳輸。
  -優點:速度快
   -缺點:佔用引腳資源多

序列通訊

  -傳輸原理:資料按位順序傳輸。
  -優點:佔用引腳資源少
  -缺點:速度相對較慢

序列通訊分類(按照資料傳送方向)

  單工(a):
     資料傳輸只支援資料在一個方向上傳輸

  半雙工(b):
     允許資料在兩個方向上傳輸,但是,在某一時刻,只允許資料在一個方向上傳輸,它實際上是一種切換方向的單工通訊;

  全雙工(c):
     允許資料同時在兩個方向上傳輸,因此,全雙工通訊是兩個單工通訊方式的結合,它要求傳送裝置和接收裝置都有獨立的接收和傳送能力。

序列通訊的通訊方式

同步通訊(帶時鐘同步訊號傳輸):

     SPI,IIC通訊介面

非同步通訊(不帶時鐘同步訊號傳輸):

    UART(通用非同步收發器),單匯流排 約定傳輸速度

STM32的串列埠通訊介面

  UART:通用非同步收發器。
  USART:通用同步非同步收發器。
    大容量STM32F10x系列晶片,包含3個USART和2個UART

UART非同步通訊方式引腳連線方法:

  -RXD:資料輸入引腳。資料接受。
  -TXD:資料傳送引腳。資料傳送。

UART非同步通訊方式特點:

全雙工非同步通訊。

  分數波特率發生器系統,提供精確的波特率。
 -傳送和接受共用的可程式設計波特率,最高可達4.5Mbits/s
  可程式設計的資料字長度(8位或者9位);
  可配置的停止位(支援1或者2位停止位);
  可配置的使用DMA多緩衝器通訊。
  單獨的傳送器和接收器使能位。
  檢測標誌:① 接受緩衝器 ②傳送緩衝器空 ③傳輸結束標誌
  多個帶標誌的中斷源。觸發中斷。
  其他:校驗控制,四個錯誤檢測標誌。

串列埠通訊過程

傳送資料:通過匯流排往USARTx控制器中的DR暫存器(TDR),寫入資料,USARTx控制器會自動通過Tx管腳傳送出去。

接收資料:USARTx控制通過Rx管腳接收到位資料,組合成8位資料,存在DR暫存器(RDR),直接讀取DR暫存器。

STM32串列埠非同步通訊需要定義的引數

1 起始位
2 資料位(8位或者9位)
3 奇偶校驗位(第9位)
4 停止位(1,15,2位)
5 波特率設定

協議規定了什麼?硬體層、電平標準、通訊資料格式、傳輸速率

開始位 +資料位 +奇偶校驗位 +停止位

位數 1 5~8 0~1 1

電平 0 0/1 0/1 1

開始位:低電平 -- 裝置檢測下降沿,代表開始

資料位:5 -- 0000 0101 5~8位 -- 8

奇偶校驗位:校驗一幀資料是否完整

奇偶校驗:資料位中1的個數+奇偶位中1的個數之和

如果是奇校驗:個數之和必須為奇數。

如果是偶校驗:個數之和必須為偶數。

例如:傳送方:0110 0011 -- 0x63 採用奇校驗 奇偶校驗位為1

接收方:0110 0011 1 --- 正確

0100 0011 1 --- 錯誤

0000 0011 1 --- 奇偶校驗正確,資料錯誤 -- 現在採用CRC校驗。

停止位:高電平 -- 匯流排空閒狀態為高電平

常用的幀格式:1+8+0+1 -- 1個開始位+8個數據位+0個奇偶校驗位+1個停止位

通訊速率:波特率 bps 每秒鐘傳送的位數,常見的波特率:9600 115200等等

進行通訊的兩個裝置,波特率必須一樣

常用的串列埠相關暫存器

  USART_SR狀態暫存器
  USART_DR資料暫存器
  USART_BRR波特率暫存器 (填寫下面計算後的數值)
  USART_CR1控制暫存器
根據該圖可瞭解序列通訊的相關配置。根據下半圖,可得到波特率的計算方法:

串列埠操作相關庫函式

void USART_Init(); //串列埠初始化:波特率,資料字長,奇偶校驗,硬體流控以及收發使能
void USART_Cmd();//使能串列埠
void USART_ITConfig();//使能相關中斷

void USART_SendData();//傳送資料到串列埠,DR
uint16_t USART_ReceiveData();//接受資料,從DR讀取接受到的資料

FlagStatus USART_GetFlagStatus();//獲取狀態標誌位,SR
void USART_ClearFlag();//清除狀態標誌位,SR
ITStatus USART_GetITStatus();//獲取中斷狀態標誌位,SR
void USART_ClearITPendingBit();//清除中斷狀態標誌位,SR

實驗4串列埠實驗的程式碼中FWLib資料夾下stm32f10x_usart.c下的stm32f10x_usart.h下,可找到相關函式的宣告與定義,然後可再進行追溯。

串列埠配置一般步驟

 ①串列埠時鐘使能,GPIO時鐘使能:RCC_APB2PeriphClockCmd();
  ②串列埠復位:USART_DeInit(); 這一步不是必須的
  ③GPIO埠模式設定:GPIO_Init(); 模式設定為GPIO_Mode_AF_PP
  ④串列埠引數初始化:USART_Init();
  ⑤開啟中斷並且初始化NVIC(如果需要開啟中斷才需要這個步驟)
    NVIC_Init();
    USART_ITConfig();
  ⑥使能串列埠:USART_Cmd();
  ⑦編寫中斷處理函式:USARTx_IRQHandler();
 ⑧串列埠資料收發:
    void USART_SendData();//傳送資料到串列埠,DR
    uint16_t USART_ReceiveData();//接受資料,從DR讀取接受到的資料
⑨串列埠傳輸狀態獲取:
    FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
    void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

具體實現

第一步:新建模版,並使能串列埠時鐘和GPIO時鐘。

跟之前一樣,先建立一個簡單的模版。
  在USER資料夾下找到system_stm32f10x.c下的stm32f10x_rcc.h.中找到void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);之前就知道這個函式是用來使能的,然後先Go To xxx找到函式的定義後,在函式中assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));

檢視函式中的第一個引數RCC_APB2PeriphGo To xxx後我們可以看到,該函式即可使能GPIOA又可使能USART1。故使能語句可這麼編寫

void My_USART1_Init(){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE);
}

串列埠復位非必須,故可省略。

第二步:GPIO埠模式設定

在USER資料夾下找到system_stm32f10x.c下的stm32f10x_gpio.h.中找到void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);該函式之前已經講解過了,程式可修改為:


另外一個USARTX_RX是輸入 ,根據上面的圖知道設為上拉輸入或者浮空

    //2. 配置模式
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//推輓輸出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
void My_USART1_Init(){
    GPIO_InitTypeDef GPIO_InitStructure; //定義結構體
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);    //使能GPIO時鐘
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE);    //使能串列埠時鐘
    
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;    //設定為複用推輓輸出
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9;    //引腳9
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;    //速度10MHz
    GPIO_Init(GPIOA,&GPIO_InitStructure);    //GPIOA模式設定
    
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;//浮空輸入
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10;    //引腳10
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;    //速度10MHz
    GPIO_Init(GPIOA,&GPIO_InitStructure);    //GPIOA模式設定
    
}

第三步:串列埠引數初始化和使能串列埠

在USER資料夾下找到system_stm32f10x.c下的stm32f10x_usart.h.中找到void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);、void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);,程式可修改為:

/* 配置USART模式 */
    //1. 時鐘使能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    //2. 模式配置
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = brr;                                        //波特率   (資料傳輸速度)
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //非硬體流 (如何決定收發的時機)
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                 //收發模式 (全雙工)
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     //RS232協議(資料格式):資料位     8個
    USART_InitStructure.USART_Parity = USART_Parity_No;                                //RS232協議(資料格式):奇偶校驗位 0個
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                            //RS232協議(資料格式):停止位     1個
    USART_Init(USART1, &USART_InitStructure);
void My_USART1_Init(){
    GPIO_InitTypeDef GPIO_InitStructure; //定義GPIO結構體
    USART_InitTypeDef USART_InitStructure;//定義USART結構體
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);    //使能GPIO時鐘
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE);    //使能串列埠時鐘
    
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;    //設定為複用推輓輸出
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9;    //引腳9
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;    //速度10MHz
    GPIO_Init(GPIOA,&GPIO_InitStructure);    //GPIOA模式設定
    
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;//浮空輸入
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10;    //引腳10
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;    //速度10MHz
    GPIO_Init(GPIOA,&GPIO_InitStructure);    //GPIOA模式設定
    
    
    USART_InitStructure.USART_BaudRate=115200;//波特率
    USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None ;//硬體流:無
    USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//TX和RX都使能故使用|
    USART_InitStructure.USART_Parity=USART_Parity_No;//奇偶校驗位:無
    USART_InitStructure.USART_StopBits=USART_StopBits_1;//停止位:1
    USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字長:8位
    
    
    USART_Init(USART1,&USART_InitStructure);//串列埠引數初始化
    USART_Cmd(USART1,ENABLE);    //使能串列埠
}

第四步:開啟中斷並且初始化NVIC

  在FWLIB資料夾下找到misc下找到void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup),函式中找到;assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));,在Go To xxx,檢視填寫格式,選擇NVIC_PriorityGroup_2,即:兩位響應優先順序和兩位搶佔優先順序。

  在USER資料夾下找到system_stm32f10x.c下的stm32f10x_usart.h.中找到void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)

寫NVIC函式

  //3. 配置接收中斷(串列埠回顯時註釋掉)
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);            //接收中斷開啟
    NVIC_InitTypeDef NVIC_InitStruct = {0};
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStruct);
//4. 初始狀態 -- 使能
    USART_Cmd(USART1, ENABLE);

第五步:編寫中斷處理函式

在檔案stm32f10x_usart.h檔案下開啟:ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);

void USART1_IRQHandler(void){
    u8 res;
    if(USART_GetITStatus(USART1,USART_IT_RXNE)){//若是接收到中斷
        res=USART_ReceiveData(USART1);//接收資料
        USART_SendData(USART1,res);//接收到在傳送資料,才可以在串列埠監視器中看到資料
        
    }
}

最後編譯就通過了,注意要把模版中資料夾SYSTEM中的uart刪除,因為定義重複了。

之後就可以上傳程式,然後利用串列埠偵錯程式,注意偵錯程式中的設定要跟程式一樣,波特率為115200,停止位1,等等。最終的實驗現象就是傳送什麼,最後在除錯軟體中就會看到什麼。

SYSTEM資料夾下,usart.c檔案中,可看到以下程式碼(實驗4串列埠實驗):

main函式中呼叫函式

void USART1_IRQHandler(void)                    //串列埠1中斷服務程式
    {
    u8 Res;
#if SYSTEM_SUPPORT_OS         //如果SYSTEM_SUPPORT_OS為真,則需要支援OS.
    OSIntEnter();    
#endif
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中斷(接收到的資料必須是0x0d 0x0a結尾)
        {
        Res =USART_ReceiveData(USART1);    //讀取接收到的資料
        
        if((USART_RX_STA&0x8000)==0)//接收未完成,判斷最高位是不是0,是0表示接收未完成,則往下執行。若為1則不往下執行(上一次接收沒清空)
            {
            if(USART_RX_STA&0x4000)//接收到了0x0d,若第二位為1則表示接收到0x0d,在往下判斷下一位是不是0x0a,若不是重新開始,是的話則則給第一位置1
                {
                if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始
                else USART_RX_STA|=0x8000;    //接收完成了 
                }
            else //還沒收到0X0D,第二位不是1,則判斷什麼時候接收到0x0d時,將第二位置為1
                {    
                if(Res==0x0d)USART_RX_STA|=0x4000;
                else//若一直沒收到的話,則一直講Res變數中的數值給變數,且通過位與來判斷該位是否溢位(若第1、2位有資料給他清零)
                    {
                    USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
                    USART_RX_STA++;
                    if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收資料錯誤,重新開始接收      
                    }         
                }
            }            
     } 
#if SYSTEM_SUPPORT_OS     //如果SYSTEM_SUPPORT_OS為真,則需要支援OS.
    OSIntExit();                                               
#endif
} 
#endif    

實驗4程式碼詳細解釋

usart.h

#ifndef __USART_H
#define __USART_H
#include "stdio.h"    
#include "sys.h" 
//////////////////////////////////////////////////////////////////////////////////     
//本程式只供學習使用,未經作者許可,不得用於其它任何用途
//ALIENTEK STM32開發板
//串列埠1初始化           
//正點原子@ALIENTEK
//技術論壇:www.openedv.com
//修改日期:2012/8/18
//版本:V1.5
//版權所有,盜版必究。
//Copyright(C) 廣州市星翼電子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改說明 
//支援適應不同頻率下的串列埠波特率設定.
//加入了對printf的支援
//增加了串列埠接收命令功能.
//修正了printf第一個字元丟失的bug
//V1.4修改說明
//1,修改串列埠初始化IO的bug
//2,修改了USART_RX_STA,使得串列埠最大接收位元組數為2的14次方
//3,增加了USART_REC_LEN,用於定義串列埠最大允許接收的位元組數(不大於2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改說明
//1,增加了對UCOSII的支援
#define USART_REC_LEN              200      //定義最大接收位元組數 200
#define EN_USART1_RX             1        //使能(1)/禁止(0)串列埠1接收
          
extern u8  USART_RX_BUF[USART_REC_LEN]; //接收緩衝,最大USART_REC_LEN個位元組.末位元組為換行符 
extern u16 USART_RX_STA;                 //接收狀態標記    
//如果想串列埠中斷接收,請不要註釋以下巨集定義
void uart_init(u32 bound);
#endif

bound 是波特率

extern 定義了一些變數 外部變數

接收從電腦傳來的資料存入buf

0x0D=回車 0x0A=換行 二個結束符

接收完成 bit15-1 bit14-1 bit13~0 ----接收的位數

void USART1_IRQHandler(void)                    //串列埠1中斷服務程式
{
    u8 Res; 
#if SYSTEM_SUPPORT_OS         //如果SYSTEM_SUPPORT_OS為真,則需要支援OS.
    OSIntEnter();    
#endif
    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 ;//=0x3fff最多2^13-1資料
                    USART_RX_STA++;//有效資料個數++
                    if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收資料錯誤,重新開始接收      
                    }         //有效的資料個數不能大於定義的資料個數
                }
            }            
     } 
#if SYSTEM_SUPPORT_OS     //如果SYSTEM_SUPPORT_OS為真,則需要支援OS.
    OSIntExit();                                               
#endif
} 
#endif    

工程檔案可供參考:

https://wwa.lanzoui.com/iG84Jvh1loj