树莓派PWM控制舵机的两种方式

  • 内容
  • 评论
  • 相关

树莓派通过PCA9685控制舵机

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控制器”,但控制舵机也没有任何问题。

NXp pca9685

PCA9685芯片只提供28针的SMT贴片,所以如果你要自己DIY,首先要能够解决SMT贴片的焊接问题,否则购买一个16路PWM Servo 舵机驱动板会是一个更好的选择, 见下图。

16路 PWM舵机驱动板 IIC PCA9685

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"