OskarBot小车驱动(四)、舵机驱动与转向

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、舵机硬件

给舵机一个一定宽度的脉冲信号,舵机就会用自身产生的标准宽度的脉冲信号与其相比较。

如果有差别,舵机就自动调谐自身的脉冲宽度,直到与之一至为止。此时舵机也就转动下好是要求的角度。

参考:舵机和舵机控制版、步进电机、伺服电机

来自 <https://blog.csdn.net/vict_wang/article/details/81559071>

 

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;

当TwT时,舵机能够到达目标,并有剩余时间;

当TwT时,舵机不能到达目标;

理论上:当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控制多路舵机

来自 <https://blog.csdn.net/weixin_37127273/article/details/80492288>

定时器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周期的时钟

*这里时钟选择APB136Mhz,而APB272MHz

* 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=15001.5ms0°)初始化所有舵机设置;

*有按键按下,对应舵机的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++;

        }

    }  

}

20181226 星期三

22:56

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值