一、PWM驱动LED呼吸灯
接线图:

PWM的初始化:

具体步骤:
①RCC开启时钟(把要用的TIM外设和GPIO外设时钟都打开)
② 配置时基单元,包括前面的时钟源选择
③配置输出比较单元,里面包括CCR的值,输出比较模式,极性选择,输出使能这些参数
④配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出模式
⑤ 运行控制,启动计数器
使用到的函数:
TIM函数之前有部分介绍过了,具体参考这篇文章 [江科大STM32]TIM定时器中断(学习笔记)下-CSDN博客
重要函数需要掌握:


不常用了解即可:

函数解释:
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);
使用方法:需要初始化哪个通道,就调用哪个函数,不同的通道对应的GPIO口也不一样,所以要按照所需的GPIO口来。
作用:根据指定初始化TIMx Channel1参数在TIM_OCInitStruct。
|------------------|------------------------------|
| 参数 | 说明 |
| TIMx | 其中x除6、7外可选1 ~ 17,用于选择TIM外设。 |
| TIM_OCInitStruct | 指向TIM_OCInitTypeDef结构体的指针 |
比较Init结构定义(只列出使用到的)
|-----------------|------------------------------------------------------|
| 参数 | 说明 |
| TIM_OCMode | 指定TIM模式 |
| TIM_OutputState | 指定TIM输出比较状态 |
| TIM_Pulse | 指定要加载到捕获比较寄存器中的脉冲值(CCR的值),取值范围为0x0000 ~ 0xFFFF之间的数字 |
| TIM_OCPolarity | 指定输出极性 |
具体参数:



TIM_Pulse的值要和时基单元里面的ARR和PSC的值一起配置,这三个值共同决定了输出PWM的周期和占空比,计算公式:

比如,这里如果要求输出一个频率为1KHz,占空比可任意调节,分辨率为1%的PWM波形,那ARR+1=100,CCR=50,PSC+1=720.这样就是输出频率为1KHz,占空比为50%
注意 :

在这里结构体并没有配置完整,对于结构体变量来说,还是一个局部变量,不给所以结构体成员赋值会出现一些问题,比如你想把高级定时器当作通用定时器输出PWM时,自然会把TIM2改TIM1,那由于结构体成员没有完全赋值,就会出现一些问题。**而且这里能不能输出PWM还会跟初始化函数在哪一行有关。**这里为避免出现问题,要么把结构体成员配置完,要么就给结构体赋初始值。这里我们使用函数给结构体赋初始值,使用到下面的函数
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);//用默认值填充每个TIM_OCInitStruct成员。
|------------------|-------------------------------|
| 参数 | 说明 |
| TIM_OCInitStruct | 指向TIM_OCInitTypeDef结构体的指针被初始化 |
如果不想把结构体所有成员都列出来,那就用这个函数先给结构体成员赋一个初始值,再更改你想更改的结构体成员的值。

配置GPIO口
TIM2的OC1通道使用的是哪个GPIO口,这里得参考一下引脚定义表

cs
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;//开漏推挽输出
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
推挽输出:
选择开漏推挽输出的原因:因为这里是用定时器来控制引脚,就需要用开漏推挽输出。只有设置成开漏推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出。

使用示波器采集PWM波形:示波器采集PWM波形
初始化代码:
cs
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode =TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period =100 - 1;//ARR
TIM_TimeBaseInitStruct.TIM_Prescaler =720 - 1;//PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter =0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
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 =50;//CCR的值
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
TIM_Cmd(TIM2,ENABLE);//启动定时器
}
LED呼吸灯
产生LED呼吸灯效果就是要不断更改CCR的值就可以了。在运行过程更改CCR,就需要用到下面函数
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);//单独更改CCR通道1的值
|----------|-----------------------------|
| 参数 | 说明 |
| TIMx | 其中x除6、7外可选1 ~ 17,用于选择TIM外设 |
| Compare1 | 表示捕获Compare1寄存器的新值 |
注意:这里单独设置了CCR的值,之前初始化那里CCR的值就要改为0了
封装使用:
cs
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}
主函数代码:
只需要在主函数while()循环里面不断调用 PWM_SetCompare1这个函数,更改CCR的值,就可以实现LED呼吸灯效果
cs
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i; //定义for循环的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_Init(); //PWM初始化
while (1)
{
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(10); //延时10ms
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(10); //延时10ms
}
}
}
引脚重映射
看图可知道TIM2通道1可以复用到PA15上

①需要用到AFIO,先开启AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
复用功能重映射函数:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState); //更改指定引脚的映射
|------------|-------------------|
| 参数 | 说明 |
| GPIO_Remap | 选择要重新映射的引脚 |
| NewState | ENABLE or DISABLE |
选择要重新映射的引脚:


**注意:**PA15上电后恢复默认为调试端口JTDI,所以想要它变为普通的GPIO或者定时器的复用通道,需要先关闭调试端口的复用,还是用上面这个函数,只是参数改变函数讲解

总结:35:00
二、PWM驱动舵机
接线图:
有关舵机简介和具体接线可参考【江科大STM32】TIM输出比较(学习笔记)-CSDN博客
PWM初始化:
初始化函数跟上面LED呼吸灯一样,只是我们这里用了TIM2的CH2,对应的GPIO口换为PA1就好了
这里算一下ARR和PSC和CCR的值:舵机要求周期为20ms,那频率就是1s/20ms=50Hz(20ms=0.02s),占空比是0.5ms~2.5ms,这里PSC和ARR的值不是固定的,多尝试几次找到合适的值就好。这里占空比是20k对应20ms,比例为20000对应20,500k对应0.5ms就是对应的比例计算出来的(占空比20000和5000只是用来和PWM周期做对比的,比如500/20000就是对应周期0.5/20,2500/20000对应2.5/20),


代码:
cs
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode =TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period =20000 - 1;//ARR
TIM_TimeBaseInitStruct.TIM_Prescaler =72 - 1;//PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter =0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
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 TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);//单独更改CCR通道2的值
|----------|-----------------------------|
| 参数 | 说明 |
| TIMx | 其中x除6、7外可选1 ~ 17,用于选择TIM外设 |
| Compare2 | 指定Capture Compare2注册新值 |
cs
void PWM_SetCompare2(uint16_t compare)
{
TIM_SetCompare2(TIM2,compare);
}
使用多通道讲解:40:11
舵机代码:
PWM_SetCompare2(Angle /180 *2000 +500);//设置占空比来源:
这里角度0度对应CCR500, 180度对应CCR2500参数计算
cs
#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);//设置占空比
//将角度线性变换,对应到舵机要求的占空比范围上
}
主函数:
cs
#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显示角度变量
}
}
每按一下按键占空比变化为30%,因为这里设置的角度是每按一下加30

三、PWM驱动直流电机
接线图:电机驱动模块注意不能接反了,不然容易烧坏驱动
PWM初始化函数:
初始化函数跟上面LED呼吸灯一样,只是我们这里用了TIM2的CH3,对应的GPIO口换为PA2就好了。同时,PWM设置CCR的值也要改为PWM_SetCompare3。电机如果发出有蜂鸣器的声音,可将PWM初始化代码中时基单元部分里的预分频器值改小,方法
直流电机初始化
这里初始化还要初始化电机方向控制脚,这里用的是PA4和PA5引脚
cs
#include "stm32f10x.h" // Device header
#include "PWM.h"
/**
* 函 数:直流电机初始化
* 参 数:无
* 返 回 值:无
*/
void Motor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
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); //将PA4和PA5引脚初始化为推挽输出
PWM_Init(); //初始化直流电机的底层PWM
}
/**
* 函 数:直流电机设置速度
* 参 数:Speed 要设置的速度,范围:-100~100
* 返 回 值:无
*/
void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0) //如果设置正转的速度值
{
GPIO_SetBits(GPIOA, GPIO_Pin_4); //PA4置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //PA5置低电平,设置方向为正转
PWM_SetCompare3(Speed); //PWM设置为速度值
}
else //否则,即设置反转的速度值
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //PA4置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_5); //PA5置高电平,设置方向为反转
PWM_SetCompare3(-Speed); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}
主函数:
按键每按下一次,电机的速度增加20
cs
#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(); //OLED初始化
Motor_Init(); //直流电机初始化
Key_Init(); //按键初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Speed:"); //1行1列显示字符串Speed:
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
Speed += 20; //速度变量自增20
if (Speed > 100) //速度变量超过100后
{
Speed = -100; //速度变量变为-100
//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位
//若出现了此现象,则应避免使用这样的操作
}
}
Motor_SetSpeed(Speed); //设置直流电机的速度为速度变量
OLED_ShowSignedNum(1, 7, Speed, 3); //OLED显示速度变量
}
}
以上代码学习均来自b站江科大。
活动发起人@小虚竹 想对你说:
这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!
提醒:在发布作品前,请将不需要的内容删除。