STM32時鍾和GPIO配置


STM32F1xx 系統時鍾

image-20201111160906639

來源:STM32F1中文參考手冊 6.2時鍾

時鍾的作用

決定了程序執行的速度,給芯片提供一個穩定的執行頻率

  1. STM32F103R8 最高速率是多少??

​ 72 MHz maximum frequency

  1. 如果采用最高頻率:執行一條指令 1/72M s ==> 1/72us

  2. 精簡指令集:幾乎所有的指令都是消耗一個時鍾節拍(1/72 us)執行

R8的時鍾來源

  1. 高速外部時鍾信號(HSE) 4 – 16M 給系統時鍾提供時鍾信號

  2. 高速內部時鍾信號(HSI) 內部RC振盪器

  3. 低速外部時鍾信號(LSE) 32.768 給RTC實時時鍾提供時鍾信號

  4. 低速內部時鍾信號(LSI)

系統時鍾來源

  1. HSI振盪器時鍾

  2. HSE振盪器時鍾

  3. PLL時鍾

R8的時鍾樹和外設分布

image-20201111161926185

來自:STM32F103R8數據手冊 2.1 Device overview

STM32時鍾的配置

  1. PLL的倍頻因子:HSE * PLLMUL = 72M Hz
  2. AHB的頻率:72M Hz
  3. APB2的頻率:72M Hz
  4. APB1的頻率:36M Hz

修改HSE_VALUE

image-20201111185616475

image-20201111185701307

設置函數static void SetSysClockTo72(void) 中的值

image-20201111190315406

image-20201111163440099

GPIO的配置

GPIO的作用

GPIO通用的輸入輸出外設

數字接口:0/1

0 -- TTL電平:0v~1.5v

1 -- TTL電平:2.5v~5v

STM32F103C8 : 0 => 0v ± 0.1 1 => 3.3v ± 0.3v

STM32 中GPIO口如何表示(理解)

PA0 PA1 PA2..... PA15 ; PB0 .....

P port 端口

A B C.... 端口號

0 1 ..... 15 端口位

每個端口最多有16個端口位

PA1 端口A 的第1位

PA0 端口A 的第0位

GPIO的相關模式

  • 輸入:4種

模擬輸入:輸入的模擬量,用於ADC轉化。

浮空輸入:輸入的數字量,

上拉輸入:輸入的數字量,向上驅動能力以及穩定信號。

下拉輸入:輸入的數字量,向下驅動能力以及穩定信號。

配置成那種模式要結合外部電路,不能導致IO口的狀態不確定。

比如說:外部是上拉,如果IO口配置成 “下拉模式”,就會導致當外部為高的時候IO口的電平不確定是高還是低,主要原因在於電阻的分壓大小。

  • 輸出:4種

    通用:GPIO口功能

    復用:除了GPIO口功能

    推挽:既能輸出低電平,又能輸出高電平

    開漏:只能輸出低電平。 -- 用於總線通信,外部具有上拉功能。

    通用推挽輸出、通用開漏輸出、復用推挽輸出、復用開漏輸出。

image-20201111195007047

寄存器配置GPIO的模式

image-20201111194501464

寄存器概念和作用

“寄存器” 是 “地址” 的別名。

操作寄存器就是操作地址。

stm32f10x.h(宏定義)文件中,就定義了很多的地址

#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
//三條總線 AHB APB2 APB1
---------------------------------------------------------
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
//APB2PERIPH_BASE:說明GPIO是在APB2總線上的外設。
----------------------------------------------------------
typedef struct
{
  __IO uint32_t CRL;	//32 bit
  __IO uint32_t CRH;	//32 bit
  __IO uint32_t IDR;	//32 bit
  __IO uint32_t ODR;	//32 bit
  __IO uint32_t BSRR;	//32 bit
  __IO uint32_t BRR;	//32 bit
  __IO uint32_t LCKR;	//32 bit
} GPIO_TypeDef; 
----------------------------------------------------------
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
//將一個地址,強制轉換為 GPIO_TypeDef 類型,
//就是將這一片空間按照 GPIO_TypeDef 分配,
//通過操作 結構體的成員 達到操作對應的地址的內容。
---------------------------------------------------------
image-20201111200546464

上述的結構體,可以通過操作 “結構體中的成員” ,從而達到對 “地址內容” 的更改,也就是完成了對 “寄存器內容” 的修改。

配置GPIO輸入 / 輸出模式

配置 7 :0 位(低8位)

image-20201111201004786

4bite 控制一個 “GPIO端口位 ” , 相關位,對應的功能如下圖所示。

image-20201111201316084

配置 15 :8(高8位)

image-20201111201406980

位 清零 與 置一

不影響其他位的值

位 清零: &= ~( );

位 置一: |= ( );

將想要 清零 或者 置一 的位 的值 對應為 1 ,帶入上面的方法中就行了

將GPIOB5配置為 “通用推挽輸出” 對應的4個位為 0011

先清零: GPIOB->CRL &= ~( 0xF << 20 ); //將4個位都清零 4*5=20。

后置位: GPIOB->CRL |= ( 0x3 << 20 ); //將對應的位,設置為所需的功能。

讀取輸入數據

出現在I/O腳上的數據在每個APB2時鍾被采樣到輸入數據寄存器

端口輸入數據寄存器(GPIOx_IDR) (x=A..E)

image-20201111230550101

如何判斷固定的位的值??

& ( );

判斷 GOIOA5 的輸入數據是1/0

( GPIOA->IDR & (0x1<<5) ) == 0 就說明外部輸入的是 0

( GPIOA->IDR & (0x1<<5) ) != 0 就說明外部輸入的是 1

也就是說,&1 對應的值不改變,否則就會對應位被置零;

所以說,當外部輸入0,整體的就是0;外部輸入1,整體不為0;

也可以判斷多個位,將想要判斷的位 寫成1,

然后 (對象) & (對應位),判斷結果,就可以知道多個位的狀態。

輸出數據

開漏模式:輸出寄存器上的 ’0’ 激活N-MOS,而輸出寄存器上的 ’1’ 將端口置於高阻狀態(PMOS從不被激活)。
推挽模式:輸出寄存器上的 ’0’ 激活N-MOS,而輸出寄存器上的 ’1’ 將激活P-MOS。

image-20201111232114036

想要控制某個位輸出 0/1 就在 寄存器對應的位寫 0/1。

輸出要求:不改變其他位的前提下,改變寄存器的值。

清零:&= ~( );

置一:|= ( );

函數代碼編寫

參考別人寫的代碼,一步步讓自己的代碼優秀起來。

//延時1us, 頻率:1/72us ; => 72個_NOP(); 就是1us
#define Delay_1us(){\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();\
}//沒有使用函數來定義,用宏定義,就是一個替代效果;盡可能讓1us延時更精准,后續的調用誤差會更小。

//延時n us , 函數調用,肯定有誤差,盡可能的減少
void Delay_us(uint32_t time)
{
	while(time--)	
		Delay_1us();
}
//延時 n ms, 沒有調用 Delay_us(); 函數,就是為了減少函數調用和返回時消耗的時間。
void Delay_ms(uint32_t time)
{
	uint64_t ms = time*1000;
	while(ms--)
		Delay_1us();
}
//Led_On:首字母大寫,其余小寫;1、能知道是宏定義; 2、用起來像是函數
#define Led_On(port, pin)	(port->ODR &= ~(0x1<<pin))
#define Led_Off(port, pin)	(port->ODR |=  (0x1<<pin))
//LED0_PORT 全部大寫,就是這個量就只是一個宏定義的常量。
#define LED0_PORT GPIOB
#define LED0_PIN	5
#define LED1_PORT GPIOEc
#define LED1_PIN	5
......
int main(void)
{
    ....
    Led_On(LED0_PORT, LED0_PIN);	//一眼就能看出來是,打開LED0
    Delay_ms(200);
    Led_Off(LED0_PORT, LED0_PIN);	//關閉LED0
    Delay_ms(200);
    ....
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM