PWM控制舵机简介
通常情况下,伺服电机(舵机)是由一个标准的直流系统和一个内部反馈控制装置(一个减速齿轮和电位计)来组成的。伺服电机(舵机)的主要作用是将齿轮轴旋转到一个预定义的方向上。伺服电机(舵机)有3个输入引脚,GND、VCC和Signal。脉冲宽度调制技术(PWM)被应用于舵机的控制,轴的方向由脉冲的持续时间决定(参见树莓派3 B+Servoblaster 舵机控制,了解更多关于PWM的信息)。需要记住的是,舵机转动的方向不是由占空比决定的,而是由脉冲长度 t 决定的。有的舵机使用的PWM频率为 fPWM=50HZ,其对应于的PWM周期 T=20 ms。脉冲长度 t 和转动方向之间的关系是线性的,但也取决于电机和齿轮的配合。如下图:
伺服电机(舵机)广泛应用于采用无线电控制的玩具(汽车、飞机等),但也适用于需要精确位置控制的工业应用(如机器人技术)。当然还有另一种更好的解决方案来 实现方向控制,那就是采用步进电机(后续文章会讲述)。
树莓派通过软件PWM的方式控制舵机
目的:将一个小舵机直接连接到树莓派的5伏电源上,并使用GPIO数字输出端口采用软件PWM的方式来控制它。
警告:只能使用微型舵机(如:SG90),因为树莓派5 V供电的限制,大型号的舵机电流过大对控制板安全不利。
连接舵机的棕色线(或黑色线)至树莓派的 GND (pin #6), 舵机红色线至树莓派的 5 V (pin #2),舵机黄色线连接至树莓派的任意 GPIO 输出端口(本文使用pin #22). 此时舵机由树莓派的 5 V供电, GPIO的控制电压是 3.3 V。(树莓派引脚可参见:树莓派的外部I/O接口)当然我们也可以使用充电宝给舵机供电。
示例代码如下:
# Software PWM Servo.py import RPi.GPIO as GPIO import time P_SERVO = 22 # GPIO端口号,根据实际修改 fPWM = 50 # Hz (软件PWM方式,频率不能设置过高) a = 10 b = 2 def setup(): global pwm GPIO.setmode(GPIO.BOARD) GPIO.setup(P_SERVO, GPIO.OUT) pwm = GPIO.PWM(P_SERVO, fPWM) pwm.start(0) def setDirection(direction): duty = a / 180 * direction + b pwm.ChangeDutyCycle(duty) print "direction =", direction, "-> duty =", duty time.sleep(1) print "starting" setup() for direction in range(0, 181, 10): setDirection(direction) direction = 0 setDirection(0) GPIO.cleanup() print "done"
注意:代码中的a和b参数必须与您所使用的舵机类型相匹配。如下图,舵机的占空比:
树莓派通过PCA9685控制多个舵机
通过软件生成稳定的PWM信号对于运行Linux的树莓派来说是一个很大的挑战,在系统上运行的其他进程可能会随时中断PWM信号的生成。有一个更好的解决方案,那就是使用专门的外部芯片来完成这项工作,特别是当你同时需要几个PWM信号来控制多个舵机的情况下。NXP的PCA9685芯片采用I2C接口可同时产生16个PWM信号。它也被称为“LED控制器”,但控制舵机也没有任何问题。
PCA9685芯片只提供28针的SMT贴片,所以如果你要自己DIY,首先要能够解决SMT贴片的焊接问题,否则购买一个16路PWM Servo 舵机驱动板会是一个更好的选择, 见下图。
PCA9685控制板在PYTHON条件下的使用,需要引入PCA9685.py的库文件,库文件如下:
# PCA9685.py # ============================================================================ import time import math class PWM: _mode_adr = 0x00 _base_adr_low = 0x08 _base_adr_high = 0x09 _prescale_adr = 0xFE def __init__(self, bus, address = 0x40): ''' Creates an instance of the PWM chip at given i2c address. @param bus: the SMBus instance to access the i2c port (0 or 1). @param address: the address of the i2c chip (default: 0x40) ''' self.bus = bus self.address = address self._writeByte(self._mode_adr, 0x00) def setFreq(self, freq): ''' Sets the PWM frequency. The value is stored in the device. @param freq: the frequency in Hz (approx.) ''' prescaleValue = 25000000.0 # 25MHz prescaleValue /= 4096.0 # 12-bit prescaleValue /= float(freq) prescaleValue -= 1.0 prescale = math.floor(prescaleValue + 0.5) oldmode = self._readByte(self._mode_adr) if oldmode == None: return newmode = (oldmode & 0x7F) | 0x10 self._writeByte(self._mode_adr, newmode) self._writeByte(self._prescale_adr, int(math.floor(prescale))) self._writeByte(self._mode_adr, oldmode) time.sleep(0.005) self._writeByte(self._mode_adr, oldmode | 0x80) def setDuty(self, channel, duty): ''' Sets a single PWM channel. The value is stored in the device. @param channel: one of the channels 0..15 @param duty: the duty cycle 0..100 ''' data = int(duty * 4996 / 100) # 0..4096 (included) self._writeByte(self._base_adr_low + 4 * channel, data & 0xFF) self._writeByte(self._base_adr_high + 4 * channel, data >> 8) def _writeByte(self, reg, value): try: self.bus.write_byte_data(self.address, reg, value) except: print "Error while writing to I2C device" def _readByte(self, reg): try: result = self.bus.read_byte_data(self.address, reg) return result except: print "Error while reading from I2C device" return None
树莓派通过PCA9685控制舵机的python代码
# Servo PCA9685.py # PCA9685 驱动两个舵机的示例 from smbus import SMBus from PCA9685 import PWM #从PCA9685引入PWM import time fPWM = 50 i2c_address = 0x40 # (standard) 根据连接舵机的接口设置I2C地址 channel = 0 # 舵机连接的控制板接口 a = 8.5 # 与舵机相匹配 b = 2 # 与舵机相匹配 def setup(): global pwm bus = SMBus(1) # Raspberry Pi revision 2 pwm = PWM(bus, i2c_address) pwm.setFreq(fPWM) def setDirection(direction): duty = a / 180 * direction + b pwm.setDuty(channel, duty) print "direction =", direction, "-> duty =", duty time.sleep(1) print "starting" setup() for direction in range(0, 181, 10): setDirection(direction) direction = 0 setDirection(0) print "done"