目录
- [1. 定时器中断应用](#1. 定时器中断应用)
-
- [1.1 内部时钟选择](#1.1 内部时钟选择)
- [1.2 计数器模式](#1.2 计数器模式)
- [1.3 HC-SR04超声波测距模块](#1.3 HC-SR04超声波测距模块)
-
- [1.3.1 工作原理](#1.3.1 工作原理)
- [1.3.2 硬件接线](#1.3.2 硬件接线)
- [1.3.3 控制超声波测距步骤](#1.3.3 控制超声波测距步骤)
- [2. 定时器输出PWM应用](#2. 定时器输出PWM应用)
-
- [2.1 通用PWM原理](#2.1 通用PWM原理)
- [2.2 输出比较通道](#2.2 输出比较通道)
- [2.3 PWM模式](#2.3 PWM模式)
- [2.4 定时器PWM库函数配置](#2.4 定时器PWM库函数配置)
- [2.5 SG90舵机](#2.5 SG90舵机)
-
- [2.5.1 SG90工作原理](#2.5.1 SG90工作原理)
- [2.5.2 SG90引脚](#2.5.2 SG90引脚)
- [2.5.3 控制方法](#2.5.3 控制方法)
- [2.6 控制舵机配置步骤](#2.6 控制舵机配置步骤)
1. 定时器中断应用
1.1 内部时钟选择

除非APB1的分频系数是1,否则通用定时器的时钟等于APB1时钟的2倍。
默认调用SystemInit函数情况下:
SYSCLK=72M、AHB时钟=72M、APB1时钟=36M
所以APB1的分频系数=AHB/APB1时钟=2,通用定时器时钟CK_INT=2*36M=72M
1.2 计数器模式
- 向下计数模式 (时钟分频因子=1)

- 向上计数

- 中央对齐模式

1.3 HC-SR04超声波测距模块

1.3.1 工作原理
- 采用IO触发测距 ,给至少10μs的高电平信号。
- 模块自动发送8个40kHz的方波 ,自动检测是否有信号返回。
- 有信号返回 ,通过IO输出一高电平 ,高电平持续时间就是超声波从发射到返回的时间 /声波从发射到返回的时间。
- HC-SR04超声波测距模块提供2cm~400cm的测距功能,精度达3mm。
1.3.2 硬件接线
四根杜邦线,VCC连接单片机3.3-5V(推荐5V供电),GND接到板子的GND,Trig为外部触发信号输入,输入一个10μs的高电平即可触发模块测距,Echo即为回响信号输出,测距结束时此管引脚输出一个低电平,电平宽度反映超声波往返时间之和。
1.3.3 控制超声波测距步骤
- 初始化超声波时钟 和定时器时钟
- 初始化超声波引脚 和定时器中断配置
- 编写定时器中断函数测距

高电平的时间 = 中断次数 + 不足一个中断的次数
c
// tim.h
#ifndef TIM_H
#define TIM_H
void Base_TIM_Init(void);
void HC04_Init(void);
float Get_Length(void);
#endif
c
// tim.c
#include "stm32f10x.h"
#include "tim.h"
uint16_t mscount = 0;
void delay_us(uint32_t us){
us *= 8;
while(us--);
}
void delay_ms(uint32_t ms){
while(ms--){
delay_us(1000);
}
}
// 定时器函数
void Base_TIM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 优先级分组配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeInitStruct.TIM_Prescaler = 72-1; // 分频因子 不能超过65535
TIM_TimeInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeInitStruct.TIM_Period = 1000-1; // 自动重装载值 不能超过65535
TIM_TimeInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 外部输入时钟分频因子
TIM_TimeInitStruct.TIM_RepetitionCounter = 0; // 重复计数值 高级定时器专用
TIM_TimeBaseInit(TIM2, &TIM_TimeInitStruct);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 配置定时器的中断使能
TIM_Cmd(TIM2, DISABLE); // 定时器先关着 用的时候再打开
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
// HC-SR04 超声波初始化函数
void HC04_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruction;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// Trig引脚 B11 输出
GPIO_InitStruction.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruction.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruction.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStruction);
// Echo引脚 B10 输入
GPIO_InitStruction.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruction.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStruction);
}
// 开启定时器
void Open_Tim(void)
{
TIM_SetCounter(TIM2, 0); // 计数器 CNT 设置为0
mscount = 0; // 中断次数为 0
TIM_Cmd(TIM2, ENABLE);
}
// 关闭定时器
void Close_Tim(void)
{
TIM_Cmd(TIM2, DISABLE);
}
// 获取定时器中断的次数, 定时器中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
mscount++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
// 获取高电平的总时间
int Get_Echo_Time(void)
{
uint16_t t=0;
t = mscount * 1000;
t += TIM_GetCounter(TIM2); // 获取 CNT 计数器的值
TIM_SetCounter(TIM2, 0); // 计数器 CNT 设置为 0
delay_ms(50);
return t;
}
float Get_Length(void)
{
uint16_t t=0;
float length=0;
int i=0;
float sum=0;
while(i != 5)
{
// 1.HC04 工作
GPIO_SetBits(GPIOB, GPIO_Pin_11);
delay_us(20);
GPIO_ResetBits(GPIOB, GPIO_Pin_11);
// 2.定时器计算高电平时间
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0); // 等待 Echo 引脚低电平到高电平 跳变
Open_Tim();
i++;
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 1); // 等待 Echo 引脚高电平到低电平
Close_Tim();
t = Get_Echo_Time();
// 3.时间换算成距离
// 声速 ≈ 340 m/s → 往返 1cm 需要 ≈ 58.8 μs
// 所以 距离(cm) = 时间(μs) / 58 是行业惯例
length = ((float)t/58.0);
sum += length;
}
length = sum/5.0;
return length;
}
c
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "LED.h"
#include "USART.h"
#include "tim.h"
#include "stdio.h"
void delay(uint16_t time)
{
uint16_t i=0;
while(time--)
{
i = 12000;
while(i--);
}
}
int main()
{
float length = 0;
LED_Init();
Base_TIM_Init();
My_Usart1_Init();
HC04_Init();
while(1)
{
length = Get_Length();
printf("%lf\r\n", length);
}
}
2. 定时器输出PWM应用
2.1 通用PWM原理
PWM,英文名Pulse width modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是通过调节占空比的变化来调节信号能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占整个信号周期的百分比,例如方波的占空比就是50%。PWM的功能有很多种,比如控制呼吸灯、控制直流电机或者舵机等驱动原件等等,是单片机的一个十分重要的功能。
即PWM模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。其框图如下图所示:

横坐标是时间变量 ,纵坐标是CNT计数值 ,CNT计数值随着时间的推进会不断经历从0到ARR ,清零复位再到ARR 的这一过程。这之中还有一个数值是CCRx 即比较值 ,通过比较值和输出配置 可以使它输出高低电平逻辑 ,这样就产生了PWM波形 。通过调节ARR的值可以调节PWM的周期 ,调节CCRx的值大小可以调节PWM占空比。
2.2 输出比较通道

2.3 PWM模式
PWM有PWM模式1 和模式2 两种模式 ,它们之间的区别用寄存器 TIMx_CCMR1 的 OC1M[2:0] 位 来分析:

PWM模式1和模式2的定义和区别,可以简单理解为:PWM模式1 的情况下,当前值小于比较值 为有效电平;PWM模式2 的情况下,当前值大于比较值为有效电平。
2.4 定时器PWM库函数配置
- 输出库函数配置
c
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
- 结构体成员
c
typedef struct
{
uint16_t TIM_OCMode; // PWM模式1 或者 模式2
uint16_t TIM_OutputState; // 输出使能 OR使能
uint16_t TIM_OutputNState; // 高级定时器控制互补通道N的状态
uint16_t TIM_Pulse; // 比较值,写CCRx
uint16_t TIM_OCPolarity; // 比较输出极性
uint16_t TIM_OCNPolarity; // 高级定时器控制互补输出通道N的极性
uint16_t TIM_OCIdleState; // 空闲状态下的电平
uint16_t TIM_OCNIdleState; // 高级定时器控制互补输出通道空闲状态下的电平
} TIM_OCInitTypeDef;
- 设置比较值函数
c
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
- 使能输出比较预装载
c
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
- 使能自动重装载的预装载寄存器允许位
c
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
2.5 SG90舵机
2.5.1 SG90工作原理

舵机的工作原理比较简单。舵机内部有一个基准电压,单片机产生的PWM信号通过信号线进入舵机,与舵机内部的基准电压作比较,获得电压差输出。电压差的正负输出到电机驱动芯片上,从而决定正反转。开始旋转的时候,舵机内部通过级联减速齿轮带动电位器旋转,使得电压差为零,电机停止转动。
2.5.2 SG90引脚
sg90有三个引脚,分别是 红线(VCC) ,棕线(GND) 和 橙线(信号线) 。通常使用5V供电,信号线接单片机引脚,用来接收单片机发送的PWM。
2.5.3 控制方法
控制sg90舵机旋转也比较简单,只需要给它输出PWM波 ,修改占空比就可以调整角度 。sg90的控制一般需要一个周期为20ms左右的时基脉冲 ,脉冲的高电平部分一般在0.5ms~2.5ms 。高电平持续时间与旋转角度的对应关系如下
T = 20ms = (ARR+1)*(PSC+1)/72 000 000
200 * 7200 / 72000 000 = 20ms = 0.02s = 1/50s
195 5
190 10
185 15
180 20
175 25
高电平持续时间设置为 比较值
| 高电平持续时间/ms | 舵机角度/° | 占空比 |
|---|---|---|
| 0.5 | 0° | 2.5% |
| 1.0 | 45° | 5% |
| 1.5 | 90° | 7.5% |
| 2.0 | 135° | 10% |
| 2.5 | 180° | 12.5% |
2.6 控制舵机配置步骤

- 使能定时器和相关IO时钟
- 初始化IO为复用模式
- 初始化定时器
- 初始化输出比较参数
- 使能预装载寄存器
- 使能定时器
- 改变比较值CCR x,达到不同的占空比效果

c
// sg90.h
#ifndef SG90_H
#define SG90_H
#include "stm32f10x.h"
void SG90_Init(void);
void SG90_Angle(uint16_t angle);
#endif
c
// sg90.c
#include "sg90.h"
#include "stm32f10x.h"
void SG90_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_OCInitTypeDef TIMOC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 使能复用功能的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3定时器的时钟
// 配置TIM3定时器的通道从默认引脚切换到手册中定义的"部分重映射引脚"上
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStruct);
TIM_InitStruct.TIM_Prescaler = 7200-1;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式,从0开始,逐次递增
TIM_InitStruct.TIM_Period = 200-1;
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_InitStruct);
TIMOC_InitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 比比较值低为有效电平
TIMOC_InitStruct.TIM_OutputState = TIM_OutputState_Enable; // 定时器输出使能
TIMOC_InitStruct.TIM_OCPolarity = TIM_OCPolarity_Low; // 低电平有效
TIM_OC2Init(TIM3, &TIMOC_InitStruct);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能定时器通道2的输出比较预装载寄存器
TIM_Cmd(TIM3, ENABLE); // 启动定时器
}
void SG90_Angle(uint16_t angle)
{
switch(angle)
{
case 180: TIM_SetCompare2(TIM3, 175); break; // 设置比较值
case 135: TIM_SetCompare2(TIM3, 180); break;
case 90: TIM_SetCompare2(TIM3, 185); break;
case 45: TIM_SetCompare2(TIM3, 190); break;
case 0: TIM_SetCompare2(TIM3, 195); break;
}
}
c
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "sg90.h"
void delay(uint16_t time)
{
uint16_t i=0;
while(time--)
{
i = 12000;
while(i--);
}
}
int main()
{
SG90_Init();
while(1)
{
SG90_Angle(135);
delay(1000);
SG90_Angle(0);
delay(1000);
}
}