標准庫開發
即利用官方提供的標准庫進行開發,相比較HAL庫來說運行效率更高,更容易理解寄存器結構,但是最終還是要學習HAL庫,官方好像停止了對標准庫的更新...
除此以外還推薦深入學習底層原理如寄存器相關的配置(在<深入底層學習>里有提),但不建議一上來就干底層,容易喪失學習興趣,底層部分慢慢學
0 准備資料
- 芯片手冊(芯片商提供):STM32F407ZGT6芯片手冊
STM32F407ZGT6芯片手冊 - 芯片中文手冊(芯片商提供):STM32F407中文手冊
STM32F407中文手冊 - 開發板原理圖(開發板商提供):正點原子探索者開發板原理圖
開發板原理圖 - 開發板開發手冊(開發板商提供):STM32F4開發指南
STM32F4開發指南 - 開發板例程(開發板商提供)
開發板例程

1 內置模塊學習
使用資料:STM32F407ZGT6芯片手冊、STM32F407中文手冊、STM32F4開發指南、開發板原理圖
1.1 GPIO
1.1.0 STM32F4的GPIO介紹
打開STM32F407ZGT6芯片手冊,目錄切換到3 Pinouts and pin description,尋找Figure 14. STM32F40x LQFP144 pinout

- STM32F407ZGT6
- 一共有7組IO口
- 每組IO口都有16個IO
- 一共16x7=112個IO
- GPIOA-GPIOG
切換到Table 6. Legend/abbreviations used in the pinout table和Table 7. STM32F40x pin and ball definitions,則是引腳的詳細介紹


打開STM32F407中文手冊,切換到7.3 GPIO 功能描述和7.4.3 GPIO 端口輸出速度寄存器 (GPIOx_OSPEEDR) (x = A..I/)
- 4種輸入模式
- 輸入浮空
- 輸入上拉
- 輸入下拉
- 模擬輸入
- 4種輸出模式
- 開漏輸出(帶上拉或者下拉)
- 開漏復用功能(帶上拉或者下拉)
- 推挽輸出(帶上拉或者下拉)
- 推挽復用功能(帶上拉或者下拉)
- 4種最大輸出速度
- 2MHZ
- 25MHZ
- 50MHZ
- 100MHZ
下拉可以找到:GPIO端口基本結構5V容忍即輸入信號電壓可以是3.3V也可以是5V,芯片手冊中用FT表示5V容忍

GPIO輸入工作模式-輸入浮空/上拉/下拉模式

浮空模式下,GPIO的狀態是不確定的,完全由外部決定
上拉模式下,GPIO沒有外部接入時狀態是高電平輸入(一般把輸入器件接地)
下拉模式下,GPIO沒有外部接入時狀態是低電平輸入(一般把輸入器件接高)
GPIO輸入工作模式-模擬輸入模式
一般用於AD轉換
GPIO輸出工作模式-開漏/推挽輸出
開漏輸入沒有輸入高電平的能力,但其低電平輸出能力較強(20ma)
推挽輸出能輸出強高低電平
GPIO輸出工作模式-開漏/推挽復用輸出
控制端由CPU端轉接到外設

打開STM32F407中文手冊,切換到目錄7.4 GPIO 寄存器
-
每組GPIO端口有10個寄存器:
- GPIO 端口模式寄存器 (
GPIOx_MODER) - GPIO 端口輸出類型寄存器 (
GPIOx_OTYPER) - GPIO 端口輸出速度寄存器 (
GPIOx_OSPEEDR) - GPIO 端口上拉/下拉寄存器 (
GPIOx_PUPDR) - GPIO 端口輸入數據寄存器 (
GPIOx_IDR) - GPIO 端口輸出數據寄存器 (
GPIOx_ODR) - GPIO 端口置位/復位寄存器 (
GPIOx_BSRR) - GPIO 端口配置鎖定寄存器 (
GPIOx_LCKR) - GPIO 復用功能低位寄存器 (
GPIOx_AFRL) - GPIO 復用功能高位寄存器 (
GPIOx_AFRH)
- GPIO 端口模式寄存器 (
-
如果配置一個IO口需要兩個位,那么32位寄存器剛好一組IO口16個IO口
-
如果配置一個IO口需要一個位,一般保留高位
-
BSRR寄存器32位分為低16位BSRRL和高16位BSRRH,BSRRL配置一組IO的16個IO的置位狀態(1),BSRRH配置復位狀態(0)
前六個不說了
GPIO 端口置位/復位寄存器 (GPIOx_BSRR)
可以寫入指定位,對其他位不產生影響,而ODR寄存器是重新寫入;置位:寫1、復位:寫0

GPIO 端口配置鎖定寄存器 (GPIOx_LCKR)
用來鎖定重要端口


GPIO 復用功能低位/高位寄存器 (GPIOx_AFRL)(GPIOx_AFRH)
- 端口復用器
STM32F4的大部分端口都有復用功能。所謂復用就是一些端口不僅僅可以作為通用IO口,也可以復用為一些外設的引腳,比如PA9,PA10可以復用為STM32F4的串口1引腳- 作用:最大限度的利用端口資源
- 這個寄存器后面細說

以上寄存器部分了解即可
打開官方庫的stm32f4xx_gpio.h文件,里面存放着關於GPIO的庫函數。
/* Function used to set the GPIO configuration to the default reset state ****/
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
/* Initialization and Configuration functions *********************************/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
/* GPIO Read and Write functions **********************************************/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
/* GPIO Alternate functions configuration function ****************************/
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
GPIO常用庫函數
⭐
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);GPIO掛載在AHB1時鍾線下,所以在用GPIO之前一定要使能時鍾
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);GPIO初始化函數;傳入參數(IO組號,IO參數結構體)- IO參數結構體
GPIO_Pin引腳號GPIO_Mode端口模式:輸入,輸出,模擬,復用GPIO_Speed端口速度GPIO_OType輸出模式:開漏,推挽(配置為輸出模式時使用)GPIO_PuPd上拉,下拉,浮空
- IO參數結構體
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);讀入指定IO口的數據;傳入參數(IO組號,IO號)void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);設置輸出高電平;傳入參數(IO組號,IO號)void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);設置輸出低電平;傳入參數(IO組號,IO號)void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);往單個IO口里寫數據;傳入參數(IO組號,IO號,數據(0或1))void GPIO_ToggleBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);IO口翻轉;傳入參數(IO組號,IO號)
其他以后用到再說,或者查一查就知道了
實驗1-1:跑馬燈
功能設計:兩個LED交替翻轉,500ms延時
打開開發板原理圖


可以看到LED0接到芯片的F9,LED1接到芯片的F10。
工程代碼地址:stm32-f4/1-1 LED
- 新建工程或者用模板工程
- delay初始化
- LED初始化
- 時鍾使能
- GPIO初始化
- IO口初始狀態
- 編寫應用程序
打開工程,新建一個HARDWARE文件下新建LED文件夾,存放LED.c和LED.h文件
工程中添加LED.c文件和LED.h文件


編寫代碼如下
#ifndef _LED_H
#define _LED_H
void LED_Init(void);
#endif
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//IO配置結構體
//時鍾使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
//初始GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //輸出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽輸出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //沒有輸出時上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed; //50Mhz
GPIO_Init(GPIOF,&GPIO_InitStructure);
//初始默認輸出高
GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10);
}
int main(void)
{
delay_init(168);
LED_Init();
while(1)
{
GPIO_SetBits(GPIOF,GPIO_Pin_9);
GPIO_ResetBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
GPIO_SetBits(GPIOF,GPIO_Pin_10);
GPIO_ResetBits(GPIOF,GPIO_Pin_9);
delay_ms(500);
}
}
可以看到兩個燈交替閃爍
實驗1-2:蜂鳴器
沒有蜂鳴器,有空寫...
實驗1-3:按鍵輸入
功能設計:按下按鍵,讀按鍵IO狀態,LED翻轉
打開開發板原理圖


工程代碼地址: stm32-f4/1-3 KEY
- 新建工程或者用模板工程
- delay初始化
- LED初始化(上面)
- KEY初始化
- 時鍾使能
- GPIO初始化
- 編寫應用程序
打開工程,新建一個HARDWARE文件下新建KEY文件夾,存放KEY.c和KEY.h文件,復制上個工程的LED文件到此文件夾,依次添加進工程
編寫代碼
#ifndef _KEY_H
#define _KEY_H
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)
void KEY_Init(void);
#endif
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//時鍾使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
//按鍵初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //輸入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //按鍵接地,所以這里接上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;
GPIO_Init(GPIOE,&GPIO_InitStructure);
}
int main(void)
{
delay_init(168);
LED_Init();
KEY_Init();
while(1)
{
if(0==KEY2) //被按下
{
delay_ms(50); //延時50ms消抖
if(0==KEY2)
{
delay_ms(50);
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
}
}
}
}
代碼圖個樂,看個效果,沒什么實用性
可以看到按鍵按下,LED翻轉
1.1.1 GPIO端口復用
STM32有很多的內置外設,這些外設的外部引腳都是與GPIO復用的。也就是說,一個GPIO如果可以復用為內置外設的功能引腳,那么當這個GPIO作為內置外設使用的時候,就叫做復用。
- 例如串口1的發送接收引腳是
PA9,PA10,當我們把PA9,PA10不用作GPIO,而用做復用功能串口1的發送接收引腳的時候,叫端口復用。

- STM32F4系列微控制器IO引腳通過一個復用器連接到內置外設或模塊。該復用器一次只允許一個外設的復用功能(AF) 連接到對應的IO口。這樣可以確保共用同一個IO引腳的外設之間不會發生沖突。
- 每個IO引腳都有一個
復用器,該復用器采用16路復用功能輸入(AF0到AF15),可通過GPIOx_AFRL(針對引腳0-7)和GPIOx AFRH(針對引腳8-15)奇存器對這些輸入進行配置,每四位控制一路復用。
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);端口復用配置;傳入參數(IO端口號,引腳號,要復用的內置外設)
就一個庫函數,但要注意幾點
- 要開GPIO和復用外設的時鍾(很大可能不一樣)
- GPIO初始化時將端口設置為復用模式
GPIO_initStructure.GPIO_Mode -GPIO_Mode_AF
[進階實用]矩陣鍵盤
[進階實用]按鍵多效果(單擊、雙擊、長按)
1.2 時鍾系統
1.2.0 時鍾樹介紹
打開STM32F407中文手冊,找到6 復位和時鍾控制 (RCC)下的6.2 時鍾,可以看到時鍾樹


各部分都可以在手冊里查到,總結一下:
- STM32有5個時鍾源:HSI、HSE、LSI、LSE、PLL
- HSI是高速內部時鍾,RC振盪器,頻率為16MHZ,精度不高。可以直接作為系統時鍾或者用作PLL時鍾輸入(
RCC_CR時鍾控制寄存器的位0:HSEON控制) - HSE是高速外部時鍾,可接石英/陶瓷諧振器,或者接外部時鍾源,頻率范圍4MHZ-26MHZ(
RCC_CR時鍾控制寄存器的位16:HSEON控制) - LSI是低速內部時鍾,RC振盪器,頻率為32KHZ,提供低功耗時鍾。主要供獨立看門狗和自動喚醒單元使用。()
- LSE是低速外部時鍾,接頻率為32.768KHZ的石英晶體。用於RTC。
- PLL為鎖相環倍頻輸出。STM32F4有兩個PLL:(
RCC_PLLCFGR RCC PLL配置寄存器)- 主PLL由HSE或者HSI提供時鍾信號,並具有兩個不同的輸出時鍾。
- 第一個輸出PLLP用於生成高速的系統時鍾
- 第二個輸出PLLQ用於生成USB OTG FS的時鍾(48MHZ),隨機數發生器的時鍾和SDIO的時鍾
- 專用的PLL(PLLI2S)用於生成精確時鍾,從而在I2S接口實現高品質音頻接口。
- HSI是高速內部時鍾,RC振盪器,頻率為16MHZ,精度不高。可以直接作為系統時鍾或者用作PLL時鍾輸入(
- 系統時鍾及外設時鍾
- 系統時鍾SYSCLK,最高168MHZ
- 來源:HSI振盪器時鍾、HSE振盪器時鍾、PLL時鍾
- 控制:
RCC_CFGR時鍾配置寄存器的SW位
- AHB高速總線時鍾HCLK,最高168MHZ
- 來源:系統時鍾分頻
- 控制:
RCC_CFGR時鍾配置寄存器的HPRE位
- APB1低速總線時鍾PCLK1,最高42MHZ
- 來源:HCLK分頻得到通常是4分頻
- 控制:
RCC_CFGR時鍾配置寄存器的PPRE1位
- APB2低速總線時鍾PCLK2,最高84MHZ
- 來源:HCLK分頻得到通常是2分頻
- 控制:
RCC_CFGR時鍾配置寄存器的PPRE2位 - 注意!對於定時器來說,如果分頻系數是1,那么頻率x1,否則x2,例如APB2分頻系數位2分頻,那么APB2的定時器頻率則是84x2=168MHZ。
- RTC時鍾
- 來源:HSR_RTC、LSE、LSI
- 控制:
RCC備份域控制寄存器RCC_BDCR:RTCSEL
- 看門狗時鍾
- 來源:LSI
- MCO時鍾輸出
- MCO:把控制器的時鍾通過外部的引腳輸出,可以外外部的設備提供時鍾。MCO1為PA8 ,MCO2為PC9。
- 控制:
RCC_CFRG時鍾配置寄存器的MCOX的PREx位
- I2S時鍾:由外部的引腳I2S_CKIN或者PLLI2SCLK提供。
- 以太網PHY時鍾:407沒有集成PHY,只能外接PHY芯片,比如LAN8720,那PHY時鍾就由外部的PHY芯片提供,大小為50M。
- USB PHY時鍾:407的USB沒有集成PHY,要想實現USB高速傳輸,只能外接PHY芯片,比如USB33000。那USB PHY時鍾就由外部的PHY芯片提供。
- 系統時鍾SYSCLK,最高168MHZ
1.2.1 查找外設掛載時鍾
首先由上面的時鍾樹可以得到大概的位置
然后打開STM32F407中文手冊,找到2 存儲器和總線架構、2.2 存儲器組織結構,即可查到詳細掛載時鍾信息

1.2.2 Systeminit
在啟動文件中,進入main之前,需要對STM32的時鍾進行設置。分析一下這里的SystemInit函數。
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset CFGR register */
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/
SetSysClock();
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
- 首句是對
FPU相關的設置,暫時不看 RCC->CR |= (uint32_t)0x00000001;內部高速時鍾使能RCC->CFGR = 0x00000000;時鍾全部失能RCC->CR &= (uint32_t)0xFEF6FFFF;外部高速時鍾,時鍾安全系統,主 PLL (PLL)失能RCC->PLLCFGR = 0x24003010;PLL 配置寄存器全部初始化RCC->CR &= (uint32_t)0xFFFBFFFF;HSE 時鍾旁路失能RCC->CIR = 0x00000000;關閉所有中斷- SRAM相關設置
SetSysClock()設置系統時鍾
static void SetSysClock(void)
{
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
/******************************************************************************/
/* PLL (clocked by HSE) used as System clock source */
/******************************************************************************/
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Select regulator voltage output Scale 1 mode */
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
/* HCLK = SYSCLK / 1*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx)
/* PCLK2 = HCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
/* PCLK1 = HCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
#endif /* STM32F40_41xxx || STM32F427_437x || STM32F429_439xx */
/* Configure the main PLL */
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
/* Enable the main PLL */
RCC->CR |= RCC_CR_PLLON;
/* Wait till the main PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}
}
RCC->CR |= ((uint32_t)RCC_CR_HSEON);使能HSE- 等待
HSE時鍾信號穩定 RCC->APB1ENR |= RCC_APB1ENR_PWREN;電源接口時鍾使能PWR->CR |= PWR_CR_VOS;調壓器輸出電壓級別選擇1RCC->CFGR |= RCC_CFGR_HPRE_DIV1;AHB不分頻,AHB時鍾直接接到HCLK上RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;APB 高速預分頻器 (APB2) 2分頻RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;APB 低速預分頻器 (APB1) 4分頻RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);選擇HSE作為PLL的時鍾輸入,然后分頻系數設置,最后輸出168MHZRCC->CR |= RCC_CR_PLLON;主 PLL (PLL) 使能while((RCC->CR & RCC_CR_PLLRDY) == 0)等待主 PLL (PLL) 時鍾就緒
1.3 中斷系統
1.3.0 中斷控制器NVIC介紹
CM4內核支持256個中斷,其中包含16個內核中斷和240個外部中斷,STM32F4采用了CM4內核的10個內核中斷和82個外部中斷。
(注意!:CM4內核的外部中斷和STM32的外部中斷不是一回事,CM4:除了內核異常都是外部中斷;STM32:外部中斷EXTI)
- 系統異常/內核中斷,體現在內核水平
- 外部中斷,體現在外設水平
打開STM32F407中文手冊,找到10 中斷和事件,10.2 外部中斷/事件控制器 (EXTI)下的表 45. STM32F405xx/07xx 和 STM32F415xx/17xx 的向量表

同時,中斷向量表寫在啟動文件startup_stm32f40_41xxx.s中,中斷向量表存放的是中斷子服務函數的入口地址

NVIC:嵌套向量中斷控制器,屬於內核外設,管理着包括內核和片上所有外設的中斷相關的功能。NVIC是Cortex-M4核心的一部分,關於它的資料不在《STM32的技術參考手冊》中,應查閱ARM公司的《Cortex M4內核編程手冊》。Cortex-M4的向量中斷統一由NVIC管理。
相關庫文件:core_cm4.h和misc.c
打開STM32F407中文手冊,找到10 中斷和事件,10.1 嵌套向量中斷控制器 (NVIC)就是相關介紹

為了管理82個外部中斷,STM32對中斷進行了分組;STM32可以將中斷分成5個組,分別為組0-4;同時,對每個中斷設置一個
搶占優先級和響應優先級。分組配置是由SCB->AIRCR寄存器的bit10-8來定義的。SCB->AIRCR是在哪里的呢?由於這是CM4內核定義的,在文檔《Cortex-M4權威指南(中文)》中能查找到。
分配方式如下

其中AIRCR寄存器來確定是用哪種分組,IP寄存器是相對應於那種分組搶占優先級和響應優先級的分配比例。例如組設置成3,那么此時所有的82個中斷優先寄存器高4位中的最高3位是搶占優先級,低1位為響應優先級。CM4中定義了8個Bit用於設置中斷源的優先級,而STM32只選用其中的4個Bit。
搶占優先級的級別高於響應優先級,而數值越小所代表的的優先級越高。
介紹一下搶占優先級、響應優先級的區別:
- 高優先級的搶占優先級是可以打斷正在進行的低搶占優先級中斷的;
- 搶占優先級相同的中斷,高響應優先級不可以打斷低響應優先級的中斷;
- 搶占優先級相同的中斷,當兩個中斷同時發生的情況下,哪個響應優先級高,哪個先執行;
- 如果兩個中斷的搶占優先級和響應優先級都是一樣的話,則看哪個中斷先發生就先執行;
除此之外有兩點需要注意: - 打斷的情況只會與搶占優先級有關, 和響應優先級無關!
- 一般情況下,系統代碼執行過程中,只設置一次中斷優先級分組,比如分組2,設置好分組之后一般不會再改變分組。隨意改變分組會導致中斷管理混亂,程序出現意想不到的執行結果。
打開core_cm4.h文件可以找到NVIC的寄存器定義
typedef struct
{
__IO uint32_t ISER[8]; //中斷使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; //中斷清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; //中斷使能懸起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; //中斷清除懸起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; //中斷有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; //中斷優先級寄存器
uint32_t RESERVED5[644];
__O uint32_t STIR; //軟件觸發中斷寄存器
} NVIC_Type;
簡單介紹一下寄存器
先介紹幾個寄存器組長度為8,這些寄存器是32位寄存器。由於STM32只有60個可屏蔽中斷,8個32位寄存器中只需要2個就有64位了,每1位控制一個中斷。
ISER[8](Interrupt Set-Enable Registers):中斷使能寄存器。其中只使用到了ISER[0]和ISER[1],ISER[0]的bit0~bit31分別對應中斷0~31。ISER[1]的bit0~27對應中斷32~59。要使能某個中斷,就必須設置相應的ISER位為1,使該中斷被使能(這僅僅是使能,還要配合中斷分組、屏蔽、I/O口映射等設置才算完整)。具體每一位對應哪個中斷參考stm32f103x.h里面第140行。ICER[8](Interrupt Clear-Enable Registers):中斷移除寄存器。該寄存器的作用於ISER相反。這里專門設置一個ICER來清除中斷位,而不是向ISER位寫0,是因為NVIC的寄存器寫1有效,寫0無效。ISPR[8](Interrupt Set-Pending Registers):中斷掛起控制寄存器。通過置1可以將正在進行的中斷掛起,執行同級或者更高級別的中斷。寫0無效。ICPR[8](Interrupt Clear-Pending Registers):中斷解掛控制寄存器。通過置1可以將正在掛起的中斷解掛。寫0無效。IABR[8](Interrupt Active-Bit Registers):中斷激活標志位寄存器。這是一個只讀寄存器,可以知道當前在執行的中斷是哪一個(為1),在中斷執行完后硬件自動清零。
最后,介紹一個寄存器組長度為240,這個寄存器為8位寄存器。240個8位寄存器,每個中斷使用一個寄存器來確定優先級。由於CM3由240個外部中斷,所以這個寄存器組的數目就是240(注意與上面寄存器的區別,一個是一個寄存器控制一個,一個是一位控制一個)。
IP[240](Interrupt Priority Registers):中斷優先級控制的寄存器。這是用來控制每個中斷的優先級。由於STM32F10x系列一共60個可屏蔽中斷,故使用IP[59]~IP[0]。其中每個IP寄存器的高4位[7:4]用來設置搶占和響應優先級(根據分組),低4位沒有用到。而兩個優先級各占幾個位又要由上面講到的中斷優先級分組決定。
打開官方庫的misc.h文件,里面存放着一些關於NVIC的庫函數。
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
NVIC常用庫函數:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);中斷分組設置;傳入參數(組號)void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);中斷優先級設置;傳入參數(NVIC優先級參數結構體)- 優先級參數結構體
NVIC_IRQChannel中斷號,stm32f4xx.h里typedef enum IRQn可查NVIC_IRQChannelPreemptionPriority中斷的搶占優先級別NVIC_IRQChannelSubPriority中斷的響應優先級別NVIC_IRQChannelCmd中斷是否使能
- 優先級參數結構體
1.3.1 STM32外部中斷EXTI
打開STM32F407中文手冊,找到10.2 外部中斷/事件控制器 (EXTI)、10.2.2 EXTI 框圖

EXTI可分為兩大部分功能,一個是產生中斷,另一個是產生事件,這兩個功能從硬件上就有所不同。
- 輸入線:EXTI控制器有25個中斷/事件輸入線,這些輸入線可以通過寄存器設置為任意一個GPIO,也可以是一些外設的事件
- 邊沿檢測電路:它會根據上升沿觸發選擇寄存器(
EXTI_RTSR)和下降沿觸發選擇寄存器(EXTI_FTSR)對應位的設置來控制信號觸發。 - 或門電路:它一個輸入來自邊沿檢測電路,另外一輸入來自軟件中斷事件寄存器(
EXTI_SWIER)。EXTI_SWIER允許我們通過程序控制就可以啟動中斷/事件線。 - 兩個與門電路:為了接入中斷屏蔽寄存器,其輸出到NVIC中斷的一路信號會被保存到掛起寄存器(
EXTI_PR)內,如果確定電路輸出為1就會把EXTI_PR對應位置1。 - 脈沖發生器:其產生的這個脈沖信號可以給其他外設電路使用,比如定時器TIM、模擬數字轉換器ADC等等。
產生中斷線路目的是把輸入信號輸入到NVIC,進一步會運行中斷服務函數,實現功能,這樣是軟件級的。而產生事件線路目的就是傳輸一個脈沖信號給其他外設使用,並且是電路級別的信號傳輸,屬於硬件級的。另外,EXTI是在APB2總線上的。
同樣,打開STM32F407中文手冊,找到10.2.5 外部中斷/事件線映射

EXTI0至EXTI15用於GPIO,通過編程控制可以實現任意一個GPIO作為EXTI的輸入源。由表可知,EXTI0可以通過SYSCFG外部中斷配置寄存器1(SYSCFG_EXTICR1)的EXTI0[3:0]位選擇配置為PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0或者PI0。其他EXTI線(EXTI中斷/事件線)使用配置都是類似的。
SYSCFG外部中斷配置寄存器1:打開STM32F407中文手冊,找到8 系統配置控制器 (SYSCFG)、8.2 SYSCFG 寄存器下的8.2.4 SYSCFG 外部中斷配置寄存器 1 (SYSCFG_EXTICR1)

打開官方庫的stm32f4xx_exti.h文件,里面存放着一些關於EXTI的庫函數。
/* Function used to set the EXTI configuration to the default reset state *****/
void EXTI_DeInit(void);
/* Initialization and Configuration functions *********************************/
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
/* Interrupts and flags management functions **********************************/
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
同時還有stm32f4xx_syscfg.h里配置選擇線上具體IO映射的函數
void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);
EXTI常用庫函數:
⭐
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);EXTI掛載在APB2時鍾線下,所以在用EXTI之前一定要使能時鍾
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);外部中斷初始化;傳入參數(外部中斷參數結構體)- 外部中斷參數結構體
EXTI_Line選擇23條中斷/事件線 EXTI_Line0-22EXTI_Mode選擇模式 中斷/事件EXTI_Trigger選擇觸發方式 上升沿,下降沿,上升下降沿EXTI_LineCmd外部中斷/事件線使能
- 外部中斷參數結構體
void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);設置IO口與中斷線的映射關系;傳入參數(GPIO口,中斷線)ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);判斷中斷線上中斷是否發生void EXTI_ClearITPendingBit(uint32_t EXTI_Line);清除中斷線上的中斷標志位
實驗2-1 外部中斷按鍵
功能設計:按下按鍵外部中斷進入中斷函數中控制LED翻轉
打開
開發板原理圖

工程代碼地址: stm32-f4/2-1 KEY-EXTI
- 新建工程或者用模板工程
- delay初始化
- LED初始化
- KEY初始化
- EXTI初始化
- APB2時鍾使能
- GPIO初始化(KEY已完成)
- 設置IO口與中斷線映射關系
- 初始化線上中斷
- 配置中斷分組,並且使能
- 編寫中斷子服務函數
- 清除中斷標志位
打開工程,新建一個HARDWARE文件下新建EXTI文件夾,存放EXTI.c和EXTI.h文件,復制上個工程的LED文件和KEY文件到此文件夾,依次添加進工程
編寫代碼
#ifndef _EXTI_H
#define _EXTI_H
void EXTI_User_Init(void);
#endif
void EXTI_User_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//時鍾使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
//按鍵GPIO初始化
KEY_Init();
//設置IO口與中斷線映射關系
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource2);
//初始化線上中斷
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
//中斷分組
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//中斷優先級初始化
NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI2_IRQHandler(void)
{
delay_ms(10);
if(0==KEY2) //被按下
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
int main(void)
{
delay_init(168);
LED_Init();
EXTI_User_Init();
while(1)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
}
}
可以看到按鍵按下,LED翻轉
1.4 定時器
- 定時器功能:定時、輸出比較、輸入捕獲、互補輸出
- 定時器分類:基本定時器、通用定時器、高級定時器
- 定時器資源:407有2個高級定時器、10個通用定時器、2個基本定時器
1.4.1 基本定時器TIM6-7
- 基本定時器特征
- 計數器16bit,只能向上計數,只有TIM6和TIM7
- 沒有外部的GPIO,是內部資源,只能用來定時
- 時鍾來自PCLK1(掛載在APB1上),可實現1-65536分頻
- 計數器上溢會觸發中斷/DMA請求
- 基本定時器框圖
打開STM32F407中文手冊,找到17 基本定時器(TIM6 和 TIM7)、17.2 TIM6 和 TIM7 的主要特性下的圖 188. 基本定時器框圖
- 時鍾源:
- 時鍾源來自
RCC的TIMx_CLK(屬於內部的CK_INT) TIMx_CLK在RCC專用時鍾配置寄存器(RCC_DCKCFGR)里查看- 掛載在APB1上,根據前面系統時鍾計算得時鍾頻率為168/4*2=84MHZ
- 時鍾源來自
- 控制器:
- 控制器用於控制定時器得:復位、使能、計數、觸發DAC
- 涉及到的寄存器:CR1/2、DIER、EGR、SR
- 時基:
- 定時器最主要的部分就是時基部分:包括預分頻器、計數器、自動重裝載寄存器
- 計數器、自動重裝載寄存器:定時器使能(CEN置1)后計數器CNT在CK_CNT驅動下向上計數,當
TIMx_CNT值與TIMx_ARR的設定值相等時就自動生成事件並TIMx_CNT自動清零,然后自動重新開始計數。 - 影子寄存器:
- PSC和ARR都有影子寄存器,功能框圖上有個影子
- 影子寄存器存在起到
緩沖的作用,用戶值->寄存器->影子寄存器->起作用;TIMx_CR1:APRE位控制 - 如果不使用影子寄存器會立馬起作用
- 時鍾源:
因為功能有限,庫函數用的不多
⭐
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);基本定時TIM6-7掛載在APB1時鍾線下,所以在用基本定時器之前一定要使能時鍾
-
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);初始化定時器;傳入參數(定時器TIMx,定時器初始化參數結構體)- 定時器初始化參數結構體
TIM_Prescaler定時器預分頻,用時鍾源頻率除以這個得到頻率,其倒數為計數一次的時間TIM_CounterMode計數方式,因為只能向上計數,無需設置TIM_Period定時器周期,一個定時周期計數幾次TIM_ClockDivision時鍾分頻,基本定時器沒有這個功能,無需設置。TIM_RepetitionCounter重復計數器,基本定時器沒有這個功能,無需設置。
- 定時器初始化參數結構體
-
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);清除定時器中斷標志位 -
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);開啟定時器更新中斷 -
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);使能定時器 -
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);獲得中斷狀態,用於觸發中斷時判斷 -
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);進入中斷執行完后清除中斷標志位
- 定時0.5S的計算
- PSC=8400-1,定時器頻率=84M/(PSC+1)=10000HZ,T=1/10000 s(1次0.1ms)
- ARR=4999,從0計數到4999,則計了5000次
- T=5000/10000=0.5S
實驗3-1 基本定時器定時
功能設計:利用基本定時器實現0.5s定時,LED閃爍
工程代碼地址: stm32-f4/3-1 BASE-TIM
基本定時器沒有引腳輸出,直接寫配置代碼
- 新建工程或者用模板工程
- LED初始化
- 定時器初始化
- APB1時鍾使能
- 定時器初始化
- 清除定時器中斷標志位
- 使能定時器中斷
- 使能定時器
- 配置中斷分組,並且使能
- 編寫中斷子服務函數
- 清除中斷標志位
打開工程,新建一個HARDWARE文件下新建TIM文件夾,存放TIM.c和TIM.h文件,復制上個工程的LED文件到此文件夾,依次添加進工程
編寫代碼
#ifndef _TIM_H
#define _TIM_H
void TIM_Init(void);
#endif
void TIM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//時鍾使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);
//定時器初始化 時鍾源84MHZ
TIM_TimeBaseInitStructure.TIM_Prescaler = 8400-1; //預分配系數8400,即T=1/10000=0.1ms
TIM_TimeBaseInitStructure.TIM_Period = 5000-1; //0.1ms*5000=500ms
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure);
//清除定時器中斷標志位
TIM_ClearFlag(TIM6,TIM_FLAG_Update);
//使能定時器中斷
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);
//使能定時器
TIM_Cmd(TIM6,ENABLE);
//配置中斷
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
}
void TIM6_DAC_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6,TIM_IT_Update)!=RESET)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
TIM_ClearITPendingBit(TIM6,TIM_IT_Update);
}
}
int main(void)
{
LED_Init();
TIM_Init();
while(1);
}
可以看到LED閃爍
1.4.2 高級定時器TIM1、TIM8
- 高級定時器特征
- 計數器16bit,上/下/兩邊 計數,TIM1和TIM8,還有一個
重復計數器RCR,獨有。 - 有4個GPIO,其中通道1-3還有互補輸出GPIO
- 時鍾來自
PCLK2(掛載在APB2上),可實現1-65536分頻
- 計數器16bit,上/下/兩邊 計數,TIM1和TIM8,還有一個
- 高級定時器框圖
打開STM32F407中文手冊,找到14 高級控制定時器(TIM1 和 TIM8)、14.2 TIM1 和 TIM8 主要特性下的圖 71. 高級控制定時器框圖
-
時鍾源:

- 內部時鍾源CK_INT
- 內部時鍾源來自RCC的TIMx_CLK
TIMx_CLK在RCC專用時鍾配置寄存器(RCC_DCKCFGR)里查看- 掛載在APB2上,根據前面系統時鍾計算得時鍾頻率為168/2*2=168MHZ
- 外部時鍾模式1(有點像編碼器模式)-外部的GPIO Tix(x=1 2 3 4)
- ① 時鍾信號輸入引腳
- 外部的
GPIO TIx,對應:TIMx_CH1/2/3/4 TIM_CCMRx的位CCxS[1:0]配置,其中CCMR1控制TI1/2,CCMR2控制TI3/4
- 外部的
- ② 濾波器
- 如果來自外部的時鍾信號頻率過高或者混雜有高頻干擾信號的話,我們就要用濾波器對ETRP信號重新采樣,來達到降頻或者去除高頻干擾的目的、
- 由
TIMx_CCMx的位ICxF[3:0]配置
- ③ 邊沿檢測
- 邊沿檢測的信號來自於濾波器的輸出,在成為觸發信號之前,需要進行邊沿檢測,決定是上升沿有效還是下降沿有效。
- 由
TIMx_CCER的位CCxP和CCxNP配置。
- ④ 觸發選擇
- 當使用外部時鍾模式1時,觸發源有兩個,一個是濾波后的定時器輸入1(
TI1FP1)和濾波后的定時器輸入2(TI2FP2)。 - 由
TIMx_SMCR的位TS[2:0]配置。
- 當使用外部時鍾模式1時,觸發源有兩個,一個是濾波后的定時器輸入1(
- ⑤ 從模式選擇
- 選定了觸發源信號后,最后我們需把信號連接到
TRGI引腳,讓觸發信號成為外部時鍾模式1的輸入最終等於CK_PSC,然后驅動計數器CNT計數。 - 具體的配置
TIMx_SMCR的位SMS[2:0]為000即可選擇外部時鍾模式1。
- 選定了觸發源信號后,最后我們需把信號連接到
- ⑥ 使能計數器
- 經過上面的5個步驟之后,最后我們只需使能計數器開始計數,外部時鍾模式1的配置就算完成。
- 使能計數器由
TIMx_CR1的位CEN配置。
- ① 時鍾信號輸入引腳
- 外部時鍾模式2-外部的GPIO ETR
- ① 時鍾信號輸入引腳
- 當使用外部時鍾模式2的時候,時鍾信號來自於定時器的特定輸入通道
TIMx_ETR,只有1個。
- 當使用外部時鍾模式2的時候,時鍾信號來自於定時器的特定輸入通道
- ② 外部觸發極性
- 來自
ETR引腳輸入的信號可以選擇為上升沿或者下降沿有效。 - 具體的由
TIMx_SMCR的位ETP配置。
- 來自
- ③ 外部觸發預分頻器
- 由於
ETRP的信號的頻率不能超過TIMx_CLK(180M)的1/4,當觸發信號的頻率很高的情況下,就必須使用分頻器來降頻。 - 具體的由
TIMx_SMCR的位ETPS[1:0]配置。
- 由於
- ④ 濾波器
- 如果
ETRP的信號的頻率過高或者混雜有高頻干擾信號的,需要使用濾波器對ETRP信號重新采樣,來達到降頻或者去除高頻干擾的目的。 - 具體的由
TIMx_SMCR的位ETF[3:0]配置,其中的fDTS是由內部時鍾CK_INT分頻得到,具體的由TIMx_CR1的位CKD[1:0]配置。
- 如果
- ⑤ 從模式選擇
- 經過濾波器濾波的信號連接到
ETRF引腳后,觸發信號成為外部時鍾模式2的輸入,最終等於CK_PSC,然后驅動計數器CNT計數。 - 具體的配置
TIMx_SMCR的位ECE為1即可選擇外部時鍾模式2。
- 經過濾波器濾波的信號連接到
- ⑥ 使能計數器
- 經過上面的5個步驟之后,最后我們只需使能計數器開始計數,外部時鍾模式2的配置就算完成。
- 使能計數器由
TIMx_CR1的位CEN配置。
- ① 時鍾信號輸入引腳
- 內部觸發輸入-ITRx(x=1 2 3 4)
- 內部觸發輸入是使用一個定時器作為另一個定時器的預分頻器。硬件上高級控制定時器和通用定時器在內部連接在一起,可以實現定時器同步或級聯。
- 由
TIMx_SMCR的位TS[2:0]配置。
- 內部時鍾源CK_INT
-
控制器:
- 控制器就是用來控制的,發送命令的
CR1、CR2、SMCR、CCER,主要學習這幾個寄存器即可。
-
時基單元:

...吐了 -
輸入捕獲:

- 輸入捕獲的作用和原理
- 輸入捕獲可以對輸入的信號的上升沿,下降沿或者雙邊沿進行捕獲,常用的有測量輸入信號的脈寬和測量PWM輸入信號的頻率和占空比這兩種。
- 輸入捕獲的大概的原理就是,當捕獲到信號的跳變沿的時候,把
計數器CNT的值鎖存到捕獲寄存器CCR中,把前后兩次捕獲到的CCR寄存器中的值相減,就可以算出脈寬或者頻率。如果捕獲的脈寬的時間長度超過你的捕獲定時器的周期,就會發生溢出,這個我們需要做額外的處理。
- 輸入捕獲的應用
- 測量脈沖和頻率
- PWM輸入模式
- ① 輸入通道
- 當使用需要被測量的信號從定時器的外部引腳―TIMx_CH1/2/3/4進入,通常叫TI1/2/3/4,在后面的捕獲講解中對於要被測量的信號我們都以TIx為標准叫法。
- ② 輸入濾波和邊沿檢測
- 對高頻信號進行濾波,濾波器的配置由CR1寄存器的位CKD[1:0]和CCMR1/2的位ICxF[3:0]控制。從ICXF位的描述可知,采樣頻率fsAMPLE可以由fcx_nr和 fprs分頻后的時鍾提供,其中是 fcx_nr內部時鍾,fors是 fcx_nr經過分頻后得到的頻率,分頻因子由CKD[1:0]決定,可以是不分頻,2分頻或者是4分頻。
- 邊沿檢測器用來設置信號在捕獲的時候是什么邊沿有效,可以是上升沿,下降沿,或者是雙邊沿,具體的由CCER寄存器的位CCxP和CCxNP決定。
- ③ 捕獲通道
- 捕獲通道就是圖中的IC1/2/3/4,每個捕獲通道都有相對應的捕獲寄存器CCR1/2/3/4,當發生捕獲的時候,計數器CNT的值就會被鎖存到捕獲寄存器中。
- 這里我們要搞清楚輸入通道和捕獲通道的區別,輸入通道是用來輸入信號的,捕獲通道是用來捕獲輸入信號的通道,一個輸入通道的信號可以同時輸入給兩個捕獲通道。比如輸入通道TI1的信號經過濾波邊沿檢測器之后的TI1FP1和TIIFP2可以進入到捕獲通道IC1l和IC2,其實這就是我們后面要講的PWM輸入捕獲,只有一路輸入信號(TI1)卻占用了兩個捕獲通道(IC1和IC2)。當只需要測量輸入信號的脈寬時候,用一個捕獲通道即可。輸入通道和捕獲通道的映射關系具體由寄存器CCMRx的位CCxS[1:0]配置。
- ④ 預分配器
- ICx的輸出信號會經過一個預分頻器,用於決定發生多少個事件時進行一次捕獲。
- 具體的由寄存器CCMRx的位ICxPS 配置,如果希望捕獲信號的每一個邊沿,則不分頻。
- ⑤ 捕獲寄存器
- 經過預分頻器的信號ICxPS是最終被捕獲的信號,當發生捕獲時(第一次),計數器CNT的值會被鎖存到捕獲寄存器CCR中,還會產生CCxI中斷,相應的中斷位CCxIF(在SR寄存器中)會被置位,通過軟件或者讀取CCR中的值可以將CCxIF清0。如果發生第二次捕獲(即重復捕獲:CCR寄存器中已捕獲到計數器值且CCxIF標志已置1),則捕獲溢出標志位CCxOF(在SR寄存器中)會被置位,CCxOF只能通過軟件清零。
- 輸入捕獲的作用和原理
-
輸出比較:

-
輸出比較的作用
- 輸出比較就是通過定時器的外部引腳對外輸出控制信號,有凍結、將通道X(x=1,2,3,4)設置為匹配時輸出有效電平、將通道X設置為匹配時輸出無效電平、翻轉、強制變為無效電平、強制變為有效電平、PWM1和PWM2這八種模式,具體使用哪種模式由寄存器CCMRx的位OCxM[2:0]配置。其中 PWM模式是輸出比較中的特例,使用的也最多。
-
輸出比較的應用
- 輸出比較模式總共有8種,常用的是PWM模式。
- 由寄存器CCMRx的位OCxM[2:0]配置。
- PWM輸出模式
- PWM輸出就是對外輸出脈寬(即占空比)可調的方波信號,
信號頻率由自動重裝寄存器ARR的值決定,占空比由比較寄存器CCR的值決定。

有效:高電平、無效:低電平 - 邊沿對齊和中心對齊
- 邊沿對齊用於直流電機,中心對齊用於交流電機
- PWM輸出就是對外輸出脈寬(即占空比)可調的方波信號,
-
① 輸出比較寄存器
- 當
計數器CNT的值跟比較寄存器CCR的值相等的時候,輸出參考信號OCxREF的信號的極性就會改變,其中OCxREF=1(高電平)稱之為有效電平,OCxREF=O(低電平)稱之為無效電平,並且會產生比較中斷CCxI,相應的標志位CCxIF (SR寄存器中)會置位。然后OCxREF再經過一系列的控制之后就成為真正的輸出信號OCx/OCxN。
- 當
-
② 死區發生器
- 在生成的參考波形
OCxREF的基礎上,可以插入死區時間,用於生成兩路互補的輸出信號OCx和OCxN,死區時間的大小具體由BDTR寄存器的位DTG[7:0]配置。死區時間的大小必須根據與輸出信號相連接的器件及其特性來調整。下面我們簡單舉例說明下帶死區的PWM信號的應用,我們以一個半橋驅動電路為例。

如上,死區就是為了防止在信號輸出切換時發生兩個MOS管同時未關斷而發生短路的情況
- 在生成的參考波形
-
③ 輸出控制
- 在輸出比較的輸出控制中,參考信號OCxREF在經過死區發生器之后會產生兩路帶死區的互補信號OCx_DT和OCxN_DT(通道13才有互補信號,通道4沒有,其余跟通道13一樣),這兩路帶死區的互補信號然后就進入輸出控制電路,如果沒有加入死區控制,那么進入輸出控制電路的信號就直接是OCxREF。
- 進入輸出控制電路的信號會被分成兩路,一路是原始信號,一路是被反向的信號,具體的由寄存器CCER的位CCxP和 CCxNP控制。經過極性選擇的信號是否由OCx引腳輸出到外部引腳CHx/CHxN則由寄存器CCER的位CxE/CxNE配置。
- 如果加入了斷路(剎車)功能,則斷路和死區寄存器BDTR的MOE、OSSl和 OSSR這三個位會共同影響輸出的信號。
-
④ 輸出引腳
- 輸出比較的輸出信號最終是通過定時器的外部IO來輸出的,分別為CH1/2/3/4,其中前面三個通道還有互補的輸出通道CH1/2/3N。更加詳細的IO說明還請查閱相關的數據手冊。
-
-
斷路功能:
-
比較多,用到一個補充一個
⭐
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);高級定時器TIM1、8掛載在APB2時鍾線下,所以在用高級定時器之前一定要使能時鍾
-
基礎定時器管理
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);初始化定時器;傳入參數(定時器TIMx,定時器初始化參數結構體)- 定時器初始化參數結構體
TIM_Prescaler定時器預分頻,該預分頻器才是定時器計數時鍾CK_CNT,它設定PSC寄存器的值。計算公式為︰計數器時鍾頻率(fCK_CNT)等夭fCK_PSC / (Psc[15:0) + 1),可實現1至65536分頻。TIM_CounterMode定時器計數方式,可設置為向上計數、向下計數以及中心對齊。高級控制定時器允許選擇任意一種。TIM_Period定時器周期,實際就是設定自動重載寄存器ARR的值,ARR為要裝載到實際自動重載寄存器(即影子寄存器)的值,可設置范圍為О至65535。TIM_ClockDivision時鍾分頻,設置定時器時鍾CK_INT頻率與死區發生器以及數字濾波器采樣時鍾頻率分頻比。可以選擇1、2、4分頻。只有在使用外部時鍾2和輸入捕獲的時候用得到。TIM_RepetitionCounter重復計數器,只有8位,只存在於高級定時器。
- 定時器初始化參數結構體
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);允許或禁止在定時器工作時向ARR的緩沖器中寫入新值void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);定時器使能
-
輸出比較功能管理
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);輸出比較初始化;傳入參數(定時器TIMx,輸出比較結構體)- 輸出比較結構體
TIM_OCMode比較輸出模式選擇,總共有八種常用的為PWM1/PWM2。它設定CCMRx寄存器OCxM[2:0]位的值。TIM_OutputState比較輸出使能,決定最終的輸出比較信號OCx是否通過外部引腳輸出。它設定TIMx_CCER寄存器CCxE/CCxNE位的值。TIM_OutputNState比較互補輸出使能,決定OCx的互補信號OCxN是否通過外部引腳輸出。它設定CCER寄存器CCxNE位的值TIM_Pulse比較輸出脈沖寬度,實際設定比較寄存器CCR的值,決定脈沖寬度。可設置范圍為О至65535。TIM_OCPolarity比較輸出極性,可選OCx為高電平有效或低電平有效。它決定着定時器通道有效電平。它設定CCER寄存器的CCxP位的值。TIM_OCNPolarity比較互補輸出極性,可選OCxN為高電平有效或低電平有效。它設定TIMx_CCER寄存器的CCxNP位的值。TIM_OCIdleState空閑狀態時通道輸出電平設置,可選輸出1或輸出0,即在空閑狀態(BDTR_MOE位為0)時,經過死區時間后定時器通道輸出高電平或低電平。它設定CR2寄存器的OISx位的值。TIM_OCNIdleState空閑狀態時互補通道輸出電平設置,可選輸出1或輸出0,即在空閑狀態(BDTR_MOE位為0)時,經過死區時間后定時器互補通道輸出高電平或低電平,設定值必須與TIM_OCIdleState相反。它設定是CR2寄存器的OISxN位的值。
- 輸出比較結構體
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);使能TIMx在CCR1上的預裝載寄存器void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);往定時器里寫入新的CCR
-
輸入捕獲功能管理
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);輸入捕獲初始化;傳入參數(定時器TIMx,輸入捕獲結構體)- 輸入捕獲結構體
TIM_Channel輸入通道選擇,可選TIM_Channel_1、TIM_Channel_2.TIM_Channel_3或TIM_Channel_4 四個通道。它設定CCMRx寄存器CCxS位的值。TIM_ICPolarity2-TIM_ICPolarity:輸入捕獲邊沿觸發選擇,可選上升沿觸發、下降沿觸發或邊沿跳變觸發。它設定CCER寄存器CCxP位和CCxNP位的值。TIM_ICSelection3-TIM_ICSelection:捕獲通道選擇,捕獲通道ICx的信號可來自三個輸入通道,分別為TIM_ICSelection_DirectTI、TIM_ICSelection_IndirectTI或TIM_ICSelection_TRC它設定CCRMx寄存器的CCxS[1:0]位的值。TIM_ICPrescaler4-TIM_ICPrescaler:輸入捕獲通道預分頻器,可設置1、2、4、8分頻,它設定CCMRx寄存器的ICxPSC[1:0]位的值。如果需要捕獲輸入信號的每個有效邊沿,則設置1分頻即可。TIM_ICFilter5-TIM_ICFilter:輸入捕獲濾波器設置,可選設置Ox0至OxOF。它設定CCMRx寄存器ICxF[3:0]位的值。一般我們不使用濾波器,即設置為0。
- 輸入捕獲結構體
-
斷路和死區管理
void TIM_BDTRConfig(TIM_TypeDef* TIMx, TIM_BDTRInitTypeDef *TIM_BDTRInitStruct);斷路和死區配置;傳入參數(定時器TIMx,斷路和死區結構體)- 斷路和死區結構體
TIM_OSSRStateTIM_OSSIStateTIM_LOCKLevelTIM_DeadTimeTIM_BreakTIM_BreakPolarityTIM_AutomaticOutput
- 斷路和死區結構體
實驗3-2 PWM輸出實驗
功能設計:實現簡單的呼吸燈效果
打開開發板原理圖


LED0接PF9腳,使用的是TIM14
打開STM32F407中文手冊,查到定時器14是通用定時器,具有PWM的功能,同時查到TIM14掛載在APB1上
工程代碼地址: stm32-f4/3-2 PWM
- 新建工程或者用模板工程
- LED初始化
- 使能AHB1時鍾
- 初始化引腳為復用輸出
- 端口復用配置
- PWM初始化
- 使能APB1時鍾
- 初始化定時器
- 初始化輸出比較
- 使能ARR
- 使能預裝載寄存器CCR
- 定時器使能
- 編寫應用層程序
打開工程,新建一個HARDWARE文件下新建PWM文件夾,存放PWM.c和PWM.h文件,復制上個工程的LED文件到此文件夾,依次添加進工程
編寫代碼
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//IO配置結構體
//時鍾使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
//初始GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //輸出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽輸出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //沒有輸出時上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed; //50Mhz
GPIO_Init(GPIOF,&GPIO_InitStructure);
//初始默認輸出高
GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10);
//復用配置
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);
}
void PWM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruture;
TIM_OCInitTypeDef TIM_OCInitStructure;
//時鍾使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);
//初始化定時器
TIM_TimeBaseInitStruture.TIM_Prescaler = 84-1; //分頻
TIM_TimeBaseInitStruture.TIM_Period = 500-1; //自動裝載值arr=500 頻率為2KHZ
TIM_TimeBaseInitStruture.TIM_CounterMode = TIM_CounterMode_Up; //向上計數模式
TIM_TimeBaseInitStruture.TIM_ClockDivision = TIM_CKD_DIV1; //不分頻
TIM_TimeBaseInit(TIM14,&TIM_TimeBaseInitStruture);
//輸出比較初始化
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1模式 小於CCR時是有效電平
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //有效電平為低電平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能
TIM_OC1Init(TIM14,&TIM_OCInitStructure);
//使能arr
TIM_ARRPreloadConfig(TIM14,ENABLE);
//使能預裝載寄存器CCR
TIM_OC1PreloadConfig(TIM14,TIM_OCPreload_Enable);
//定時器使能
TIM_Cmd(TIM14,ENABLE);
}
int main(void)
{
u16 ledPwmVal = 0;
u8 dir = 0; //0遞增,1遞減
delay_init(168);
LED_Init();
PWM_Init();
while(1)
{
delay_ms(10);
if(0==dir) ledPwmVal++;
else ledPwmVal--;
if(ledPwmVal>300) dir=1;
if(ledPwmVal==0) dir=0;
TIM_SetCompare1(TIM14,ledPwmVal);
}
}
可以看到呼吸燈閃爍
實驗3-3 輸入捕獲實驗
需要學習一下串口知識
功能設計:捕獲兩次按鍵按下的時間差



工程代碼地址: stm32-f4/3-3 CATCH
- 新建工程或者用模板工程
- KEY初始化
- 使能AHB1時鍾
- 初始化引腳為復用輸出
- 端口復用配置
- 定時器初始化
- 使能APB1時鍾
- 初始化定時器
- 初始化輸入捕獲
- 配置中斷開啟NVIC
- 開啟捕獲中斷
- 定時器使能
- 編寫中斷程序
1.4.2 通用定時器
TIM1和TIM8高級定時器的功能包括:
- 16位向上、向下、向上/下自動裝載計數器
- 16位可編程(可以實時修改)預分頻器,計數器時鍾頻率的分頻系數為1~65535之間的任意數值
- 多達4個獨立通道:
- 輸入捕獲
- 輸出比較
- PWM生成(邊緣或中間對齊模式)
- 單脈沖模式輸出
- 死區時間可編程的互補輸出
- 使用外部信號控制定時器和定時器互聯的同步電路
- 允許在指定數目的計數器周期之后更新定時器寄存器的重復計數器
- 剎車輸入信號可以將定時器輸出信號置於復位狀態或者一個已知狀態
- 如下事件發生時產生中斷/DMA:
- 更新:計數器向上溢出/向下溢出,計數器初始化(通過軟件或者內部/外部觸發)
- 觸發事件(計數器啟動、停止、初始化或者由內部/外部觸發計數)
- 輸入捕獲
- 輸出比較
- 剎車信號輸入
- 支持針對定位的增量(正交)編碼器和霍爾傳感器電路
- 觸發輸入作為外部時鍾或者按周期的電流管理
通用定時器TIM2、TIM3、TIM4和TIM5定時器功能包括:
- 16位向上、向下、向上/向下自動裝載計數器
- 16位可編程(可以實時修改)預分頻器,計數器時鍾頻率的分頻系數為1~65536之間的任意數值
- 多達4個獨立通道:
- 輸入捕獲
- 輸出比較
- PWM生成(邊緣或中間對齊模式)
- 單脈沖模式輸出
- 使用外部信號控制定時器和定時器互連的同步電路
- 如下事件發生時產生中斷/DMA:
- 更新:計數器向上溢出/向下溢出,計數器初始化(通過軟件或者內部/外部觸發)
- 觸發事件(計數器啟動、停止、初始化或者由內部/外部觸發計數)
- 輸入捕獲
- 輸出比較
- 支持針對定位的增量(正交)編碼器和霍爾傳感器電路
- 觸發輸入作為外部時鍾或者按周期的電流管理
通用定時器TIM9-14定時器功能包括:
- 16位向上、向下、向上/向下自動裝載計數器
- 16位可編程(可以實時修改)預分頻器,計數器時鍾頻率的分頻系數為1~65536之間的任意數值
- 多達4個獨立通道:
- 輸入捕獲
- 輸出比較
- PWM生成(邊緣或中間對齊模式)
- 單脈沖模式輸出
- 使用外部信號控制定時器和定時器互連的同步電路
- 如下事件發生時產生中斷/DMA:
- 更新:計數器向上溢出/向下溢出,計數器初始化(通過軟件或者內部/外部觸發)
- 觸發事件(計數器啟動、停止、初始化或者由內部/外部觸發計數)
- 輸入捕獲
- 輸出比較
基本型定時器TIM6和TIM7的主要功能包括:
- 16位自動重裝載累加計數器
- 16位可編程(可實時修改)預分頻器,用於對輸入的時鍾按系數為1~65536之間的任意數值分頻
- 觸發DAC的同步電路 (注:此項是TIM6/7獨有功能)
- 在更新事件(計數器溢出)時產生中斷/DMA請求
1.5 UART串口
1.5.0 串口通信協議
- 物理層:規定通訊系統中具有機械、電子功能部分的特性,確保原始數據在物理媒介中傳輸。就是硬件部分。
- 協議層:協議層主要規定通訊邏輯,統一收發雙方的數據打包、打包標准。就是軟件部分
- 串口通信轉換

- RS232標准串口主要用於工業設備直接通信
- 電平轉換芯片一般有MAX3232、SP3232


- USB轉串口主要用於電腦和設備通信
- 電平轉換芯片一般有CH340、PL2303、CP2102、FT232
- 使用時電腦需要安裝電平轉換芯片的驅動

- 電平標准(電信號)RS232、RS485、TTL
- RS232:負邏輯;邏輯"1"為-3到-15V,邏輯"0"為3-15V;
- RS485:差分信號負邏輯;邏輯"1”以兩線間的電壓差為-(26)V表示,邏輯"0"以兩線間的電壓差為+(26)V表示。
- TTL:邏輯“1”為+5V,邏輯“0”為0V;單片機上UART用的就是TLL電平
- RS232和RS485、TTL的區別
- RS232、RS485、TTL是指電平標准(電信號)
- TTL電平標准 是 低電平為0,高電平為1(對地,標准數字電路邏輯)。
- RS232電平標准 是 正電平為0,負電平為1(對地,正負6-15V皆可,甚至可以用高阻態)。
- RS485與RS232類似,但是采用差分信號邏輯,更適合長距離、高速傳輸。

- 起始位:由1個邏輯0的數據位表示
- 結束位:由0.5、1、1.5或2個邏輯1的數據位表示
- 有效數據:在起始位后緊接的就是有效數據,有效數據的長度通常約定為5、6、7或8位長
- 校驗位:可選,為了數據的抗干擾性
- 校驗方法選擇:1-奇校驗(odd)、2-偶校驗(even)、3-0校驗(space)、4-1校驗(mark)、5-無校驗(noparity)
- 奇校驗(odd):有效數據和校驗位中"1"的個數為奇數
- 偶校驗(even):有效數據和校驗位中"1"的個數為偶數
- 0校驗(space):不管有效數據中內容是什么,校驗位總為"0"
- 1校驗(mark):不管有效數據中內容是什么,校驗位總為"1"
- 無校驗(noparity):不含任何校驗位
- 校驗方法選擇:1-奇校驗(odd)、2-偶校驗(even)、3-0校驗(space)、4-1校驗(mark)、5-無校驗(noparity)
1.5.1 STM32F4串口USART介紹
- UART:通用異步收發器
- USART:通用同步異步收發器
USART即通用同步異步收發器,用於靈活的與外部設備全雙工數據交換,它支持多種通信傳輸方式,可以通過小數波特率發生器提供多種波特率。
打開STM32F407ZGT6芯片手冊,目錄切換到3 Pinouts and pin description,尋找Table 7. STM32F40x pin and ball definitions


UART只是異步傳輸功能,所以沒有SCLK、nCTS和nRTS功能引腳。
打開STM32F407中文手冊,找到26 通用同步異步收發器 (USART)、26.3 USART 功能說明中的圖 246. USART 框圖

- ① 功能引腳
- TX:發送數據輸出引腳
- RX:接收數據輸入引腳
- SW_RX:數據接收引腳,只用於單線和智能卡模式,屬於內部引腳,沒有具體外部引腳
- nRTS:請求以發送(Request To Send),n表示低電平有效。如果使能RTS流控制,當USART接收器准備好接收新數據時就會將nRTS變成低電平;當接收寄存器已滿時,nRTS將被設置為高電平。該引腳只適用於硬件流控制
- nCTS:清除以發送(Clear To Send),n表示低電平有效。如果使能CTS流控制,發送器在發送下一幀數據之前會檢測nCTS引腳,如果為低電平,表示可以發送數據,如果為高電平則在發送完當前數據幀之后停止發送。該引腳只適用於硬件流控制
- SCLK:發送器時鍾輸出引腳。這個引腳僅適用於同步模式
- ② 數據寄存器
- USART數據寄存器(
USART_DR)只有低9位有效,並且第9位數據是否有效要取決於USART控制寄存器1(USART_CR1)的M位設置,當M位為0時表示8位數據字長,當M位為1表示9位數據字長,我們一般使用8位數據字長 USART_DR包含了已發送的數據或者接收到的數據。USART_DR實際是包含了兩個寄存器,一個專門用於發送的可寫TDR,一個專門用於接收的可讀RDR。當進行發送操作時,往USART_DR寫入數據會自動存儲在TDR內;當進行讀取操作時,向USART_DR讀取數據會自動提取RDR數據TDR和RDR都是介於系統總線和移位寄存器之間。串行通信是一個位一個位傳輸的,發送時把TDR內容轉移到發送移位寄存器,然后把移位寄存器數據每一位發送出去,接收時把接收到的每一位順序保存在接收移位寄存器內然后才轉移到RDRUSART支持DMA傳輸,可以實現高速數據傳輸
- USART數據寄存器(
- ③ 控制器
打開官方庫的stm32f4xx_usart.h文件,里面存放着關於USART的庫函數。
串口常用庫函數:
⭐ 串口掛載在APB1和APB2上,具體在中文手冊中查
-
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);串口初始化;傳入參數- 串口參數結構體
USART_InitTypeDefUSART_BaudRate波特率設置。一般設置為2400、9600、19200、115200。USART_WordLength數據幀字長,可選8位或9位。一般使用8數據位,如果使能了奇偶校驗則一般設置為9數據位USART_StopBits停止位設置,可選0.5個、1個、1.5個和2個停止位,一般選1個USART_Parity奇偶校驗控制選擇,可選USART_Parity_No(無校驗)、USART_Parity_Even(偶校驗)以及USART_Parity_Odd(奇校驗)USART_ModeUSART模式選擇,有USART_Mode_Rx和USART_Mode_TxUSART_HardwareFlowControl硬件流控制選擇,只有在硬件流控制模式才有效,可選有⑴使能RTS、⑵使能CTS、⑶同時使能RTS和CTS、⑷不使能硬件流
- 串口參數結構體
-
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);串口時鍾初始化;- 串口時鍾參數結構體
USART_ClockInitTypeDefUSART_Clock同步模式下SCLK引腳上時鍾輸出使能控制,可選禁止時鍾輸出(USART_Clock_Disable)或開啟時鍾輸出(USART_Clock_Enable);USART_CPOL同步模式下SCLK引腳上輸出時鍾極性設置,可設置在空閑時SCLK引腳為低電平(USART_CPOL_Low)或高電平(USART_CPOL_High)。USART_CPHA同步模式下SCLK引腳上輸出時鍾相位設置,可設置在時鍾第一個變化沿捕獲數據(USART_CPHA_1Edge)或在時鍾第二個變化沿捕獲數據。USART_LastBit選擇在發送最后一個數據位的時候時鍾脈沖是否在SCLK引腳輸出,可以是不輸出脈沖(USART_LastBit_Disable)、輸出脈沖(USART_LastBit_Enable)。
- 串口時鍾參數結構體
-
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);串口使能 -
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);使能相關中斷 -
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);串口發送數據 -
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);串口接收數據 -
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);串口獲取狀態標志位 -
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);串口清除狀體標志位 -
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);串口獲取中斷狀態標志位 -
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);串口清除中斷狀態標志位
實驗4-1 串口收發實驗
功能設計:電腦和開發板串口收發數據,並且控制LED開關


RXD和TXD連接PA9和PA10
查看芯片手冊,連接在USART1上

查看中文手冊,掛載在APB2上
工程代碼地址: stm32-f4/4-1 UART
- 新建工程或者用模板工程
- LED初始化
- 串口初始化
- 使能APB2時鍾
- 使能AHB1時鍾
- 串口GPIO初始化,設置為復用
- 引腳復用映射配置
- 串口參數初始化
- 中斷分組初始化NVIC
- 初始化串口中斷
- 使能串口
- 編寫串口中斷子服務函數
- 串口中斷狀態
- 收發數據
- 傳輸狀態
打開工程,新建一個HARDWARE文件下新建USART文件夾,存放USART_User.c和USART_User.h文件,復制上個工程的LED文件到此文件夾,依次添加進工程
編寫代碼
void USART_User_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//IO配置結構體
USART_InitTypeDef USART_InitStructure;//串口配置結構體
NVIC_InitTypeDef NVIC_InitStructure;
//使能串口時鍾和引腳時鍾
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//初始GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //輸出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽輸出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //沒有輸出時上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed; //50Mhz
GPIO_Init(GPIOA,&GPIO_InitStructure);
//復用配置
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//串口參數初始化
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1,&USART_InitStructure);
//中斷初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//初始化串口中斷
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
//串口使能
USART_Cmd(USART1,ENABLE);
USART_ClearFlag(USART1,USART_FLAG_TC);
}
void USART1_IRQHandler(void)
{
u8 RES;
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
RES= USART_ReceiveData(USART1);
USART_SendData(USART1,RES);
if(RES==0x01)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
}
}
}
int main(void)
{
delay_init(168);
LED_Init();
USART_User_Init();
while(1)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
}
}
正常收發,功能實現
1.6 看門狗
1.6.1 獨立看門狗
-
獨立看門狗介紹
簡單來說就是一個12位的遞減計數器,當計數器從某個值減到0的時候,看門狗系統就會產生一個復位信號,即IWDG RESET,如果在沒有減到0的時候,刷新了數值,就不會產生復位信號。 -
獨立看門狗&窗口看門狗
- 獨立看門狗:寵物狗
- 窗口看門狗:警犬

- 獨立看門狗時鍾:RC振盪器LSI提供,32khz,不是十分精確,適用於對時間精度要求低的情況
- 計數器時鍾:LSI提供,經過一個8位預分頻器(通過寄存器
IWDG_PR設置分頻因子),計數器時鍾CK_CNT=40/4*2^(PRV) - 計數器:12位遞減計數器,最大為0xFFF
- 重裝載寄存器:12位寄存器,裝着要刷新到寄存器里的值,這個值決定着看門狗溢出時間
Tout=(4*(2^prv)*rlv)/32 (s),prv是預分頻寄存器值,rlv是重裝載寄存器的值。 - 關鍵字寄存器(控制寄存器):0XAAAA:把RLR的值重裝載到CNT上;0x5555:PR和RLR這兩個寄存器可寫;0xCCCC:啟動IWDG(一旦看門狗啟動就關不掉)
- 狀態寄存器:表明寄存器RVU和PVU的狀態,軟件操作不了
- 用來檢測和解決由程序引起的故障
- 當一個程序完整運行的周期是50ms,那么可以設置一個60ms的看門狗,當程序運行時間超過60ms還沒有喂狗,則說明程序出現故障,則啟動看門狗復位

/* Prescaler and Counter configuration functions ******************************/
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);
void IWDG_SetReload(uint16_t Reload);
void IWDG_ReloadCounter(void);
/* IWDG activation function ***************************************************/
void IWDG_Enable(void);
/* Flag management function ***************************************************/
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);
IWDG庫函數
⭐ IWDG由LSI提供時鍾源,不需要使能時鍾
-
兩個參數設置函數
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);設置預分頻系數void IWDG_SetReload(uint16_t Reload);設置重裝載值
-
看門狗使能函數
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);使能寫預分頻和重裝載值void IWDG_Enable(void);獨立看門狗使能
-
喂狗函數
void IWDG_ReloadCounter(void);重裝載值
-
獲取狀態函數
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);重裝載/預分頻更新
實驗5-1 獨立看門狗實驗
功能設計:啟動獨立看門狗,按鍵按一下喂一次狗,超過時間則復位
用到按鍵KEY_UP、LED、看門狗(芯片內置)
工程代碼地址: stm32-f4/5-1 IWDG
- 新建工程或者用模板工程
- KEY初始化
- LED初始化
- 看門狗初始化
- 使能寫預分頻和重裝載值
- 設置預分頻系數
- 設置重裝載值
- 喂狗
- 使能看門狗
- 編寫應用函數(按鍵按一下喂狗)
打開工程,新建一個HARDWARE文件下新建IWDG文件夾,存放IWDG.c和IWDG.h文件,復制上個工程的LED文件和KEY到此文件夾,依次添加進工程
編寫代碼
void IWDG_Init(void)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_4); //(4*(2^prv)*rlv)/32
IWDG_SetReload(500); //1s
IWDG_ReloadCounter(); //喂狗
IWDG_Enable(); //使能看門狗
}
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//時鍾使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//按鍵初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //輸入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //按鍵接高,所以這里接下拉
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
int main(void)
{
delay_init(168);
LED_Init();
KEY_Init();
delay_ms(500);
GPIO_ResetBits(GPIOF,GPIO_Pin_9);
IWDG_Init(); //一定要放在最后初始化
while(1){
if(1==KEYUP) //被按下
{
delay_ms(10); //延時10ms消抖
if(1==KEYUP)
{
delay_ms(10);
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);
IWDG_ReloadCounter();
}
}
}
}
上電后不按LED將閃爍,按下按鍵LED不閃爍
1.6.2 窗口看門狗
- 窗口和獨立看門狗的區別
- 窗口看門狗有一個刷新窗口0x7F-0x40,並不是所有時間都可以刷新
- 窗口看門狗由PCLK1提供時鍾源,獨立看門狗由LSI提供時鍾源
- 窗口看門狗有中斷,獨立看門狗沒有中斷
- 窗口看門狗的計數器6位遞減,獨立看門狗12位遞減

- 窗口看門狗時鍾:窗口看門狗時鍾來自PCLK1,PCLK1最大是42M,由RCC時鍾開啟
- 計數器時鍾:
CNT_CLK=CK_CLK/(2^WDGTB)、CK_CLK=PCLK1/4096 - 計數器
- 窗口值:下窗口值固定0x40,上窗口由寄存器CFR設置

/* Function used to set the WWDG configuration to the default reset state ****/
void WWDG_DeInit(void);
/* Prescaler, Refresh window and Counter configuration functions **************/
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
void WWDG_SetWindowValue(uint8_t WindowValue);
void WWDG_EnableIT(void);
void WWDG_SetCounter(uint8_t Counter);
/* WWDG activation function ***************************************************/
void WWDG_Enable(uint8_t Counter);
/* Interrupts and flags management functions **********************************/
FlagStatus WWDG_GetFlagStatus(void);
void WWDG_ClearFlag(void);
WWDG庫函數
⭐
void RCC_APB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);WWDG掛載在APB1時鍾線下,所以在用WWDG之前一定要使能時鍾
- 設置函數
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);設置分頻因子WWDG_SetWindowValue(uint8_t WindowValue);設置窗口值void WWDG_SetCounter(uint8_t Counter);
- 使能函數
void WWDG_EnableIT(void);使能看門狗中斷void WWDG_Enable(uint8_t Counter);使能看門狗
實驗5-2 窗口看門狗實驗
功能設計:
使用到LED、WWDG(芯片內置)
工程代碼地址: stm32-f4/ 5-2 WWDG
- 新建工程或者用模板工程
- LED初始化
- 看門狗初始化
- 使能時鍾
- 設置分頻系數
- 設置上窗口值
- 開啟提前喚醒中斷
- NVIC中斷分組和中斷初始化
- 清除中斷標志位
- 使能看門狗
- 喂狗
- 編寫中斷服務函數
- 喂狗
- 清除中斷標志位
打開工程,新建一個HARDWARE文件下新建IWDG文件夾,存放WWDG.c和WWDG.h文件,復制上個工程的LED文件到此文件夾,依次添加進工程
編寫代碼
void WWDG_Init(void)
{
NVIC_InitTypeDef NVIC_InitTStruture;
//使能時鍾
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);
//設置分頻系數
WWDG_SetPrescaler(WWDG_Prescaler_8);
//設置上窗口值
WWDG_SetWindowValue(0x5f);
//開啟提前喚醒中斷
WWDG_EnableIT();
//中斷分組和初始化中斷
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTStruture.NVIC_IRQChannel=WWDG_IRQn;
NVIC_InitTStruture.NVIC_IRQChannelCmd= ENABLE;
NVIC_InitTStruture.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitTStruture.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitTStruture);
//清除中斷標志位
WWDG_ClearFlag();
//使能看門狗
WWDG_Enable(0x7F&0x7F);
//喂狗
WWDG_SetCounter(0x7F&0x7F);
}
void WWDG_IRQHandler()
{
WWDG_SetCounter(0x7F&0x7F);
WWDG_ClearFlag();
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
}
int main(void)
{
delay_init(168);
LED_Init();
delay_ms(500);
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //點亮燈
delay_ms(500);
WWDG_Init();
while(1){
GPIO_SetBits(GPIOF,GPIO_Pin_9); //熄滅燈
}
}
LED閃爍,如果關掉看門狗,不閃爍
