江科大PWM笔记:呼吸灯、舵机控制、电机调速
*psc预分频器,决定计数脉冲的频率
arr自动重装载寄存器,决定了多久是一个周期
ccr捕获/比较寄存器,决定占空比
cnt计数器寄存器,不能写,只能读
1
在理解呼吸灯原理之前,先了解一些基本公式:
1. 频率公式 PWM频率 = 定时器时钟频率 / (PSC + 1) / (ARR + 1)计数频率 = 定时器输入时钟 / (PSC + 1) CCR 乘以计数单位 = 高电平持续的时间(脉宽)。 定时器时钟频率和定时器输入时钟都是一个东西,表示72MHz2. 占空比公式 占空比 = CCR / (ARR + 1) × 100% 3. 分辨率公式 分辨率(步数)= ARR + 1
注意1000Hz就是1s1000次变化,也就是1ms一个周期,1s等于1000ms
设置ARR,PSC,CCR一些基本的:
#include "stm32f10x.h" // Device header void PWM_Init(void) { 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_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); 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); TIM_Cmd(TIM2, ENABLE); } void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2, Compare); }主函数:
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "PWM.h" uint8_t i; int main(void) { OLED_Init(); 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); } } }实现效果:
实现逻辑:
执行PWM_Init函数,使psc,arr,ccr初始化,执行PWM_SetCompare1修改ccr的值,i越高占比就越高,也就是慢慢变亮,后面i减小,也就是慢慢变暗。
2
在了解为什么可以驱动舵机之前先要知道:
所以舵机都遵循:
0.5ms 高电平 → 转到 0°
1.5ms 高电平 → 转到 90°
2.5ms 高电平 → 转到 180°
标准舵机要求控制信号的频率固定为 50Hz
设置基本psc,arr,ccr:
#include "stm32f10x.h" // Device header void PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 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); 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 = 20000 - 1; //ARR TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 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_OC2Init(TIM2, &TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); } void PWM_SetCompare2(uint16_t Compare) { TIM_SetCompare2(TIM2, Compare); }设置映射关系:
#include "stm32f10x.h" // Device header #include "PWM.h" void Servo_Init(void) { PWM_Init(); } void Servo_SetAngle(float Angle) { PWM_SetCompare2(Angle / 180 * 2000 + 500); }SetCompare是得到CCR的值
angle等于0时,ccr等于500,arr固定为2000,
计数单位为72MHz/72=1MHz/s,即1微秒就是一个计数周期
500乘计数单位为高电平持续时间,也就是500微秒,0.5ms
angle等于180时,ccr等于2000,arr固定为2000,
计数单位为72MHz/72=1MHz/s,即1微秒就是一个计数周期
2500乘计数单位为高电平持续时间,也就是2500微秒,2.5ms
主函数逻辑:
#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(); Servo_Init(); Key_Init(); OLED_ShowString(1, 1, "Angle:"); while (1) { KeyNum = Key_GetNum(); if (KeyNum == 1) { Angle += 30; if (Angle > 180) { Angle = 0; } } Servo_SetAngle(Angle); OLED_ShowNum(1, 7, Angle, 3); } }当按下按钮时,满足第一个if条件,执行Servo_SetAngle函数,舵机旋转30度,当大于180度时,回到0度。
实现效果:
注意舵机不能无限转,范围一般是0-180度。
3
在了解驱动电机之前需要知道:
1.人听到的永远是PWM的频率,通过调整psc和arr使频率在20khz以上,人就听不到了。
2.oc控制的是引脚输出,具体对应关系如下:
| 通道 | 对应的 CCR 寄存器 | 对应的 GPIO 引脚(默认) |
|---|---|---|
| 通道1(OC1) | CCR1 | PA0 |
| 通道2(OC2) | CCR2 | PA1 |
| 通道3(OC3) | CCR3 | PA2 |
| 通道4(OC4) | CCR4 | PA3 |
PWN文件:
#include "stm32f10x.h" // Device header void PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; 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 = 36 - 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_OC3Init(TIM2, &TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); } void PWM_SetCompare3(uint16_t Compare) { TIM_SetCompare3(TIM2, Compare); }通过修改PSC使PWN频率在20kHz以上
硬件模块motor文件:
#include "stm32f10x.h" // Device header #include "PWM.h" void Motor_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); PWM_Init(); } void Motor_SetSpeed(int8_t Speed) { if (Speed >= 0) { GPIO_SetBits(GPIOA, GPIO_Pin_4); GPIO_ResetBits(GPIOA, GPIO_Pin_5); PWM_SetCompare3(Speed); } else { GPIO_ResetBits(GPIOA, GPIO_Pin_4); GPIO_SetBits(GPIOA, GPIO_Pin_5); PWM_SetCompare3(-Speed); } }pp表示推挽输出,主动让cpu控制高低电平
GPIO_Speed_50MHz表示最高承受上限是50MHz
之所以设置成一个高电平,另一个低电平就是因为电流方向驱动电机转起来了。
主函数部分:
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Motor.h" #include "Key.h" uint8_t KeyNum; int8_t Speed; int main(void) { OLED_Init(); Motor_Init(); Key_Init(); OLED_ShowString(1, 1, "Speed:"); while (1) { KeyNum = Key_GetNum(); if (KeyNum == 1) { Speed += 20; if (Speed > 100) { Speed = -100; } } Motor_SetSpeed(Speed); OLED_ShowSignedNum(1, 7, Speed, 3); } }实现逻辑:
当按下按钮,speed加20(全局变量未初始化,自动赋值为0),执行函数Motor_SetSpeed,并将相应的引脚置高或低电平,并将speed上传至ccr,最后显示speed。
如果speed超过100,启动反转,再按下就再加20,并一样执行函数Motor_SetSpeed,并将相应的引脚置高或低电平,并将speed上传至ccr,最后显示speed。
一些小知识:
占空比越高,转的就越快。这是因为平均电压高。当停的时候,由于机械惯性会继续转,所以取平均电压,占空比越高,平均电压越高,电流越大,速度就越快
而呼吸灯是因为人的视觉停留,所以占空比高,才看出的慢慢变亮,再慢慢变暗。
实现效果:
