注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出!
高级定时器(STM32F1 系列中为 TIM1 和 TIM8)和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。高级定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对电机控制方面。
由于TIM高级定时器结构复杂,功能众多,可以直接看野火的开发书籍,不做过多详述,文章主要侧重几个常用实验,并在代码中层层递进。
PWM 信号输出控制 SG90 舵机
该例程将通过将使用 STM32 输出 PWM 信号,控制 SG90 舵机进行转动,使用的是 TIM3 的通道1 为 PA6 口,舵机接线如下:

控制原理及 PWM 配置
伺服电机通常被称为舵机,它是一种带有输出轴的小装置。当我们向伺服器发送一个控制信号时,输出轴就可以转到特定的位置。只要控制信号持续不变,伺服机构就会保持轴的角度位置不改变。如果控制信号发生变化,输出轴的位置也会相应发生变化。
舵机的内部结构如上图所示。你可以看到控制电路,马达,一组齿轮和外壳。

舵机内部的控制电路,电位计(可变电阻器)和电机均被连接到电路板上,如内部结构图的右边部分。控制电路通过电位计可监控舵机的当前角度。
如果轴的位置与控制信号相符,那么电机就会关闭。如果控制电路发现这个角度不正确,它就会控制马达转动,直到它达到指定的角度。舵机角度根据制造商的不同而有所不同。比如,一个180度的舵机,它可以在0度至180度之间运动。由于限位装置被安装在主输出装置上,超出这个范围机械结构就不能再转动了。
舵机的输出功率与它所需要转动的距离成正比。如果输出轴需要转动很长的距离,马达就会全速运转,如果它只需要短距离转动,马达就会以较慢的速度运行,这叫做速度比例控制。
舵机的控制一般需要一个 20ms 的时基脉冲,该脉冲的高电平部分一般为 0.5ms~2.5ms 范围内的角度控制脉冲部分。

控制线用于传输角度控制信号。这个角度是由控制信号脉冲的持续时间决定的,这叫做脉冲编码调制(PCM)。舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为 0.5ms-2.5ms 范围,总间隔为 2ms。脉冲的宽度将决定马达转动的距离。例如:1.5毫秒的脉冲,电机将转向90度的位置(通常称为中立位置,对于180°舵机来说,就是90°位置)。如果脉冲宽度小于1.5毫秒,那么电机轴向朝向0度方向。如果脉冲宽度大于1.5毫秒,轴向就朝向180度方向。

综上,设置PWM周期为20ms = (7200*200)/72000000=0.02,所以TIM_Period = 199,TIM_Prescaler = 7199。
以180度舵机 SG90 为例,对应的控制关系是这样的:
| 脉冲宽度 | 转动角度 | 设置占空比 |
|---|---|---|
| 0.5 ms | 0° | 175 |
| 1.0 ms | 45° | 180 |
| 1.5 ms | 90° | 185 |
| 2.0 ms | 135° | 190 |
| 2.5 ms | 180° | 195 |
STM32工程目录
很久不接触 STM32 工程,文件结构差点乱了,这里贴个目录树:
├─Doc │ readme.txt │ ├─Libraries │ ├─CMSIS │ │ └─startup │ │ │ └─FWlib │ ├─inc │ └─src │ ├─Listing │ ├─Output │ ├─Project │ └─RVMDK(uv5) │ └─DebugConfig │ └─User │ main.c │ stm32f10x_conf.h │ stm32f10x_it.c │ stm32f10x_it.h │
├─PWM │ bsp_pwm.c │ bsp_pwm.h │ └─SysTick bsp_SysTick.c bsp_SysTick.h
参考代码
bsp_pwm.h
#ifndef _BSP_PWM_H_
#define _BSP_PWM_H_
#include "stm32f10x.h"
// 这里使用 TIM3
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
#define GENERAL_TIM_Period 199
#define GENERAL_TIM_Prescaler 7199
// TIM3 输出比较通道
#define GENERAL_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT GPIOA
#define GENERAL_TIM_CH1_PIN GPIO_Pin_6
void GENERAL_TIM_Init(void);
#endif
bsp_pwm.c
#include "bsp_pwm.h"
static void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道1 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
}
static void GENERAL_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM_Period;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM_Prescaler;
// 时钟分频因子 ,配置死区时间时需要用到
// TIM_TimeBaseStructure.TIM_ClockDivision = 0;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
/*--------------------输出比较结构体初始化-------------------*/
// 占空比配置
// uint16_t CCR_Val = 100;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置为PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 输出通道电平极性配置
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
// 输出比较通道 1
//TIM_OCInitStructure.TIM_Pulse = CCR_Val;
TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
}
main.c
#include "stm32f10x.h"
#include "bsp_pwm.h"
#include "bsp_SysTick.h"
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* 定时器初始化 */
GENERAL_TIM_Init();
/* 配置SysTick 为10us中断一次 */
SysTick_Init();
while(1)
{
SysTick_Delay_Ms( 1000 );
TIM_SetCompare1(TIM3, 175); // 0°
SysTick_Delay_Ms( 1000 );
TIM_SetCompare1(TIM3, 180); // 45°
SysTick_Delay_Ms( 1000 );
TIM_SetCompare1(TIM3, 185); // 90°
SysTick_Delay_Ms( 1000 );
TIM_SetCompare1(TIM3, 190); // 135°
SysTick_Delay_Ms( 1000 );
TIM_SetCompare1(TIM3, 195); // 180°
SysTick_Delay_Ms( 1000 );
}
}
舵机测试