树莓派遇上Java 04:舵机篇

本文时间久远,部分内容可能过时,仅供参考。

马达篇介绍过如何用树莓派控制马达转动,但在一些情况下不仅想让其转动,还想让它转动到指定角度并固定在那个位置,这时就要用到伺服电机/舵机,它们本质也是马达,但是通过内置的闭环控制系统和接收的特定信号可以实现旋转特定角度的功能。

舵机通常用在控制遥控车前轮转向、机器人关节活动、机械臂运动这些对位置精度要求较高的地方,它需要的控制信号也不是简单的高低电平,而是PWM波。

舵机 Servo

舵机有三根线,黄色信号、红色正极和褐色负极,正负极可以接在L298N的5伏电压输出口和负极上,信号线则接在树莓派PWM针脚上。

舵机使用的PWM波是一种周期20毫秒(即50赫兹)由高低电平组成的控制信号,通过控制高电平所持续的时间就可以让舵机到达指定的角度。一般来说,对于180度舵机高电平在信号周期内的持续时间和舵机角度有以下关系:

0.5毫秒----------0度 
1.0毫秒----------45度 
1.5毫秒----------90度 
2.0毫秒----------135度 
2.5毫秒----------180度

即如果想让舵机转到45度位置并保持该角度,那么需要向舵机输出一个周期是20毫秒的信号,并且信号的前1毫秒是高电平,后19毫秒是低电平。由于舵机的运动需要时间,所以这个信号要足够长才能保证舵机移动到正确的位置。如果就位后信号仍然在持续,舵机内部将产生一个力矩来阻止外力改变其角度,固定在该位置。

树莓派3B输出PWM波有两种方法,首先它拥有23个能输出高低电平信号的GPIO针脚,可以通过软件控制它们产生所需的波形,只是非硬件的计时器不够精确,即使成功让舵机转动也不可避免会出现抖动,效果并不理想。此外,树莓派3B本身配有的4个PWM针脚可以精确输出波形,但是在软件层如何设定参数以实现正确的波形输出很令人困惑,我在StackOverFlow上找到下面一段话:

It says here that we're looking to create pulse of 1ms to 2ms in length, every 20ms or so. Assuming this 19.2Mhz base clock is indeed correct, setting pwm clock to 400 and pwm range to 1000, should give a pulse at 48Hz or every 20.8 ms. Then setting pwm value to 48 should give you a 1ms long pulse and a pwm value of 96 should give you a 2ms long pulse. But you need to set the chip in pwm-ms mode. (Lots of shoulds here, since I do not have an osciolloscope either)

这段话提到时钟频率这种我并不熟悉的硬件概念,重要的是随后提供的一些数据,尝试几次,我找到了能准确控制自己舵机的参数,下面是一个简单示例:

import com.pi4j.io.gpio.*;
import com.pi4j.wiringpi.Gpio;

/**
 * Created by apqx on 2016/9/11.
 * 
 * 此程序中的PWM值仅适用于DS3218舵机,其它型号舵机请自行尝试PWM值
 * 
 * DS3218:可180度旋转,pwm控制旋转到固定的角度
 * pwm=70则舵机处于中位
 * pwm=117则舵机相对中位逆时针旋转90度
 * pwm=23则舵机相对中位顺时针旋转90度
 */
public class Demo {

    public static void main(String[] args) throws Exception {
        GpioController gpio = GpioFactory.getInstance();
        // DS3218
        GpioPinPwmOutput DS3218 = gpio.provisionPwmOutputPin(RaspiPin.GPIO_26);
        Gpio.pwmSetMode(Gpio.PWM_MODE_MS);
        Gpio.pwmSetRange(1000);
        Gpio.pwmSetClock(400);
        // 舵机处于中位
        DS3218.setPwm(70);
        // 舵机相对中位顺时针旋转90度
        DS3218.setPwm(23);
        // 则舵机相对中位逆时针旋转90度
        DS3218.setPwm(117);
        
        gpio.shutdown();
    }

}

树莓派3B有4个PWM输出针脚:GPIO01GPIO26GPIO23GPIO24,但实际上GPIO01GPIO26都是PWM0GPIO23GPIO24都是PWM1,也就是说GPIO01GPIO26GPIO23GPIO24的输出信号分别是一样的,控制一个针脚输出,另一个对应的针脚也会同时输出相同的信号。这就很尴尬,意味着接在对应针脚上的舵机会自动随之做同样的动作。

解决方法是在软件上做手脚,比如,当控制GPIO01舵机运动前禁止GPIO26输出,当控制GPIO26舵机运动前禁止GPIO01输出并恢复GPIO26的输出,这样即使它们同属PWM0也可以做到单独控制,只是不能同时。

import com.pi4j.io.gpio.*;
import com.pi4j.wiringpi.Gpio;

/**
 * Created by apqx on 2016/9/11.
 * 
 * 此程序中的PWM值仅适用于DS3218舵机,其它型号舵机请自行尝试PWM值
 * 
 * DS3218:可180度旋转,pwm控制旋转到固定的角度
 * pwm=70则舵机处于中位
 * pwm=117则舵机相对中位逆时针旋转90度
 * pwm=23则舵机相对中位顺时针旋转90度
 */
public class Demo {
    private static GpioPinPwmOutput DS3218_1;
    private static GpioPinPwmOutput DS3218_2;
    private static GpioController gpio;

    public static void main(String[] args) throws Exception{
        gpio = GpioFactory.getInstance();
        initPWM();
        // DS3218
        DS3218_1 = gpio.provisionPwmOutputPin(RaspiPin.GPIO_26);
        DS3218_2 = gpio.provisionPwmOutputPin(RaspiPin.GPIO_01);

        // DS3218_1输出前停止DS3218_2输出
        stopDS3218_2();
        DS3218_1.setPwm(50);

        // 延时1秒等待舵机到达指定角度
        Thread.sleep(1000);

        // DS3218_2输出前停止DS3218_1输出,并恢复DS3812_2输出
        stopDS3218_1();
        startDS3218_2();
        DS3218_2.setPwm(50);

        // 延时1秒等待舵机到达指定角度
        Thread.sleep(1000);

        gpio.shutdown();

    }
    // 停止DS3218_1输出
    public static void stopDS3218_1() {
        DS3218_1.unexport();
        gpio.unprovisionPin(DS3218_1);
    }
    // 恢复DS3218_1输出
    public static void startDS3218_1() {
        DS3218_1 = gpio.provisionPwmOutputPin(RaspiPin.GPIO_26);
        initServo();

    }
    // 停止DS3218_2输出
    public static void stopDS3218_2() {
        DS3218_2.unexport();
        gpio.unprovisionPin(DS3218_2);
    }
    // 恢复DS3218_2输出
    public static void startDS3218_2() {
        DS3218_2 = gpio.provisionPwmOutputPin(RaspiPin.GPIO_01);
        initServo();

    }
    // 设置控制舵机的PWM初始参数
    private static void initPWM() {
        Gpio.pwmSetMode(Gpio.PWM_MODE_MS);
        Gpio.pwmSetRange(1000);
        Gpio.pwmSetClock(400);
    }

}

具体使用逻辑需要考虑各种情况做出合理判断以避免错误,但如果只是让舵机动起来,就是这么简单。

arrow_upward