一 定时器
1 定时器介绍
STM32F103C8T6微控制器内部集成了多种类型的定时器,这些定时器在嵌入式系统中扮演着重要角色,用于计时、延时、事件触发以及PWM波形生成、脉冲捕获等应用。
常用定时器:
- TIM1:这是一个高级定时器,不仅具备基本的定时中断功能,还拥有内外时钟源选择、输入捕获、输出比较、编码器接口以及主从触发模式等多种功能。这使得TIM1能够适用于各种复杂的应用场景,为开发者提供强大的时间控制和信号处理能力。
- TIM2、TIM3和TIM4:这些是通用定时器,同样具有定时功能,但在功能上与高级定时器有所区别。通用定时器通常用于实现一些基本的定时任务,如LED闪烁、脉冲宽度测量等。
2 定时器分类

每个定时器都由一个16位计数器、预分频器 和自动重装寄存器的时基单元组成。
预分频器可以对时钟进行分频,计数器则对预分频后的时钟进行计数。
当计数器的值达到设定值时,会触发中断,从而执行相应的定时任务。

3 定时器溢出时间计算
你怎么知道设置多少秒时应该怎么给定时器赋值,就是通过这个公式,一般习惯把PSC+1设置为Ft的整数倍
Ft=72MHz,1/M秒是1微秒,PSC=7199时,(PSC+1)/Ft为100us,即计一个数要100us,我需要设置500us,所以让ARR=4就行了

Tout:定时器溢出时间
Ft:定时器的时钟源频率
ARR:自动重装载寄存器的值
PSC:预分频器寄存器的值
如果要定时 500ms(Ft=72M)呢,则让:ARR=4999,PSC=7199
4 定时器配置流程

二 PWM
0 输出比较原理
输出比较可以通过比较定时计数器的值 CNT 与设定的比较值 CCR,可以控制输出引脚的电平状态(置高或置低),从而实现生成一定频率和占空比的 PWM 波形。

1 介绍
PWM波形(Pulse Width Modulation,脉冲宽度调制波形)是一种占空比可变的脉冲波形。这种调制方式通过改变脉冲的宽度来控制电路中的信号强度和频率。具体来说,PWM波形中的高电平持续时间和低电平持续时间可以根据需要进行调整,从而实现对模拟信号电平的数字编码。
2 配置

3 应用
3.1 呼吸灯
3.1.1 功能需求
使用定时器 4 通道 3 生成 PWM 波控制 LED1 ,实现呼吸灯效果。
频率:2kHz,PSC=71,ARR=499
为什么选择定时器4和通道3呢?
------看原理图,LED1的引脚是PB8,再看数据手册,PB8要用TIM4_CH3


3.1.2 pwm.c
第一步:自然是要先写初始化函数pwm_init咯
cpp
void pwm_init(uint16_t arr, uint16_t psc)
{
pwm_handle.Instance = TIM4;
pwm_handle.Init.Prescaler = psc;
pwm_handle.Init.Period = arr;
pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&pwm_handle);
}
arr和psc见"一"
第二步:msp函数
cpp
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM4)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
__HAL_RCC_TIM4_CLK_ENABLE();
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_8; // 两个LED对应的引脚
gpio_initstruct.Mode = GPIO_MODE_AF_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
}
}
为什么使用推挽输出?看参考手册

第三步:修改CCE值的函数
还在pwm_init里继续写,看流程图,该使用HAL_TIM_PWM_ConfigChannel()了,要什么给什么
cpp
TIM_OC_InitTypeDef pwm_config = {0};
pwm_config.OCMode = TIM_OCMODE_PWM1;
pwm_config.Pulse = arr/2;
pwm_config.OCPolarity = TIM_OCPOLARITY_LOW;
HAL_TIM_PWM_ConfigChannel(&pwm_handle, &pwm_config, TIM_CHANNEL_3);
pwm_config是结构体变量,通过结构体变量访问结构体成员进行初始化
PWM只有两个OCMODE,这里我们选1,先不看别的

第四步:使能输出,并启动计数器
使用HAL_TIM_PWM_Start(&pwm_handle, TIM_CHANNEL_3);
第五步:修改CCR寄存器值
使用__HAL_TIM_SET_COMPARE(&pwm_handle, TIM_CHANNEL_3, val)
cpp
void pwm_compare_set(uint16_t val)
{
__HAL_TIM_SET_COMPARE(&pwm_handle, TIM_CHANNEL_3, val);
}
现在pwm_init里是这样
cpp
// init函数
void pwm_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef pwm_config = {0};
pwm_handle.Instance = TIM4;
pwm_handle.Init.Prescaler = psc;
pwm_handle.Init.Period = arr;
pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&pwm_handle);
pwm_config.OCMode = TIM_OCMODE_PWM1;
pwm_config.Pulse = arr/2;
pwm_config.OCPolarity = TIM_OCPOLARITY_LOW;
HAL_TIM_PWM_ConfigChannel(&pwm_handle, &pwm_config, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&pwm_handle, TIM_CHANNEL_3);
}
3.2 舵机
3.2.1 介绍、功能需求
市面上常见的舵机型号有 SG90、MG90S、MG995、MG996R 等等,主要是扭矩大小、工作电压大小、齿轮材质塑料或金属的不同。
一般分为180度和360度:
- 180度:可以控制旋转角度、有角度定位。上电后舵机自动复位到0度,通过一定参数的脉冲信号控制它的角度。
- 360°舵机版本不可控制角度,只能控制顺时针旋转、逆时针旋转、停止和调节转速。
我们今天的主角是 SG90,180度版。

|------|------------|-----|
| SG90 | PWM信号线(橙色) | PA6 |
| | VCC(红线) | 5V |
| | GND(棕色线) | GND |

3.2.2 sg90.c
代码基于呼吸灯代码上修改
sg90.c
cpp
#include "sg90.h"
TIM_HandleTypeDef tim3_handle = {0};
// init函数
void tim3_init(void)
{
TIM_OC_InitTypeDef pwm_config = {0};
tim3_handle.Instance = TIM3;
tim3_handle.Init.Prescaler = 7200 - 1;
tim3_handle.Init.Period = 200 - 1;
tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&tim3_handle);
pwm_config.OCMode = TIM_OCMODE_PWM1;
pwm_config.Pulse = 100;
pwm_config.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&tim3_handle, &pwm_config, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&tim3_handle, TIM_CHANNEL_1);
}
//msp函数
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOB时钟
__HAL_RCC_TIM3_CLK_ENABLE();
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_6; // 两个LED对应的引脚
gpio_initstruct.Mode = GPIO_MODE_AF_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
}
}
//修改CCR值的函数
void tim3_compare_set(uint16_t val)
{
__HAL_TIM_SET_COMPARE(&tim3_handle, TIM_CHANNEL_1, val);
}
void sg90_init(void)
{
tim3_init();
}
基于呼吸灯的pwm.c更改了定时器通道、函数名和引脚,基本结构没变
main.c
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "sg90.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
sg90_init();
// uint16_t i = 0;
while(1)
{
sg90_angle_set(0);
delay_ms(1000);
sg90_angle_set(20);
delay_ms(1000);
sg90_angle_set(40);
delay_ms(1000);
sg90_angle_set(90);
delay_ms(1000);
sg90_angle_set(180);
delay_ms(1000);
// for(i = 5; i <= 25; i++)
// {
// tim3_compare_set(i);
// delay_ms(100);
// }
//
// for(i = 5; i <= 25; i++)
// {
// tim3_compare_set(30 - i);
// delay_ms(100);
// }
// led1_on();
// led2_off();
// delay_ms(500);
// led1_off();
// led2_on();
// delay_ms(500);
}
}

数一个数是100us(配置的72MHz时钟,ARR和PSC分别配置的是199和7199),也就是0.1ms,所以 旋转0°,得数5个数;旋转180°,得数25个数------这是main函数注释掉的部分,此为方法一
然而这样显得不太智能,不如封装一个函数,直接输入角度,它就可以转动对应角度值,就是void sg90_angle_set(uint16_t angle),写在sg90.c中,别忘了在.h文件中补充函数声明,此为方法二
cpp
void sg90_angle_set(uint16_t angle)
{
uint16_t CCRx = (1.0 / 9.0) * angle + 5.0;
tim3_compare_set(CCRx);
}
