树莓派遇上 Java 04:舵机篇
立泉本文时间久远,部分内容可能过时,仅供参考。
马达篇介绍过如何用树莓派控制马达转动,但在一些情况下不仅要让其转动,还想控制转动到指定角度并固定在那个位置。这时要用到伺服电机/舵机,它们本质也是马达,通过内置的闭环控制系统和接收的特定信号可实现旋转特定角度的功能。
舵机通常用在控制遥控车前轮转向、机器人关节活动、机械臂运动这些对位置精度要求较高的地方,它需要的控制信号不是简单的高低电平,而是 PWM 波。
舵机有三根线,黄色信号、红色正极和褐色负极,正负极可接在 L298N 的 5V 电压输出口和负极上,信号线则接在树莓派的 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)
文中提到时钟频率这种我不熟悉的硬件概念,关键是随后列出的一些参考数据,我尝试几次后成功找到控制 DS3218 的参数,下面是一个简单示例:
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 输出针脚:GPIO01、GPIO26、GPIO23、GPIO24,但实际上 GPIO01 和 GPIO26 都是 PWM0,GPIO23 和 GPIO24 都是 PWM1,即 GPIO01 和 GPIO26、GPIO23 和 GPIO24的输出信号分别是一样的,控制一个针脚输出,另一个对应的针脚也会同时输出相同信号。这就很尴尬,意味着接在对应针脚上的舵机会随之做相同动作。
解决方法是在软件上做手脚,比如,控制 GPIO01 运动前禁止 GPIO26 输出,控制 GPIO26 运动前禁止 GPIO01 输出并恢复 GPIO26 输出,这样即使它们同属 PWM0 也可以做到单独控制,只是不能同时。
import com.pi4j.io.gpio.*;
import com.pi4j.wiringpi.Gpio;
/**
* Created by apqx on 2016/9/11.
*/
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);
}
}
具体使用逻辑需考虑实际情况做出合理判断避免错误,如果只是让舵机动起来,就是这么简单。