STM32-OC输出比较和PWM

news2024/9/21 16:41:40

本内容基于江协科技STM32视频内容,整理而得。

文章目录

  • 1. OC输出比较和PWM
    • 1.1 OC输出比较
    • 1.2 PWM(脉冲宽度调制)
    • 1.3 输出比较通道(高级)
    • 1.4 输出比较通道(通用)
    • 1.5 输出比较模式
    • 1.6 PWM基本结构
    • 1.7 参数计算
  • 2. 舵机和直流电机
    • 2.1 舵机简介
      • 2.1.2 硬件电路
    • 2.2 直流电机及驱动简介
    • 2.3 电机驱动硬件电路
  • 3. 输出比较库函数及代码
    • 3.1 输出比较库函数
    • 3.2 PWM驱动LED呼吸灯
      • 3.2.1 硬件连接
      • 3.2.2 代码流程
      • 3.2.3 代码
    • 3.3 PWM驱动舵机
      • 3.3.1 硬件连接
      • 3.3.2 代码流程
      • 3.3.3 代码
    • 3.4 PWM驱动直流电机
      • 3.4.1 硬件连接
      • 3.4.2 代码流程
      • 3.4.3 代码

1. OC输出比较和PWM

1.1 OC输出比较

  • 输出比较可以通过比较CNT与CCR(捕获/比较寄存器)寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能。
    image.png

1.2 PWM(脉冲宽度调制)

  • 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来有效地获得所需要的模拟参量,常应用于电机控速等领域。

  • PWM参数:
    频率 = 1 / TS
    占空比 = TON / TS
    分辨率 = 占空比变化步距
    image.png
    高低电平跳变的数字信号可以等效为中间这个虚线所表示的模拟量,当上面电平时间长一点,下面电平短一点的时候,等效的模拟量就偏向于上面;当下面电平时间长一点,上面电平时间短一点的时候,等效的模拟量就偏向于下面。

  • 频率
    TS代表一个高低电平变换周期的时间,周期的倒数就是频率,PWM频率越快,那它等效模拟的信号就越平稳,不过同时性能开销就越大,一般来说PWM的频率都在几K到几十KHz,这个频率就已经足够快了。

  • 占空比
    占空比决定了PWM等效出来的模拟电压的大小,占空比越大,等效的模拟电压就越趋近于高电平;占空比越小,等效的模拟电压就越趋近于低电平,等效关系一般来说是线性的。

  • 分辨率
    分辨率:比如有的占空比只能是1%、2%、3%等等这样以1%的步距跳变,那分辨率就是1%。如果可以是1.1%、1.2%、1.3%等等这样以0.1%的步距跳变,那分辨率就是0.1%。分辨率就是占空比变化的精细程度。这个分辨率需要多高,得看实际项目的需求,如果即要高频率,又要高分辨率,这就对硬件电路要求比较高。如果要求不高的话,1%的分辨率也就足够使用了。
    使用PWM波形,可以在数字系统等效输出模拟量,就能实现LED控制亮度、电机控速等功能。

1.3 输出比较通道(高级)

image.png
image.png

1.4 输出比较通道(通用)

image.png
image.png

  • ETRF输入:是定时器的一个小功能,一般不用。
  • CCR1:捕获/比较寄存器1;
  • CCER:捕获/比较使能寄存器;
  • CCMR1:捕获/比较模式寄存器1;
  • 左边是CNT计数器和CCR1(第一路捕获/比较寄存器),当CNT>CCR1或CNT=CCR1时,就会给输出模式控制器传一个信号,输出模式控制器就会改变它输出OC1 REF(ref是指参考信号)的高低电平。
  • REF信号可以往上前往主模式控制器,可以把REF映射到主模式的TRGO输出上;
  • REF信号往下走就到达极性选择,主要走下面一条路:给寄存器CCER的CC1P位写0,信号往上走,就是信号电平不翻转;寄存器写1,信号往下走,就是信号通过一个非门取反,则输出信号就是输入信号高低电平反转的信号。这就是极性选择:就是选择是不是要把高低电平反转一下。
  • 输出使能电路:选择要不要输出;
  • OC1引脚:是CH1通道的引脚。

1.5 输出比较模式

就是输出模式控制器里面执行的逻辑。模式控制器的输入是CNT和CCR的大小关系,输出是REF的高低电平,

模式 描述
冻结 CNT=CCR时,REF保持为原状态
匹配时置有效电平 CNT=CCR时,REF置有效电平
匹配时置无效电平 CNT=CCR时,REF置无效电平
匹配时电平翻转 CNT=CCR时,REF电平翻转
强制为无效电平 CNT与CCR无效,REF强制为无效电平
强制为有效电平 CNT与CCR无效,REF强制为有效电平
PWM模式1 向上计数: CNT < CCR时,REF置有效电平,CNT ≥ CCR时,REF置无效电平
向下计数:CNT > CCR时,REF置无效电平,CNT ≤ CCR时,REF置有效电平
PWM模式2 向上计数: CNT < CCR时,REF置无效电平,CNT ≥ CCR时,REF置有效电平
向下计数:CNT > CCR时,REF置有效电平,CNT ≤ CCR时,REF置无效电平
  • 冻结
    当正在输出PWM波,突然想暂停一会输出,可以设置为该模式。当切换为冻结模式后,输出就暂停了,并且高低电平也维持为暂停时刻的状态,保持不变。

  • 匹配时模式

    • 有效电平和无效电平是高级定时器里的说法,是和关断、刹车这些功能配合的。置有效电平就是置高电平,置无效电平就是置低电平。这三个模式都是当CNT和CCR值相等时,执行操作。这些模式就是可以用做波形输出。
    • 匹配时电平翻转:可以输出一个频率可调,占空比始终为50%的PWM波形。比如设置CCR为0,那CNT每次更新清0时,就会产生一次CNT=CCR的事件,这就会导致输出电平翻转一次,每更新两次,输出为一个周期。并且高电平和低电平的时间始终是相等的,也就是占空比始终为50%。当改变定时器更新频率时,输出波形的频率也会随之改变,输出波形的频率=更新频率/2,因为更新两次才为一个周期。
  • 强制模式
    如果想暂停波形输出,并且在暂停期间保持低电平或高电平,则可以设置为强制模式。

  • PWM模式1和PWM模式2
    PWM1和PWM2可以用于输出频率和占空比都可调的PWM波形。
    PWM模式2实际上就是PWM模式1输出的取反。

1.6 PWM基本结构

蓝色线:CNT;黄色线:ARR;红色线:CCR;绿色线:输出REF
不需要更新事件的中断申请。在配置好时基单元后,CNT就可以开始不断地自增运行了,CCR设置的高,则占空比大;设置的低,占空比小。

  • 输出比较单元的最开始是CCR捕获/比较寄存器。
  • 输出模式控制单元里是PWM模式1的执行逻辑。
  • REF是一个频率可调,占空比可调的PWM波形。再通过极性选择,输出使能,最终通向GPIO口。这样就能完成PWM波形的输出了。
    • 蓝色CNT从0开始自增,一直到黄色ARR,也就是99,之后清0继续自增,红色线CCR=30,绿色是输出REF,CNT < CCR置高电平,CNT > CCR置低电平。当CNT溢出清0后,CNT又小于CCR,所以置高电平。这样下去,REF电平就不断变化,并且它的占空比是受CCR值的调控的,如果CCR设置高一点,输出的占空比就变大;CCR设置低一些,输出的占空比就变小。

1.7 参数计算

image.png
image.png
PWM频率等于计数器的更新频率

2. 舵机和直流电机

2.1 舵机简介

  • 舵机是一种根据输入PWM信号占空比来控制输出角度的装置;
  • 输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms。

舵机内部是由直流电机驱动的,内部还有一个控制电路板,是一个电机的控制系统板。舵机内部执行逻辑:PWM信号输入到控制板,给控制板一个指定的目标角度,电位器检测输出轴的当前角度,若大于目标角度,电机就会反转,小于目标角度,电机正转,最终使输出轴固定在指定角度。
image.pngimage.png
这里的PWM波形(输入信号脉冲宽度)是当作一个通信协议来使用的。

2.1.2 硬件电路

image.pngimage.png

2.2 直流电机及驱动简介

  • 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转。
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作。
  • TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向。里面一路有四个开关管,所以可以控制正反转。

H桥电路由两个推挽电路组成,中间接电机:左边,上管导通,下管断开,左边输出就是接在VM的电机电源正极;下管导通,上管断开,就是接在PGND的电源负极。左上和右下导通,电流从左流向右边;右上和左下导通,电流从右流向左边。
H桥可以控制电流流过的方向,所以能控制电机正反转。
image.png

2.3 电机驱动硬件电路

image.png
PWMA引脚要接PWM信号输出端,其他两个引脚AIN2和AIN1可以任意接两个普通的GPIO口,这三个引脚给一个低功率的控制信号,驱动电路就会从VM汲取电流,来输出到电机,这样就能完成低功率的控制信号控制大功率设备的目的了,
image.png
当IN1和IN2都为高电平,两个输出没有电压差,电机是不会转的。当IN1和IN2都为低电平,两个输出直接关闭,电机也是不会转的。
IN1低电平,IN2高电平,电机处于反转状态,但转与不转取决于PWM,PWM给高电平,那输出就是一低一高,有电压差了,就可以转,定义为反转;如果PWM给低电平,那输出两个低电平,电机还是不转。如果PWM是一个不断翻转的信号,那电机就是快速地反转、停止、反转、停止,如果PWM频率足够快,那电机就可以连续稳定地反转了,并且速度取决于PWM信号的占空比。这里的PWM就是使用PWM来等效一个模拟量的功能。

3. 输出比较库函数及代码

3.1 输出比较库函数

// stm32f10x_tim.h
// 用结构体来初始化输出比较单元的。配置输出比较,参数1(TIMx):选择定时器,参数2:输出比较的参数
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_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);

// 更改输出极性,带N的就是高级定时器里互补通道的配置,
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);

// 用来单独修改输出使能参数
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);


引脚重映射:TIM2_CH1能从PA0映射到PA15,实现这一功能需要使用AFIO,则就要开启AFIO时钟。

// 引脚重映射配置
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

3.2 PWM驱动LED呼吸灯

3.2.1 硬件连接

LED的正极连接在PA0引脚。

  • 实现功能:通过更改CCR的值来更改占空比。(占空比决定了PWM等效出来的模拟电压的大小,占空比越大,等效的模拟电压就越趋近于高电平;占空比越小,等效的模拟电压就越趋近于低电平,等效关系一般来说是线性的。)因此通过调节占空比的大小可以控制PA0引脚的电平,控制LED的亮度。

image.png

3.2.2 代码流程

  1. PWM初始化
    1. RCC开启时钟,TIM外设和GPIO外设时钟打开;
    2. 配置时基单元,包括前面的时钟源选择;
    3. 配置输出比较单元,里面包括CCR的值、输出比较模式、极性选择、输出使能;
    4. 配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置(使用TIM2定时器–输出比较通道,所以GPIO配置为复用推挽输出)
    5. 运行控制,启动计数器,这样就能输出PWM了。
  2. ARR和PSC设置
    1. PSC = 720 - 1;ARR = 100 - 1。因此输出的PWM波形频率为1KHz。
    2. 占空比Duty = CCR / (ARR + 1) = CCR / 100
  3. main函数
    1. 通过更改CCR的值(调用TIM_SetCompare1函数)来调节占空比的值。

3.2.3 代码

  • PWM.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟  -- TIM2_CH1_ETR在PA0引脚*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用
	
	/*GPIO初始化*/
	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);							//将PA0引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式		
	
	/*配置时钟源*/
	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 = 720 - 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_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}
  • main.c
#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
		}
	}
}

3.3 PWM驱动舵机

3.3.1 硬件连接

实现功能:通过设置占空比来控制舵机的角度,并在OLED上显示舵机角度。
image.png

3.3.2 代码流程

  1. 开启时钟(TIM2和GPIOA)
  2. 配置GPIO
  3. 配置时钟(时钟源、时基单元(ARR=20000,PSC=72)、输出比较单元、TIM2使能)
  4. 角度值转换为占空比:
    周期:20ms,高电平:0.5ms ~2.5ms。
高电平角度占空比
0.5ms-90°500/20000
1ms-45°1000/20000
1.5ms1500/20000
2ms45°2000/20000
2.5ms45°2500/20000

转换为:Angle / 180 * 2000 + 500

角度CCR
0500
1802500
  1. main函数
    1. 按键1按下,角度每次自增30,当角度超过180度后,角度值清零
    2. OLED显示角度

3.3.3 代码

  • PWM.c
#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_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	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 = 20000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 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_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);		//设置CCR2的值
}
  • Servo.c
#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);	//设置占空比
												//将角度线性变换,对应到舵机要求的占空比范围上
}

  • main.c
#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显示角度变量
	}
}

3.4 PWM驱动直流电机

3.4.1 硬件连接

实现功能:通过占空比控制直流电机的速度
电机驱动模块的PWMA接在PA2引脚(TIM2_CH3),AIN1接在PA4引脚,AIN2接在PA5引脚。
image.png

3.4.2 代码流程

  1. 开启时钟(TIM2和GPIOA)

  2. 配置GPIO

  3. 配置时钟(时钟源、时基单元、输出比较单元、TIM2使能)

  4. 配置电机:
    a. 设置速度(范围:-100~100):
    电机正转,速度值大于0,PA4为高电平,PA5为低电平;
    电机反转,速度值小于0,PA4为低电平,PA5为高电平。

  5. main函数

    1. 按键按下,速度值自增20
    2. OLED显示速度

3.4.3 代码

  • PWM.c
#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
#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
#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显示速度变量
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1904259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库逆向工程工具reverse_sql

reverse_sql 是一个用于解析和转换 MySQL 二进制日志&#xff08;binlog&#xff09;的工具。它可以将二进制日志文件中记录的数据库更改操作&#xff08;如插入、更新、删除&#xff09;转换为反向的 SQL 语句&#xff0c;以便对系统或人为产生的误操作进行数据回滚和恢复。 *…

LabVIEW的Actor Framework (AF) 结构介绍

LabVIEW的Actor Framework (AF) 是一种高级架构&#xff0c;用于开发并发、可扩展和模块化的应用程序。通过面向对象编程&#xff08;OOP&#xff09;和消息传递机制&#xff0c;AF结构实现了高效的任务管理和数据处理。其主要特点包括并发执行、动态可扩展性和强大的错误处理能…

图神经网络实战(16)——经典图生成算法

图神经网络实战&#xff08;16&#xff09;——经典图生成算法 0. 前言1. 图生成技术2. Erdős–Rnyi模型3. 小世界模型小结系列链接 0. 前言 图生成算法是指用于创建模拟图或网络结构的算法&#xff0c;这些算法可以根据特定的规则和概率分布生成具有特定属性的图&#xff0c…

华为OSPF配置DR和BDR与指定DR

基础配置 <Huawei>sys #进入配置模式 Enter system view, return user view with CtrlZ. [Huawei]un in en #关闭报文弹窗 Info: Information center is disabled. [Huawei]sys R1 #设备名更改为R1 [R1]int g0/0/0 …

Vue3中的Composables组合式函数,Vue3实现minxins

Vue3中的Composables是什么 Vue3中的Composables 简单理解其实就是类React Hooks式的组合式函数封装方法。 Vue官方称为Composables 组合式函数。 1.抽离复用逻辑时 Vue2写法 &#xff08;1&#xff09;Vue2 中的mixins混入器写法缺点 (Vue3 optionsApi写法同理) 新建minxins…

硅纪元视角 | 中国电信“星辰大模型·软件工厂”,两分钟完成应用开发,效率飞跃!

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…

动手学深度学习(Pytorch版)代码实践 -循环神经网络- 56门控循环单元(`GRU`)

56门控循环单元&#xff08;GRU&#xff09; 我们讨论了如何在循环神经网络中计算梯度&#xff0c; 以及矩阵连续乘积可以导致梯度消失或梯度爆炸的问题。 下面我们简单思考一下这种梯度异常在实践中的意义&#xff1a; 我们可能会遇到这样的情况&#xff1a;早期观测值对预测…

Windows电脑下载、安装VS Code的方法

本文介绍Visual Studio Code&#xff08;VS Code&#xff09;软件在Windows操作系统电脑中的下载、安装、运行方法。 Visual Studio Code&#xff08;简称VS Code&#xff09;是一款由微软开发的免费、开源的源代码编辑器&#xff0c;支持跨平台使用&#xff0c;可在Windows、m…

C++ 模版进阶

目录 前言 1. 非类型模版参数 1.1 概念与讲解 1.2 array容器 2. 模版的特化 2.1 概念 2.2 函数模版特化 2.3 类模版特化 2.3.1 全特化 2.3.2 偏特化 3.模版的编译分离 3.1 什么是分离编译 3.2 模版的分离编译 3.3 解决方法 4. 模版总结 总结 前言 本篇文章主要…

在mac下 Vue2和Vue3并存 全局Vue2环境创建Vue3新项目(Vue cli2和Vue cli4)

全局安装vue2 npm install vue-cli -g自行在任意位置创建一个文件夹vue3&#xff0c;局部安装vue3,注意不要带-g npm install vue/cli安装完成后&#xff0c;进入目录&#xff0c;修改vue为vue3 找到vue3/node-moudles/.bin/vue&#xff0c;把vue改成vue3。 对环境变量进行配置…

【6】图像分类部署

【6】图像分类部署 文章目录 前言一、将pytorch模型转为ONNX二、本地终端部署2.1. ONNX Runtime部署2.2. pytorch模型部署&#xff08;补充&#xff09; 三、使用flask的web网页部署四、微信小程序部署五、使用pyqt界面化部署总结 前言 包括将训练好的模型部署在本地终端、web…

在Linux操作系统中去修复/etc/fstab文件引起的系统故障。

如果/etcfstab文件中发生错误&#xff0c;有可能导致系统无法正常启动。 比如&#xff1a;系统里的一块磁盘被删除&#xff0c;但是/etc/fstab中关于这块磁盘的信息依然被保存在文件/etc/fstab中。 主要看倒数后两行&#xff0c;系统提示&#xff0c;敲ctrlD或者是直接输入密码…

LeetCode 744, 49, 207

目录 744. 寻找比目标字母大的最小字母题目链接标签思路代码 49. 字母异位词分组题目链接标签思路代码 207. 课程表题目链接标签思路代码 744. 寻找比目标字母大的最小字母 题目链接 744. 寻找比目标字母大的最小字母 标签 数组 二分查找 思路 本题比 基础二分查找 难的一…

redhat7.x 升级openssh至openssh-9.8p1

1.环境准备&#xff1a; OS系统&#xff1a;redhat 7.4 2.备份配置文件&#xff1a; cp -rf /etc/ssh /etc/ssh.bak cp -rf /usr/bin/openssl /usr/bin/openssl.bak cp -rf /etc/pam.d /etc/pam.d.bak cp -rf /usr/lib/systemd/system /usr/lib/systemd/system.bak 3.安装…

阿里云存储应用

如何做好权限控制 小浩在梳理门户网站静态资源时&#xff0c;发现有些资源是仅内部员工可访问&#xff0c;有些资源是特定的注册客户可访问&#xff0c;还有些资源是匿名客户也可以访问。针对不同场景、不同用户&#xff0c;小浩该如何规划企业门户网站静态资源的权限控制呢&a…

解析商场智能导视系统背后的科技:AR导航与大数据如何助力商业运营

在布局复杂的大型商场中&#xff0c;顾客常常面临寻找特定店铺的挑战。商场的规模庞大&#xff0c;店铺众多&#xff0c;使得顾客在享受购物乐趣的同时&#xff0c;也不得不面对寻路的难题。维小帮商场智能导航导视系统的电子地图、AR导航营销能为顾客提供更加便捷的购物体验。…

Linux—网络设置

目录 一、ifconfig——查看网络配置 1、查看网络接口信息 1.1、查看所有网络接口 1.2、查看具体的网络接口 2、修改网络配置 3、添加网络接口 4、禁用/激活网卡 二、hostname——查看主机名称 1、查看主机名称 2、临时修改主机名称 3、永久修改主机名称 4、查看本…

2023年了,还在手动px转rem吗?

px-to-rem 使用amfe-flexible和postcss-pxtorem在webpack中配置px转rem npm i amfe-flexible -Snpm i postcss-pxtorem -D在main.js中 import flexible from amfe-flexible Vue.use(flexible);index.html中 <meta name"viewport" content"widthdevice-w…

用 Echarts 画折线图

https://andi.cn/page/621503.html

Floyd判圈算法——环形链表(C++)

Floyd判圈算法(Floyd Cycle Detection Algorithm)&#xff0c;又称龟兔赛跑算法(Tortoise and Hare Algorithm)&#xff0c;是一个可以在有限状态机、迭代函数或者链表上判断是否存在环&#xff0c;求出该环的起点与长度的算法。 …