STM32输出PWM波总结
前言
刚刚用STM32(F103ZET)点过灯我就开始了PWM控制舵机的学习。所有的问题都是从比较基础的环节开始的,在此总结一下我所遇到的问题,希望与大家交流交流。
有很多博文都讲述了关于PWM波的输出原理,所以我再次也就不再赘述。
正文
1.选用:PWM输出我使用的是定时器3,通道4,PB1端口。
…以下是相关定时器,通道和端口的映射:
------------------------TIM1_ETR PA12 PE7
------------------------TIM1_CH1 PA8 PE9
------------------------TIM1_CH2 PA9 PE11
------------------------TIM1_CH3 PA10 PE13
------------------------TIM1_CH4 PA11 PE14
------------------------TIM1_BKIN PB12 PA6 PE15
------------------------TIM1_CH1N PB13 PA7 PE8
------------------------TIM1_CH2N PB14 PB0 PE10
------------------------TIM1_CH3N PB15 PB1 PE12
------------------------TIM2_CH1_ETR PA0 PA15 PA0 PA15
------------------------TIM2_CH2 PA1 PB3 PA1 PB3
------------------------TIM2_CH3 PA2 PB10
------------------------TIM2_CH4 PA3 PB11
------------------------TIM3_CH1 PA6 PB4 PC6
------------------------TIM3_CH2 PA7 PB5 PC7
------------------------TIM3_CH3 PB0 PC8
TIM3_CH4 PB1 PC9
------------------------TIM4_CH1 PB6 PD12
------------------------TIM4_CH2 PB7 PD13
------------------------TIM4_CH3 PB8 PD14
------------------------TIM4_CH4 PB9 PD15
------------------------TIM5_CH4
2.代码1:PWM主程序:
名称:servomotor.c
变量:(TIM_TypeDef* timer,uint8_t tim_channe1,uint16_t freq,uint16_t duty)
//(定时器(TIMx),通道(1~4),定时重装值(2565),占空比(455~2565))
注意:虽然有一套关于PWM周期与占空比的计算方式,但是因为我们用的是HSI(高速内部RC振荡器 72MHz)所以有一定的误差,特别是误差累积起来后,计算出的数值与现实数值有很大差异。
计算方法:
频率:
F = 72M / ((定时器装入值+1)*(分频+1))(单位:Hz)
占空比:
W = TIM3->CCR1 / 定时器装入值(单位:%)
/*------------------------------------------------------------------------------------------
仿真时添加端口格式:例如PC5 为 PORTC.5 。
PWM占空比主要由寄存器 CCRx 控制,其值与 CNT 比较并输出。
--------------------------------------------------------------------------------------------*/
#include "servomotor.h"
void Pwm_Start(TIM_TypeDef* timer,uint8_t tim_channe1,uint16_t freq,uint16_t duty)//定时器,通道,定时重装值,占空比(455~2565)
{
// u16 arr = 36000/freq;//设定自动重装寄存器周期的值,freq单位为KHz,所以直接用72000就可。
// uint32_t arr;
GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量,用来初始化GPIO//1.声明结构体
//时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOx的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 ,ENABLE);//使能TIM3的时钟
//端口
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;// 锁定所用端口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;// 设置时钟
GPIO_Init(GPIOB,&GPIO_InitStructure);//写入
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//声明一个结构体变量,用来初始化定时器
TIM_OCInitTypeDef TIM_OCInitStructure;//根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//TIM3初始化
TIM_DeInit(timer);//把定时器3设置为缺省值
TIM_TimeBaseInitStructure.TIM_Period = freq; //设置自动重装载寄存器周期的值(在72MHz的时钟下执行?次溢出)
TIM_TimeBaseInitStructure.TIM_Prescaler = 71;//TIMx时钟频率预分频值 将72MHz 72分频为1MHz。
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0x0;//设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(timer, & TIM_TimeBaseInitStructure);//写入
/*-----------------------------
TIM_OCMode TIM_OCPolarity两者可控制PWM波形
------------------------------*/
//PWM初始化
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//TIM的PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//PWM输出使能
TIM_OCInitStructure.TIM_Pulse= duty;//设置占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//TIM输出比较极性高
switch(tim_channe1)
{
case 1: TIM_OC1Init(timer,&TIM_OCInitStructure);
TIM_OC1PreloadConfig(timer,TIM_OCPreload_Enable);//输出比较预装载
break;
case 2: TIM_OC2Init(timer,&TIM_OCInitStructure);
TIM_OC2PreloadConfig(timer,TIM_OCPreload_Enable);
break;
case 3: TIM_OC3Init(timer,&TIM_OCInitStructure);
TIM_OC3PreloadConfig(timer,TIM_OCPreload_Enable);
break;
case 4: TIM_OC4Init(timer,&TIM_OCInitStructure);
TIM_OC4PreloadConfig(timer,TIM_OCPreload_Enable);
break;
}
TIM_ARRPreloadConfig(timer,ENABLE);//使能TIM3的ARR的预装载寄存器
TIM_Cmd(timer,ENABLE);//使能TIM3
}
3.代码2:PWM头文件:
名称:servomotor.h
变量:
#ifndef __SERVOMOTOR_H
#define __SERVOMOTOR_H
#include "stm32f10x.h"
void Pwm_Start(TIM_TypeDef* timer,uint8_t tim_channe1,uint16_t freq,uint16_t duty);
#endif
4.代码3:main.c:
/*---------------------------------------------------------------------
TIM3 CH4 PB1
---------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "delay.h"
#include "servomotor.h"
int main()
{
delay_init();
while(1)
{
Pwm_Start(TIM3,4,29988,1510);//占空比取值范围(455~2565)
delay_ms(200);
Pwm_Start(TIM3,4,29988,2265);
delay_ms(200);
Pwm_Start(TIM3,4,29988,2565);
delay_ms(200);
}
}
5.审查
我们怎样才能检查端口输出波形的正确性呢?
这时需要进行KEIL5仿真,因为篇幅过长,所以有关keil5仿真功能大家可以去搜索一下相关文章进行设置。
对于main.c函数中的while循环的第一句:
Pwm_Start(TIM3,4,29988,1510);//占空比取值范围(455~2565)
其PB1端口输出波形周期与括号内倒数第二个元素相关,占空比与倒数第一个元素相关。
对于main.c函数中的while循环的第一句:
关于此句的输出波形为:(周期=20ms,高电平持续时间=1ms)
----------特别声明:因为我们用的是HSI(高速内部RC振荡器 72MHz)所以受环境温度电压等等的影响,函数Pwm_Start(x,y,z,w);中, z和w变量需要进行相应的调整!因为是向上计数所以,设置周期随x增大而增大,占空比随w增大而减小。
6.总结与链接
(1)因为算是刚刚入门STM32,当我完成后,发现舵机并不能被驱动,后来才知道开发板的5V输出端口不支持ST-link供电(电压依然是3.3V)所以难以驱动,后来改用串口烧录(供电)便可。
(2)在使用ST-link联合Keil5烧录STM32时时常常出现无法识别芯片等问题,个人感觉ST-link老版本固件更加好用。
友情链接: 安装方法.
(3)注意:keil5进行仿真后无法与STlink进行联合调试,请注意备份工程。如果已进行了仿真可以将User目录下“工程名.uvoptx”和“工程名.uvguix.电脑用户名”两个文件删除,以及OBJ目录下(工程编译生成文件夹)所有文件删除。删除之前必须关闭Keil5。再次打开你会发现所有编译设置都已初始化,将其再次配置即可。
当你的keil工程不兼容或者烧录时总是闪退也可以这样做。
拓展 : 另一种控制方式 : 频率固定, 占空比可变.
main.c
/*
TIM4_CH4 PB9 PD15
TIM3_CH1 PA6 PB4 PC6
TIM3_CH2 PA7 PB5 PC7
*/
#include "stm32f10x.h"
#include "delay.h"
#include "servomotor.h"
int main()
{
uint16_t a = 0;
delay_init();
Pwm_Start(); // PWM 初始化
while(1)
{
for(a = 1;a < 480;a++)
{
TIM_SetCompare1(TIM3,a); // 设置 TIM3 通道一 ARR 值
TIM_SetCompare2(TIM3,a); // 设置 TIM3 通道二 ARR 值
TIM_SetCompare4(TIM4,a); // 设置 TIM4 通道四 ARR 值
delay_ms(4);
}
for(a = 480;a > 1;a--)
{
TIM_SetCompare1(TIM3,a);
TIM_SetCompare2(TIM3,a);
TIM_SetCompare4(TIM4,a);
delay_ms(4);
}
}
}
servomotor.h
#ifndef __SERVOMOTOR_H
#define __SERVOMOTOR_H
#include "stm32f10x.h"
void Pwm_Start(void);
#endif
servomotor.c
/*------------------------------------------------------------------------------------------
仿真时添加端口格式:例如PC5 为 PORTC.5 。
PWM占空比主要由寄存器 CCRx 控制,其值与 CNT 比较并输出。
--------------------------------------------------------------------------------------------*/
#include "servomotor.h"
void Pwm_Start(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3 ,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4 ,ENABLE);
//端口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_DeInit(TIM3);//把定时器3设置为缺省值
TIM_DeInit(TIM4);//把定时器4设置为缺省值
TIM_TimeBaseInitStructure.TIM_Period = 720-1; //设置自动重装载寄存器周期的值(在72MHz的时钟下执行?次溢出)
TIM_TimeBaseInitStructure.TIM_Prescaler = 100-0;//TIMx时钟频率预分频值 72* 10/72000000 = 720/72000000S = 100KHZ
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;//设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInitStructure.TIM_RepetitionCounter =0;
TIM_TimeBaseInit(TIM3, & TIM_TimeBaseInitStructure);//写入
TIM_TimeBaseInit(TIM4, & TIM_TimeBaseInitStructure);//写入
/*-----------------------------
TIM_OCMode TIM_OCPolarity两者可控制PWM波形
------------------------------*/
//PWM初始化
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//TIM的PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//PWM输出使能
TIM_OCInitStructure.TIM_Pulse= 0;//设置占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//TIM输出比较极性高
TIM_OC1Init(TIM3,&TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);//输出比较预装载 PA6 PB4 PC6
TIM_OC2Init(TIM3,&TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);// PA7 PB5 PC7
TIM_OC4Init(TIM4,&TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable);// TIM4_CH4 PB9
TIM_ARRPreloadConfig(TIM3,ENABLE);//使能TIM3的ARR的预装载寄存器
TIM_Cmd(TIM3,ENABLE);//使能TIM3
TIM_ARRPreloadConfig(TIM4,ENABLE);//使能TIM4的ARR的预装载寄存器
TIM_Cmd(TIM4,ENABLE);//使能TIM4
}