STM32常用知识点总结
谨以此篇,记录我的stm32学习路线和感悟。
我一共接触过两款stm32的单片机,第一款是b站江科大自化协(如今叫江协科技)stm32系列课程中所教的STM32F103C8T6,第二款是野火的STM32F407ZGT6霸天虎开发板。
我认为其中比较重要的知识点为单片机内部的时钟树,中断控制器和外设资源中的定时器,串口,ADC。而其他的知识点比如I2C,SPI等通信协议,电源控制等完全可以在实际项目中的时候去学习。先去把最基础的框架搭建好,再去进行深入的学习。再搞的差不多之后可以去学习FREERTOS,这是一个实时性操作系统,对于比较复杂的工程可以大大提高代码的逼格(不是),系统工作的实时性。
( 温馨提示:PC端观看本文观感会好一点 )
目录
GPIO
CortexM4内核的F4系列的GPIO工作模式及配置
1.引脚选择和模式设置:
2.输出模式配置:
3.数据读取和写入:
结构体成员配置介绍
GPIO_Mode:有四种模式选择分别是:输入模式、输出模式、复用模式以及模拟模式
GPIO_Speed:就是配置引脚的响应速度,有四个挡可以选择。
GPIO_OType:有两个可选
GPIO_PuPd:有上拉、下拉、和不上拉也不下拉 三种模式
接下来介绍cortexm3内核的STM32F103系列的GPIO相关部分
在Cortex-M3里,对于GPIO的配置种类有8种
4种输入模式:
输入浮空(GPIO_Mode_IN_FLOATING)
输入上拉 (GPIO_Mode_IPU)
输入下拉 (GPIO_Mode_IPD)
模拟输入 (GPIO_Mode_AIN)
4种输出模式:
开漏输出 (GPIO_Mode_Out_OD)
开漏复用输出 (GPIO_Mode_AF_OD)
推挽式输出 (GPIO_Mode_Out_PP)
推挽式复用输出 (GPIO_Mode_AF_PP)
时钟树
STM32的5个时钟源
STM32F407ZGT6时钟树
时钟配置:
2、初始化之后的状态
STM32F103C8T6时钟树
SysTick(系统定时器)的使用方法
NVIC
NVIC结构体
结论:
外部中断
中断资源
外部中断结构体配置
串口
串口资源
串口结构体代码示例
定时器
STM32F407的 定时器资源
定时器的溢出时间计算:
STM32F103的 定时器资源
定时器功能
PWM相关知识点
定时器结构体初始化
输出比较结构体
最后
GPIO
先来简单说一下GPIO。
GPIO通用输入输出端口的简称,是STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能
接下来介绍F407和F103的GPIO部分
STM32F103的GPIO结构体初始化成员与F407有所不同
主要区别为103把引脚的输入输出模式都用一个成员GPIO_Mode来表示,而407中把这个结构体成员拆分为GPIO_Mode,GPIO_OType,GPIO_PuPd 三个成员。
CortexM4内核的F4系列的GPIO工作模式及配置
在STM32F407中,STM32F407ZGT6
一共114个IO口
F407系列微控制器中的GPIO(通用输入输出)是一种重要的外设,用于与外部设备进行数字信号的输入和输出。GPIO以寄存器的方式进行配置和控制,下面是在STM32F407中使用GPIO的一般步骤和一个详细示例:
1.引脚选择和模式设置:
2.输出模式配置:
3.数据读取和写入:
此图展示的是F407的GPIO结构体成员,包括引脚选择,配置输入或输出,配置引脚速率,配置输入和输出模式。
结构体成员配置介绍
GPIO_Mode:有四种模式选择分别是:输入模式、输出模式、复用模式以及模拟模式
GPIO_Speed:就是配置引脚的响应速度,有四个挡可以选择。
GPIO_OType:有两个可选
分别是推挽和开漏:推挽具有一定的驱动能力,能简单驱动外设设备,而开漏是没有驱动能力的,需加上拉电阻才能输出高电平。
GPIO_PuPd:有上拉、下拉、和不上拉也不下拉 三种模式
以上就是对stm32f4系列各个结构体成员以及配置模式的介绍
以配置STM32F407GPIOF6引脚为例,
RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOF, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //选择GPIOF引脚
/*设置引脚模式为输出模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //选择GPIOF引脚
/*设置引脚的输出类型为推挽输出*/
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出类型 (输出类型寄存器)
/*设置引脚为上拉模式*/
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //推挽输出类型 (输出类型寄存器)
/*设置引脚速率为2MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //输出速度100MHz (输出速度寄存器)
//结构体成员数据全部传入配置函数
/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
补充一句,在STM32F407中把引脚模式配置为复用功能的时候,需要使用GPIO_PinAFConfig()函数将引脚与相应的功能连接上,否则不好使。这是我在调试一个陀螺仪的时候注意到的,我以为和103一样,都默认初始化引脚之后就不用管了
/* 连接 PXx 到 USARTx_Tx*/
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3, GPIO_AF_USART2);
/* 连接 PXx 到 USARTx__Rx*/
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2, GPIO_AF_USART2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Init(GPIOA, &GPIO_InitStructure);
STM32F103 微控制器提供了多个 GPIO 引脚,用于与外部设备进行数字信号的输入和输出。每个引脚都可以通过配置来灵活地设置为输入或输出。作为输入引脚时,GPIO 可以读取外部设备的状态,如按钮的按下状态或传感器的测量值。作为输出引脚时,GPIO 可以控制外部设备的操作,如驱动 LED 灯的亮灭。
接下来介绍cortexm3内核的STM32F103系列的GPIO相关部分
typedef struct
{
uint16_t GPIO_Pin;
uint16_t GPIO_Speed;
uint16_t GPIO_Mode;
}GPIO_InitTypeDef;
在Cortex-M3里,对于GPIO的配置种类有8种
4种输入模式:
输入浮空(GPIO_Mode_IN_FLOATING)
可以做KEY识别,RX1 浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的
输入上拉 (GPIO_Mode_IPU)
IO内部上拉电阻输入. 将一个输入端口连接至高电平信号的电路拓扑中。这种情况,在外部没有将该输入口拉向地线时,输入端口处于高电平状态。
输入下拉 (GPIO_Mode_IPD)
IO内部下拉电阻输入。 将一个输入端口连接至低电平信号的电路拓扑中,一般为地。这种情况,在外部没有将该输入口拉向高电平时,输入端口处于低电平状态。
模拟输入 (GPIO_Mode_AIN)
应用ADC模拟输入,或者低功耗下省电。 将连续的物理信号转换为数字信号(即模拟信号),将其送入数字电路中。通常,模拟输入要采用模数转换器(ADC)等外部元件来实现。
4种输出模式:
开漏输出 (GPIO_Mode_Out_OD)
输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
在开漏输出模式下,一个输出端口可以踢动低电平先后,但是无法提供高电平信号。
IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。
可以读IO输入电平变化,实现C51的IO双向功能。
开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。
开漏输出的这一特性一个明显的优势就是可以很方便的调节输出的电平,因为输出电平完全由上拉电阻连接的电源电平决定。所以在需要进行电平转换的地方,非常适合使用开漏输出。
开漏输出的这一特性另一个好处在于可以实现"线与"功能,所谓的"线与"指的是多个信号线直接连接在一起,只有当所有信号全部为高电平时,合在一起的总线为高电平;只要有任意一个或者多个信号为低电平,则总线为低电平。而推挽输出就不行,如果高电平和低电平连在一起,会出现电流
开漏输出应用:
模拟I2C使用开漏输出_OUT_OD,接上拉电阻,能够正确输出0和1;
倒灌,损坏器件。所以总线一般会使用开漏输出.
开漏复用输出 (GPIO_Mode_AF_OD)
GPIO_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)。
推挽式输出 (GPIO_Mode_Out_PP)
推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中
各负责正负半周的波形放大任务
电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。
输出既可以向负载灌电流,也可以从负载抽取电流。
推拉式输出级既提高电路的负载能力,又提高开关速度。
GPIO_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的。
推挽输出结构是由两个MOS或者三极管受到互补控制的信号控制,两个管子时钟一个在导通,一个在截止。
推挽输出的最大特点是可以真正的输出高电平和低电平,在两种电平下都具有驱动能力。
所谓的驱动能力,就是指输出电流的能力。对于驱动大负载(即负载内阻越小,负载越大)时,例如IO输出为5V,驱动的负载内阻为10ohm,于是根据欧姆定律可以正常情况下负载上的电流为0.5A(推算出功率为2.5W)。一般的IO不可能输出这么大的电流。于是造成的结果就是输出电压会被拉下来,达不到标称的5V。
推挽输出高低电平的电流都能达到几十mA。
当输出引脚需要高电平时,PMOS晶体管被打开,输出端口拉向VCC。当输出引脚需要低电平时,NMOS晶体管被打开,将输出端口拉向GND。
推挽输出的缺点是,如果当两个推挽输出结构相连在一起,一个输出高电平,另一个输出低电平,电流会从第一个引脚的VCC通过上端MOS再经过第二个引脚的下端MOS直接流向GND,也就是会发生短路,进而可能造成端口的损害。这也是为什么推挽输出不能实现" 线与"的原因。
推挽输出在输出的时候是通过单片机内部的电压,所以他的电压是不能改变的。
一般情况下,使用推挽输出。
注意:推挽状态下,是可以读取IO口的电平状态的。
推挽式复用输出 (GPIO_Mode_AF_PP)
GPIO_AF_PP ——片内外设功能(I2C的SCL,SDA)。
以初始化GPIOA6,A7两个引脚为例,F103
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_6 | GPIO_Pin_7);
时钟树
单片机工作的心脏,梦开始的地方!
时钟树是STM32为了实现低功耗而设计的功能完善构成复杂的时钟系统,它可以根据不同的外设和应用场合,选择合适的时钟源和频率,以提高系统性能和降低功耗。
STM32的5个时钟源
任何一个外设在使用之前,必须首先使能其相应的时钟。
一般情况下通过高速外部晶振(HSE)产生时钟信号,经过PLL锁相环先经过分频因子分频,再经过倍频因子倍频 ,之后再经过一个分频因子分频。构成锁相环(PLL)时钟,最终使用PLL时钟作为系统时钟。
STM32F407ZGT6时钟树
以STM32F407为例,霸天虎开发板的外部晶振为25MHZ,在时钟树图里,HSE经过分频因子 M=25 (M的值取决于外部晶振的频率,通过配置M的值将外部晶振分频为1MHZ,如果外部晶振为8MHZ,则将M配置为8) 将25MHZ分频为1MHZ,经倍频因子 N=336 倍频为336MHZ,再经过分频因子 P=2 分频为168MHZ,最终锁相环时钟(PLL)作为系统时钟源,AHB总线的最大时钟为168MHZ,APB2时钟线为84MHZ,APB1时钟线为42MHZ。
主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号(通过选择器),并具有两个不同的输出时钟第一个输出 PLLP 用于生成高速的系统时钟(最高 168MHz)第二个输出 PLLQ 用于生成 USB OTG FS 的时钟(48MHz),随机数发生器的时钟和 SDIO时钟。
专用 PLL(PLLI2S)用于生成精确时钟,从而在 I2S 接口实现高品质音频性能。
时钟配置:
1、对于HSI、HSE、PLL等时钟源配置,没有专门的固件库函数,可以通过SystemInit函数来操作配置。该函数具体实现过程如下(也可以根据寄存器自己操作):
(1)、系统复位之后,先调用SystemInit函数,该函数的作用是初始化系统时钟,设置PLL等
(2)、打开HSE,等待其稳定,
(3)、设置AHB、APBx、等分频系数
(4)、设置HSE为主PLL时钟源,并且配置主PLL里面的分频和倍频参数,然后产生PLLCLK并将其使能,并选择系统时钟(SYSCLC)为PLLCLK
2、初始化之后的状态
SYSCLK(系统时钟) =168MHZ
AHB总线时钟(HCLK=SYSCLK)=168MHZ
APB1总线时钟(PCLK1=SYSCLK/4)=42MHZ
APB2总线时钟(PCLK2=SYSCLK/2)=84MHZ
PLL主时钟 =168MHZ
系统复位后先调用的是SystemInit函数,其次是main函数,这一点在启动文件里面写了。
STM32F103C8T6时钟树
STM32F103的时钟树由四个时钟源、一个总线矩阵、一个PLL(锁相环)倍频器、一个CSS(时钟安全系统)检测器、一个USB预分频器、一个RTC预分频器、一个MCO输出选择器等部分组成
103外接晶振一般为4~16MHZ
总线矩阵:由多层AHB总线矩阵构成,用于连接Cortex-M内核、DMA控制器、外设和存储器。总线矩阵包括以下几条总线:
PLLXTPRE是一个分频器,它可以选择HSE时钟的一分频或二分频作为PLL的输入时钟源;
PLLSRC是一个选择器,它可以选择HSI时钟的二分频或HSE时钟(经过PLLXTPRE分频)作为PLL的输入时钟源;
PLLMUL是一个倍频器,它可以将PLL的输入时钟源进行2~16倍的倍频,得到PLL的输出时钟源;
prescalear为预分频。
SysTick(系统定时器)的使用方法
简介:该定时器(也称“滴答计时器”)寄存器,24位,只能递减,该寄存器存在于内核,嵌套在NVIC中,所有的Cortex-M内核单片机都具有该定时器。SysTick_Config(uint32_t ticks)初始化函数位于Core_cm4.h中,计数器每计数一次的时间为 1/SYSCLK 秒,一般我们设置系统时钟 SYSCLK 等于 168M(F103一般设置为72M)。调用Systick定时器,只需要调用SysTick_Config(uint32_t ticks)函数,向函数中写入初始值,如果时钟源选择的是AHB=168MHZ,那么,每递减一次的时间就是1/168M,需要多少时间就设多大初始值。当递减到零时会产生异常(中断)请求。
代码讲解(通常做delay函数):
系统初始化之后可以通过变量SystemCoreClock获取系统变量,如果SYSCLK(系统时钟)=168MHZ, 那么变量等于168000000,那么 SysTick_Config(SystemCoreClock / 1000000) 就代表每计数 168M / 1000000 次就产生中断,以F407为例,如若选择AHB168M做时钟源,每计数一次的时间为1/168M秒,那么计数(168000000)/ 1000000 次之后,也就是 (168000000)/ 1000000 * 1/168M =1/1000000秒,也就是1us就产生一次中断,这样就实现了1us的延时,同理,若修改1000000的值为1000,那么就是160M/1000 * 1/168M 秒,也就是1ms.F103延时计算原理和F407一样,无非就是时钟源频率不同罢了。
static __IO u32 TimingDelay;
void SysTick_Init(void)
{
/* SystemFrequency / 1000 1ms中断一次
* SystemFrequency / 100000 10us中断一次
* SystemFrequency / 1000000 1usÖ中断一次
*/
if (SysTick_Config(SystemCoreClock / 1000000))
{
while (1);
}
}
void Delay_us(__IO u32 nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}
NVIC
NVIC的全称是Nested vectoredinterrupt controller,即嵌套向量中断控制器。
对于M3和M4内核的MCU,每个中断的优先级都是用寄存器中的8位来设置的。8位的话就可以设置2^8 =256级中断,实际中用不了这么多,所以芯片厂商根据自己生产的芯片做出了调整。比如ST的STM32F1xx和F4xx只使用了这个8位中的高四位[7:4],低四位取零,这样2^4=16,只能表示16级中断嵌套。
对于这个NVIC,有个重要的知识点就是优先级分组,抢占优先级和子优先级,下面就以STM32为例进行介绍,STM32F1xx和F4xx都是只使用了这个8位寄存器的高四位[7:4]。
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。
在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套。Reset、NMI、Hard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。
对于初学者还有一个比较纠结的问题就是系统中断(比如:PendSV,SVC,SysTick)是不是一定比外部中断(比如SPI,USART)要高,答案:不是的,它们是在同一个NVIC下面设置的。
NVIC结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_x); 一个工程里面只进行一次分组就好
NVIC_InitStructure.NVIC_IRQChannel = 中断源; //选择中断源 ,比如串口中断,定时器中断或者外部中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //设置抢占优先级为2 具体数字为几需根据中断优先级分组进行设置,如果分组为0,那么设置抢占优先级为1是无效的,因为0组不包括抢占优先级只包括16个(4bit)响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //设置响应优先级为2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断;
NVIC_Init(&NVIC_InitStructure); //初始化以上参数;
配置完中断需要写出对应中断源的中断函数,且函数名必须与中断向量表里的函数名保持一致。
要注意的几点是:
1)如果指定的抢占式优先级别或响应优先级别超出了选定的优先级分组所限定的范围,将可能得到意想不到的结果;
2)抢占式优先级别相同的中断源之间没有嵌套关系;
3)如果某个中断源被指定为某个抢占式优先级别,又没有其它中断源处于同一个抢占式优先级别,则可以为这个中断源指定任意有效的响应优先级别
结论:
1)抢占优先级越小,优先级越高;相同抢占优先级的中断不能嵌套;
2)相同抢占优先级N个中断发生时,响应优先级越小的中断首先执行(不能嵌套),如果响应优先级也均相同,则根据各中断对应向量表的位置来确定,向量表中越靠前的中断先响应
外部中断
中断资源
STM32F407 的中断控制器支持 22 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F407 的 22 个外部中断为:
STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。 STM32F103的 19 个外部中断为:
外部中断结构体配置
接下来就来到了配置的环节,我们将一步一步配置好我们的外部中断函数,就让我们开始吧!(具体就不一一介绍怎么编写的了,其实就是复制粘贴,找到相应的参数就好了)
所以,一个外部中断配置过程需要初始化GPIO结构体,NVIC结构体,EXTI外部中断结构体
其中外部中断初始化结构体的成员为
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Linex; //选择外部中断线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //设置触发模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //设置上升沿触发或者下降沿触发或者双边沿触发,以按键触发外部中断为例,按键一头接地另一头接GPIO,当按键按下的时候电位升高此时为上升沿,当按键松开之后点位降低,此时为下降沿。如果此处配置为上升沿,那么只有在产生上升沿的时候才会触发外部中断,也就是按键按下的时候触发一次松开按键不触发。如果此处配置为下降沿,那么只有在产生下降沿的时候才会触发外部中断,也就是按键按下的时候不触发松开按键的时候才会触发,如果配置为双边沿,那么按键按下和松开的时候都会触发外部中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
同样的,配置完NVIC之后需要写出外部中断函数,以外部中断配置按键控制LED举例
// 外部中断4服务程序
void EXTI4_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY0==0) // 下降沿触发
{
LED0 =! LED0;
LED1 =! LED1;
}
EXTI_ClearITPendingBit(EXTI_Line4);//清除LINE4上的中断标志位
}
串口
串口资源
不同型号的芯片所具有的串口资源不一样,STM32F103C8T6只有3个串口,而STM32F407具有六个串口
APB2 : USART1
APB1 : USART2、USART3
在这里引用一下江科大老师的PPT,简单介绍下各种通信协议和对串口通信的简介,在STM32F407中的串口资源为
两个进行串口通信的设备需要共地
串口作为工具,了解其工作原理,并会修改波特率等代码参数就行了
串口结构体代码示例
下面介绍一个STM32F407串口通用代码,与F103的区别主要为GPIO初始化结构体成员有变化,以及两个系列板子的串口资源有所不同以外,其他都一样
代码中使用了串口接收中断,有中断就要配置NVIC结构体,所以代码配置了GPIO,NVIC,USART结构体,基本套路为打开相应外设时钟,初始化各个结构体成员,通过修改结构体成员来配置为我需要的工作模式,比如修改波特率
#include "usart.h"
uint8_t USARTx_RxData;
uint8_t USARTx_RxFlag;
/**
* @brief USART GPIO 配置,工作模式配置。115200 8-N-1
* @param 无
* @retval 无
*/
void USARTx_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_AHB1PeriphClockCmd(USARTx_RX_GPIO_CLK|USARTx_TX_GPIO_CLK,ENABLE);
/* 使能 USART 时钟 */
USARTx_CLOCKCMD(USARTx_CLK, ENABLE);
/* GPIO初始化 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 配置Tx引脚为复用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = USARTx_TX_PIN ;
GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStructure);
/* 配置Rx引脚为复用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = USARTx_RX_PIN;
GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStructure);
/* 连接 PXx 到 USARTx_Tx*/
GPIO_PinAFConfig(USARTx_RX_GPIO_PORT,USARTx_RX_SOURCE,USARTx_RX_AF);
/* 连接 PXx 到 USARTx__Rx*/
GPIO_PinAFConfig(USARTx_TX_GPIO_PORT,USARTx_TX_SOURCE,USARTx_TX_AF);
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
/* 抢断优先级为1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级为1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
/* 配置串DEBUG_USART 模式 */
/* 波特率设置:DEBUG_USART_BAUDRATE */
USART_InitStructure.USART_BaudRate = USARTx_BAUDRATE;
/* 字长(数据位+校验位):8 */
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/* 停止位:1个停止位 */
USART_InitStructure.USART_StopBits = USART_StopBits_1;
/* 校验位选择:无校验 */
USART_InitStructure.USART_Parity = USART_Parity_No;
/* 硬件流控制:不使用硬件流 */
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
/* USART模式控制:同时使能接收和发送 */
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* 完成USART初始化配置 */
USART_Init(USARTx, &USART_InitStructure);
/* 使能串口 */
USART_Cmd(USARTx, ENABLE);
}
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
void USARTx_SendByte(uint8_t Byte)//发送单个字节
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
void USARTx_SendArray(uint8_t *Array,uint16_t Length)//
{
uint16_t i;
for(i=0;i<Length;i++)
{
USARTx_SendByte(Array[i]);
}
}
void USARTx_SendString(char *String)//发送字符串
{
uint8_t i;
for (i =0; String[i] != '\0';i ++)
{
USARTx_SendByte(String[i]);
}
}
uint32_t USARTx_Pow(uint32_t X,uint32_t Y)
{
uint32_t Result = 1;
while(Y--)
{
Result *=X;
}
return Result;
}
void USARTx_SendNumber(uint32_t Number, uint8_t Length)//发送数字
{
uint8_t i;
for (i=0;i<Length;i++)
{
USARTx_SendByte(Number /USARTx_Pow(10,Length-i-1)%10+'0');
}
}
uint8_t USARTx_GetRxFlag(void)
{
if (USARTx_RxFlag == 1)
{
USARTx_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t USARTx_GetRxData(void)//返回接收数据
{
return USARTx_RxData;
}
void USARTx_IRQHandler(void)//串口接收中断
{
if(USART_GetFlagStatus(USARTx,USART_IT_RXNE)==SET)
{
USARTx_RxData = USART_ReceiveData(USARTx);
USARTx_RxFlag=1;
USART_ClearITPendingBit(USARTx,USART_IT_RXNE);//清除中断标志位
}
}
/*********************************************END OF FILE**********************/
#ifndef __USART_H
#define __USART_H
#include "stm32f4xx.h"
#include <stdio.h>
//引脚定义
/*******************************************************/
#define USARTx USART1
/* 不同的串口挂载的总线不一样,时钟使能函数也不一样,移植时要注意
* 串口1和6是 RCC_APB2PeriphClockCmd
* 串口2/3/4/5/7是 RCC_APB1PeriphClockCmd
*/
#define USARTx_CLK RCC_APB2Periph_USART1
#define USARTx_CLOCKCMD RCC_APB2PeriphClockCmd
#define USARTx_BAUDRATE 115200 //串口波特率
#define USARTx_RX_GPIO_PORT GPIOA
#define USARTx_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define USARTx_RX_PIN GPIO_Pin_10
#define USARTx_RX_AF GPIO_AF_USART1
#define USARTx_RX_SOURCE GPIO_PinSource10
#define USARTx_TX_GPIO_PORT GPIOA
#define USARTx_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define USARTx_TX_PIN GPIO_Pin_9
#define USARTx_TX_AF GPIO_AF_USART1
#define USARTx_TX_SOURCE GPIO_PinSource9
/************************************************************/
void USARTx_Config(void);
void USARTx_SendByte(uint8_t Byte);
void USARTx_SendArray(uint8_t *Array,uint16_t Length);
void USARTx_SendString(char *String);
void USARTx_SendNumber(uint32_t Number, uint8_t Length);
uint8_t USARTx_GetRxData(void);
uint8_t USARTx_GetRxFlag(void);
#endif /* __USART_H */
移植修改时只需要修改宏定义里的串口号以及其相应的GPIO口,波特率就OK了
定时器
STM32F407的 定时器资源
定时器的溢出时间计算:
Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
ARR:自动重装载寄存器,用于装载计数器
PSC:PSC预分频器(分频范围1~65535)
Tclk:外设时钟周期,可以在STM32F407的芯片手册中找到时钟框图
(备注:ARR是代表计数值,而外设时钟经过分频之后,为ARR提供计数时钟,【即每个时钟来,ARR就加1】)
STM32F103的 定时器资源
STM32F103系列的单片机一共有11个定时器,其中:
2个高级定时器
4个普通定时器
2个基本定时器
2个看门狗定时器
1个系统嘀嗒定时器
出去看门狗定时器和系统滴答定时器的八个定时器列表;
8个定时器分成3个组;
TIM1和TIM8是高级定时器
TIM2-TIM5是通用定时器
TIM6和TIM7是基本的定时器
这8个定时器都是16位的,它们的计数器的类型除了基本定时器TIM6和TIM7都支持向上,向下,向上/向下这3种计数模式
定时器功能
计数器三种计数模式
向上计数模式:从0开始,计到arr预设值,产生溢出事件,返回重新计时
向下计数模式:从arr预设值开始,计到0,产生溢出事件,返回重新计时
中央对齐模式:从0开始向上计数,计到arr产生溢出事件,然后向下计数,计数到1以后,又产生溢出,然后再从0开始向上计数。(此种技术方法也可叫向上/向下计数)
基本定时器(TIM6,TIM7)的主要功能:
只有最基本的定时功能,基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动,可产生定时中断
通用定时器(TIM2~TIM5)的主要功能:
除了基本的定时器的功能外,还具有测量输入信号的脉冲长度( 输入捕获) 或者产生输出波形( 输出比较和PWM)
高级定时器(TIM1,TIM8)的主要功能:
高级定时器不但具有基本,通用定时器的所有的功能,还具有控制交直流电动机所有的功能,你比如它可以输出6路互补带死区的信号,刹车功能等等
定时器时钟原理
通用定时期内部时钟的产生:
从截图可以看到通用定时器(TIM2-7)的时钟不是直接来自APB1,而是通过APB1的预分频器以后才到达定时器模块。
当APB1的预分频器系数为1时,这个倍频器就不起作用了,定时器的时钟频率等于APB1的频率;
当APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1时钟频率的两倍。
自动装在寄存器arr值的计算:
Tout= ((arr+1)*(psc+1))/Tclk;
Tclk:TIM3的输入时钟频率(单位为Mhz)。
Tout:TIM3溢出时间(单位为us)。
计时1S,输入时钟频率为72MHz,加入PSC预分频器的值为35999,那么:
((1+psc )/72M)*(1+arr )=((1+35999)/72M)*(1+arr)=1秒
则可计算得出自动窗装载寄存器arr=1999
PWM相关知识点
通用定时器PWM工作原理
以PWM模式2,定时器3向上计数,有效电平是高电平,定时器3的第3个PWM通道为例:
定时器3的第3个PWM通道对应是PB0这引脚,三角顶点的值就是TIM3_ARR寄存器的值,上图这条红线的值就TIM3_CCR3
当定时器3的计数器(TIM3_CNT)刚开始计数的时候是小于捕获/比较寄存器(TIM3_CCR3)的值,
此时PB0输出低电平,随着计数器(TIM3_CNT)值慢慢的增加,
当计数器(TIM3_CNT)大于捕获/比较寄存器(TIM3_CCR3)的值时,这时PB0电平就会翻转,输出高电平,计数器(TIM3_CNT)的值继续增加,
当TIM3_CNT=TIM3_ARR的值时,TIM3_CNT重新回到0继续计数,PB0电平翻转,输出低电平,此时一个完整的PWM信号就诞生了。
PWM输出模式;
STM32的PWM输出有两种模式:
模式1和模式2,由TIMx_CCMRx寄存器中的OCxM位确定的(“110”为模式1,“111”为模式2)。区别如下:
110:PWM模式1,在向上计数时,一旦TIMx_CNT
在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
111:PWM模式2-在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为有效电平,否则为无效电平。
由以上可知:
模式1和模式2正好互补,互为相反,所以在运用起来差别也并不太大。而从计数模式上来看,PWM也和TIMx在作定时器时一样,也有向上计数模式、向下计数模式和中心对齐模式
PWM的输出管脚:
不同的TIMx输出的引脚是不同(此处设计管脚重映射)
TIM3复用功能重映射:
注:重映射是为了PCB的设计方便。值得一提的是,其分为部分映射和全部映射
PWM输出频率的计算:
PWM输出的是一个方波信号,信号的频率是由TIMx的时钟频率和TIMx_ARR这个寄存器所决定的
输出信号的占空比则是由TIMx_CRRx寄存器确:
占空比=(TIMx_CRRx/TIMx_ARR)*100%
F就是PWM输出的频率,单位是:HZ;
ARR就是自动重装载寄存器(TIMx_ARR);
PSC 就是预分频器(TIMx_PSC);
STM32 高级定时器PWM的输出
一路带死区时间的互补PWM的波形图
STM32F103C8T6这款单片机一共有2个高级定时器TIM1和TIM8
这2个高级定时器都可以同时产生3路互补带死区时间的PWM信号和一路单独的PWM信号,
具有刹车输入功能,在紧急的情况下这个刹车功能可以切断PWM信号的输出
还具有支持针对定位的增量(正交)编码器和霍尔传感器电路
高级控制定时器(TIM1 和TIM8) 由一个16位的自动装载计数器组成,它由一个可编程的预分频器驱动
它适合多种用途,包含测量输入信号的脉冲宽度( 输入捕获) ,或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM等)。
使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节。
高级控制定时器(TIM1 和TIM8) 和通用定时器(TIMx) 是完全独立的,它们不共享任何资源
死区时间
H桥电路为避免由于关断延迟效应造成上下桥臂直通,有必要设置死区时间
死区时间可有效地避免延迟效应所造成的一个桥臂未完全关断,而另一桥臂又处于导通状态,避免直通炸开关管。
死区时间越大,电路的工作也就越可靠,但会带来输出波形的失真以及降低输出效率。
死区时间小,输出波形要好一些,但是会降低系统的可靠性,一般这个死区时间设置为us级
元器件死区时间是不可以改变的,它主要是取决于元器件的制作工艺和材料!
原则上死区时间当然越小越好。设置死区时间的目的,其实说白了就是为了电路的安全。最佳的设置方法是:在保证安全的前提下,设置的死区时间越小越好。以不炸功率管、输出不短路为目的。
STM32死区时间探究
设置寄存器:就是刹车和死区控制寄存器(TIMx_BDTR)
这个寄存器的第0—7位,这8个位就是用来设置死区时间的,使用如下:
以TIM1为例说明其频率是如何产生的。
定时器1适中产生路线:
系统时钟-> AHB预分频 -> APB2预分频 –> TIM1倍频器–> 产生TIM1的时钟系统
流程图看可以看出,要想知道TIM1的时钟,就的知道系统时钟,AHB预分频器的值,还有APB2预分频器的值,只要知道了这几个值,即可算出TIM1的时钟频率?
这些值从何来,在“SystemInit()”这个时钟的初始化函数中已经给我们答案了,在这个函数中设置的系统时钟是72MZ,AHB预分频器和APB2预分频器值都是设置为1,由此可算出:TIM1时钟频率:
72MHZ了,TDTS=1/72MHZ=13.89ns
定时器结构体初始化
下面介绍TIM结构体初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启时钟,TIM2是APB1总线的外设
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //指定参数分频值(选择1分频),DIV22分频 DIV4 44分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//选择计数模式;
TIM_TimeBaseInitStructure.TIM_Period=10000-1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器,高级定时器才拥有,因此此处给0.
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); //配置时基单元
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
在这里PSC为预分频器的数值,在STM32F103中,APB1总线的频率为36MHZ,经定时器倍频区倍频为72MHZ,预分频器的作用是设置定时器多久计数一次,以上面代码为例,在这里配置PSC的值为720-1(定时器从0开始计时,所以需要减1),那么定时器计数一次的时间为 72M/720=1/100000秒,ARR的值为计数了ARR次之后产生定时器中断,以上代码中ARR配置为10000,那么定时器的经过10000次1/100000秒就会产生一次定时器中断,也就是100ms。
此处用公式计算,定时器的频率为 72M / 720/10000=1/10秒
在F407中,定时器频率计算方法,设置方法,定时器结构体成员与F103一样,不过是F407的时钟线频率更快而已。
输出比较结构体
//输出比较单元配置
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1 ;
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0;//设置CCR数值
TIM_OC2Init(TIM2, &TIM_OCInitStruct);
此处用到的是定时器2的通道二引脚,通过引脚定义表可知,此处应该初始化的GPIO引脚为GPIOA1,如需要更改或增加其他引脚,只需要按照引脚定义表增加或更改相应通道即可。
在PWM模式下,CCR(Capture/Compare Register)用于控制占空比。占空比是指高电平信号在一个周期内的持续时间与整个周期的比例。通过调整CCR的值,可以改变PWM信号的占空比,从而控制输出信号的电平。
在输出比较模式下,CCR用于控制初始相位。初始相位是指输出信号与参考信号之间的相位差。通过调整CCR的值,可以改变输出信号的初始相位,从而实现相位控制。
总结起来,CCR在PWM模式下用于控制占空比,在输出比较模式下用于控制初始相位
此处的占空比在结构体里面设置为0
可以在main函数中使用库函数单独更改CCR的值来更该占空比
此处通道2的配置代码为
void pwm_setcompare(uint16_t compare)
{
TIM_SetCompare2(TIM2, compare);
}
最后
总结一下,在此文中先后介绍了GPIO工作模式,结构体等相关知识点,还有系统的时钟框架,无论是延时函数,还是定时器的配置都和时钟树息息相关,然后就是对于中断的理解,首先是NVIC中断控制器,几乎所有的中断都由NVIC控制,有中断就要初始化NVIC,且中断的函数名字必须要与中断向量表里的名字一样,之后就是外部中断,相关应用及配置方法已经写出。最后就是串口以及定时器的配置原理和各种工作模式需要掌握,这些都搞懂了之后对于单片机的理解整体就会愈发清晰,可以决定往下面学的的内容,比如物联网模块,摄像头,RTOS系统,树莓派等等
先写这么多,感谢你读到此处,我也独自走了很远的路,才将这篇文章将送到你的眼前。
身处命运的漩涡,不断前进的信念永远是我最强大的武器。
祝你一路顺风。