用stm32控制舵机——以stm32F103C8T6为例

舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。使用stm32控制机器时,经常要用到舵机,如使某个部位转到特定的角度,或者在行进过程中的方向控制,这篇文章将以stm32F103C8T6为例,从分析舵机的原理出发,到介绍使用stm的TIM功能输出PWM波,掌握理论后进行实战,先控制一个舵机上手,然后控制多个舵机。

一、舵机的原理

在这里插入图片描述
如图所示,一个舵机由变速齿轮箱,电位器,电路板与直流电机组成。电机的高速、短周期运动由齿轮箱转换为慢速、长周期的运动,最终到最外端的齿轮,齿轮的转动带动电位器转动,电位器的电位与信号线进行比较,从而实现转动到特定角度的功能。
伺服电机由信号线输入的PWM信号控制。信号的频率应为50Hz,周期为20ms,PWM的占空比决定了舵机旋转到的角度。

二、如何控制一个舵机

舵机的控制由一个脉冲宽度调制信号(PWM波)来实现,该信号在这个实验里使用stm32来发出。
通常来说,1ms的脉宽对应0度位置,1.5ms对应90度,2ms对应180度。这一数据会由于舵机型号不同而略有差异。
通常来说,1ms的脉宽对应0度位置,1.5ms对应90度,2ms对应180度。这一数据会由于舵机型号不同而略有差异。

三、使用stm32TIM功能输出PWM波来控制舵机

根据STM32F10xxx参考手册,
通用TIM 定时器与高级TIM功能包括:
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意
数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
在这里使用4个独立通道:PWM生成,要注意的是,基本定时器并不具备该功能。
不同型号的芯片包含的TIM模块可能不同,使用时建议先查询数据手册。


配置时基单元
要使用该功能,首先要配置时基单元,时基单元可以理解为定时器的心脏,它主要包含:
● 计数器寄存器(TIMx_CNT)
● 预分频器寄存器 (TIMx_PSC)
● 自动装载寄存器 (TIMx_ARR)
之间的关系是:
在这里插入图片描述
驱动定时器的时钟:CK_CNT=CK_INT/(PSC+1),CNT开始计数,由ARR决定产生更新事件。

预分频器
预分频器可以将计数器的时钟频率按1到65536之间的任意值进行分频,选中的时钟由分频器分频后进行计数,在这里使用内部时钟INT,经过分频后CK_CNT=CK_INT/(PSC+1)
在这里插入图片描述
那么如何得知内部时钟的频率呢?参考时钟树,可以发现:
在STM32F10xxx中,TIM1和TIM8挂载在APB2总线上,其他都挂载到APB1总线上
在这里插入图片描述
在这里插入图片描述
定时器时钟频率分配由硬件按以下2种情况自动设置: 1. 如果相应的APB预分频系数是1,定时器的时钟频率与所在APB总线频率保持一致2. 否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍。
在标准库中,APB1与APB2预分频系数是2,所以定时器时钟TIMxCLK=36MHz*2=72MHz

计数器模式

  • 向上计数模式

在向上计数模式中,计数器CNT从0计数到自动加载值(ARR中的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
这里还有一个自动加载影子寄存器,简单来说就是当写入新数据至影子计数器时,若ARPE=0,则ARR寄存器值立即装入,若ARPE=1,要等到下一个更新事件才会写入。

  • 向下计数模式

在向下计数模式中,计数器从自动加载值开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出的事件。

  • 中央对齐模式

在这里使用向上或者向下计数模式

**
在PWM模式(模式1或模式2)下,TIMx_CNT和TIMx_CCRx始终在进行比较。
PWM模式1与PWM模式2的描述如下:(见STM32F10xxx参考手册)
PWM模式1——在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为 有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。

综上所述,产生PWM波的步骤如下:
1.配置预分频寄存器PSC的值为(7200-1),内部时钟为72MHz,则计数一次的时间为(7200/72000000)s=0.1ms;
2.配置自动装载寄存器ARR的值为(200-1),那么产生一次计数中断的周期为20ms,与驱动舵机需要的周期相符
3.使用输出比较功能的PWM模式,更改CCR寄存器的值即可改变占空比,如前面所说的,脉宽为1ms对应0°,2ms对应180°

四、实战——先从控制一个舵机入手

在这里,将以十分常见的SG90舵机为例(下图),使用stm32C8T6驱动
长这个样子
在这里插入图片描述
首先,我们新建bsp_servo.h与bsp_servo.c文件

本实验使用TIM3的输出通道1,在bsp_servo.h文件中定义宏

/*先使用TIM1的通道1*/
#define            GENERAL_TIM                   TIM3
#define            GENERAL_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            GENERAL_TIM_CLK               RCC_APB1Periph_TIM3
//配置预分频寄存器PSC的值为(7200-1),内部时钟为72MHz,则计数一次的时间为(7200/72000000)s=0.1ms;
#define            GENERAL_TIM_Prescaler         (72000-1)
//配置自动装载寄存器ARR的值为(200-1),那么产生一次计数中断的周期为20ms,与驱动舵机需要的周期相符
#define            GENERAL_TIM_Period            (20-1)

// TIM3 输出比较通道1
#define            GENERAL_TIM_CH1_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM_CH1_PORT          GPIOA
#define            GENERAL_TIM_CH1_PIN           GPIO_Pin_6

之后来到bsp_servo.c文件,配置GPIO端口为复用,输出端口为PA6,具体使用哪个端口可以参考对应型号的data sheet。

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

使用TIM功能的PWM模式
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=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
	
	
	/*--------------------输出比较结构体初始化-------------------*/	
	
	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_High;
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = 0;
	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使用TIM_SetComparex(TIMx, Compare);来配置占空比。

首先定义一个延时函数,使舵机有时间转动特定的角度

void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

为了方便使用定义一个宏

#define SOFT_DELAY Delay(0x0FFFFF);

前面将TIM3配置为PWM模式1,即为向上计数,CNT<CCR时通道为有效电平,这一段时间即为脉冲宽度,ARR=(200-1),要使舵机在0°到180°旋转,CRR应在10-20

int main(void)
{
	GENERAL_TIM_Init();
	
	while(1)
	{
		for(int i = 5;i < 26;i++)//结果测试,设置CCR的值为5-6对应舵机旋转到0°-180°
		{
			TIM_SetCompare1(GENERAL_TIM , i);
			SOFT_DELAY;
		}
	}
}

此时将舵机上电,信号线连接到PA6,可以看到舵机旋转
在这里插入图片描述

五、控制多个舵机

控制一个舵机成功后,进一步控制多个舵机,先以TIM3增加输出通道为例,这里增加Channel2,输出端口配置为PA7
首先配置bsp_servo.h与bsp_servo.c中的CH2

// TIM3 输出比较通道2
#define            GENERAL_TIM_CH2_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM_CH2_PORT          GPIOA
#define            GENERAL_TIM_CH2_PIN           GPIO_Pin_7

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);
	
	 // 输出比较通道1 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH2_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH2_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=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
	
	
	/*--------------------输出比较结构体初始化-------------------*/	
	
	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_High;
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 2
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
		// 使能计数器
	TIM_Cmd(GENERAL_TIM, ENABLE);
}

然后来到main.c,这里是使用TIM_SetCompare2(GENERAL_TIM ,Compare);对于函数
TIM_SetComparex(TIMy,Compare);设置的是TIMy模块CHx的脉宽。

int main(void)
{
	GENERAL_TIM_Init();
	
	while(1)
	{
		for(int i = 5;i<26;i++)
		{
	    TIM_SetCompare1(GENERAL_TIM ,i);
			SOFT_DELAY;
		}
		for(int i = 5;i<26;i++)
		{
	    TIM_SetCompare2(GENERAL_TIM ,i);
			SOFT_DELAY;
		}
	}
}

可以看到两个舵机按照程序转动起来。
在这里插入图片描述

之后要增加控制舵机的数据,可以先增加TIMx的通道数,一个通用或者高级定时器有四个输出通道,还可以增加新的TIM,达到控制多个舵机的目的。为了避免篇幅过于冗长,这里不挂出具体代码。
最后再来一个挂载四个的
在这里插入图片描述

  • 78
    点赞
  • 737
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值