一、什么是定时器
在STM32中,定时器(TIM)是功能最丰富、结构最强大,应用也最广泛的片上外设
定时器就像单片机里的一块手表,所有跟时间有关的操作都需要用到它,时钟树为定时器提供时钟信号,定时器来执行和时间相关的操作
这是定时器内部结构的框图,可分为时基单元,输入捕获,输出比较,从模式控制器
二、定时器的结构
1、时基单元
依次是时钟来源,开关(负责使能和禁止时基单元),预分频器PSC(对输入的时钟信号进行降频),计数器CNT(负责对左侧输入的脉冲进行计数),自动重装寄存器ARR(设置定时周期),重复计数器RCR(设置重复计数的次数),update(设置update事件)
(一)时钟来源
时基单元的时钟来源主要有三种,分别是RCC(来自时钟树),TRIG(来自从模式控制器的触发信号),ETRF(来自外部参考信号),作为初学者,我们只需要了解RCC即可
这是参考手册中的时钟树,我们可以看到定时器2,3,4,5,6,7,12,13,14从APB1获取时钟,而下面的定时器1,8,9,10,11从APB2获取时钟,且中间还多了一个倍频器。
(二)PSC
一般来说,从时钟来源过来的信号频率太高,后面的PSC就是用来降低频率的,预分频器PSC的分频器的分频系数等于(PSC+1),PSC的值为0~65535,例如,输入的时钟信号是8MHz,PSC=7,那么,经过PSC降频后的频率就为8/(7+1)=1MHz。
(三)CNT
经过预分频后,时钟信号进入到CNT,CNT负责对左侧输入的脉冲计数,可以是递增,也可以是递减,取决与我们选择的计数方向,有上计数,下计数,中心对齐。
例如我们选择上计数,CNT就会从0开始递增,一直递增到和ARR的值相等,这个时候再来一个时钟脉冲,CNT的值就会溢出,重新开始从0计数,当ARR的值等于5时,CNT 从 0 计数到 5 再到溢出复位,每来 6 个脉冲就会溢出一次,这时我们可以计算出定时周期=ARR+1。下计数只是CNT的值递减。中心对齐用的比较少,而且比较复杂,暂时不考虑。
(四)RCR
作用是设置重复计数的次数(重复计算的次数=RCR+1,取值范围0~65535),对于CNT来说,每计满一个定时周期溢出一次,溢出次数达到RCR设置的值后,就会产生update事件,RCR=0,CNT每发生(0+1)次溢出,产生一次update事件,RCR=1,CNT每发生(1+1)次溢出,产生一次update事件,依次类推,RCR=65535,CNT每发生(0+65535)次溢出,产生一次update事件。
用石英手表举例,我们现在需要手表每隔五分钟响铃一次,那update事件就是响铃,五分钟代表秒针转五圈为一个周期,即RCR=4,秒针每一秒跳一下,石英表的石英晶振是32.768kHZ,PSC的降频系数应设置成32768,这样经过分频后,输出的脉冲信号就是1秒一次,即CNT代表秒针,每跳一次定时周期加1,秒针每分钟跳60下,则ARR为59。
(五)预加载功能
这里的RCR之所以是虚线,是因为重复计数器不是所有的定时器都有这个功能,只有高级定时器才有RCR。
可以看到,只有TIM1和TIM4是高级定时器,高级定时器阉割一部分功能后,就是通用定时器,再阉割一部分,就是基本定时器。对于我们学习的STM32F103C8T6来说,他只有四个定时器,分别是TIM1,2,3,4。
TIM1就是高级定时器,而其他三个都是通用寄存器。
另外,在图中这三个寄存器的背后都有一块阴影,阴影和前面的寄存器之间,都有一个斜向上的箭头,且产生update事件后,会有三个反馈回来的箭头,表示的其实是寄存器的预加载机制,前面的这个寄存器叫做活动寄存器(Active Reg),后面的阴影叫做影子寄存器(Shadow Reg),所谓的寄存器预加载,就是一种缓冲机制。
比如我们向寄存器写入一个值,这个值会先进入影子寄存器中,直到某个事件发生之后,这个值才会进入前面的活动寄存器,这个时候我们写入的值才会生效。这么做的原因是为了安全。
比如先设置一个定时周期,以上计数为例,每来一个脉冲,计数器CNT的值加一,增长到与ARR的值相等后,再来一个时钟脉冲就会溢出,产生update更新,然后从0开始重新计数。现在,我们想在定时器运行过程中突然改变ARR,假设ARR=7,那就是每八个脉冲CNT会溢出一次,然后在运行过程(计数到第5个脉冲时)中将ARR改成3,这个时候CNT的值比ARR大,这个时候无论我们怎么增加CNT的值,都不可能再和ARR相等了,除非计数到最大值65535发生溢出才能恢复正常,我们将这段定时器波形称为定时器跑飞了。
但是如果我们使能了ARR寄存器的预加载就不一样了,我们在计数过程中将ARR的值改为3,这个值先写入影子寄存器中,但没有生效,所以还是8个脉冲发生一次溢出并产生一个update事件,此时影子寄存器的值才进入活动寄存器,接下来就可以按照新的定时周期进行计数了,有了预加载,就不会出现定时器跑飞的情况了。
回到时基单元,发现PSC和RCR后都有影子寄存器的存在,但是这两个框右上方斜向上的箭头是实线,这表明这两个寄存器的预加载 功能是强制使能的,关不掉,ARR使用的是虚线,表示可手动开关,而且默认关闭,为了防止定时器跑飞,我们一般选择手动打开
(六)自制延迟函数
下面用定时器的功能来实现自制延迟函数,整体需要两个函数,首先先获取单片机当前的时间,再在延迟函数里通过当前单片机的时间+要延迟的时间来得到延迟结束的时间。
同时设置一个volatile volatile uint32_t类型的变量来保存当前的时间,volatile类型保证编译器每次读写它时,都要实际执行访问,不能直接沿用之前读到的值。这个值随着时间的流逝不断增加的,如图,每过一毫秒,currentMilliSeconds的值加1,那么,要想让currentMilliSeconds值每毫秒增加1,就要用到定时器。
CubeMX
选择始终来源为内部时钟,也就是RCC。外部时钟的频率在时钟树可以看到是8MHz,想要得到1KHz的频率,我们需要把PSC的值设置为7,计数周期选为上计数,ARR的值设置为999,这样计数器溢出的频率就是1MHz/1000=1kHz,不需要重复计数,就让RCR=0,这样就可以每1ms产生一次update事件。再使能ARR的预加载功能,防止当ARR的值改变时,定时器跑飞。用定时器来实现中断,在中断响应函数里将currentMilliSeconds的值++,这样就实现每过1ms,这个变量的值加一。
再中断设置里使能定时器1update中断,随后生成代码。
这个库函数的用途是以中断的方式启动时基单元,除此之外还需要一个回调函数,每发生一次update中断,就调用一次我们准备的这个回调函数,因为多个定时器需要共用这个回调函数,所以在使用时,首先需要判断一下,通过参数传进来的这个句柄是不是属于我们需要用的这个定时器的,再将currentMilliSeconds自增。
下面用一个程序验证我们写的延迟函数,将板载LED的PC13引脚设置为开漏输出模式。
调用我们写的延迟函数实现板载LED闪烁,就可以实现板载LED的闪烁。
2、输出比较
所谓输出比较,就是通过定时器向外输出精确定时的方波信号,在时基单元下方,有四条定时的通道,这些通道既可以作为输入也可以作为输出,作为输入时,输入信号从CH1进来,然后使用定时器,对他的时间参数进行测量,这个功能叫做输入捕获。而作为输出时,通过定时器产生精确定时的信号,通过右边输出出去。例如,使用定时器产生PWM信号(脉冲宽度调制信号),它是一种周期信号,把高电压占一个周期内的比例叫做占空比。PWM的特点就是周期恒定,占空比可调,可以通过调节占空比来调节信号的大小。
用方波信号来近似的等效不规则的信号,信号越强的地方,PWM的占空比就越大,这样就可以使用PWM波形来表示任意的信号。下面用周期为1ms,占空比为50%的PWM波来解释输出比较的原理,假设时钟源的频率是8MHz,PSC=999,计数方式为上计数,ARR=9,RCR=0,定时周期就是千分之一秒,也就是一毫秒。
四个通道都有一个CCR(捕获/比较寄存器),互补输出只有高级定时器才有,CNT的计数值会与CCR进行比较,比较的结果有三种,分别是CNT<CCR,CNT>CCR,CNT=CCR。
再接着后面的模式选择,总共有八种模式,最常用的就是PWM1,PWM2偶尔用到,剩下的六种模式基本用不到,模式选择完之后有两条输出通道,上面那一条是正常输出,下面那一条由于经过反相器,所以叫互补输出。
需要互补输出的原因是因为有时候需要PWM的信号来去驱动mos管,这是一个H桥驱动电路,由四个mos管组成,我们只看左边的这两个,它们俩需要交替工作,同一时刻一个导通,另一个就断开,不能同时导通,否则会短路,因此需要一对互补的PWM波进行驱动。
极性选择就是对2节点来的PWM信号选择正向输出还是反向输出。
(一 )呼吸灯实验
还是现在CubeMX中完成基础配置,改变时基单元各部分的参数使之最后输出的频率是1kHz,并启用ARR的预加载功能防止定时器跑飞,然后我们就选取定时器1的通道1向外输出PWM波。
通道的模式选择里面共有11种模式,上面3三种是输出捕获,后面是输出比较。
这四种是PWM的产生,与PWM模式有关
这三种代表强制输出,与Force Active或Force Inactive有关
其他几种则是出PWM之外的6种模式
而后面的通道选择则是使能哪种输出,或者两种输出都使能。
因为后面我们需要用到两颗LED来验证互补输出,所以在模式选择PWM Generation CH1 CH1N,选择完之后,可以看到CubeMX已经帮我们分配好了两个引脚。
这是用来设置CH1的PWM参数
(1)mode
如果选择PWM mode 1,代表选择的PWM1,PWM mode 2,代表选择PWM2
(2)Pulse
用来设置CCR寄存器的初始值,CCR决定PWM的占空比,先把他设置为0,表示占空比为0
(3)Output compare preload
设置CCR寄存器的预加载,示意图种它的后面有阴影,代表它可以使用预加载功能,可以手动开关,我们将它使能
(4)CH Polarity和CHN Polarity
一个用来设置正常输出的极性,另一个用来设置互补输出的极性,前面说过正极性(High)就是输出正常的波形,负极性(Low)就是输出取反的波形 ,这里两种输出都选择正极性,由于互补输出本身就带一个反相器,所以 这里两种输出模式输出的波形是相反的,体现在硬件上就是两颗LED交替呼吸灯的形式。
接着生成代码
随后完成硬件电路连接
(二)代码实现
这是两个分别启动PWM正常输出和互补输出的库函数
这是实现对时基单元种各个寄存器的读写函数
想要实现呼吸灯的功能,就需要一条正弦函数,其曲线随时间变化,纵轴就是占空比,时基单元决定了PWM的周期,而周期就是ARR的值加1,CCR决定了高电压所占的时间,所以,占空比的计算公式就是高电压所占的时间比上周期,即:
由于图像中的纵轴是占空比,所以我们要把后边换成这个正选函数。
我们也可以通过这个公式的变体来计算CCR的值,上面已经说了占空比就是这个正弦函数,即:
整体代码思路如上,首先启动PWM的两种输出模式,在while循环里修改PWM的占空比,因为计算占空比需要用到时间t,所以要先调用HAL_GetTick函数获取当前时间,此时获取的时间单位是ms因此要乘0.001转换成s,随后计算占空比,在计算CCR的值,在此之前需要获取ARR寄存器的值,并将最后的计算结果写入CCR寄存器。由于用到sin函数,因此需要在头文件中加入#include “math.h”
3、输入捕获
就是用定时器对外来信号的时间参数进行测量,与输出比较相反,输出比较是利用定时器对外输出精准的信号。
从这副图中,左侧的外部信号通过通道1进入,让通道1工作在输入捕获模式下,在外部信号发生一次从低电平到高电平的信号变化时,CNT拍照记录此时CNT的计数值并传递给CCR1,再读取CCR1的值就可以知道外部型号在何时发生跳变,简而言之就是捕捉信号变化的时间点,并保存。利用这个原理,就可以对输入信号的时间参数进行测量。
比如我们可以用输入捕获测量脉冲宽度,一个信号上升沿和下降沿的时间间隔,就是它的脉冲宽度,用通道1来捕获它的上升沿,通道2来捕获下降沿
首先闭合时基单元的开关来启动定时器,之后CNT的值就会开始递增,在递增的过程中首先会先遇到上升沿,此时CNT的值为4,这个值会被拍照保存到CCR1中,再遇到下降沿,此时CNT的值为7,这个值保存到CCR2中,因此,脉宽=(CCR2-CCR1)*分辨率,这里的分辨率就是CNT的值每记一个数所需的时间,假设此时分辨率是1us,那此时脉宽就等于3us,这就是输入捕获的一个基本用法,还可以用来测量PWM的周期和占空比,还能解析从遥控发过来的红外信号。
这是通道1输入捕获的内部结构图,总共可以分成四个阶段,输入滤波,边沿检测,信号选择和分频。
(一)输入滤波
一阶段的作用就是对从CH1通道进来的含有毛刺的原始信号进行滤波,使之变成干净的方波信号
(二)边沿检测
这一阶段有两种输出,分别是上升沿脉冲和下降沿脉冲,也就是检测到一个上升沿就发送一个上升沿脉冲,以及检测到一个下降沿就发送一个脉冲。
(三)信号选择
节点5中有3种不同的信号来源,分别是TRC(从模式控制器来的信号)、直接、间接,定时器内部有四个通道,把这些通道两两分成一组,1 2是一对,3 4是一对,在一对通道的内部,信号是可以相互引用的,如果信号来自于通道本身,这种方式叫做直接,如果来自对侧就叫做间接。例如通道1选择捕获上升沿脉冲,间接方式,那信号流动的方向如上图,捕获的就是对侧CH2通道的上升沿
(四)分频
就是对来到的信号分频,如果把分频系数设置为2,本来在节点5,每遇到一个上升沿产生一个脉冲,经过分频之后,需要经过两个上升沿,才产生一个脉冲,在节点6产生的这个脉冲,会产生ccx的事件,这个事件发生时,CNT的值会被拍照保存到CCR中,这个时候再把CCR寄存器中的值读取出来,就可以知道信号发生变化时的节点了。
HC-SR04超声波测距模块
一边用来发送超声波,另外一边接收,就可以根据公式 距离=声速(340m/s)*传播时间/2 算出与障碍物之间的距离,在开始测量之前,需要先给Trigger引脚施加一个大于10us的脉冲,左边的发送接口开始向外发送一串超声比,频率是40kHz,总共发送8个周期,整个过程大概持续0.2ms,声波发送完成以后,Echo引脚上会出现一个上升沿,发送出去的超声波会在碰到障碍物后反射回来,被右边的接口接收,此时Echo引脚上就出现一个下降沿,只需要测量Echo引脚上的脉冲宽度,就可以算出超声波的传播时间,而公式中的传播时间就是Echo引脚上的接收到的脉冲宽度。
将Trigger引脚接在PA0上,采用推挽输出模式,Echo引脚接在通道1上,通道1设置为上升沿直接模式 ,通道2设置成下降沿间接模式,这样就可以检测Echo上的脉冲宽度。
下面说 Cube MX上的配置
PA0设置成推挽输出模式,PA13为板载LED,设置为开漏输出,初始为高电压。
前面说了需要通道1为输入捕获直接模式,捕获上升沿,通道2为输入捕获间接模式,捕获下降沿。Prescaler Division Ratio选择分频系数,这里不需要分频。Input Filter用来设置输入滤波器参数,暂时用不到。
前面说了CNT的分辨率是1us,带入公式中计算出测距分辨率是0.17mm,这个精度足够了。
分辨率是1us,意味着进入计算周期是1us,PSC分频之后是1MHz,输入信号为8MHz的话,则PSC=7,另外要保证定时器的定时周期要大于脉冲的脉宽,因为如果测量过程中发生溢出,测量无意义,Echo引脚上的最大脉宽是38ms,所以要保证定时周期大于38ms,ARR=65535的话,则定时周期为(65535+1)*1us=65.536ms>38ms,是安全的。同时要使能ARR的预加载功能,设置完成后生成代码。
硬件连接
代码实现
整体思路是不断检测前方20cm内是否有障碍物,如果检测到障碍物就点亮板载LED,否则熄灭。因为需要 不断检测,所以要写在while循环内。
总共分成7步
这是整个测量的过程 ,首先是Trig引脚,在开始测量之前,我们需要想该引脚发送一个大于 10us的信号。Echo引脚用来返回测量结果,只需要测量该引脚上返回的脉宽,就可以得到和障碍物之间的举距离。CNT是计数器,随时间递增,在开始测量时,我们需要让它的值归零因为在上一次测量结束时,CNT的值可能不为0。最后两个标志位用来告知我们测量是否结束,在测量开始时也需要清除这两个标志位。
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM1_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { //1.让计数器CNT归零 _HAL_TIM_SET_COUNter(&htim1, 0); //2.清除CC1、CC2的中断标志位 __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC1); __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC2); //3.启动输入捕获模式 HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_2); //4.向Trig引脚发送脉冲 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); for(uint32_t i=0;i<10;i++); //for循环一次耗时1us HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); //5.等待测量结束 uint8_t sucess=0; //测量是否成功,0-失败,1-成功 uint32_t expireTime=HAL_GetTick()+50; //超时时间 while(HAL_GetTick()<expireTime) { uint32_t cc1Flag = __HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1); //cc1标志位 uint32_t cc2Flag = __HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC2); //cc2标志位 if(cc1Flag && cc2Flag) //如果cc1和cc2标志位都为1,说明测量成功 { sucess=1; //测量成功 break; //跳出循环 } } //6.关闭定时器 HAL_TIM_IC_Stop(&htim1, TIM_CHANNEL_1); HAL_TIM_IC_Stop(&htim1, TIM_CHANNEL_2); //7.计算测量结果 if(sucess == 1) //如果测量成功 { uint32_t cc1 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_1); //读取cc1的捕获值 uint32_t cc2 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_2); //读取cc2的捕获值 float pulseWidth = (cc2 - cc1) * 1e-6f; //计算脉冲宽度 float distance = pulseWidth * 340.0f / 2; //计算距离 if(distance<0.2) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); //点亮LED } else { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); //熄灭LED } } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }4、从模式控制器
从模式控制器的功能可以分为两种,一种是作为从机被控制(例如:让一个外部信号的上升沿到来时,将当前定时器的计数器清零),另外一种是作为主机控制别的设备(例如:希望定时器每隔一段时间触发一次 ADC 采样)。
(一)作为从机
首先从TRGI输入控制信号经过从模式控制器,来控制定时器,定时器会根据来的信号做出不同的动作,例如定时器的启、停、复位、增、减。
(1)Slave Mode disable-从模式禁止
即不使用从机功能,本来是可以通过TRGI输入控制信号来控制定时器,选择这个模式之后,无论外部输出什么信号,定时器都不会受到影响
(2)Reset Mode-复位模式
在这种模式下,我们只需要向TRGI输入一个上升沿,CNT就会被清零,同时产生Update事件
(3)Gated Mode-门模式
使用TGRI控制时基单元的开关,规则是高电压的时候导通,低电压的时候关断
(4)Trigger Mode-触发模式
使用TRGI的上升沿来启动定时器,这种模式下,只要想TRGI输入一个上升沿,就可以启动定时器
(5)External Clock Mode1-外部时钟模式1
把TRGI作为定时器的时钟,在这种 模式下,需要通过TRGI输入一个时钟信号,把这个时钟信号当作时基单元的信号来源,之前说过时基单元的信号来源有三种,就是这个模式,与从时钟树输入时钟信号是一样的
TRGI的来源
从图中可以看到 ,TRGI的来源有4种,这里说一下最下面的TI1FP1,TI2FP2,前面我们说过在输入捕获模式下,有三种不同的信号来源,分别是TRC、直接、间接,通道1的直接就是TI1FP1,通道2的直接就是TI2FP2,而他们都是遵循TIxFPy的命名方式,TI(Timer Input-定时器输入),x-从哪个通道来,F(Filtered-经过滤波之后的),P(Polarized-极性选择之后的),y-到哪个通道去,如此那么TI2PF1就是 从通道2输入,经过滤波和极性 选择之后给通道1用的。
(二)作为主机
首先从TRGO输出控制信号,这个控制信号可以拿来控制其他模块,比如控制另外一个定时器、ADC、DAC。
(1)Enable-使能
通过TRGO把时基单元的开关状态输出出去,开关导通的时候输出高电平,开关断开的时候输出低电平
(2)Update-更新
也就是CNT每溢出一次,产生一次Update事件,就向TGRO输出一个脉冲
作为主机或者作为从机这两种功能是可以同时使用的 ,也就是说在TRGI接收控制信号的同时,也可以通过TRGO向外输出控制信号,这两个是互不冲突的
(三)测量占空比
先用定时器3产生一个周期为1ms,占空比为20%的PWM信号,定时器3属于通用定时器,通用定时器和高级定时器相比,缺少了重复计数器RCR和互补输出等功能。进入定时器3的信号频率为8MHz,想要得到占空比为20%的PWM信号,则需要PSC=7,ARR=999,CRR=200。输出比较通道1的模式选择PWM1,极性选择正极性。
整体的测量思路就是,首先将PWM信号通过定时器1通道1输入进来,使用通道1捕获这个通道的上升沿,再使用通道2捕获下降沿,再将TI1FP1作为从模式控制器的触发输入,也就是TRGI,从模式控制器选择从机复位模式,也就是使用TRGI对定时器清零,这样就可以测量PWM的占空比了。
(1)测量原理
由于通道1捕获的是上升沿,所以每当PWM信号出现一个上升沿时,TI1FP1就会出现一个脉冲,因为TI1FP1已经选为从模式控制器的TRGI了,所以每当TI1FP1有一个脉冲时,就会对CNT清零,同时产生一个update事件,因为通道2捕获下降沿,所以遇到PWM的下降沿时,CNT的值会保存在CCR2中,遇到上升沿时,CNT的值保存在CCR1中,所以CCR2的值就等于脉宽,CCR1的值就等于PWM的周期
整体还是分为6步
(2)代码实现
#include<stdarg.h> #include<stdio.h> #include<string.h> static void UART_Printf(const char *format, ...) { char tmp[128]; // Adjust the buffer size as needed va_list argptr; va_start(argptr, format); vsprintf((char*)tmp, format, argptr); va_end(argptr); HAL_UART_Transmit(&huart1, (const uint8_t *)&tmp, strlen(tmp), HAL_MAX_DELAY); } int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); MX_TIM3_Init(); MX_TIM1_Init(); /* USER CODE BEGIN 2 */ //HAL_UART_Transmit(&huart1,(const uint8_t*)"你好世界",12,HAL_MAX_DELAY); HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { //1.清除CC1标志位 __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC1); //2.启动定时器 HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2); //3.等待CC1标志位并再次清除 while(__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1) == 0); __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC1); //4.再次等待CC1标志位 while(__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1) == 0); //5.停止定时器 HAL_TIM_IC_Stop_IT(&htim1, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(&htim1, TIM_CHANNEL_2); //6.计算占空比和频率 uint16_t ccr1 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_1); uint16_t ccr2 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_2); float period = ccr1*1e-6; //单位为秒 float pulseWidth = ccr2*1e-6; //单位为秒 float duty = pulseWidth/period; //占空比 UART_Printf("脉宽 = %.1fus, 周期 = %.1fus, 占空比 = %.1f%%", pulseWidth*1e6f, period*1e6f , duty*100.0f); HAL_Delay(500); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }三、总结
STM32 定时器的本质是一个将 CPU 从繁琐计时中解放出来的“高精度硬件自动化引擎”:底层依靠PSC、CNT、ARR时基三件套构建稳定精准的时间基准;向上则依托核心寄存器CCR实现“一出一入”两大功能——往外通过输出比较(PWM)自动生成可调占空比的方波,高效驱动电机、舵机与灯光;往里借助输入捕获硬件锁存时间快照,零延迟精准测量超声波脉宽与转速频率;二者结合即可满足嵌入式开发中绝大多数高实时性控制与测量需求。
人话:定时器太难学,主播学不会