文章目录
前言
1 深入了解STM32定时器的工作原理,掌握脉宽调制(PWM)的生成方法。
2 掌握使用STM32F103的Tim2~Tim5中的一个定时器的某一个通道与LED相连,并利用定时器计数方式控制LED周期性地亮-灭。
3 学习如何在STM32F103上使用定时器的PWM模式,以呼吸灯的方式使LED渐亮渐灭,并通过Keil虚拟示波器观察PWM输出波形。
4 利用另一个定时器通道编程采集上述PWM输出信号,并获取其周期和脉宽,并将数据通过串口输出显示。
5 学习HC-SR04超声波测距模块的工作原理,并使用STM32F103完成一个超声波测距方案。
一、TIM定时器
1.简介
(1)TIM(Timer)定时器
(2)定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
定时器就是一个计数器,当这个计数器的输入是一个准确可靠的基准时钟的时候,那在对这个基准时钟进行计数的过程,实际上就是计时的过程。
比如在STM32中,定时器的基准时钟一般都是主频72MHz,比如我对72MHz计72个数,所记时间就是72*1/72000000=1us;如果计72000个数,那就是72000*1/72000000=1ms。
(3)16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
- 计数器就是用来执行技术定时的一个寄存器,每来一个时钟,计数器加1。
- 预分频器可以对计数器的时钟进行分频,让计数更加灵活。
- 自动重装寄存器就是计数的目标值,就是想要计多少个时钟申请中断。
- 计数器、预分频器、自动重装寄存器构成了定时器最核心的部分,我们把这一块电路称为时基单元,均为16位寄存器,2^16=65536,如果预分频器设置最大,自动重装也设置最大,那定时器的最大的定时时间就1/720000006553565535=59.65s,接近一分钟。
如果想这里的最大定时时间仍满足不了需求,STM32的定时器还支持级联的模式,也就是一个定时器的输出,当作另一个定时器的输入,这样,最大定时时间就是1/72000000*65536*65536*65536*65536 这个时间大概是8000多年,如果还嫌短,那就再级联一个定时器,最大定时时间还会延长65536*65536倍,这个时间大概是34万亿年。
(4)不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
(5)根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
2.定时器类型
有高级定时器、通用定时器和基本定时器,这三种定时器是由高级到低级向下兼容的。
- 高级定时器拥有通用定时器的全部功能
- 通用定时器拥有基本定时器的全部功能
类型 | 编号 | 总线 | 功能 |
---|---|---|---|
高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
TM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
1个高级定时器和3个通用定时器
(1)高级定时器
- 增加重复次数计数器,可以实现每隔几个计数周期,才发生一次更新事件和更新中断,相当于对输出的更新信号又做了一次分频,将最大计数时间延长了65536倍。原来的结构是每个计数周期完成后都会发生更新。
- DTG(Dead Time
Generate)死区生成电路,右边的输出引脚由一个变为了两个互补的输出,可以输出一对互补的PWM波,用于驱动三相无刷电机。
四轴飞行器、电动车的后轮、电钻等,都可能用的是三相无刷电机,三相无刷电机的驱动电路一般需要3个桥臂,每隔桥臂2个大功率开关来控制,总共需要6个大功率开关管来控制,所以这里输出PWM引脚的前三路就变为了互补的输出,而地思路却没什么变化,因为三相无刷电机只需要三路就行了。为了防止互补输出的PWM驱动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象,所以前面这里就加上了死区生成电路,在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。
- 刹车输入,为电机驱动提供安全保障,如果外部引脚BKIN(Break
IN)产生了刹车信号,或者内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生。
(2)通用定时器
-
带黑色阴影的寄存器,都是有影子寄存器这样的缓冲机制的。
-
包括预分频器、自动重装载寄存器和捕获比较寄存器。
结构组成
-
时基单元,由预分频器、计数器、自动重装载寄存器构成,预分频器对时钟进行预分频,计数器自增计数,当计数器计到自动重装值时,计数值清零同时产生更新中断和更新事件。
- 对于通用定时器而言,计数器的计数模式就不止向上计数这一种了,通用定时器支持向上计数模式,向下计数模式和中央对齐模式。
- 向上计数模式,计数器从0开始,向上自增,计到重装值,清零同时申请中断,然后开始下一轮,依次循环
- 向下计数模式,从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环。
- 中央对齐模式,就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后继续下一轮,依次循环。
- 对于通用定时器而言,计数器的计数模式就不止向上计数这一种了,通用定时器支持向上计数模式,向下计数模式和中央对齐模式。
-
内外时钟源选择,对于基本定时器而言,基准计数时钟只能选择内部时钟,也就是系统频率72MHz,而对于通用定时器,时钟源可以选择内部的72MHz时钟,还可以选择外部时钟
- TIMx_ETR引脚上的外部时钟。
- 比如可以在TIM2_ETR(External)引脚(复用在了PA0)上接一个外部方波时钟,然后配置一下内部的极性、边沿检测和预分频器电路,再配置一下输入滤波电路,这些电路可以对外部时钟进行一定整形,因为是外部引脚的时钟,所以难免会有毛刺,所以需要对输入的波形进行滤波。
- 滤波后的信号
- 一路ETRF,进入触发控制器,可以直接选择作为时基单元的时钟,或者相对ETR时钟进行计数,把这个定时器当作计数器来使用,就可以配置这一路的电路,在STM32中,这一路也叫做“外部时钟模式2”
- 一路TRGI,主要是用作触发输入来使用的,可以触发定时器的从模式或者将触发输入作为外部时钟来使用,当TRGI当作外部时钟来使用的时候,这一路就叫做“外部时钟模式1”
- TIMx_ETR引脚上的外部时钟。
-
编码器接口,读取正交编码器的输出波形。
-
主模式输出,可以把定时器内部的一些事件映射到TRGO引脚上,用于触发其他定时器、DAC或者ADC,比如将更新事件映射到TRGO,用于触发DAC。
-
输出比较电路,总共有4个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形,驱动电机。
-
输入捕获电路,总共有4个通道,分别对应CH1到CH4的引脚,可以用于测量输入方波的频率、占空比等。
-
捕获/比较寄存器,由输入捕获和输出比较电路共用,因为输入捕获和输出比较不能同时使用,所以寄存器和引脚都共用。
(3)基本定时器
1. 图中向上的折线箭头UI,代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,一般叫做更新中断,这个更新中断之后会通往NVIC,再配置好NVIC的定时器通道,那定时器的更新中断就能得到CPU的响应了。
2. 向下的箭头U,代表的是会产生一个事件,这里对应的事件就叫做“更新事件”,更新事件不会触发中断,但可以触发内部其他电路的工作。
基本流程
- 从基准时钟,到预分频器,再到计数器,计数器计数自增,同时不断地与自动重装载寄存器进行比较,计数值与自动重装值相等时,即更新时间到,这时会产生一个更新中断和更新事件,CPU响应更新中断,就完成了定时中断的任务。
基本结构
-
预分频器、计数器和自动重装寄存器,构成了最基本的计数计时电路,将这一块电路称作时基单元。
-
预分频器之前,连接的是基准计数时钟的输入,由于基本定时器只能选择内部时钟,所以可以直接认为,CK_PSC时钟线直接连到了内部时钟CK_INT上。
- 内部时钟CK_INT的来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHz,所以通向时基单元的计数基准频率就是72MHz。
-
预分频器,可以对72MHz的计数时钟进行预分频。
- 比如这个寄存器写0,就是不分频,或者说1分频,输出频率=输入频率=72MHz。如果预分频器写1,就是2分频,输出频率=输入频率/2=36MHz,以此类推。预分频器的值和实际的分频系数相差了1,即实际分频系数=预分频器的值+1。
- 16位寄存器,可以写的最大值为216-1=65536,也就是65536分频,对输入的基准频率提前进行一个分频的操作。
-
计数器,可以对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就+1。
- 16位寄存器,里面的值可以从0一直加到65535,如果再加的话,计数器就会回到0重新开始。计数器的值在计时过程中会不断地自增运行。当自增运行到目标值时,产生中断,就完成了定时的任务。
-
自动重装载寄存器,存储目标值
- 16位寄存器,存储我们写入的计数目标,在运行的过程中,计数值不断自增,自动重装值是固定的,当计数值等于自动重装值时,也就是定时时间到了,就会产生一个中断信号,并且清零计数器。计数器自动开始下一次的计数计时。
3.主模式触发DAC
- 主从触发模式,可以让内部的硬件在不受程序的控制下实现自动运行,可以减轻CPU的负担。
- 正常情况下,如果想要每隔一段时间触发DAC,就要先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC转换,然后DAC输出,会使主程序处于频繁被中断的状态。会影响主程序的运行和其他中断的响应。
- 使用主模式可以把定时器的更新事件,TRGO(Trigger
Out)的位置,然后TRGO直接接到DAC的触发转换引脚上,这样定时器的更新就不需要通过中断来触发DAC转换了。仅需要把更新事件通过主模式映射到TRGO,然后TRGO就会直接去触发DAC了。整个过程不需要软件的参与,实现了硬件的自动化,这就是主模式的作用。
4.定时中断基本结构
-
PSC预分频器、CNT计数器、ARR自动重装载寄存器构成的时基单元。
-
运行控制,表示控制寄存器的一些位,比如启动停止,向上和向下计数等等,操作这些寄存器就能控制时基单元的运行了。
-
可选时钟源
-
RCC提供的内部时钟 (内部时钟模式)
-
ETR引脚提供的外部时钟 (外部时钟模式2)
-
触发输入(TRGI)作为外部时钟 (外部时钟模式1)
- ETR外部时钟
- ITRx其他定时器
- TIx输入捕获通道
-
编码器模式
-
-
产生更新中断,中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。
- 中断输出控制,由于定时器模块很多地方都要申请中断,比如更新要申请中断,触发信号也要申请中断,输入捕获和输出比较匹配时也会申请中断,这些中断都要经过中断输出控制,如果需要这个中断,就允许,如果不要这个中断,就禁止。中断输出控制就是一个中断输出的允许位。
5.预分频器时序
基本定义
- CK_PSC,表示预分频器的输入时钟。
- CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止。
- CK_CNT,计数器时钟,既是预分频器的时钟输出,也是计数器的时钟输入。
基本流程
- 开始时,计数器未使能,计数器时钟不运行。
- 使能后,前半段,预分频器系数为1(1分频),计数器的时钟等于预分频器前的时钟,后半段,预分频器系数变为2(2分频),计数器的时钟就也变为预分频器前时钟的一半了。
- 在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在计数值到达FC(ARR自动重装值)之后,计数值变为0。当计数值计到和重装值相等,并且下一个时钟来临时,计数值才清零。同时产生一个更新事件。
缓冲机制
- 预分频寄存器实际上有两个,预分频控制寄存器只供读写用,并不直接决定分频系数。
- 缓冲寄存器(也叫做影子寄存器),才是真正起作用的寄存器
- 比如在某个时刻,把预分频寄存器由0改成1,如果在此时立刻改变时钟的分频系数,就会导致,在一个计数周期内,前半部分和后半部分的频率不一样,计数记到一半,计数频率突然就改变了,虽然一般不会有什么太大影响。
- 缓冲寄存器,使得我们在计数计到一半的时候改变了分频值,但是这个变化并不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频寄存器的值才会被传递到缓冲寄存器里面去,此时才会生效。所以,有了缓冲机制,即使我们在计数中途改变了预分频值,计数频率仍然会保持为原来的频率,直到本轮计数完成,在下一轮计数时,改变后的分频值才会起作用。
- 预分频计数器内部实际上也是靠计数来完成分频的,当预分频值为0时,计数器就一直为0,直接输出原频率,当预分频值为1时,计数器就0、1、0、1这样计数,在回到0
的时候,输出一个脉冲,这样输出频率就是输入频率的2分频。
6.计时器时序
基本定义
- CK_INT 内部时钟72MHz
- CNT_EN 时钟使能,高电平启动
- CK_CNT 计数器时钟,这里分频系数为2,2分频
基本流程
- 计数器在时钟的每个上升沿自增,当增到0036(自动重装值)的时候,发生溢出,再来一个上升沿,计数器清零,计数器溢出,产生一个更新事件脉冲。另外还会置一个更新中断标志位UIF,这个标志位只要置1了,就会去申请中断,中断响应后,需要在中断程序中手动清零该标志位
- 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR +
1)
————————————————
版权声明:本文为CSDN博主「YRr YRr」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_73640344/article/details/134004426
-
本程序的主要内容。
-
调用接口 align_api.py
-
predict 用于生成预测结果
- 输入文件
- 输出文件
-
content_align
- 用于字段
-
sample_api
- 用于任意词
-
二、PWM
1.定义
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
2.频率
是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);
也就是说一秒钟PWM有多少个周期
单位: Hz
表示方式: 50Hz 100Hz
3.周期
T=1/f
周期=1/频率
50Hz = 20ms 一个周期
如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
4.占空比
是一个脉冲周期内,高电平的时间与整个周期时间的比例
单位: % (0%-100%)
表示方式:20%
周期: 一个脉冲信号的时间 1s内测周期次数等于频率
脉宽时间: 高电平时间
上图中 脉宽时间占总周期时间的比例,就是占空比
比方说周期的时间是10ms,脉宽时间是8ms 那么低电平时间就是2ms 总的占空比 8/(8+2)= 80%
这就是占空比为80%的脉冲信号
而我们知道PWM就是脉冲宽度调制 通过调节占空比,就可以调节脉冲宽度(脉宽时间) 而频率 就是单位时间内脉冲信号的次数,频率越大
以20Hz 占空比为80% 举例 就是1秒钟之内输出了20次脉冲信号 每次的高电平时间为40ms
我们换更详细点的图
上图中,周期为T
T1为高电平时间
T2 为低电平时间
假设周期T为 1s 那么频率就是 1Hz 那么高电平时间0.5s ,低电平时间0.5s 总的占空比就是 0.5 /1 =50%
5.原理
以单片机为例,我们知道,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平
假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号
我们知道,电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压
比方说 占空比为50% 那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比 得到的电压就是3.75V
pwm的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,“占周期”变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降
也就是,在一定的频率下,通过不同的占空比 即可得到不同的输出模拟电压
pwm就是通过这种原理实现D/A转换的。
总结:
PWM就是在合适的信号频率下,通过一个周期里改变占空比的方式来改变输出的有效电压
PWM频率越大,相应越快,
三、用定时器控制小灯周期性闪烁
使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin,连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。
1.代码
Time.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
//1.开启时钟,通用定时器TIM2是APB1总线的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟为内部时钟
TIM_InternalClockConfig(TIM2);//上电默认就是内部时钟,不写亦可以
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频,1分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数器模式 向上计数
TIM_TimeBaseInitStructure.TIM_Period=10000 - 1 ;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler= 14400-1;//PSC预分频器的值
//T = (PSC*ARR)/72MHz
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值 高级计数器使用的,这里不需要
//2.配置时基单元
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//3.使能更新中断,开启更新中断到NVIC的通路
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//4.NVIC优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//响应优先级
NVIC_Init(&NVIC_InitStructure);
//5.启动定时器
TIM_Cmd(TIM2,ENABLE);
}
//定时器2的中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
GPIOA->ODR ^= GPIO_Pin_0;//把GPIO口A的第0位的输出电平取反
//清除标志位
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
Tim.h
#ifndef __TIME_H
#define __TIME_H
void Timer_Init(void);
#endif
主函数main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"//使用延时函数
#include "OLED.h"
#include "Timer.h"
void LED0_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// GPIO_SetBits(GPIOA,GPIO_Pin_0);
}
int main(void)
{
// OLED_Init();
LED0_Init();
Timer_Init();
while(1)
{
// GPIO_ResetBits(GPIOA,GPIO_Pin_0);//亮
};
}
2.运行效果
定时器2s
四、PWM呼吸灯
1.代码
PWM.C
#include "stm32f10x.h" // Device header
//使用重映射
void PWM_Init1(void)
{
//开启TIM2时钟,TIM2是APB1总线的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//开启AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//更改重映射
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
//选择时基单元的时钟为内部时钟
TIM_InternalClockConfig(TIM2);
//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数器模式,向上计数
TIM_TimeBaseInitStructure.TIM_Period=100-1;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值,高级计数器使用的 这里不需要
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//设置输出比较的模式
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//设置输出比较的极性
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse=0;//设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_Init(void)
{
//开启TIM2时钟,TIM2是APB1总线的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟为内部时钟
TIM_InternalClockConfig(TIM2);
//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//计数器模式,向上计数
TIM_TimeBaseInitStructure.TIM_Period=100-1;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器的值,高级计数器使用的 这里不需要
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//设置输出比较的模式
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//设置输出比较的极性
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse=0;//设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);//设置CCR的值
}
PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_Init1(void);
#endif
主函数main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"//使用延时函数
#include "OLED.h"
#include "PWM.h"
uint8_t i;
int main(void)
{
OLED_Init();
PWM_Init();
while(1)
{
for(i=0;i<=100;i++)
{
//设置CCR寄存器的值
PWM_SetCompare1(i);
Delay_ms(10);
}
for(i=0;i<=100;i++)
{
PWM_SetCompare1(100-i);
Delay_ms(10);
}
};
}
2.运行效果
3.keil仿真
缩放后波形的占空比有明显的变化:
五、采集PWM输出信号,获得其周期和脉宽,并重定向输出到串口显示
1)使用HAL库建立工程
2)选择的单片机型号
3)配置RCC
4)配置SYS
5)
单片机端口配置使用
6)串口通信配置,如下图所示
7)定时器定时配置(TIM2),按照图中步骤进行配置即可
8)PWM波形捕获(TIM1),按照图中步骤进行配置即可
代码:
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
uint8_t i = 0;
float Duty = 0;
float Frequency = 0;
uint16_t Cap_val1 = 0;
uint16_t Cap_val2 = 0;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN WHILE */
printf("串口通信测试\r\n");
HAL_TIM_Base_Start_IT(&htim2); // 使能定时器及其更新中断
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 使能定时器及其PWM输出
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); // 使能定时器及其输入捕获
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2); // 使能定时器及其输入捕获
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 10); // 设置一个PWM波形进行测量
while (1)
{
// 串口发送 频率 占空比
printf("Cap_val1 is :%d , Cap_val2 is : %d \r\n", Cap_val1, Cap_val2);
printf("Duty is :%0.2f%% Frequency is : %0.2f ms\r\n", Duty, Frequency);
HAL_Delay(1000);
}
}
// 定时TIM2 定时亮灯的中断函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *tim)
{
if (tim == &htim2)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
// 定时输入捕获回调函数 计算占空比和频率
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
Cap_val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
}
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
Cap_val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
Duty = 100 - (float)Cap_val2 / (float)Cap_val1 * 100;
Frequency = 0.001 * Cap_val1;
}
}
}
烧录运行结果
六、学习 HC-SR04超声波测距模块工作原理,使用 stm32F103 完成一个超声波测距方案
1.产品特点
HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测
距精度可达高到的非接触式距离感测功能,测距精度可达高到 3mm ;模块包括超声波发射器、接收器与控制电路。
2.基本工作原理
(1)采用 IO 口 TRIG 触发测距,给最少 10us 的高电平信呈。
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 口 ECHO 输出一个高电平,高电平持续的时间就是超声
波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
3.实验设备
4.代码
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "Serial.h"
#include "Servo.h"
#include "Ultrasound.h"
int main()
{
Serial_Init();
Servo_Init();
Ultrasound_Init();
char * a="dis:";
while(1)
{ float dis=Test_Distance();
Serial_SendString(a);
Serial_SendNumber(dis,2);
Delay_ms(500);
}
}
Ultrasound.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
uint16_t Cnt;
uint16_t OverCnt;
void Ultrasound_Init(){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//trig 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//echo 接受引脚 下拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM4);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 60000 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
}
float Test_Distance(){
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_us(20);
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==RESET){
};
TIM_Cmd(TIM4, ENABLE);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==SET){
};
TIM_Cmd(TIM4, DISABLE);
Cnt=TIM_GetCounter(TIM4);
float distance=(Cnt*1.0/10*0.34)/2;
TIM4->CNT=0;
Delay_ms(100);
return distance;
}
5.实验现象
总结
在实验中,我深入学习和理解了STM32定时器的工作原理。我掌握了定时器计数方式控制LED的亮灭状态,并成功地实现了LED以2s的频率周期性地亮灭。随后,我学习了脉宽调制(PWM)的生成方法,并将LED的控制方式切换至PWM模式。通过适当调整周期和占空比,我成功实现了LED以呼吸灯的方式渐亮渐灭。
在观察PWM输出波形的过程中,我使用了Keil虚拟示波器。通过观察波形图,我能够直观地了解到PWM信号的变化情况,从而验证我的编程结果。这使我更加熟悉了使用示波器进行波形分析的方法。
在任务中,我采用了定时器的另一个通道来编程采集上述PWM输出信号,并获取其周期和脉宽。通过对信号的计数和处理,我成功获得了PWM信号的周期和脉宽的数值。随后,我将这些数据通过串口进行显示输出,进一步验证了我的程序正确性。
尽管对于选做任务五中的超声波测距方案,由于时间和资源的限制,我未能完成实验。但是,通过学习HC-SR04超声波测距模块的工作原理,我了解到了超声波测距技术的基本原理和应用场景。这为我今后在该领域的研究和开发提供了宝贵的参考。
通过这次实验,我不仅加深了对STM32定时器原理和PWM生成方法的理解,还掌握了相关的编程技巧。同时,通过使用虚拟示波器和串口通信,我提高了对信号分析和数据输出的能力。这次实验使我更加熟悉了STM32F103单片机的应用和编程,也增强了我的实践能力和解决问题的能力。我相信这些所学将为我以后的学习和工作提供很好的基础和帮助。