目录

 TIM定时器输出比较功能

#主要输出PWM波形#

一、TIM输出比较

1.输出比较简介

2.PWM简介

3.输出比较通道(通用)

TIMx_CCER寄存器

4.输出模式比较

5.PWM结构

6.参数计算

二、舵机介绍

1.硬件电路

三、直流电机及驱动介绍

1.硬件电路

2.各引脚功能

四、PWM初始化

五、定时器库函数

例题

1.呼吸灯闪烁

main.c

 pwm.c

pwm.h

2.*引脚重映射代码使用

重映射代码整合: 

3.PWM驱动舵机

main.c

servo.c

servo.h

key.c

key.h

4.PWM驱动直流电机

main.c

moter.c

moter.h


 TIM定时器输出比较功能

#主要输出PWM波形#

一、TIM输出比较

1.输出比较简介

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

在定时器中的位置👇

2.PWM简介

  • PWMPulse Width Modulation)脉冲宽度调制(数字输出信号)
  • 在具有惯性的系统(LED,电机)中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域(控制输出高低电平的时间比例)
  • PWM波形是驱动电机的必要条件
  • PWM参数:

频率 = 1 / TS   (高低电平变换周期时间)        

占空比 = TON(高电平时间) / TS   (一个周期)       

分辨率 = 占空比变化步距(占空比变化精细程度)

3.输出比较通道(通用)

在定时器中的位置👇

输出使能电路:CC1E是使能,1使能输出,0失能输出

TIMx_CCER寄存器

从图中可以看出:1通道是OC1REF信号加了个反相器进入,而0通道是原始信号OC1REF进入,不进行处理

CC1P=0时:OC1高电平有效

CC1P=1时:OC1低电平有效

即:

CC1P=0时:OC1有效电平是高电平

CC1P=1时:OC1有效电平是低电平

OC1与OC1REF的关系只受CC1P的影响(CC1E=1)

 

我们从头分析(整个过程CC1E=1,OC1的输出是允许的):

1.假定OC1REF有效(OC1REF=1),那么从OC1REF到OC1的整条信号链上的信号都是有效信号,我们称OC1输出了有效信号。

那这个有效信号是高电平还是低电平呢?

这就是由CC1P决定的:

2.假定OC1REF无效(OC1REF=0),那么从OC1REF到OC1的整条信号链上的信号都是无效信号,我们称OC1输出了无效信号。

无效信号的高电平和低电平也是由CC1P决定:

OC1REF决定了OC1输出电平是否有效,而CC1P决定了有效电平的极性

OC1的极性只有与CC1P指定的有效极性一致,OC1才能是有效的(绿色部分)

(文章引用【转】STM32定时器输出比较模式中的疑惑 - Ady Lee - 博客园

库函数中的参数运用:

TIM_OCPolarity_High高极性,极性不翻转,REF波形直接输出 /有效电平为高电平

TIM_OCPolarity_Low 低极性,极性翻转,REF电平取反/有效电平为低电平

4.输出模式比较

输出模式控制器,模式可通过TIMx_CCMR1寄存器控制

模式

描述

英文显示

冻结

CNT=CCR时,CNT,CCR无效,REF保持为原状态

TIM_OCMode_Timing

匹配时置有效电平(1)

CNT=CCR时,REF置有效电平(1)

TIM_OCMode_Active

匹配时置无效电平(0)

CNT=CCR时,REF置无效电平(0)

 TIM_OCMode_Inactive  

匹配时电平翻转

CNT=CCR时,REF电平翻转(方便输出f可调,占空比始终为50%)

TIM_OCMode_Toggle 

强制为无效电平

CNT与CCR无效,REF强制为无效电平

  TIM_ForcedAction_InActive

强制为有效电平

CNT与CCR无效,REF强制为有效电平

TIM_ForcedAction_Active

PWM模式1

向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平

向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平

TIM_OCMode_PWM1  

PWM模式2

向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平

向下计数:CNT>CCR时,REF置有效电平,CNT≤CCR时,REF置无效电平

TIM_OCMode_PWM2    

 匹配时电平翻转模式,RCC=0,每当CNT清零,产生CNT=CCR事件,输出电平翻转,每更新俩次,输出为一个周期,且高低电平事件始终相等,占空比50%,当改变定时器更新频率,输出波形也会随之改变

输出波形频率=更新频率/2

CNT自增或清零与RCC展示👇

5.PWM结构

结构图以PWM模式1举例工作流程:

ARR(黄)CNT(蓝)CCR(红)

CCR设置高一些,占空比变大,反之,占空比变小

REF频率可调,占空比也可调PWM波形

6.参数计算

  • PWM频率=计数器溢出频率    Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  • PWM占空比:    Duty = CCR / (ARR + 1)
  • PWM分辨率(占空比最小的变化步距):    Reso = 1 / (ARR + 1)
  • ARR越大,CCR范围越大,对应分辨率越细腻

二、舵机介绍

  • 舵机是一种根据输入PWM信号占空比来控制输出角度的装置
  • 输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms
  • 内部工作流程:内部电板是电机的控制系统,PWM信号输入到控制板,给控制板一个指定的目标角度,电位器检测输出轴的当前角度,若大于目标角度,电机就会反转,若小于目标角度,电机就会正转,最终使输出轴固定在指定角度

1.硬件电路

三、直流电机及驱动介绍

  • 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
  • TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向(俩个推挽输出组成)

1.硬件电路

2.各引脚功能

STBY高电平正常工作,低电平待机


输入IN1 IN2都为高电平 输出O1 O2都为低电平,没有电压差,电机停止不动

输入IN1 IN2都为低电平 输出O1 O2关闭,电机不转


输入IN1低电平 IN2高电平 电机处于反转状态 若PWM高电平,输出为一高一低,反转,若PWM为低电平,输出都为低电平,电机不动

输入IN1 高电平 IN2低电平 电机处于正转状态 若PWM高电平,输出为一高一低,正转,若PWM为低电平,输出都为低电平,电机不动


四、PWM初始化

    ①开启时钟RCC,将TIM外设和GPIO外设时钟打开
    ②配置时基单元和时钟源选择
    ③配置输出比较单元,包括CCR值,输出比较模式,极性选择,输出使能参数
    ④配置GPIO口,初始化为复用推挽输出配置
    ⑤运行控制,启动计数器,输出PWM


五、定时器库函数

    1.有关输出比较的库函数

    ①配置输出比较模块
    用结构体初始化输出比较单元
    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_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);

    ⑩①使能主输出,使PWM正常输出
    高级定时器使用
    void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
 

例题

1.题目:产生一个频率为1KHz,占空比50%,分辨率为1%的PWM波形

根据公式

72MHZ/(PSC+1)/(ARR+1)=1000
0.5=CCR/(ARR+1)
0.01=1/(ARR+1)

ARR=100-1

RSC=720-1

CCR=50

#include "stm32f10x.h"                  // Device header
//PWM初始化函数
//产生一个频率为1KHz,占空比50%,分辨率为1%的PWM波形
/*
72MHZ/(PSC+1)/(ARR+1)=1000
0.5=CCR/(ARR+1)
0.01=1/(ARR+1)
*/
void PWM_init(void)
{
	/*
	①开启时钟RCC,将TIM外设和GPIO外设时钟打开
	②配置时基单元和时钟源选择
	③配置输出比较单元,包括CCR值,输出比较模式,极性选择,输出使能参数
	④配置GPIO口,初始化为复用推挽输出配置
	⑤运行控制,启动计数器,输出PWM
	*/
	//①开启时钟RCC,将TIM外设和GPIO外设时钟打开
	//APB1开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启TIM2时钟
	
	//使用TIM2的OC1/CH1通道,输出PWM

	//②配置时基单元和时钟源选择
	//选择内部时钟
	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);

	//③配置输出比较单元,包括CCR值,输出比较模式,极性选择,输出使能参数
	//选择PA0口→第一个输出通道
	TIM_OCInitTypeDef TIM_OCInitStructure;

    /*
	结构体没有给所有成员赋值,结构体为一个局部变量
	未给成员赋初始值,成员值不确定
	可能会出现不确定问题
	eg.修改TIM2→TIM1,设置4路PWM输出,有三个没有输出
	原因是缺少成员赋值
	解决问题:
	1.将结构体所有成员都配置完整
	2.给结构体成员赋初始值,再修改部分结构体成员
    TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);给结构体赋初始值
    */
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体所有成员赋初始值

	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出比较极性
	/*
	TIM_OCPolarity_High高极性,极性不翻转,REF波形直接输出 /有效电平为高电平           
    TIM_OCPolarity_Low 低极性,REF电平取反 /有效电平为低电平
	PWM2模式与PWM1模式相反
	*/
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出使能
	TIM_OCInitStructure.TIM_Pulse=50;//设置CCR
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);

	//通过引脚图,是PA0输出,重映射列表中查找PA15引脚也可以
	//④配置GPIO口,初始化为复用推挽输出配置
	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);

	//⑤运行控制,启动计数器,输出PWM
	TIM_Cmd(TIM2,ENABLE);

}

1.呼吸灯闪烁

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "pwm.h"
uint16_t i;

void led_pro(void)
{
		for(i=0;i<=100;i++)
		{
			TIM_SetCompare1(TIM2,i);
			//设置CCR的值,并不直接是占空比,占空比是CCR和ARR+1共同决定
			Delay_ms(10);//延时10ms
		}
		for(i=100;i>0;i--)//不要写i>=0,因为无符号整形恒大于等于0
		{
			TIM_SetCompare1(TIM2,i);
			//设置CCR的值,并不直接是占空比,占空比是CCR和ARR+1共同决定
			Delay_ms(10);//延时10ms
		}

}

int main(void)
{	
	
  OLED_Init();
	PWM_init();
	
	while(1)
	{
		led_pro();
	}
}

 pwm.c(与例题1代码一样)

可更换GPIO口,若一个引脚俩个功能都需要使用的话

/*
代码不同的地方,配置GPIO(此代码可更换GPIO口)

	使用其他的外设也是按照引脚表一一对应,使定死状态
	若一个引脚默认复位功能俩个功能都要使用,就要进行重定义功能/重映射
	eg.既要使用USART2的TX引脚,又要使用TIM2的CH3通道,引脚冲突,无法使用,就需要再重定义功能列表寻找带有共同功能的引脚
	CH3通道还可以是PB10引脚,就可以避免俩个引脚的冲突
	若再重映射列表找不到,外设复用GPIO就不能挪位置
	重映射功能是配置AFIO完成实现
*/	
	//步骤:
	//1.启动AFIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

	//2.引脚重映射配置 PA0→PA15选择部分重映射方式1或者完全重映射
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//PA0→PA15
//PA15上电默认复用调试端口JTDI,若将他作为普通的GPIO口或者复用定时器的通道,先关闭调试端口的复用

	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//将JATG复用解除,正常使用PA15引脚
    //如果将PA15,PB3,PB4当作GPIO使用,先打开AFIO时钟,再将JTAG复用解除
	//如果想重映射定时器或者其他外设的复用脚,先打开AFIO时钟,再用AFIO重映射外设复用的引脚,解除调试端口

	//通过引脚图,是PA0输出,重映射列表中查找PA15引脚也可以
	//④配置GPIO口,初始化为复用推挽输出配置
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//推挽复用输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

pwm.h

#ifndef __PWM_H_
#define __PWM_H_

void PWM_init(void);


#endif

2.*引脚重映射代码使用

    使用其他的外设也是按照引脚表一一对应,使定死状态
    若一个引脚默认复位功能俩个功能都要使用,就要进行重定义功能/重映射


    eg.既要使用USART2的CTS引脚,又要使用TIM2的CH1通道,引脚冲突,无法使用,就需要再重定义功能列表寻找带有共同功能的引脚
    CH1通道还可以是PA15引脚,就可以避免俩个引脚的冲突
    若再重映射列表找不到,外设复用GPIO就不能挪位置
    重映射功能是配置AFIO完成实现
    
    步骤:
    1.启动AFIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


    2.引脚重映射配置 PA0→PA15选择部分重映射方式1或者完全重映射
    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//PA0→PA15


    3.PA15上电默认复用调试端口JTDI,若将他作为普通的GPIO口或者复用定时器的通道,先关闭调试端口的复用

关闭复用调试端口使用的函数

SWJSWDJTAG俩调试方式

JTAG基本上带有5个引脚:

TDITest Data In串行输入引脚

TDOTest Data Out,串行输出引脚

TCKTest Clock,时钟引脚

TMSTest Mode Select,模式选择(控制信号)引脚

TRSTTest Reset,复位引脚

SWD引脚

SWDIOSerial Wire Data Input Output,串行数据输入输出引脚

SWCLKSerial Wire Clock,串行线时钟引脚

 GPIO_PinRemapConfig引脚重映射的第一个参数的三种选择  

*     @arg GPIO_Remap_SWJ_NoJTRST      : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST

解除JTREST引脚的复用,PB4为正常GPIO口

  *     @arg GPIO_Remap_SWJ_JTAGDisable  : JTAG-DP Disabled and SW-DP Enabled

解除JTAG调试端口复用,PA15,PB3,PB4为GPIO口

 *     @arg GPIO_Remap_SWJ_Disable      : Full SWJ Disabled (JTAG-DP + SW-DP)

解除SWD和JTAG的调试端口,PA13,PA14,PA15,PB3,PB4全部变为GPIO口,之后STLINK下载不进去程序,只能使用串口下载,下载一个新的,没有解除调试端口的程序,才可以把调试端口弄回

    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//将JATG复用解除,正常使用PA15引脚

总结:

如果将PA15,PB3,PB4当作GPIO使用,先打开AFIO时钟,再将JTAG复用解除

如果想重映射定时器或者其他外设的复用脚,先打开AFIO时钟,再用AFIO重映射外设复用的引脚(若重映射引脚正好是调试端口,再解除调试端口)

重映射代码整合: 

重映射GPIO使得CH1重定义到PA15

对PA15进行初始化且复用推挽输出配置

	//通过引脚图,是PA0输出,重映射列表中查找PA15引脚也可以
	//④配置GPIO口,初始化为复用推挽输出配置
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//PA0→PA15
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//将JATG复用解除,正常使用PA15引脚

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//推挽复用输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

3.PWM驱动舵机

舵机要求的周期为20ms,所以频率为1/20ms=50Hz
PWM频率公式:50Hz=72000000/(PSC+1)/(ARR+1)→数字随意PSC+1=72,ARR+1=20000
舵机要求高电平时间是0.5ms~2.5ms
占空比=CCR/(ARR+1)
20K对应20ms,CCR=500,对应0.5ms/CCR=2500,对应2.5ms
因此,ARR=20k-1,RSC=72-1

舵机电源需求5V,不可连接在面包板正极(正极只有3.3V,输出功率不大,带不动电机),而是连接在STLINK的5V输出引脚


main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "servo.h"
#include "key.h"
float angle;

void structure_pro(void)
{
	uint8_t key_num;
	servo_setangle(0);
	key_num=key_scan();
	if(key_num==1)
	{
		angle+=30;
		if(angle>180)
		{
			angle=0;
		}
		servo_setangle(angle);

	}
}

void OLED_pro(void)
{
	OLED_ShowNum(2,5,angle,3);
}

int main(void)
{	
	
  OLED_Init();	
	servo_init();
	key_init();
	OLED_ShowString(1,1,"Angle:");

	while(1)
	{
		structure_pro();
	}
}

servo.c

#include "stm32f10x.h"                  // Device header
#include "servo.h"
//舵机初始化
/*
舵机要求的周期为20ms,所以频率为1/20ms=50Hz
PWM频率公式:50Hz=72000000/(PSC+1)/(ARR+1)→数字随意PSC+1=72,ARR+1=20000
舵机要求高电平时间是0.5ms~2.5ms
占空比=CCR/(ARR+1)
20K对应20ms,CCR=500,对应0.5ms/CCR=2500,对应2.5ms
因此,ARR=20k-1,RSC=72-1
*/
void servo_init(void)
{
	//①开启时钟RCC,将TIM外设和GPIO外设时钟打开
	//APB1开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启TIM2时钟(APB1)
	
	//使用TIM2的2通道,输出PWM
	//②配置时基单元和时钟源选择
	//选择内部时钟
	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);
	//③配置输出比较单元,包括CCR值,输出比较模式,极性选择,输出使能参数
	//选择PA1口→第2个输出通道
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体所有成员赋初始值
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出比较极性
	/*
	TIM_OCPolarity_High高极性,极性不翻转,REF波形直接输出 /有效电平为高电平           
    TIM_OCPolarity_Low 低极性,REF电平取反/有效电平为低电平
	PWM2模式与PWM1模式相反
	*/
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出使能
	TIM_OCInitStructure.TIM_Pulse=0;//设置CCR,CCR范围500~2500
	/*
	同一个定时器不同通道输出的PWM特点:
	它们的频率相同,因为不同通道公用一个计数器
	它们的占空比由各自的CCR决定,因此占空比可以各自设定
	它们的相位同步,因为计数器的更新,所有PWM同时跳变
	如果驱动多个舵机或者直流电机,使用同一个定时器不同通道的PWM就完全可以了
	*/
	TIM_OC2Init(TIM2,&TIM_OCInitStructure);
	
  /*
	结构体没有给所有成员赋值,结构体为一个局部变量
	未给成员赋初始值,成员值不确定
	可能会出现不确定问题
	eg.TIM2→TIM1,设置4路PWM输出,有三个没有输出
	原因是缺少成员赋值
	解决问题:
	1.将结构体所有成员都配置完整
	2.给结构体成员赋初始值,再修改部分结构体成员
  TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);给结构体赋初始值,在结构体定义下面一行
  */
	
	//④配置GPIO口,初始化为复用推挽输出配置

	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);

	//⑤运行控制,启动计数器,输出PWM
	TIM_Cmd(TIM2,ENABLE);


}

/*
0°→500


180°→2500
2500-500=2000
2000/180=?一个角度的数值
x *2000/180=RCC数值
*/
void servo_setangle(float angle)
{
		TIM_SetCompare2(TIM2,angle*2000/180+500);//舵机每个角度的数值

}

servo.h

#ifndef __SERVO_H_
#define __SERVO_H_
void servo_setangle(float angle);
void servo_init(void);

#endif

key.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "key.h"
void key_init(void)
{
		GPIO_InitTypeDef GPIO_InitStructure;
	
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	  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_Init(GPIOB,&GPIO_InitStructure);

}

uint_8 key_scan(void)
{
	uint8_t num=0;
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0);
		Delay_ms(20);
		num=1;
	}
	
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0);
		Delay_ms(20);
		num=2;
	}
	
	return num;
}

key.h

#ifndef __KEY_H_
#define __KEY_H_

void key_init(void);

uint_8 key_scan(void);


#endif

#一开始,按键按下OLED显示角度而舵机不转动,根UP一一比对,发现uint8_t,我没有有始有终,有的地方写成了unsigned char下次注意#

4.PWM驱动直流电机

电机的第一个引脚VM,电机电源接在STLINK5v引脚,VCC逻辑电源接在面包板3.3v正极

STBY,待机控制引脚,不需要待机,直接接逻辑电源3.3v

AIN1和AIN2是方向控制,任意接俩个GPIO即可(PA4,PA5),PWMA是速度控制,接PWM输出脚(PA2对应TIM2通道3)

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "pwm.h"
#include "moter.h"
#include "key.h"
int8_t angle;
//在pwm驱动下,会发出蜂鸣器的声音
//若要避免,可以加大pwm频率,当频率足够大,超过人耳范围(20hz~20khz),就听不到
void structure_pro(void)
{
	uint8_t key_num;
	moter_set_speed(0);
	key_num=key_scan();
	if(key_num==1)
	{
		angle+=20;
		if(angle>100)//这里数值记得范围 int8_t=char,范围-128~127
		{
			angle=-100;
		}
		moter_set_speed(angle);
	}
}

void OLED_pro(void)
{
	OLED_ShowSignedNum(2,5,angle,3);
}

int main(void)
{	
	
  OLED_Init();	
	moter_init();
	key_init();
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		structure_pro();
		OLED_pro();
	}
}

moter.c

其中代码改变的是通道和GPIO引脚,其余都相同

#include "stm32f10x.h"                  // Device header
//在通道3输出一个1khz的PWM

void moter_init(void)
{
	/*
	①开启时钟RCC,将TIM外设和GPIO外设时钟打开
	②配置时基单元和时钟源选择
	③配置输出比较单元,包括CCR值,输出比较模式,极性选择,输出使能参数
	④配置GPIO口,初始化为复用推挽输出配置
	⑤运行控制,启动计数器,输出PWM
	*/
	//①开启时钟RCC,将TIM外设和GPIO外设时钟打开
	//APB1开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启TIM2时钟(APB1)
	
	//使用TIM2的CH3通道,输出PWM
	//②配置时基单元和时钟源选择
	//选择内部时钟
	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);
	//③配置输出比较单元,包括CCR值,输出比较模式,极性选择,输出使能参数
	//选择PA2口→第3个输出通道
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体所有成员赋初始值
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出比较极性
	/*
	TIM_OCPolarity_High高极性,极性不翻转,REF波形直接输出 /有效电平为高电平           
  TIM_OCPolarity_Low 低极性,REF电平取反/有效电平为低电平
	PWM2模式与PWM1模式相反
	*/
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出使能
	TIM_OCInitStructure.TIM_Pulse=0;//设置CCR,CCR范围500~2500
	/*
	同一个定时器不同通道输出的PWM特点:
	它们的频率相同,因为不同通道公用一个计数器
	它们的占空比由各自的CCR决定,因此占空比可以各自设定
	它们的相位同步,因为计数器的更新,所有PWM同时跳变
	如果驱动多个舵机或者直流电机,使用同一个定时器不同通道的PWM就完全可以了
	*/
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);
	
  /*
	结构体没有给所有成员赋值,结构体为一个局部变量
	未给成员赋初始值,成员值不确定
	可能会出现不确定问题
	eg.TIM2→TIM1,设置4路PWM输出,有三个没有输出
	原因是缺少成员赋值
	解决问题:
	1.将结构体所有成员都配置完整
	2.给结构体成员赋初始值,再修改部分结构体成员
  TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);给结构体赋初始值,在结构体定义下面一行
  */
	
	//④配置GPIO口,初始化为复用推挽输出配置
 //通道3对应PA2
 //电机方向初始化控制引脚
	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);

	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
	TIM_Cmd(TIM2,ENABLE);

}

//设置速度函数
//速度值用有符号数字显示,负号表示反转
void moter_set_speed(int8_t speed)
{
	if(speed>=0)//正转
	{
		//设置俩个电平,一高一低
		GPIO_SetBits(GPIOA,GPIO_Pin_4);
		GPIO_ResetBits(GPIOA,GPIO_Pin_5);
		TIM_SetCompare3(TIM2,speed);
	}
	else//反转
	{
		//设置俩个电平,一高一低
		GPIO_SetBits(GPIOA,GPIO_Pin_4);
		GPIO_ResetBits(GPIOA,GPIO_Pin_5);
		TIM_SetCompare3(TIM2,-speed);
	}
}

moter.h

#ifndef __MOTER_H_
#define __MOTER_H_

void moter_init(void);
void moter_set_speed(int8_t speed);


#endif

#代码基本上是相同的,改变的是通道和GPIO口#

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐