【STM32】定时器、PWM

一 定时器

1 定时器介绍

STM32F103C8T6微控制器内部集成了多种类型的定时器,这些定时器在嵌入式系统中扮演着重要角色,用于计时、延时、事件触发以及PWM波形生成、脉冲捕获等应用。
常用定时器:

  1. TIM1:这是一个高级定时器,不仅具备基本的定时中断功能,还拥有内外时钟源选择、输入捕获、输出比较、编码器接口以及主从触发模式等多种功能。这使得TIM1能够适用于各种复杂的应用场景,为开发者提供强大的时间控制和信号处理能力。
  2. 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);
}
相关推荐
Jie_jiejiayou2 小时前
定时器详解以及呼吸灯实现 — STM32(HAL库)
stm32·单片机·嵌入式硬件·定时器
XH1.2 小时前
学习RT-thread(RT-thread定时器)
stm32·单片机·学习
QT 小鲜肉2 小时前
【个人成长笔记】在 Linux 系统下撰写老化测试脚本以实现自动压测效果(亲测有效)
linux·开发语言·笔记·单片机·压力测试
申克Lab2 小时前
STM32 串口概念 UART协议
stm32·单片机·嵌入式硬件
小莞尔3 小时前
【51单片机】【protues仿真】基于51单片机自动浇花系统
单片机·嵌入式硬件
沐欣工作室_lvyiyi4 小时前
基于51单片机的宠物喂食器的设计与实现(论文+源码)
单片机·嵌入式硬件·毕业设计·51单片机·宠物
hazy1k7 小时前
51单片机基础-最小系统设计
stm32·单片机·嵌入式硬件·mcu·51单片机·proteus
奋斗的牛马8 小时前
FPGA—ZYNQ学习spi(六)
单片机·嵌入式硬件·学习·fpga开发·信息与通信
清风6666668 小时前
基于单片机的智能高温消毒与烘干系统设计
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业