參考博客:江科大STM32筆記
Stm32外設?
一、GPIO
基礎?
GPIO位結構
?
I/O引腳的保護二極管是對輸入電壓進行限幅的上面的二極管接VDD, 3.3V,下面接VSS, 0V,當輸入電壓
- >3.3V
那上方這個二極管就會導通,輸入電壓產生的電流就會大部分充入VDD而不會流入內部電路;
? - <0V(這個電壓是相對于VSS的電壓,所以是可以有負電壓的)
小于0的情況大概率是電源反接,此時,那這時下方這個二極管就會導通,電流會從I/O直接流出去,然后再流到地,而不會從內部電路汲取電流,也是可以保護內部電路的;
? - 在0~3.3v之間
那兩個二極管均不會導通,這時二極管對電路沒有影響,這就是保護二極管的用途。
開關:如果上面導通、下面斷開,就是上拉輸入模式;如果下面導通、上面斷開,就是下拉輸入模式;如果兩個都斷開,就是浮空輸入模式。
上拉和下拉的作用——>為了給輸入提供一個默認的輸入電平
因為對應一個數字的端口,輸入不是高電平就是低電平,那如果輸入引腳什么都不接,那就不確定算高電平還是低電平。而實際情況是,如果啥也不接,這時輸入就會處于一種浮空的狀態,引腳的輸入電平極易受外界干擾而改變。為了避免引腳懸空導致的輸入數據不確定,我們就需要在這里加上拉或者下拉電阻了,如果接入上拉電阻,當引腳懸空時,還有上拉電阻來保證引腳的高電平,所以上拉輸入又可以稱作是默認為高電平的輸入模式。下拉也是同理,就是默認為低電平的輸入方式。
注:
1、這個上拉電阻和下拉電阻的阻值都是比較大的,是一種弱上拉和弱下拉,目的是盡量不影響正常的輸入操作,所以當IO引腳有電流時,不會經過上下拉電阻。
2、只有輸入才有上下拉輸入,因為只有輸入才要確定一個高電平或者低電平
?英文原文檔是施密特觸發器,(模電里這叫遲滯/滯回比較器,也就是施密特觸發器的電路)
施密特觸發器的作用就是對輸入電壓進行整形的,它的執行邏輯是,如果輸入電壓大于某一閾值,輸出就會瞬間升為高電平,如果輸入電壓小于某一閾值,輸出就會瞬間降為低電平。?
接下來經過施密特觸發器整形的波形就可以直接寫入輸入數據寄存器了,我們再用程序讀取數據輸存器對應某一位的數據,就可以知道端口的輸入電平了。最后上面這還有兩路線路,這些就是連接到片上外設的一些端口,其中有模擬輸入,這個是連接到ADC上的,因為ADC需要接收模擬量,所以這根線是接到施密特觸發器前面的;另一個是復用功能輸入,這個是連接到其他需要讀取端口的外設上的,比如串口的輸入引腳等,這根線接收的是數字量,所以在施密特觸發器后面。
輸出部分可以由 輸出數據寄存器或片上外設 控制,兩種控制方式通過這個數據選擇器接到了輸出控制部分。
如果選擇通過輸出數據寄存器進行控制,就是普通的IO口輸出,寫這個數據寄存器的某一位就可以操作對應的某個端口了。
位設置/清除寄存器:這個可以用來單獨操作輸出數據寄存器的某一位,而不影響其它位。因為這個輸出數據寄存器同時控制16個端口,并且這個寄存器只能整體讀寫,所以如果想單獨控制其中某一個端口而不影響其他端口的話,就需要一些特殊的操作方式。?
- 第一種方式是先讀出這個寄存器,然后用 按位與 和 按位或 的方式更改某一位,最后再將更改后的數據寫回去,在C語言中就是&=和 |=的操作,這種方法比較麻煩,效率不高,對于IO口的操作而言不太合適;
-
第二種方式是通過設置這個位設置和位清除寄存器,如果我們要對某一位進行置1的操作,在位設置寄存器的對應位寫1便可,剩下不需要操作的位寫0,這樣它內部就會有電路,自動將輸出數據寄存器中對應位置為1,而剩下寫0的位則保持不變,這樣就保證了只操作其中某一位而不影響其它位,并且這是一步到位的操作。如果想對某一位進行清0的操作,就在位清除寄存器的對應位寫1即可,這樣內部電路就會把這一位清0了,這就是第二種方式也就是這個位設置和位清除寄存器的作用。【作用:將設置/清除寄存器的某一位寫1/0就能達到單獨影響輸出寄存器的某一位,從而單獨影響某個端口】
-
第三種操作方式【了解即可】 ,就是讀寫STM32中的“位帶”區域,這個位帶的作用就跟51單片機的位尋址作用差不多,在STM32中,專門分配的有一段地址區域,這段地址映射了RAM和外設寄存器所有的位,讀寫這段地址中的數據,就相當于讀寫所映射位置的某一位,這就是位帶的操作方式,這個方式我們本課程暫時不會用到。我們的教程主要使用的是庫函數來操作的,庫函數使用的是讀寫位設置和位清除寄存器的方法
上面是P-MOS,下面是N-MOS,這個MOS管就是一種電子開關,我們的信號來控制開關的導通和關閉,開關負責將IO口接到VDD或者VSS,
在這里可以選擇推挽、開漏或關閉三種輸出方式。?
-
推挽輸出模式
在推挽輸出模式下,P-MOS和N-MOS均有效,數據寄存器為1時,上管導通,下管斷開,輸出直接接到VDD,就是輸出高電平,數據寄存器為0時,上管斷開,下管導通,輸出直接接到VSS,就是輸出低電平,這種模式下,高低電平均有較強的驅動能力,所以推挽輸出模式也可以叫強推輸出模式。在推挽輸出模式下,STM32對IO口具有絕對的控制權,高低電平都由STM32說的算。 -
開漏輸出模式
在開漏輸出模式下,這個P-MOS是無效的,只有N-MOS在工作,數據寄存器為1時,下管斷開,這時輸出相當于斷開,也就是高阻模式;數據寄存器為0時,下管導通,輸出直接接到VSS,也就是輸出低電平;這種模式下,只有低電平有驅動能力,高電平是沒有驅動能力的。那這個模式有什么用呢,這個開漏模式可以作為通信協議的驅動方式,比如I2C通信的引腳,就是使用的開漏模式,在多機通信的情況下,這個模式可以避免各個設備的相互干擾,另外開漏模式還可以用于輸出5V的電平信號。
比如在I0口外接一個上拉電阻到5V的電源,當輸出低電平時,由內部的N-MOS直接接VSS,當輸出高電平時,由外部的上拉電阻拉高至5V,這樣就可以輸出5V的電平信號,用于兼容一些5V電平的設備,這就是開漏輸出的主要用途。?
開漏模式下,輸出1時,兩個mos管都相當于關斷,左側相當于斷路。外接5V的電能只能流向右側,故輸出5V。反之,輸出0時,左下方mos管導通,外接5V的電能流到左下方Vss,且兩者之間幾乎沒有電壓降,可看做5V電壓降在了上拉電阻上,故引腳輸出0V?
- 關閉
剩下的一種狀態就是關閉,這個是當引腳配置為輸入模式的時候,這兩個MOS管都無效,也就是輸出關閉,端口的電平由外部信號來控制。
GPIO八種工作模式?
模式 | Mode |
模擬輸入模式 | GPIO_Mode_AIN |
浮空輸入模式 | GPIO_Mode_IN_FLOATING |
下拉輸入模式 | GPIO_Mode_IPD |
上拉輸入模式 | GPIO_Mode_IPU |
通用開漏輸出模式 | GPIO_Mode_Out_OD |
通用推挽輸出模式 | GPIO_Mode_Out_PP |
復用開漏輸出模式 | GPIO_Mode_AF_OD |
復用推挽輸出模式 | GPIO_Mode_AF_PP |
?輸入模式
當I/O端口配置為輸入時:
- 輸出緩沖器被禁止?
- 施密特觸發輸入被激活
- 根據輸入配置(上拉,下拉或浮動)的不同,弱上拉和下拉電阻被連接
- 出現在I/O腳上的數據在每個APB2時鐘被采樣到輸入數據寄存器
- 對輸入數據寄存器的讀訪問可得到I/O狀態?
?輸出模式
當I/O端口配置為輸出時:
- 輸出緩沖器被激活─ 開漏模式:輸出寄存器上的 ’ 0 ’ 激活 N-MOS ,而輸出寄存器上的 ’ 1 ’ 將端口置于高阻狀態 (P-MOS 從不被激活 ) 。─ 推挽模式:輸出寄存器上的 ’ 0 ’ 激活 N-MOS ,而輸出寄存器上的 ’ 1 ’ 將激活 P-MOS 。
- 施密特觸發輸入被激活
- 弱上拉和下拉電阻被禁止
- 出現在I/O腳上的數據在每個APB2時鐘被采樣到輸入數據寄存器
- 在開漏模式時,對輸入數據寄存器的讀訪問可得到I/O狀態
- 在推挽式模式時,對輸出數據寄存器的讀訪問得到最后一次寫的值。
?復用功能模式
當I/O端口配置為復用功能時:?
- 在開漏或推挽式配置中,輸出緩沖器被打開
- 內置外設的信號驅動輸出緩沖器 ( 復用功能輸出 )
- 施密特觸發輸入被激活
- 弱上拉和下拉電阻被禁止
- 在每個 APB2 時鐘周期,出現在 I/O 腳上的數據被采樣到輸入數據寄存器
- 開漏模式時,讀輸入數據寄存器時可得到 I/O 口狀態
- 在推挽模式時,讀輸出數據寄存器時可得到最后一次寫的值
?模擬輸入模式
當I/O端口配置為模擬輸入時:??
- 輸出緩沖器被禁止;
- 禁止施密特觸發輸入,實現了每個模擬 I/O 引腳上的零消耗。施密特觸發輸出值被強置為 ’0’ ;
- 弱上拉和下拉電阻被禁止;
- 讀取輸入數據寄存器時數值為 ’0’ 。
AFIO?
作用:
1、復用與重映射
2、引腳選擇(EXIT外部中斷)?
?通用模式下,CPU直接去控制IO引腳
?復用模式下,CPU將控制權交給了其它片上外設
項目:按鍵控制LED?
LED.C
#include "stm32f10x.h" // Device headervoid LED_Init_A(uint16_t GPIO_Pin)
{//開啟GPIOA的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//初始化GPIOAGPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//開漏模式GPIO_InitStruct.GPIO_Pin = GPIO_Pin;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);
}void LED_ON_A(uint16_t GPIO_Pin)
{GPIO_SetBits(GPIOA,GPIO_Pin);
}void LED_OFF_A(uint16_t GPIO_Pin)
{GPIO_ResetBits(GPIOA,GPIO_Pin);
}void LED_Turn_A(uint16_t GPIO_Pin)
{if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin) == 0)//引腳為低電平轉變為高電平{GPIO_SetBits(GPIOA,GPIO_Pin);}else//引腳為和高電平轉變為低電平{GPIO_ResetBits(GPIOA,GPIO_Pin);}
}
?Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"void Key_Init_B(uint16_t GPIO_Pin)
{//開啟GPIOB的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//初始化GPIOBGPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;//上拉輸入GPIO_InitStruct.GPIO_Pin = GPIO_Pin;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);
}uint8_t Key_GetNum(uint16_t GPIO_Pin)
{uint8_t KeyNum = 0; switch(GPIO_Pin){case GPIO_Pin_1:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 1; //置鍵碼為1}break;case GPIO_Pin_2:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 2; //置鍵碼為1}break;case GPIO_Pin_3:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 3; //置鍵碼為1}break;case GPIO_Pin_4:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 4; //置鍵碼為1}break;case GPIO_Pin_5:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 5; //置鍵碼為1}break;case GPIO_Pin_6:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 6; //置鍵碼為1}break;case GPIO_Pin_10:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 10; //置鍵碼為1}break;case GPIO_Pin_11:if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //讀PB1輸入寄存器的狀態,如果為0,則代表按鍵1按下{Delay_ms(20); //延時消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按鍵松手Delay_ms(20); //延時消抖KeyNum = 11; //置鍵碼為1}break;}return KeyNum;
}
?main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"int main(void)
{LED_Init_A(GPIO_Pin_1 | GPIO_Pin_2);Key_Init_B(GPIO_Pin_10 | GPIO_Pin_11);while(1){uint8_t keyNum = Key_GetNum(GPIO_Pin_10 | GPIO_Pin_11);if(keyNum == 10){LED_Turn_A(GPIO_Pin_10);}else if(keyNum == 11){LED_Turn_A(GPIO_Pin_11);}}}
二、EXTI外部中斷?
基礎
?
三、USART
基礎
通信接口?
?USART和UART的區別
USART(universal synchronous asynchronous receiver and transmitte): 通用同步異步收發器
????????USART是一個串行通信設備,可以靈活地與外部設備進行全雙工數據交換。
UART(universal asynchronous receiver and transmitter): 通用異步收發器
????????異步串行通信口(UART)就是我們在嵌入式中常說的串口,它還是一種通用的數據通信議。 ? ? ??
- 異步通信:沒有共享時鐘信號,每個字符前后有起始位和停止位,適用于較低速的數據傳輸。
- 同步通信:共享時鐘信號,數據傳輸速率較高,但需要額外的時鐘信號支持。? ? ? ? ? ? ? ? ? ??
注:是不是同步就看是不是共用了一根時鐘線,共用就是同步,反之則是異步?
區別:
????????USART是指單片機的一個端口模塊,可以根據需要配置成同步模式(SPI,I2C),也可以將其配置為異步模式,而UART只能是異步模式。所以說UART姑且可以稱之為一個與SPI,I2C對等的“協議”,而USART則不是一個協議,而是更應該理解為一個實體。
????????相比于同步通訊,UART不需要統一的時鐘線,接線更加方便。但是,為了正常的對信號進行解碼,使用UART通訊的雙方必須事先約定好波特率,即單位事件內傳輸碼元的個數。
UART的連接方式?
1、芯片與芯片的連接方式?
?
2、?芯片與PC機的連接方式
?
交叉連接,己方Tx連接對方的Rx,己方Rx連接對方Tx
?數據幀?
?注:發送數據要倒著發 也就是 小端的方式
空閑狀態:高電平
起始位:發送方將線路拉低表示一幀到來(低電平),長度為1
數據位:低電平0,高電平1,總長度是8位或9位
校驗位:奇偶校驗,可以沒有,也可以是數據位最后一位
停止位:發送方將線路拉高表示一幀結束(高電平),長度可以是0.5,1,2
校驗位和數據位
奇偶校驗規則?
采用9位奇校驗的方式,也就是8位數據位 1位校驗位
發送方發送1010 1010時 發現‘1’的個數是偶數 所以校驗位要補個‘1’
假如接收方有一個‘1’變成‘0’ 然后 ‘1’的總個數為4 是偶數 所以數據傳輸出錯
波特率?
波特率:單位時間內發送的碼元個數,這里一個碼元就是1位,所以看成比特率應該也沒問題?
?波特率9600:一秒發送9600個碼元,也就是一秒發送9600個比特,每個比特的發送需要消耗0.104ms
硬件流控?
發送方發送數據后,并不知道接收方是否已經接收并保存數據了,如果還沒有接收并保存,發送方發送數據過快(比如發送方發送了10個幀,接收方只來得及保存了1個),可能會使舊數據因新數據覆蓋而丟失數據,可以讓接收方收到數據后返回一個應答表示數據已收到了,發送到收到答復才繼續發。?
?帶硬件流控的連線圖:
接收方接收數據寄存器為空時,rts為低電平,發送方cts收到低電平,us發送方把數據發給接收方,接收方完全接收這一幀后,把rts置高電平表示暫時無法接收數據,發送方cts收到高電平,于是暫停發送數據,等到接收方將輸入數據寄存器的值讀走后,接收方rts為低電平,發送方cts收到低電平,便可以進行下一個數據幀的發送