目录
#定时器的介绍
#怎么去理解定时器的预分频系数
#使用定时器实现完成触发中断
#定时器触发中断基本函数配置
#在使用TIMER 触发中断的时候为什么不需要配置EXTI中断这个选项
#使用定时器完成输出PWM
#PWM基本知识介绍
#函数配置生成PWM
这个系列所有笔记用来记录,笔者学习过程中,产生的疑惑,个人的理解,同时掺杂着个人理解,希望对各位读者姥爷有帮助,参考文章连接在最后
#定时器的介绍
这是一张,数据手册里面的定时器框图,里面是定时器这个外设的整体运作模式,比较重要。
TIMER 定时器 单片机开发的一种常用外设,定时器类型分为 高级定时器 通用定时器 基本定时器 这三种,这里需要注意的是,只有高级定时器,通用定时器,具有四个输出通道,这四个通道对应四个引脚,总的来说就是一个定时器,可以作用四个引脚,让这四个引脚,使用定时器的功能,具体功能注释如下。
输入捕获(input captuer)测量输入PWM 频率 间隔 波形
输出比较(input compare)用于输出PWM
PWM输出(pwm generation) 用于输出PWM,与上者相同
单脉冲模式(one-pluse mode output)
同时这里要注意,基本定时器,没有能力使用以上四个功能,主要用于驱动DAC,可以参考下文数据手册,读者姥爷们,那有的读者姥爷就要问了,定时器不同的类型,到底有什么具体的不同,具体参考中文数据手册,手册连接放在最后
高/普 定时器,上面这个多功能描述,最为常用的定时器,作用其实是,输出比较输出PWM,定时器触发中断,本篇主要简单介绍,定时器,以及使用STMF4完成举例。
#定时器预分频系数,周期相关介绍
这一部分其实对应着时基单元,PSC:预分频系数 ,对timer时钟(CK_PSC)进行分频结果为cnt CNT:计时器时钟(这里注意不是,定时器时钟)ARR:自动重装载值,当CNT的值达到ARR的值就会触发中断,具体计算公式如下
CK_CNT=CK_PSC/(PSC+1)
这里解释一下为什么PSC要+1,PSC预分频系数 16位寄存器取值0~65535之间,在软件中赋值结构体变量 PSC 后面通常是跟-1的,所以在公式中,给+1了(个人看法)所以就+1,这些东西就是 TIM_TimeBaseInitTypeDef,这个结构体变量需要配置的东西,需要注意的是,ARR就是TIM_Period,也就是自动重装载值,当CNT计数到达,ARR,会重头开始计数。
#使用定时器实现完成触发中断
#定时器触发中断基本函数配置
首先,我们来看一下,timer.c文件中的介绍配置流程 ,然后我们按照这个流程进行,函数配置,
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//EBABLE TIMER3 ClockTIM_TimeBaseInitTypeDef TimerBaseInitStructure;//配置 timer struct 变量
TimerBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TimerBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up;
TimerBaseInitStructure.TIM_Period = 10000;
TimerBaseInitStructure.TIM_Prescaler = 8400 - 1; TIM_TimeBaseInit(TIM3, &TimerBaseInitStructure);//初始化 timerNVIC_InitTypeDef NVIC_InitStructure;//配置 inturputer control 管理器
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;NVIC_Init(&NVIC_InitStructure);TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);//开启 timer3 中断
TIM_Cmd(TIM3,ENABLE);
}
void TIM3_IRQHandler(void)
{if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET){ // {}中为中断处理printf("IRQHandler!!!\r\n");// 反转LED灯if( flag == 0 ){GPIO_SetBits(GPIOB, GPIO_Pin_2);flag = 1;}else{GPIO_ResetBits(GPIOB, GPIO_Pin_2);flag = 0;}}TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
按照上述流程,这边配置好了,timer的初始化,然后配置,timer3 中断的响应函数,然后下面在配置,一下串口和板载LED
void uart1_init(uint32_t __Baud)
{GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);//IO口用作串口引脚要配置复用模式GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//TX引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//IO口用作串口引脚要配置复用模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_StructInit(&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//RX引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(GPIOA,&GPIO_InitStructure);USART_InitTypeDef USART_InitStructure;//定义配置串口的结构体变量RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//开启串口1的时钟USART_DeInit(USART1);//大概意思是解除此串口的其他配置USART_StructInit(&USART_InitStructure);USART_InitStructure.USART_BaudRate = __Baud;//设置波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字节长度为8bitUSART_InitStructure.USART_StopBits = USART_StopBits_1;//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);//将相关参数初始化给串口1USART_ClearFlag(USART1,USART_FLAG_RXNE);//初始配置时清除接受置位USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//初始配置接受中断USART_Cmd(USART1,ENABLE);//开启串口1NVIC_InitTypeDef NVIC_InitStructure;//中断控制结构体变量定义NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道指定为USART1NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//主优先级为0NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//次优先级为1NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//确定使能NVIC_Init(&NVIC_InitStructure);//初始化配置此中断通道}
void LED_GPIO_Init(void)
{//板载LED引脚初始化配置RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_ResetBits(GPIOB,GPIO_Pin_2);
}
这里基本配置完成之后,然后扔到main()里面去调用,然后实验现象是,串口每秒打印IRQHandler,同时板载LED每秒进行闪烁。
#include "board.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "button.h"
#include "exti.h"
#include "timer.h"
extern uint8_t flag;//
void TIM3_IRQHandler(void);//声明末尾定义的函数int main(void)
{LED_GPIO_Init();board_init();uart1_init(9600);flag = 0;Timer_Init();while(1){}}
#在使用TIMER 触发中断的时候为什么不需要配置EXTI中断这个选项
EXTI(External interrupt/event controller)外部中断/事件控制器,片上外设之一,能够检测外部输入信号边沿产生变化时(上升 下降 ),并由此产生中断,通常用于GPIO这种不能自己产生中断源 的片上外设,而timer定时器 这个片上外设本身可以产生 中断源,自然也就使用不到 exti 在此产生中断源进行配置 timer 产生中断源 nvic 在进行配置 在编写 中断响应函数 然后就能使用 ,所以timer定时器 配置使用不到 exti 这个片上外设 功能 使用 exti 的配置流程如下 本文实验中断流程如
GPIO (电平发声变化)-> EXTI(产生中断源) ->NVIC(配置中断优先级) ->IRQ(定义函数)
TIMER(产生中断源) ->NVIC(配置中断优先级) ->IRQ(编写中断响应函数)
总结:TIMER 片上外设本身可以产生中断源 不需要在使用 EXTI 这个片上外设 在此产生中断源 所以不需要使用 EXTI 这个片上外设去产生中断源 EXTI这个片上外设 一般用于检测 GPIO
引脚出现电平 变化的时候 触发产生中断源 进行触发中断使用
#使用定时器完成输出PWM
#PWM基本知识介绍
冲量相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。
这句话描述的是物理学中的动量守恒原理,同时也是PWM能够生成指定电压的原理,特别是在信号处理或控制系统分析中的应用。当两个形状不同的窄脉冲(可以理解为短暂且强度大的力或者能量输入)对一个具有惯性的系统施加作用时,如果它们的总冲量(即在时间上的力与持续时间的乘积,等于物体动量的变化量)相等,那么最终系统的行为——比如位移、速度或加速度变化——将基本相似,尽管输入的脉冲形状不同。
这反映了无论输入信号如何变化,只要总的动量转移是相同的,系统的响应将是类似的,因为系统的动态响应主要取决于冲量,而不是脉冲的具体形状。这也是工程设计中经常考虑的一种简化模型,PWM就像是BUCK电路,通过开关来控制电压稳定在指定的范围,而PWM是控制占空比,就是高电平的在整个周期内的占比,来控制整体输出电压,控制在指定范围输出,PWM(Pulse Width Modulation脉宽调制)
频率:PWM的频率是整个周期的倒数。
占空比:占空比是指一个周期内高电平所占的比例。
单片机 高电平是 3.3V 低电平是 0V 那么问题来了,如果需要输出1.65V,那么就需要使用PWM这个模式输出电平,简单原理阐述,PWM输出不同电压电平,是利用高电平在一段时间周期里面的占比,这一段时间周期通常 ms为单位,从而显示出不同的电平,
50% 对应 3.3*0.5 =1.65V
60% 对应 3.3*0.6 = 1.98V
具体不同占空比,对应多少V电压就是这么换算的,
#函数配置生成PWM
timer,外设输出PWM,也就是定时器框图的这一部分,一个定时器的OC(输出比较),有四个引脚可以使用,进行输出PWM,也就是 TIM_CH1 TIM_CH2 TIM_CH3 TIM_CH4 ,
void PWM_GPIO_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_TIM3);//开启复用模式 GPIOB5不在作为 普通GPIO引脚使用 而是作为timer 输出PWM通道使用 }
void Time_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = 1000;
TIM_TimeBaseStruct.TIM_Prescaler = 500 - 1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct); }
void Timer_OCInit(void)
{
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = ENABLE;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OC2Init(TIM2,&TIM_OCInitStruct);//输出通道2 InitTIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
//enable CC2R 也就是 TIM_SetCompare2(timx,cc2r) 就是开启这个函数TIM_Cmd(TIM3, ENABLE); //使能TIM3}
void pwm_breathing_lamp(void)
{uint32_t brightness = 0;// 当前亮度int step = 10; // 亮度改变的步长// 逐渐增加亮度for(brightness = 0; brightness < 1000; brightness += step){TIM_SetCompare2(TIM3, brightness); // 使用TIM3的Channel 2delay_ms(10);}// 逐渐减少亮度for(brightness = 1000; brightness > 10; brightness -= step){TIM_SetCompare2(TIM3, brightness); // 使用TIM3的Channel 2delay_ms(10);}
}
int main(void)
{board_init();uart1_init(9600);PWM_GPIO_Init();Time_Init();Timer_OCInit();while(1){pwm_breathing_lamp();}
}
【立创·天空星STM32F407VET6】入门手册 - 飞书云文档 (feishu.cn)
链接:https://pan.baidu.com/s/12SM_KSvwXXyTqAvosVA-gQ
提取码:zhiy
最后希望对你有所帮助!!!个人理解,如有失误,欢迎指出,核实立改