标准库开发
即利用官方提供的标准库进行开发,相比较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_ICPolarity
2-TIM_ICPolarity:输入捕获边沿触发选择,可选上升沿触发、下降沿触发或边沿跳变触发。它设定CCER寄存器CCxP位和CCxNP位的值。TIM_ICSelection
3-TIM_ICSelection:捕获通道选择,捕获通道ICx的信号可来自三个输入通道,分别为TIM_ICSelection_DirectTI、TIM_ICSelection_IndirectTI或TIM_ICSelection_TRC它设定CCRMx寄存器的CCxS[1:0]位的值。TIM_ICPrescaler
4-TIM_ICPrescaler:输入捕获通道预分频器,可设置1、2、4、8分频,它设定CCMRx寄存器的ICxPSC[1:0]位的值。如果需要捕获输入信号的每个有效边沿,则设置1分频即可。TIM_ICFilter
5-TIM_ICFilter:输入捕获滤波器设置,可选设置Ox0至OxOF。它设定CCMRx寄存器ICxF[3:0]位的值。一般我们不使用滤波器,即设置为0。
- 输入捕获结构体
-
断路和死区管理
void TIM_BDTRConfig(TIM_TypeDef* TIMx, TIM_BDTRInitTypeDef *TIM_BDTRInitStruct);
断路和死区配置;传入参数(定时器TIMx,断路和死区结构体)- 断路和死区结构体
TIM_OSSRState
TIM_OSSIState
TIM_LOCKLevel
TIM_DeadTime
TIM_Break
TIM_BreakPolarity
TIM_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内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到RDR
USART
支持DMA
传输,可以实现高速数据传输
- USART数据寄存器(
- ③ 控制器
打开官方库的stm32f4xx_usart.h
文件,里面存放着关于USART
的库函数。
串口常用库函数:
⭐ 串口挂载在APB1和APB2上,具体在中文手册中查
-
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
串口初始化;传入参数- 串口参数结构体
USART_InitTypeDef
USART_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_Mode
USART模式选择,有USART_Mode_Rx和USART_Mode_TxUSART_HardwareFlowControl
硬件流控制选择,只有在硬件流控制模式才有效,可选有⑴使能RTS、⑵使能CTS、⑶同时使能RTS和CTS、⑷不使能硬件流
- 串口参数结构体
-
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
串口时钟初始化;- 串口时钟参数结构体
USART_ClockInitTypeDef
USART_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闪烁,如果关掉看门狗,不闪烁