STM32——TIM定时器输出比较+PWM+舵机+直流电机
TIM定时器输出比较介绍与有关PWM的呼吸灯,舵机,直流电机代码编写,内含引脚重映射代码使用
目录
TIM定时器输出比较功能
#主要输出PWM波形#
一、TIM输出比较
1.输出比较简介
- OC(Output Compare)输出比较
- 输出比较可以通过比较CNT计数器与CCR捕获/比较寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
- 每个高级定时器和通用定时器都拥有4个输出比较通道
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
在定时器中的位置👇

2.PWM简介
- PWM(Pulse 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口或者复用定时器的通道,先关闭调试端口的复用
关闭复用调试端口使用的函数
SWJ为SWD和JTAG俩调试方式
JTAG基本上带有5个引脚:
TDI:Test Data In串行输入引脚
TDO:Test Data Out,串行输出引脚
TCK:Test Clock,时钟引脚
TMS:Test Mode Select,模式选择(控制信号)引脚
TRST:Test Reset,复位引脚
SWD引脚
SWDIO:Serial Wire Data Input Output,串行数据输入输出引脚
SWCLK:Serial 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口#
更多推荐



所有评论(0)