程序地带

第9章 舵机控制


 


有一种电机可以在程序的控制下,在一定范围内连续改变输出轴角度并且可以保持住。这种电机最早被用在航模和船模等遥控模型中,控制各种舵面的转动,这就是舵机。现在舵机除了运用在 遥控模型中,也大量的运用在各种机器人、机械臂的关节以及智能小车的转向机构中。下图就是一种标准舵机的外形。



9.1 舵机分类


1. 按照舵机的控制电路可以分为:模拟舵机和数字舵机。模拟舵机和数字舵机的机械结构可以说是完全相同的,模拟舵机的控制电路为纯模拟电路,需要一直发送目标信号,才能转到指定的位置,响应速度较慢,无反应区较大;数字舵机内部控制电路则加上了微控制器,只需要发送一次目标信号,即可到达指定位置,速度比模拟舵机更快,无反应区也更小。


2. 按照使用对象的不同,可以分为:航模舵机、车模舵机、船模舵机和机器人舵机。航模舵机一般要求速度快、精度高,而车模和船模用的舵机一般要求具有大扭矩和防水性好。


3. 按照内部机械材质,又可分成:塑料齿舵机和金属齿舵机。塑料齿舵机内部的传动齿轮是塑料的,重量轻价格便宜,扭矩一般较小无法做大;金属齿舵机的扭矩大,舵机更结实耐用,但是相比塑料齿更重也更贵。 4. 按照外部接口和舵机的控制方式,又可分为:PWM 舵机和串行总线舵机。


9.2 舵机结构 舵机主要由以下几个部分组成:舵盘、外壳、直流电机、减速齿轮组、角度传感器、控制驱动电路和接口线缆等。常见的舵机内部结构如下图所示。


其中角度传感器负责舵机的位置反馈,直接装在舵机的主输出轴上,将轴旋转后产生的角度变化变成电压信号发回控制电路;


控制驱动电路:接收外部接口传来的信号和角度传感器反馈的电压值,以及驱动直流电机旋转;


减速齿轮组则是降低直流电机的转速并且放大扭矩;


市面上常见的廉价舵机通常采用小型的直流有刷电机和塑料材质减速齿轮组,传感器一般使用电位器返回模拟电压;而一些稍贵的则会使用金属齿轮组,一些高端的舵机内部甚至会采用无刷电机和磁电编码器。


9.3 舵机工作原理 模拟舵机和数字舵机内部电路不同,所以原理上稍有差别,这里以模拟舵机进行讲解。模拟舵机内部的控制驱动电路板从外界接收控制信号,经过处理后变为一个直流偏置电压,在控制板内部有一个基准电压,这个基准电压由电位器产生,将外部获得的直流偏置电压与电位器的电压进行比较获得电压差,并输出到电机驱动芯片驱动电机,电压差的正负决定电机的正反转,大小决定旋转的角度,电压差为0 时,电机停止转动。大致原理框图如下图所示。


从图中可以看到,舵机内部是闭环控制的,所以这一类电机实际上是一种位置(角度)伺服的简化版伺服电机,将工业伺服电机的三闭环控制简化成了只有一个位置闭环。舵机这个名字是国内 起的一种俗称,本质上属于伺服电机,它的英文就直接叫Servo,或者RC Servo。


9.4 舵机控制原理 舵机的输入有三根线,一般的中间的红色线为电源正极,咖啡色线的为电源负极,黄色色线为控制线号线。如下图所示。



舵机的控制通常采用PWM 信号,例如需要一个周期为20ms 的脉冲宽度调制(PWM),脉冲宽度部分一般为0.5ms-2.5ms 范围内的角度控制脉冲部分,总间隔为2ms。当脉冲宽度为1.5ms 时,舵机旋转至中间角度,大于1.5ms 时舵机旋转角度增大,小于1.5ms 时舵机旋转角度减小。舵机分90°、180°、270° 和360° 舵机,以180° 的舵机为例来看看脉冲宽度与角度的关系,见下 图所示。



上图中脉冲宽度与舵机旋转角度为线性关系,其他舵机控制脉冲也类似,0.5ms 对应0 度,2.5ms对应最大旋转角度,脉冲宽度与旋转角度也是线性关系。


9.5 舵机几个参数介绍 舵机速度的单位是sec/60°,就是舵机转过60° 需要的时间,如果控制脉冲变化宽度大,变化速度快,舵机就有可能在一次脉冲的变化过程中还没有转到目标角度时,而脉冲就再次发生了变化,舵机的转动速度一般有0.16sec/60°、0.12sec/60° 等,0.16sec/60° 就是舵机转动60° 需要0.16 秒的时间。旋转停留时间大于240ms/90度


舵机的速度还有工作电压有关,在允许的电压范围内,电压越大速度越快,反之亦然。


舵机扭矩的单位是KG*CM,这是一个扭矩的单位,可以理解为在舵盘上距离舵机轴中心水平距离1CM 处,舵机能够带动的物体重量,如下图所示。


通常说的55g 舵机、9g 舵机等,这里的55g 和9g 指的是舵机本身的重量。


 

SG9g舵机参数

9.6 舵机基本控制实验


程序在其它程序的基础上修改,注释没有改,但功能能实现:


 


 


/*主程序main */
// TIM—通用定时器-4路PWM输出应用
#include "stm32f10x.h"
//#include "bsp_led.h"
#include "bsp_GeneralTim.h"
#include "bsp_rccclkconfig.h"
#include "bsp_systick.h"
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* led 端口配置 */
//LED_GPIO_Config();
HSE_SetSysClk( RCC_PLLMul_6 );
/* 定时器初始化 */
GENERAL_TIM_Init();
while(1)
{
TIM_SetCompare4(TIM3,15);
SysTick_Delay_ms(3000);
TIM_SetCompare4(TIM3,10);
SysTick_Delay_ms(3000);
TIM_SetCompare4(TIM3,5);
SysTick_Delay_ms(3000);
TIM_SetCompare4(TIM3,15);
SysTick_Delay_ms(3000);
TIM_SetCompare4(TIM3,20);
SysTick_Delay_ms(3000);
TIM_SetCompare4(TIM3,25);
SysTick_Delay_ms(3000);
}
}
/*********************************************END OF FILE**********************/
/*通用定时器TIM3 .C程序 */
#include "bsp_GeneralTim.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);
//
//// 输出比较通道2 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);
//
//// 输出比较通道3 GPIO 初始化
//RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE);
// GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH3_PIN;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
// 输出比较通道4 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStructure);
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler 都有
// *TIM_CounterMode TIMx,x[6,7]没有,其他都有
// * TIM_Period 都有
// * TIM_ClockDivision TIMx,x[6,7]没有,其他都有
// * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef;
// *-----------------------------------------------------------------------------
// */
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)
static void GENERAL_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
// 配置周期,这里配置为100K
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);
/*--------------------输出比较结构体初始化-------------------*/
// 占空比配置
//uint16_t CCR1_Val = 5;
//uint16_t CCR2_Val = 4;
//uint16_t CCR3_Val = 3;
//200个脉冲式20ms,1.5ms是15个脉冲0度;
//1ms=10脉冲=-45度;0.5ms=5脉冲=-90度;20脉冲=45度;25脉冲=90度
uint16_t CCR4_Val = 10;
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 = CCR1_Val;
//TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
//TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 2
//TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
//TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
//TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 3
//TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
//TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
//TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 4
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
}
/*********************************************END OF FILE**********************/
/*通用定时器TIM3 .h程序 */
#ifndef __BSP_GENERALTIME_H
#define __BSP_GENERALTIME_H
#include "stm32f10x.h"
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
#define GENERAL_TIM_Period 200-1
#define GENERAL_TIM_Prescaler 7200
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
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
TIM3 输出比较通道3
//#define GENERAL_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB
//#define GENERAL_TIM_CH3_PORT GPIOB
//#define GENERAL_TIM_CH3_PIN GPIO_Pin_0
// TIM3 输出比较通道4
#define GENERAL_TIM_CH4_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH4_PORT GPIOB
#define GENERAL_TIM_CH4_PIN GPIO_Pin_1
/**************************函数声明********************************/
void GENERAL_TIM_Init(void);
#endif/* __BSP_GENERALTIME_H */
/*RCC晶振8M改为12MHz .C程序 */
#include "bsp_rccclkconfig.h"
void HSE_SetSysClk( uint32_t RCC_PLLMul_x )
{
ErrorStatus HSEStatus;
// 把RCC 寄存器复位成复位值
RCC_DeInit();
// 使能 HSE
RCC_HSEConfig(RCC_HSE_ON);
HSEStatus = RCC_WaitForHSEStartUp();
if( HSEStatus == SUCCESS )
{
// 使能预取指
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
// 配置 PLLCLK = HSE * RCC_PLLMul_x
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_x);
// 使能PLL
RCC_PLLCmd(ENABLE);
// 等待PLL稳定
while( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET );
// 选择系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while( RCC_GetSYSCLKSource() != 0x08 );
}
else
{
/* 如果HSE 启动失败,用户可以在这里添加处理错误的代码 */
}
}
void HSI_SetSysClk( uint32_t RCC_PLLMul_x )
{
__IO uint32_t HSIStatus = 0;
// 把RCC 寄存器复位成复位值
RCC_DeInit();
// 使能 HSI
RCC_HSICmd(ENABLE);
HSIStatus = RCC->CR & RCC_CR_HSIRDY;
if( HSIStatus == RCC_CR_HSIRDY )
{
// 使能预取指
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
// 配置 PLLCLK = HSE * RCC_PLLMul_x
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_x);
// 使能PLL
RCC_PLLCmd(ENABLE);
// 等待PLL稳定
while( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET );
// 选择系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while( RCC_GetSYSCLKSource() != 0x08 );
}
else
{
/* 如果HSI 启动失败,用户可以在这里添加处理错误的代码 */
}
}
void MCO_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/*RCC晶振8M改为12MHz .h程序 */
#ifndef __BSP_RCCCLKCONFIG_H
#define __BSP_RCCCLKCONFIG_H
#include "stm32f10x.h"
void HSE_SetSysClk( uint32_t RCC_PLLMul_x );
void MCO_GPIO_Config(void);
void HSI_SetSysClk( uint32_t RCC_PLLMul_x );
#endif /*__BSP_RCCCLKCONFIG_H */
/*systick延时 .C程序 */
#include "bsp_systick.h"
#if 0
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
// 判断 tick 的值是否大于 2^24,如果大于,则不符合规则
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
// 初始化reload寄存器的值
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
// 配置中断优先级,配置为15,默认为最低的优先级
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
// 初始化counter的值为0
SysTick->VAL = 0;
// 配置 systick 的时钟为 72M
// 使能中断
// 使能systick
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
#endif
void SysTick_Delay_us(uint32_t us)
{
uint32_t i;
SysTick_Config(72);
for(i=0; i<us; i++)
{
while( !((SysTick->CTRL) & (1<<16)) );
}
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
void SysTick_Delay_ms(uint32_t ms)
{
uint32_t i;
SysTick_Config(72000);
for(i=0; i<ms; i++)
{
while( !((SysTick->CTRL) & (1<<16)) );
}
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}
/*systick延时 .h程序 */
#ifndef __BSP_SYSTICK_H
#define __BSP_SYSTICK_H
#include "stm32f10x.h"
#include "core_cm3.h"
void SysTick_Delay_us(uint32_t us);
void SysTick_Delay_ms(uint32_t ms);
#endif /* __BSP_SYSTICK_H */

 


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011878611/article/details/109757325

随机推荐

Vue.js8个周期

Vue.js8个周期beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedbeforeDestroydestroyed ...

奶油小孙 阅读(286)

vb.net TextBox 失去焦点和获得焦点

创建一个TextBox,id=tBox,获得焦点就是tBox.Focus(),失去焦点的方法没有找到,只是找到了失去焦点的事件:Priv...

chinaherolts2008 阅读(513)

SSM常见面试题

SpringMVC篇什么是SpringMVC?简单介绍下你对springMVC的理解?SpringMVC是一个基于JAVA实现了MVC设计模式的请求驱动类型的轻量级Web框架࿰...

守望码灵 阅读(732)

2.7 使用PyTorch实现简单机器学习

2.7使用PyTorch实现简单机器学习本篇我们将使用PyTorch的一个自动求导的包——Antograd,利用这个包及对应的Tensor,利用自动反向求导传播来求梯度&#...

清风有信 阅读(297)

二叉树的中序遍历

packagedaily20210105;importjava.util.ArrayList;importjava.util.List;importjava.util.Stack;/***@a...

Zhao1iang 阅读(948)

c++ vs 创建删除文件_Linux的文件管理

今天学习了Linux里边的一些文件管理的基础命令在这里跟大分享一下,下面呢咱们正式开始。这里呢大家要先知道liunx的目录结构,他是跟Windows不同的Windows:以...

希夷文化苑 阅读(919)

2021-01-05java面向对象及三大特性-封装继承多态

面向过程     步骤清晰简单,计划每一步做什么     适合处理一些简单的问题面向对象      分类的思维模式,思考问题首先将需要解决的分类,然后对这些分...

weixin_44528634 阅读(271)