一、呼吸灯
关于如何输出PWM波形,我们需要先知道,PWM波其实就是一种占空比可调的波形,通过STM32的输出比较功能来控制一个周期内高低电平的占比从而达到输出PWM波形。
我们需要先开启基本的时基单元,然后通过调用TIM_OC1Init来调出输出比较的配置代码,这里在引出的时候只需配置如下的部分就行,未配置的部分是高级定时器才需要用到的。输出模式配置为PWM模式1,输出极性为高,输出使能,其余看注释了解一下。值得一提的是。因为没有配置高级定时器代码部分,所以没有给赋初值,而为了避免程序出现问题,在上面需要使用TIM_OCStructInit(&TIM_OC1InitStruture);来进行赋初始值的功能以保证程序不会因为赋初值而运行不了或无法达到预期结果。最后别忘了TIM_Cmd使能。而最后的PWM_SetCompare1()函数里是用来写入CCR1的值的,通过主函数中循环改变CCR1的值来改变占空比。
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
通过公式就可以看出来调节占空比只需要调节CCR或者ARR值就可以实现,这里调节CCR来实现。
然后配置GPIO口来输出PWM波形,注意,这里的输出IO口只能是PA0,需要使用其他的IO口则需要进行引脚重定义。
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO用于PA15引脚重映射
//GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//从PA0挪到PA15
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//TIM2是APB1外设总线的
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);//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;//RCC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//重装寄存器值
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_OCInitTypeDef TIM_OC1InitStruture;
TIM_OCStructInit(&TIM_OC1InitStruture);//赋初始值
TIM_OC1InitStruture.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OC1InitStruture.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OC1InitStruture.TIM_OutputState=TIM_OutputState_Enable;
TIM_OC1InitStruture.TIM_Pulse=0;//CCR 调节灯亮度通过占空比,在主函数调节
TIM_OC1Init(TIM2,&TIM_OC1InitStruture);//初始化
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
主函数部分通过for循环使得CCR1的值发生改变从而达到驱动呼吸灯的目的。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "PWM.h"
uint8_t i;
int main (void)
{
OLED_Init();
PWM_Init();
while(1)
{
for(i=0;i<=100;i++)//for循环改变CCR的值从而改变占空比
{
PWM_SetCompare1(i);
Delay_ms(10);
}
for(i=0;i<=100;i++)
{
PWM_SetCompare1(100-i);
Delay_ms(10);
}
}
}
二、直流电机驱动(江科协代码全过程)
key.c
这里的key.h配置用来控制电机转速的,写一个按键按下输入低电平有效返回1。
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
PWM.c
与上面的相差无几,注意的是TIM_Pulse 给0 ,后面也调用TIM_SetCompare3进行CCR的变化。
#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_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
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 = 36 - 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_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR3的值
}
Motor.c
配置一个设置电机转向翻转的GPIO输出用两个IO口控制,一个输出低电平一个输出高电平接入TB6612芯片的转向控制输出。同时用TIM_SetCompare3来进行调速,需要注意的是翻转时的speed参数要给负值,在主函数中同时给负,保证为实际仍然为整数。
#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只能给正数
}
}
main.c
主函数则用if判断输入然后每按下一次按键速度+20,大于100进入else,PA4和PA5互换输出电平,实现转向翻转。
#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显示速度变量
}
}
直流电机驱动芯片引脚图示意,VM给5V,STNDBY给3.3v相当于使能,Motor A接电机两引脚,INPUT部分PWMA接PWM输出引脚,另外两个接电平翻转输出引脚。