STM32 学习 PWM输出以及应用

PWM的基本结构

PWM 的参数计算

PWM的频率就等于计数器的溢出频率 Freq = CK_PSC(PSC+1)/(ARR+1) 频率等于时间除以周期(一段时间内出现了多少个周期,就是频率)

PWM占空比计算: Duty = CCR (ARR+1) 可以看作为占空比计算=高电平/周期

PWM 占空比:从图上可以看出CCR 的数值应该是ARR+1 到 0 这个范围的 CCR等于ARR+1 的时候占空比就刚好是100%,如果将CCR 设置为102 那么占空比不会变化,所以CCR 的数值取决于ARR的数值,ARR越大那么CCR 就越大,对应的分辨率就越大。

补充概念:MOS管(大功率电子开关)输出高电平导通,低电平断开,如果上下管都断开,那就是高阻态

舵机简介

舵机硬件电路

直流电机及驱动简单介绍

TB6612驱动模块硬件电路

呼吸灯 代码部分

初始化函数:配置主要分为三个部分

结构体参数配置输出比较单元,四个函数对于四个单元

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

给输出比较结构体赋值(默认值)

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

配置强制输出模式

void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);

单独更改CCR数值的函数

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

重新映射(pin remapping)。

在电子电路设计和微控制器编程中,引脚重映射是指将特定功能的引脚重新分配到不同的物理引脚位置上。

这里我们可以用到这个引脚重映射函数

void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

这里我们的 TIM2 是在 CH1 通道上的。如果该功能与 USART 产生冲突,就可以将引脚功能重新映射到别的引脚上,具体位置参考重定义功能列表。

这里的PA15端口,就有TIM2_CH1 这个通道,所以我们可以将PA1 引脚映射到PA15 上面,达到一样的效果

将TIM2 映射到PA15端口,并且解除端口的JTAD 复用

             //打开AFTO时钟 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO ,ENABLE);
 GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE );
 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE );

初始化函数部分

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
        /*初始化TIM2 跟GPIO时钟*/
                RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 ,ENABLE);
                RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE);
        
        /*
        用于引脚重映射
                RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO ,ENABLE);
                GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE );
          GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE );
        */

        //初始化GPIO 设置为复用推挽输出,将控制交给片上外设寄存器操作
        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_InternalClockConfig(TIM2);
        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);
         //计算公式 PWM频率 = 时钟源 / (ARR+1) / (PSC+1)
         //PWM占空比 = CCR / (ARR+1)
         //PWM分辨率 = 1 (ARR+1)
         
         TIM_Cmd(TIM2,ENABLE);
}


void PWM_SetCompare1 (uint16_t Compare)
{
                TIM_SetCompare1(TIM2,Compare);
}

主函数部分,通过控制CCR的数值,达到呼吸灯的效果

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "PWM.h"
#include "Delay.h"
uint8_t i;
int main(void){
  OLED_Init();
  OLED_ShowString(1,1,"Hellowrold!");
  PWM_Init();
  while(1){
    for (i=0;i<=100;i++)
    {
      PWM_SetCompare1(i);
      Delay_ms(10);
    }
    
    for (i=0;i<=100;i++)
    {
      PWM_SetCompare1(100-i);
      Delay_ms(10);
    }
  }
}

程序效果:LED灯呈现高—>低—>高的亮度循环

SG90舵机代码部分

接线图

注意,如果使用多个通道同时输出PWM,由于他们使用的是同一个时钟,所以频率必须相同,但是他们的CCR都是可以变化的

初始化函数

PWM部分
#include "stm32f10x.h"                  // Device header

// 函数功能:初始化 PWM
void PWM_Init(void)
{
    /*初始化 TIM2 和 GPIOA 时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /*
    用于引脚重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE );
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE );
    */

    // 初始化 GPIOA 的引脚 1 为复用推挽输出模式,将控制交给片上外设寄存器操作
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    // 复用推挽输出模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;          // 选择 GPIOA 的引脚 1
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // 输出速度为 50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置 TIM2 使用内部时钟源
    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 = 20000 - 1;              // 设置自动重装载值(ARR)
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               // 设置预分频系数(PSC)
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;          // 重复计数器值为 0
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);            // 初始化 TIM2 的时基单元

    // 输出比较电路初始化
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;             // 选择 PWM 模式 1
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     // 输出极性为高电平有效
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 使能输出
    TIM_OCInitStructure.TIM_Pulse = 0;                            // 设置比较值(CCR)初始为 0
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);                      // 初始化 TIM2 的通道 2

    // 计算公式:PWM 频率 = 时钟源 / (ARR+1) / (PSC+1)
    // PWM 占空比 = CCR / (ARR+1)
    // PWM 分辨率 = 1 / (ARR+1)

    TIM_Cmd(TIM2, ENABLE); // 使能 TIM2
}

// 函数功能:设置 TIM2 通道 2 的比较值
void PWM_SetCompare2(uint16_t Compare)
{
    TIM_SetCompare2(TIM2, Compare);
}
舵机部分,通过公式换算,让角度数值更加直观
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

// 函数功能:初始化舵机相关的硬件(实际上是调用 PWM 的初始化函数)
void Servo_Init(void)
{
    PWM_Init();
}

// 函数功能:设置舵机的角度
// 参数 Angle:期望设置的舵机角度
void Servo_SetAngle(float Angle)
{
    // 根据角度计算对应的比较值,以设置 PWM 的占空比从而控制舵机角度
    // 这里假设舵机的控制脉冲范围是 500 - 2500,对应 0 - 180 度
    PWM_SetCompare2(2000/180*Angle + 500);
}
主函数
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Servo.h"
#include "Delay.h"

int main(void){
    OLED_Init();
    // 在 OLED 屏幕第一行第一列显示字符串"Hellowrold!"(可能存在拼写错误,应为"Hello world!")
    OLED_ShowString(1,1,"Hellowrold!");
    Servo_Init();

    while(1){
        // 将舵机角度设置为 180 度
        Servo_SetAngle(180);
        // 循环从 0 度到 180 度,每次增加 20 度
        for(int i=0;i<=180;i+=20){
            Delay_ms(1000);
            // 设置舵机角度为当前循环变量的值
            Servo_SetAngle(i);
        }
    }
}

实现效果 舵机步距20°,频率一秒

PWM TB6612驱动直流电机

硬件电路部分

主函数

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"
#include "Motor.h"

int main(void){
    OLED_Init();
    // 在 OLED 屏幕第一行第一列显示字符串"Speed:"
    OLED_ShowString (1,1,"Speed:");
    Motor_Init();
    // 设置电机初始速度为 100
    Motor_SetSpeed(100);

    while(1){
        // 循环调整电机速度,从 40 逐步增加到 160,再从 -160 逐步增加到 40
        for(int i=40;i<=160;i+=40){
            if(i==160){
                // 当速度达到 160 时,将速度重置为 -160,实现循环往复
                i = -160;
            }
            // 在 OLED 屏幕第一行第七列显示当前电机速度值,占 5 个字符宽度
            OLED_ShowSignedNum(1,7,i,5);
            // 设置电机速度为当前循环变量的值
            Motor_SetSpeed(i);
            // 延迟 5 秒
            Delay_ms(5000);
        }
    }
}

驱动部分

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

// 函数功能:初始化电机控制相关的硬件
void Motor_Init(void)
{		
    // 使能 GPIOA 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置 GPIO 结构体
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;  // 选择 GPIOA 的引脚 4 和引脚 5
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // 设置 GPIO 速度为 50MHz
    
    // 初始化 GPIOA 的指定引脚
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 初始化 PWM 模块
    PWM_Init();
}

// 函数功能:设置电机的速度
// 参数 Speed:速度值,可以是正数、负数或零
void Motor_SetSpeed(int8_t Speed)
{
    if (Speed >= 0)
    {
        // 如果速度为正数,设置 GPIOA 的引脚 4 为高电平,引脚 5 为低电平
        GPIO_SetBits(GPIOA, GPIO_Pin_4);
        GPIO_ResetBits(GPIOA, GPIO_Pin_5);
        // 根据正数速度值设置 PWM 的比较值
        PWM_SetCompare3(Speed);
    }
    else 
    {
        // 如果速度为负数,设置 GPIOA 的引脚 5 为高电平,引脚 4 为低电平
        GPIO_SetBits(GPIOA, GPIO_Pin_5);
        GPIO_ResetBits(GPIOA, GPIO_Pin_4);
        // 根据负数速度值的绝对值设置 PWM 的比较值
        PWM_SetCompare3(-Speed);
    }
}

实现效果 直流电机速度变化,呈现在OLED屏幕上

### ESP32 PWM 占空比计算 对于ESP32而言,PWM占空比可以通过下面的公式进行计算: \[ \text{占空比} (\%) = \left( \frac{\text{高电平时间}}{\text{周期时间}} \right) \times 100\% \] 当涉及到具体数值设定时,在编程环境中设置PWM通道的占空比如下所示[^1]。假设目标是使舵机转动到特定角度,则需要先了解该角度对应的精确脉冲宽度(即高电平持续时间),再基于此转换成适合写入程序中的占空比值。 在Arduino环境下操作ESP32时,通常会利用`ledcWrite()`函数来配置PWM输出。这里需要注意的是,实际编写代码时所使用的占空比是以最大分辨率下的比例形式给出的而不是百分比。例如,默认情况下分辨率为8位(范围是从0至255)。因此,如果要表示50%的占空比,则应传递给`ledcWrite()`函数的参数为127.5(取整后为128)。 ```cpp // 设置PWM频率和初始占空比 const int frequency = 50; // 对于大多数舵机来说,这是标准频率 int channel = 0; int resolution = 8; void setup() { ledcSetup(channel, frequency, resolution); } void loop() { float angle = 90; // 假设想要达到的角度 int dutyCycle = map(angle, 0, 180, 20, 120); // 将角度映射到对应脉宽 // 将得到的结果除以最小单位时间并乘上满量程值得到最终占空比 int pwmValue = (dutyCycle / 20000.0f * pow(2, resolution)); ledcWrite(channel, pwmValue); } ``` 上述代码片段展示了如何根据所需角度计算相应的PWM占空比,并将其应用于ESP32上的指定PWM通道中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值