树莓派遇上Java 04:舵机篇
立泉本文时间久远,部分内容可能过时,仅供参考。
马达篇介绍过如何用树莓派
控制马达转动,但在一些情况下不仅想让其转动,还想让它转动到指定角度并固定在那个位置,这时就要用到伺服电机
/舵机
,它们本质也是马达,但是通过内置的闭环控制系统和接收的特定信号可以实现旋转特定角度的功能。
舵机
通常用在控制遥控车前轮转向、机器人关节活动、机械臂运动这些对位置精度要求较高的地方,它需要的控制信号也不是简单的高低电平,而是PWM
波。
舵机
有三根线,黄色信号、红色正极和褐色负极,正负极可以接在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
输出针脚: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.
*
* 此程序中的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);
}
}
具体使用逻辑需要考虑各种情况做出合理判断以避免错误,但如果只是让舵机
动起来,就是这么简单。