大家好,欢迎来到IT知识分享网。
OskarBot小车驱动(四)、舵机驱动与转向
【目录】
– 1、舵机硬件
– 1.1 舵机的内部结构
– 1.2 舵机的闭环检测机制
– 2、舵机的控制
– 2.1 舵机的控制信号
– 2.2 舵机的控制原理
– 2.3 舵机的追随特性
– 3、舵机控制代码详解
– 3.1 模拟方式产生PWM信号
– 3.2 利用定时器直接输出PWM信号
– (1)修改CCR值,改变PWM的高电平持续时间
– (2)优化:一个定时器输出多个通道,控制多个舵机
– 3.3 利用定时器中断,修改定时时间,改变高电平持续时间
– (1)舵机结构体数组设置
– (2)TIM3定时器初始化设置
– (4)舵机引脚初始化
– (5)定时器3的中断处理函数
– (6)计算舵机设置参数,更新当前值cur
– (7)通过手柄按键值,更新舵机参数
1、舵机硬件
给舵机一个一定宽度的脉冲信号,舵机就会用自身产生的标准宽度的脉冲信号与其相比较。
如果有差别,舵机就自动调谐自身的脉冲宽度,直到与之一至为止。此时舵机也就转动下好是要求的角度。
参考:舵机和舵机控制版、步进电机、伺服电机
来自 <OskarBot小车驱动(四)、舵机驱动与转向>
1.1 舵机的内部结构
舵机和步进电机的动力是有着很大区别的,舵机的驱动力来自——直流电机,通过变速齿轮的传动和变速,将动力传输到输出轴,同时,舵机内部都设有角度传感器和控制电路板,用来参与舵机的转动角度的控制和信号的反馈检测工作。
位置检测器(角度传感器)是它的输入传感器,舵机转动的位置变化,位置检测器的电阻值就会跟着变化。通过控制电路读取该电阻值的大小,就能根据阻值适当调整电机的速度和方向,使电机向指定角度旋转
1.2 舵机的闭环检测机制
2、舵机的控制
2.1 舵机的控制信号
舵机有很多规格但所有的舵机都有外接三根线, 分别用棕、红、橙三种颜色进行区分,由于舵机品牌不同,颜色也会有所差异,
棕色为接地线, 红色为电源正极线,橙色为信号线。
舵机的转动的角度是通过调节PWM(脉冲宽度调制)信号的占空比来实现的,
标准PWM(脉冲宽度调制)信号的周期固定为20ms(50Hz),理论上脉宽分布应在1ms 到2ms 之间,
但是,事实上脉宽可由0.5ms 到2.5ms 之间,脉宽和舵机的转角0°~180°相对应。
脉宽调制(PWM)信号,如下图,直观反映了PWM信号和舵机转动角度的关系,你也可以简单的理解为,通过给舵机通电的时间控制,结合角度传感器的反馈信号检测和控制,实现了舵机的精确角度控制。
2.2 舵机的控制原理
内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。
最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。
舵机的原理和控制,保持时间为Tw;
当Tw≥△T时,舵机能够到达目标,并有剩余时间;
当Tw≤△T时,舵机不能到达目标;
理论上:当Tw=△T时,系统最连贯,而且舵机运动的最快。
实际过程中w不尽相同,连贯运动时的极限△T比较难以计算出来
2.3 舵机的追随特性
假设现在舵机稳定在A点,这时候CPU发出一个PWM信号,舵机全速由A点转向B点,在这个过程中需要一段时间,舵机才能运动到B点。
3、舵机控制代码详解
3.1 模拟方式产生PWM信号
模拟高低电平持续时间
/*角度范围 0~180*/ int Angle_J1 = 0; int Angle_J2 = 0;
/* Function Servo_J1 * @brief 舵机1控制函数 * @param[in] v_iAngle 角度:0~180° */ void Servo_J1(int v_iAngle)/*定义一个脉冲函数,用来模拟方式产生PWM值*/ { int pulsewidth; //定义脉宽变量 pulsewidth = (v_iAngle * 11) + 500; //将角度0~180转化为500-2480 的脉宽值 GPIO_SetBits(Servo_J1_PORT, Servo_J1_PIN ); //将舵机接口电平置高 delay_us(pulsewidth); //延时脉宽值的微秒数,90度脉宽1490,延时1490us=1.5ms GPIO_ResetBits(Servo_J1_PORT, Servo_J1_PIN ); //将舵机接口电平置低 delay_ms(20 – pulsewidth/1000); //延时周期内剩余时间,延时20-1.5ms }
/* Function Servo_J2 * @brief 舵机2控制函数 * @param[in] v_iAngle 角度:0~180° */ void Servo_J2(int v_iAngle)/*定义一个脉冲函数,用来模拟方式产生PWM值*/ { int pulsewidth; //定义脉宽变量 pulsewidth = (v_iAngle * 11) + 500; //将角度转化为500-2480 的脉宽值 GPIO_SetBits(Servo_J2_PORT, Servo_J2_PIN ); //将舵机接口电平置高 delay_us(pulsewidth); //延时脉宽值的微秒数 GPIO_ResetBits(Servo_J2_PORT, Servo_J2_PIN ); //将舵机接口电平置低 delay_ms(20 – pulsewidth/1000); //延时周期内剩余时间 } |
云台舵机控制
/* Function front_detection * @brief 云台舵机向前 */ void front_detection() { int i = 0; //此处循环次数减少,为了增加小车遇到障碍物的反应速度 for(i=0; i <= 15; i++) //产生PWM个数,等效延时以保证能转到响应角度 { Servo_J1(90); //模拟产生PWM } }
/* Function left_detection * @brief 云台舵机向左 */ void left_detection() { int i = 0; for(i = 0; i <= 15; i++) //产生PWM个数,等效延时以保证能转到响应角度 { Servo_J1(175); //模拟产生PWM } }
/* Function right_detection * @brief 云台舵机向右 */ void right_detection() { int i = 0; for(i = 0; i <= 15; i++) //产生PWM个数,等效延时以保证能转到响应角度 { Servo_J1(5); //模拟产生PWM } } |
3.2 利用定时器直接输出PWM信号
(1)修改CCR值,改变PWM的高电平持续时间
配置STM32的定时器和GPIO复用功能,然后就是通过修改定时器计数器的比较寄存器的数值来达到控制PWM的高电平占空比的目的。
ARR+PSC+时钟决定周期,CCR值决定高电平持续时间。
一、配置定时器工作在PWM模式
二、使用TIM_SetCompare(TIMX,CCR)更新CCR值
定时器输出PWM控制,利用TIM2的CH1(PA0)和TIM2的CH2(PA1)输出PWM波形。
void RCC_config_TIM2_CH1(void ) { //使能复用以及GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE); //使能TIM2时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
}
void TIM2_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; //PA0—TIM2通道1复用引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure); //PA1–TIM2通道2复用引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure); }
void TIM2_CH1_PWM_OUT(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t Prescaler =0,Period,Pulse;
Prescaler = /*72M/36M – 1=*/ 1; Period = 36000-1; Pulse = 36000 *0.2;
/* TIM2时基单元配置 重要配置:TIM_Prescaler(预分频值)TIM_Period(定是周期) 将TIM_Period设置成999,则计数器会数1000个(TIM_Period+1) 节拍为一个定时器的周期。这个和后面需要配置的TIM_Pulse共同 控制着定时器输出波形的占空比。
TIM_Prescaler用来指定TIM时钟的分频值。也就是说它是进一步来 分频TIM clock的。 简单来说也就是定时器每一次数数的时间间隔是多少。 */ TIM_TimeBaseStructure.TIM_Prescaler = Prescaler;//预分频值 TIM_TimeBaseStructure.TIM_CounterMode = TIM_OutputState_Enable; TIM_TimeBaseStructure.TIM_Period = Period;//定是周期 决定输出频率 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频因子 TIM_TimeBaseInit( TIM2, &TIM_TimeBaseStructure);
/* TIM2通道1:pwm模式配置 重要配置:TIM_Pulse(脉冲宽度) */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = Pulse; TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; TIM_OC1Init( TIM2, &TIM_OCInitStructure); TIM_OC2Init( TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);//使能或者失能 TIMx 在 CCR1 上的预装载寄存器 TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);//使能或者失能 TIMx 在 CCR2 上的预装载寄存器 TIM_ARRPreloadConfig(TIM2, ENABLE);//使能或者失能 TIMx 在 ARR 上的预装载寄存器 TIM_Cmd(TIM2, ENABLE); }
void ADVANCE_TIM2_Init(void) { RCC_config_TIM2_CH1(); TIM2_GPIO_Config(); TIM2_CH1_PWM_OUT(); TIM_SetCompare1(TIM2 , 100); // 修改 TIM1_CCR1 来控制占空比 TIM_SetCompare2(TIM2 , 100); // 修改 TIM1_CCR2 来控制占空比 } |
定时器初始化配置完之后,在主循环之前要对舵机进行一次复位操作,从上面的图中可以看出,在高电平持续时间为1.5ms的情况下,舵机能够回中,因此需要调用以下函数,设置定时器的初始比较寄存器的值为1500,即可以使得定时器复位回中。
TIM_SetCompare1(TIM2 , 1500); // 修改 TIM1_CCR1 来控制占空比
(2)优化:一个定时器输出多个通道,控制多个舵机
参考:STM32之使用PWM控制多路舵机
来自 <OskarBot小车驱动(四)、舵机驱动与转向>
定时器TIM3,输出CH1~4共计4个通道,直接改变CCR值改变高电平持续时间。
3.3 利用定时器中断,修改定时时间,改变高电平持续时间
定时器仅用来定时,不用于输出电平时,利用TIM3定时器来设置舵机引脚高电平持续时间。
每次进入定时器中断,根据获取的舵机参数当前值cur,修改定时器参数arr值,改变定时时间,设定引脚先后输出高、低电平。
轮询8个舵机,完成所有舵机的高、低电平脉宽设置;
(1)舵机结构体数组设置
servo duoji_doing[DJ_NUM];//舵机结构体变量,数组,8个 u8 duoji_index1;//舵机序号0~7 /* “servo.h”,舵机定义结构体 typedef struct { uint8_t valid;//有效 TODO uint16_t aim; //执行目标 uint16_t time; //执行时间 float cur; //当前值 float inc; //增量 }servo; */ |
(2)TIM3定时器初始化设置
/Servo PWM timer 定时器3初始化接口:为舵机提供,20ms周期的时钟 *这里时钟选择APB1为36Mhz,而APB2为72MHz * TIM3_Int_Init(20000, 72); //Tout=arr*psc/(Tclk=36M)=20000*72/36M=40ms; * 貌似不对,应该选择 APB2=72MHz,Tout=arr*psc/(Tclk=72M)=20000*72/72M=20ms; * 定时时间,默认值,高电平持续:Tout=arr*psc/(Tclk=36M)=1500*72/72M=1500us=1.5ms(舵机角度为0°) * 定时时间,低电平持续:Tout=arr*psc/(Tclk=36M)=18500*72/72M=18500us=18.5ms(高电平+低电平,整个周期20ms) */ void TIM3_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3的时钟, //定时器参数设置,舵机初始化 TIM3_Int_Init(20000, 72); TIM_TimeBaseStructure.TIM_Period = arr; //自动重装值20000,计数到20000个 TIM_TimeBaseStructure.TIM_Prescaler = (psc-1); //预分频72,时钟 36MHz/ TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 TIM_ARRPreloadConfig(TIM3, DISABLE);//使能ARR重载 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断参数 NVIC设置 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断 //NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x0000); NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级0级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 TIM_Cmd(TIM3, ENABLE); //使能TIM3 } |
(4)舵机引脚初始化
// 伺服类外设(数字舵机)初始化 // 1、引脚初始化 SERVO1~7,使能引脚时钟,引脚复用功能输出 // 2、定时器初始化 void servo_init(void) { GPIO_InitTypeDef GPIO_InitStructure; u8 i;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_12|GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIO_InitStructure);
//舵机控制初始化 for(i=0;i<DJ_NUM;i++) { duoji_doing[i].cur = 1500;//当前值1500,1.5ms,0度 duoji_doing[i].inc = 0;//增量,初始值为0 }
// duoji_doing[0].cur = 1700; // duoji_doing[0].inc = 0;
duoji_index1 = 0;//默认舵机序号为0 systick_ms = 0;
//舵机定时器初始化, TIM3_Int_Init(20000, 72); } |
(5)定时器3的中断处理函数
/定时器3中断处理:每次更新ARR值,更新定时时间; *无需周期循环,一次高低电平周期就完成一个舵机的角度设置,接下去设置下一个舵机 *按键没按下,不更新CUR值,用默认的CUR=1500(1.5ms,0°)初始化所有舵机设置; *有按键按下,对应舵机的CUR值更新,duoji_index1递增到对应舵机时,更新定时器的值,改变高电平持续时间(其它舵机继续默认设置) / void TIM3_IRQHandler(void) { static u8 flag = 0;//标志位,循环设置高低电平 static u8 duoji_index1 = 0;//舵机序号(0~7),SERVO 1~8,标志位 int temp;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3中断发生与否 { TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIM3的中断待处理位
if(duoji_index1 == 8) //计数到8,清0 { duoji_index1 = 0; }
if(!flag) //进入中断处理,高电平定时周期 { TIM3->ARR = ((unsigned int)(duoji_doing[duoji_index1].cur));//更新定时时间周期,以舵机当前值cur,更新ARR值,更新定时时间 cur=1500对应1.5ms servo_set(duoji_index1, 1);//0号舵机,信号线SERVO 1,引脚PB12,赋值为1,高电平 duoji_inc_handle(duoji_index1);//舵机处理函数uoji_inc_handle,更新舵机结构体数组的当前值cur } else//进入中断处理,低电平定时周期,flag=1,duoji_index1=0; { //temp = 2500 – (unsigned int)(duoji_doing[duoji_index1].cur);//高低电平周期(20ms)-当前值=2500-1500=1000 //感觉不对,应该是20ms后剩下的整个时间,如下: temp = 20000 – (unsigned int)(duoji_doing[duoji_index1].cur);//高低电平周期(20ms)-当前值=20000-1500=18500 if(temp < 20) temp = 20;//快要接近180°了, TIM3->ARR = temp;//18500,对应18.5ms servo_set(duoji_index1, 0);//低电平 duoji_index1 ++;//处理下一个舵机,所有舵机完成初始化设置,flag=0,duoji_index1=1; } flag = !flag;//标志位,循环处理高低电平 } } |
(6)计算舵机设置参数,更新当前值cur
//舵机设置参数函数,更新舵机结构体数组的当前值cur(默认1500) void duoji_inc_handle(u8 index) { if(duoji_doing[index].inc != 0) { if(abs_float(duoji_doing[index].aim – duoji_doing[index].cur) <= abs_float(duoji_doing[index].inc + duoji_doing[index].inc)) //变化幅度较小,目标-当前值<= 增量+增量 { duoji_doing[index].cur = duoji_doing[index].aim;//目标值,直接赋值为当前值 duoji_doing[index].inc = 0; } else //变化幅度较大,目标-当前值>增量+增量 1900-1500>26+26 { duoji_doing[index].cur += duoji_doing[index].inc;//前一个数值+增量=1500+26,重新赋值为当前值 } } } |
(7)通过手柄按键值,更新舵机参数
/*根据手柄按键值,更新舵机参数:aim目标值;time执行时间inc增量 前置关键函数(ps2.c):parse_psx_buf(psx_buf+1, psx_buf[0]);将按键值的首地址,手柄模式,结合对应的常量字符数组,组合成uart_receive_buf[8]的字符串 *处理串口1接收到的uart_receive_buf字符命令, //”#001P0600T2000!^$DST:1!>”, //LU,上 //”#000P1900T0300!^#000P1500T0300!>”, //LR,右 //”#001P2400T2000!^$DST:1!>”, //LD,下 //”#000P1100T0300!^#000P1500T0300!>”, //LL,左 */ void do_action(u8 *uart_receive_buf) { u16 index, pwm, time,i; zx_uart_send_str(uart_receive_buf);//串口1接收数据,通过串口3转发出去tb_usart3_send_str(str) i = 0; while(uart_receive_buf[i]) { if(uart_receive_buf[i] == ‘#’) { index = 0; i++; while(uart_receive_buf[i] && uart_receive_buf[i] != ‘P’) { index = index*10 + uart_receive_buf[i]-‘0’; //index数值:0~5;0:LR,LL;1:LU,LD;2:RU三角,RD叉;3:RR圆圈,RL方;4:L1,L2;5:R1,R2; //不同按键,控制不同的舵机? i++; } } else if(uart_receive_buf[i] == ‘P’) { pwm = 0; i++; while(uart_receive_buf[i] && uart_receive_buf[i] != ‘T’) { pwm = pwm*10 + uart_receive_buf[i] – ‘0’;//pwm系数(目标值),一般都是0600(LU)与2400(LD);除了 LR右(1900)与LL左(1100),右转1.9ms(45°,2ms),左转1.1ms(-45°,1ms) i++; } } else if(uart_receive_buf[i] == ‘T’) { time = 0; i++; while(uart_receive_buf[i] && uart_receive_buf[i] != ‘!’) { time = time*10 + uart_receive_buf[i]-‘0’;//time系数,一般都是2000();除了 LR右与LL左,两个都是0300 i++; }
if(index < DJ_NUM && (pwm<=2500)&& (pwm>=500) && (time<=10000)) { //duoji_doing[index].inc = 0; if(duoji_doing[index].cur == pwm)pwm += 0.1; if(time < 20)time = 20; duoji_doing[index].aim = pwm;//目标值,1900 与 1100,或 2400 与 0600 duoji_doing[index].time =time;//执行时间,300 或 2000 duoji_doing[index].inc = (duoji_doing[index].aim – duoji_doing[index].cur) / (duoji_doing[index].time/20.000);//更新舵机结构体数组,增量inc = (目标值-当前值)/(执行时间300/20) //LR右与LL左,index系数为0控制第0个舵机,pwm系数为1900,time执行时间300,inc=26 }
//sprintf(cmd_return, “#%dP%dT%d!\r\n”, index, pwm, time, duoji_doing[index].inc); //uart1_send_str(cmd_return);
} else { i++; } } } |
2018年12月26日 星期三
22:56
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/140928.html