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屏幕上