笔记

输出比较简介

  • OC(Output Compare) 输出比较
  • 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置0、置1或翻转的操作,用于输出一定频率和占空比的PWM波形
  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能

输出比较模式

image-20240813111307049

PWM简介

  • PWM(Pulse Width Modulation) 脉冲宽度调制
  • 具有惯性的系统中,可以通过一系列脉冲的宽度进行调制,来等效地获得所需要地模拟参量,常应用于电机控速等领域
  • PWM参数:

    • 频率 = 1 / Ts
    • 占空比 = Ton / Ts,等效值=高电平值*占空比
    • 分辨率 = 占空比的变化步距(比如1%,2%,3%,分辨率就是1%)

image-20240813110408532

image-20240813111402119

  • PWM频率:Freq = CK_PSC / (PSC+1) / (ARR+1)
  • PWM占空比:Duty = CCR / (ARR+1)
  • PWM分辨率:Reso = 1 / (ARR+1)

舵机简介

  • 舵机是一种根据输入PWM信号占空比来控制输出角度地装置
  • 输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms

image-20240813112527162

直流电机及驱动简介

  • 直流电机是一种将电能转换为机械能地装置,又两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
  • TB6612时一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

image-20240813113532857

image-20240813113554199

项目实现

PWM驱动LED呼吸灯

接线图

image-20240813114135998

  1. RCC开启时钟,打开TIM外设和GPIO外设的时钟
  2. 配置时基单元,包括时钟源的选择
  3. 配置输出比较单元,包括CCR的值、输出比较模式、极性选择和输出使能
  4. 配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出的配置
  5. 运行控制,启动计数器

四个输出比较单元的初始化函数:

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);

更改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);

编辑PWM.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);            //开启TIM2的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);            //开启GPIOA的时钟
    
    /*GPIO重映射*/
//    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);            //开启AFIO的时钟,重映射必须先开启AFIO的时钟
//    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);            //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);        //将JTAG引脚失能,作为普通GPIO引脚使用
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;        //GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                            //将PA0引脚初始化为复用推挽输出    
                                                                    //受外设控制的引脚,均需要配置为复用模式        
    
    /*配置时钟源*/
    TIM_InternalClockConfig(TIM2);        //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    
    /*时基单元初始化*/
    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_TimeBaseInit,配置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值
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
    
    /*TIM使能*/
    TIM_Cmd(TIM2, ENABLE);            //使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
    TIM_SetCompare1(TIM2, Compare);        //设置CCR1的值
}

编辑PWM.h

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);

#endif

编辑main.c

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

int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    PWM_Init();            //PWM初始化
    
    while (1)
    {
        for (int i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(i);            //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
            Delay_ms(10);                //延时10ms
        }
        for (int i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(100 - i);    //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
            Delay_ms(10);                //延时10ms
        }
    }
}

编译,下载。

此处使用了端口复用功能,可以避免端口的冲突。

PWM驱动舵机

接线图

image-20240813123855017

编辑PWM.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);            //开启TIM2的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);            //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                            //将PA1引脚初始化为复用推挽输出    
                                                                    //受外设控制的引脚,均需要配置为复用模式
    
    /*配置时钟源*/
    TIM_InternalClockConfig(TIM2);        //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    
    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;                //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    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;            //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置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值
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
    
    /*TIM使能*/
    TIM_Cmd(TIM2, ENABLE);            //使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare2(uint16_t Compare)
{
    TIM_SetCompare2(TIM2, Compare);        //设置CCR2的值
}

编辑PWM.h

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);

#endif

编辑Servo.c

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

/**
  * 函    数:舵机初始化
  * 参    数:无
  * 返 回 值:无
  */
void Servo_Init(void)
{
    PWM_Init();                                    //初始化舵机的底层PWM
}

/**
  * 函    数:舵机设置角度
  * 参    数:Angle 要设置的舵机角度,范围:0~180
  * 返 回 值:无
  */
void Servo_SetAngle(float Angle)
{
    PWM_SetCompare2(Angle / 180 * 2000 + 500);    //设置占空比
                                                //将角度线性变换,对应到舵机要求的占空比范围上
}

编辑Servo.h

#ifndef __SERVO_H
#define __SERVO_H

void Servo_Init(void);
void Servo_SetAngle(float Angle);

#endif

编辑main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum;            //定义用于接收键码的变量
float Angle;            //定义角度变量

int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    Servo_Init();        //舵机初始化
    Key_Init();            //按键初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Angle:");    //1行1列显示字符串Angle:
    
    while (1)
    {
        KeyNum = Key_GetNum();            //获取按键键码
        if (KeyNum == 1)                //按键1按下
        {
            Angle += 30;                //角度变量自增30
            if (Angle > 180)            //角度变量超过180后
            {
                Angle = 0;                //角度变量归零
            }
        }
        Servo_SetAngle(Angle);            //设置舵机的角度为角度变量
        OLED_ShowNum(1, 7, Angle, 3);    //OLED显示角度变量
    }
}

编译,下载

PWM驱动直流电机

接线图

image-20240813131341906

评论

This is just a placeholder img.