目录
一、输出比较简介
1、输出比较
- OC(Output Comapre)输出比较
- 输出比较可以通过比较CNT和CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率的占空比的PWM波形(CC是捕获/比较的意思,R是Register,寄存器的意思),这个捕获/比较寄存器是输入捕获和输出比较共用的,当使用输入捕获时,他就是捕获寄存器,当时用输出比较时,它就是比较寄存器。
- 每个高级定时器和通用定时器都有4个输出比较通道
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
- 在输出比较这里这块电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的一个值,当CNT大于CCR、小于CCR、等于CCR时,输出就会输出对应的置1或置0
2、PWM简介
- PWM(Pluse Width Modulation)脉冲宽度调制
- 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟量,常用于电机控速等领域,也就是说,使用这个PWM波形,是用来等效地实现一个模拟信号的输出,也就是以一个很快的频率,给电机通电、断电,也就使电机维持在一个中等速度
- PWM参数:频率=1/Ts 占空比=Ton/Ts 分辨率=占空比变化步距
3、定时器的结构
1、输出比较通道(通用)
在这个图里,左边就是CNT计数器和CCR1第一路的捕获/比较寄存器 ,他俩进行比较,当CNT>CCR1或者CNT=CCR1时就会给输出模式控制器传一个信号,然后输出模式控制器就会改变它输出OC1REF的高低电平(REF时reference的缩写,意思是参考信号),然后上面还有个ETRF输入,这是定时器的一个小功能一般不用了解,接着REF信号可以前往主模式控制器,你可以把这个REF映射到主模式的TRGO输出上去,但主要还是下面那一路(极性选择部分),给这个寄存器写0,信号会往上走,就是信号电平不翻转进来啥样出去啥样,写1的话,信号就会往下走,就是信号通过一个非门取反,那输出信号就是输入信号高低电平反转的信号,再往后就是输出使能电路就是选择要不要输出,最后就是OC1引脚,这个引脚就是CH1的通道的引脚
2、输出比较通道(高级)
- 右侧为外围电路,则OC1和OC1N为互补输出,分别控制上管和下管的导通和截止
- 理想情况下,上管导通的瞬间下管要在同一瞬间关闭,但是实际情况下可能会因为器件的不理想导致上管未完全关闭,下管就导通了,出现了短暂的上下管短暂导通的情况,引起器件发热,所以有了死区生成电路,它会在上管关闭的时候延迟一小段,再导通下管,下管导通的时候,延迟一小段,在导通上管
4、输出模式控制器模式
下图为输出模式控制器可以设置的模式
- 第一个模式是冻结,描述的实CNT=CCR时,REF保持原态,比如在输出PWM波,突然想暂停一会儿输出,就可以设置成这个模式,高低电平维持在暂停时刻
- 匹配时值置有效电平,匹配时值置无效电平,匹配时值电平翻转,就是CNT=CCR时,REF分别置高电平,低电平,电平翻转,在匹配时值电平翻转模式下,可以产生一个占空比为50%的方波
- 强制为无效电平和强制为有效电平两个模式和冻结模式相似,如果你想暂停波形输出,并且在暂停期间保持低电平或者高电平,那你就可以设置这两个模式
- PWM2就是PWM1的取反
5、PWM 产生原理
首先左上角这里,是时基单元和运行控制的部分,再左边是时钟源选择,这里省略了,配置好时基单元,这里的CNT就可以开始不断地自增运行。
输出比较单元电路最开始,是CCR捕获/比较寄存器,CCR是我们自己设置的,CNT不断自增运行同时他俩还在不断的比较,后面就是输出模式控制器,一PWM1模式为例,在右上角的图中,在CNT还未计数到CCR时,置高电平,大于CCR时,置低电平,当计数到ARR然后归零时,又置高电平,往复循环,且占空比随CCR改变,如果CCR设置的高,那么输出的占空比就大,CCR设置的低一些,占空比就小一些,最后经过极性选择和输出使能就可以输出了。
6、参数计算
PWM的频率等于计数器的更新频率
二、外部设备
1、舵机
型号SG90
执行逻辑:PWM信号输入到控制板,给控制板一个指定的目标角度,然后,电位器检测输出轴的当前角度,如果大于目标角度,电机会反转,如果小于目标角度,电机会正转最终使输出轴固定在指定角度
2、直流电机
三、代码部分
1、PWM驱动LED呼吸灯
初始化步骤
- RCC开启时钟,把我们要用的TIM外设和GPIO外设的时钟打开
- 配置时基单元,包括前面的时钟源选择
- 配置输出比较单元包括CCR的值、极性选择、输出使能
- 配置GPIO(复用推挽输出)
- 运行控制
下面时STM32T_TIM的一些小功能配置,用的也不多
//这个函数是给输出比较结构体赋一个默认值的
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_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
//用来配置快速使能
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
//这个功能在手册里,外部事件时清除REF信号那一节有介绍
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
比较重要的
//下面四个函数是用来配置输出比较模块的,OC就是Output Compare
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);
//仅高级定时器使用,在使用高级定时器输出PWM时,需要调用这个函数使能主输出,否则PWM将不能正常输出
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
//这些就是单独设置输出比较的极性的
//这里带个N就是高级定时器里的互补通道的配置,OC4没有互补通道,所以就没有OC4N的函数
//这里的函数可以设置极性,在结构体初始化的那个函数里也可以设置极性,这两个地方设置极性的作用是一样的只不过用结构体是一起初始化的,在这里是一个函数的单独修改。
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
//该函数是用来单独修改使能参数
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
//输出比较模式,是用来单独更改输出比较模式的函数
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
//这四个函数是用来单独更改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);
拓展:AFIO与引脚重映射功能
在STM32的引脚定义表中,定义了片上外设和GPIO口的连接关系。在这里,TIM2的CH1引脚复用在了PA0引脚上。部分外设和GPIO口的连接关系如下图所示:
如果要选择确定的外设输出,就必须在对应的GPIO口输出,这个对应关系是硬性确定的,不可更改。但是STM32依然给予了使用者一次“更改”的机会,这就是重定义,或者称为重映射。如果在默认复用功能中要使用的两个功能在同一个引脚冲突,就可以使用这个方法将其中一个外设的引脚重映射到另一个端口上去。配置重映射是用AFIO完成的。
由上图可知,TIM2的CH1可以从PA0挪动到PA15引脚上,下面是使用AFIO进行这一操作的操作流程:
- 使用RCC开启AFIO时钟
- 使用函数
GPIO_PinRemapConfig
函数进行引脚重映射配置,其中第一个参数可给的参数值选项非常多,它们代表重映射的方式,每个方式与重映射的对应关系可以参考手册(这里以TIM2复用功能重映射表为例):如果想把TIM2_CH1从PA0重映射到PA15,就可以使用部分重映射模式1,或者完全重映射模式。在参数列表中可以找到,部分重映射模式1,部分重映射模式2,完全重映射三种方式(如果都不使用,就是没有重映射):
3、如果原端口上电后存在默认复用功能,需要根据实际需要关闭原来的复用功能。(如果不是这一步省略)
在这里PA15上电后默认作为调试端口JTDI,故我们需要关闭PA15的调试功能。关闭调制功能同样需要用到GPIO_PinRemapConfig函数,它的参数中以下三个是用来解除调试端口的复用的:第一个参数GPIO_Remap_SWJ_NoJTRST中的SWJ指SWD和JTAG两种调试方式。NoJTRST意为接触JTRST引脚的复用,在引脚定义表中可以找到,PB4上电后默认复用为NJTRST,如果在GPIO_PinRemapConfig函数中使用该参数,那么PB4就可以作为正常的GPIO口使用,其余的四个仍然为调试端口,不能当作GPIO口使用。
第二个参数GPIO_Remap_SWJ_JTAGDisable,用来接触JTAG调试端口的复用,使用后PA15、PB3、PB4变回正常的GPIO,而PA13和PA14仍为SWD的调试端口。
第三个参数GPIO_Remap_SWJ_Disable,用来接触所有SWD和JTAG的调试端口的调试功能,使用后PA13、PA14、PA15、PB3、PB4全部变成普通的GPIO,STM32将失去调试功能(非常危险)。一旦调用这个参数并且下载程序之后,这之后使用STLINK将无法下载程序。这时就需要用串口下载一个新的没有接触调试端口的程序,才可以将调试端口重新使能。
调试端口的映像也可以在手册中找到:这里博主使用的芯片有可能存在bug,PA15引脚重映射功能无法实现。
PWM.C
#include "stm32f10x.h" // Device header
/**
* @brief PWM输出初始化函数
* @param 无
* @retval 无
*/
void PWM_Init(void)
{
// 1.配置时基单元
// 用RCC外设时钟控制打开定时器的基准时钟和外设GPIO的工作时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 引脚重映射配置,可以将TIM1_CH1的输出信号从PA0重映射到PA15
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
// 选择时基单元的时钟源(这里使用内部时钟)
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);
// 2.配置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);
// 3.初始化输出比较单元(通道)
// 这里使用PA0口,对应第一个输出比较通道
TIM_OCInitTypeDef TIM_OCInitStruct; // 在这个结构体中有部分是高级定时器才拥有的成员
TIM_OCStructInit(&TIM_OCInitStruct); // 如果不对结构体成员赋初始值,那么它的值将不确定,这样可能会导致一些奇怪的问题
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // OC输出模式 配置为PWM模式1
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // OC输出极性
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // OC输出使能
TIM_OCInitStruct.TIM_Pulse = 0; // Pulse的值会被加载到CCR寄存器中
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
// 4.配置运行控制,打开定时器
TIM_Cmd(TIM2, ENABLE);
}
/**
* @brief 更改比较/捕获寄存器的值CCR(当 ARR + 1 == 100 时 CCR 即为占空比)
* @param Compare 无符号16位整型数,注意:它只能是正数
* @retval 无
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i;
int main()
{
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);
}
}
}
2、PWM驱动舵机
PWM.C
#include "stm32f10x.h" // Device header
/**
* @brief PWM输出初始化函数
* @param 无
* @retval 无
*/
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 1.配置时基单元
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);
// 2.配置GPIO输出端PA1为复用推挽输出模式
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);
// 3.初始化输出比较单元(通道)
// 这里使用PA1口,对应第二个输出比较通道
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // OC输出模式 配置为PWM模式1
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // OC输出极性
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // OC输出使能
TIM_OCInitStruct.TIM_Pulse = 0; // CCR:500~2500
TIM_OC2Init(TIM2, &TIM_OCInitStruct);
// 4.配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
/**
* @brief 更改比较/捕获寄存器的值CCR
* @param Compare 无符号16位整型数,注意:它只能是正数,范围是500~2500
* @retval 无
*/
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
Servo.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
/**
* @brief 舵机初始化(模块化封装底层函数)
* @param 无
* @retval 无
*/
void Servo_Init(void)
{
PWM_Init();
}
/**
* @brief 设置舵机的旋转角度
* @param Angle,设置的角度,它的值可以是 0 ~ 180
* @retval 无
*/
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500); // Angle通过换算得到对应的CCR的值
}
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 按键初始化函数,初始化PB0
* @param 无
* @retval 无
*/
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 返回按下按键的值,若不按下按键默认返回0
* @param 无
* @retval KeyNum 按键对应的值
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) // 读取PB0端口的值
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0); // 如果不松手,程序将在此等待
Delay_ms(20);
KeyNum = 1;
}
return KeyNum;
}
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()
{
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);
}
}
3、PWM驱动直流电机
接线图和程序源码如下所示(由于博主使用的单片机芯片有bug,TIM2的CH3无法输出PWM波形,故电机驱动的PWM输入接单片机的PA3口,使用TIM2的第四个输出比较通道。博主在PB11口多加装了一个按键用来切换电机转向)
PWM.c
#include "stm32f10x.h" // Device header
/**
* @brief PWM输出初始化函数
* @param 无
* @retval 无
*/
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 1.配置时基单元
// 选择时基单元的时钟源(这里使用内部时钟)
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
// 提高PWM波的频率可以解决直流电机发出类似蜂鸣器的声音的问题
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; // ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; // PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 2.配置GPIO输出端为复用推挽输出模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3.初始化输出比较单元(通道)
// 这里使用PA3口,对应第四个输出比较通道
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // OC输出模式 配置为PWM模式1
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // OC输出极性
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // OC输出使能
TIM_OCInitStruct.TIM_Pulse = 0; // CCR
TIM_OC4Init(TIM2, &TIM_OCInitStruct);
// 4.配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
/**
* @brief 更改比较/捕获寄存器CCR的值
* @param Compare 无符号16位整型数,注意:它只能是正数
* @retval 无
*/
void PWM_SetCompare4(uint16_t Compare)
{
TIM_SetCompare4(TIM2, Compare);
}
Motor.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
/**
* @brief 直流电机初始化函数
* @param 无
* @retval 无
*/
void Motor_Init(void)
{
PWM_Init();
// 初始化电机方向控制脚PA4、PA5
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);
}
/**
* @brief 控制电机的转向和速度函数
* @param Speed 电机速度,可以为正也可以为负
* @retval 无
*/
void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0) // 正传
{
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare4(Speed);
}
else // 反转
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare4(- Speed); // Speed 此时为负数,但是SetCompare的参数必须为正,故在前添加负号
}
}
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 这里的速度是GPIO的输出速度,在输入模式下这个参数选择没有用处
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 返回按下按键的值,若不按下按键默认返回0
* @param 无
* @retval KeyNum 按键对应的值
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) // 读取1端口的值
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); // 如果不松手,程序将在此等待
Delay_ms(20);
KeyNum = 1;
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) // 读取11端口的值
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); // 如果不松手,程序将在此等待
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
main.c
#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()
{
OLED_Init();
Key_Init();
Motor_Init();
OLED_ShowString(1, 1, "Speed:");
while(1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
Speed += 20;
if (Speed > 100)
{
Speed = -100;
}
}
if (KeyNum == 2) // 按下第二个按键电机反向旋转
{
Speed = -Speed;
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(1, 7, Speed, 3);
}
}