|
本帖最后由 shihezichen 于 2017-1-17 22:16 编辑
使用树莓派很容易发生GPIO不够用的情况.
例如我做智能小车, 需要三个超声波探头/两个红外探头/2个电机控制板, 所需的GPIO口远多于当前树莓派能提供的接口.
此时有两种解决方法:
1. 使用树莓派做上位机, Arduino做下位机, 通过Arduion来对接各种外设, 然后树莓派再通过串口与Arduion通信. 完成各种数据的采集.
2. 使用PCF8574T扩展树莓派的GPIO口(理论上PCF8575也是可以的, 而且一片可以扩充出16个口)
本文讲述如何使用PCF8574T扩展的方式.
1) PCF8574模块的购买:
在某宝购买即可, 大概十几块钱
2) PCF8574与树莓派连接:
本质上就是I2C设备的连接, PCF8574作为slave接入树莓派.
连接: VCC, GND 连接树莓派的+5V口和GND, SDA和SCL连接树莓派的P02(SDA1)和P03(SCL1)
若有多个PCF8574, 则顺序串接到前一个PCF8574的尾部插槽上.
3) PCF8574上的跳线:
即A0,A1,A2. 如果只有一片PCF8574, 可以保持默认跳线不动; 此时它在树莓派总线上的地址为0x20;
若有多片PCF8574,则需要跳线使得它们地址不同.
三个跳线帽, 可以产生8种组合,因此最多可以串接8片PCF8574, 总共扩展出8*8=64个口.
3) 外设与PCF8574的连接:
电机驱动板:
以L298N为例, 它有两种口, 一种是IN口, 一种是END口. IN口可以通过PCF8574控制; END口需要输入PWM信号, 所以我用树莓派GPIO口来直接控制(若有人试验成功, 请告知我如何通过PCF8574设置PWM,我理解是不行的)
超声波模块:
以HC-SR04为例,它有两种口, 一种是Echo口, 一种是Trig口. 其中Echo口需要不断检测高低电平,而且存在电平自动变化的情况. 这个在I2C总线上估计是做不到的(若有人试验成功, 请告知我如何通过PCF8574持续监测某一个pin的高电平变为低电平,我理解是不行的).
因此, Echo口直接连接树莓派GPIO, Trig口可以连接到PCF8574上.
其他模块:
类似的, 只要是对端口是高低电平控制的, 都可以通过PCF8574来控制,减少对树莓派GPIO口的占用.
硬件连接完毕后. 下面进入到程序部分:
写了三个程序文件:
1) i2c_io.py : 负责对i2c的端口进行读写的类
2) general_io.py: 提供G_IO类, 对i2c端口和GPIO端口进行了统一封装, 上层使用者可以不再区分树莓派主板自带的GPIO端口和PCF8574T扩展出来的IO口, 可使用相同的函数进行访问.
3) l298N.py: 对电机的操作类, Moto_Ctrl. 对电机进行前进/后退/左右转向的操作, 以及PWM调速. 会用到general_io.py中的G_IO类.
第一个文件: i2c_io.py
- #!/usr/bin/python
- #coding=utf-8
- # Author: 边城量子(QQ:1502692755)
- # write and read the pin of PCF8574 which connect to raspberry pi over the i2c bus.
- # only support the write and read of the pin;
- # will support the event in the future
- import smbus
- from log import debug, log
- class I2C_IO:
- # i2c_index: the i2c chip index ,
- # using '$ sudo i2cdetect -l' to check how many i2c chip
- # i2c_addr: the device address, e.g. 0x20, 0x23 , etc.
- # usging '$ sudo i2cdetect -1' to check how many devices at chip 1
- def __init__(self, i2c_index, i2c_addr):
- self.index= i2c_index
- self.addr = i2c_addr
- self.bus = smbus.SMBus(i2c_index)
- # pin: pin number, from 0~7
- # value: 0/1 or False/True or LOW/HIGH
- # only modify the bit according to the pin
- def output(self, pin, value):
- data = self.bus.read_byte( self.addr )
- if value == 1:
- value = (1 << pin) | data
- else:
- value = (~(1 << pin)) & data
- debug("I2C_IO::output(): old:%s, new: %s to pin %s" % (bin(data), bin(value),pin) )
- return self.bus.write_byte( self.addr, value)
- # pin: pin number, from 0~7
- # return:
- # the state of the pin, HIGH or LOW
- # only return the bit according to the pin
- def input(self, pin):
- data = self.read_byte(self.addr)
- mask = i<<pin
- if mask & data == mask:
- return 1
- else:
- return 0
- def setup(self, pin, direction):
- pass
复制代码
第二个文件: general_io.py
- #!/usr/bin/python
- #coding=utf-8
- # Author: 边城量子(QQ:1502692755)
- # suport write and read for GPIO and I2C IO using the unify class and functions
- # the GPIO port like 12, 26, or "12", "26"
- # the I2C port like "I7", "I3", must starts with "I"
- import i2c_io
- import RPi.GPIO as GPIO
- from log import debug, log
- class G_IO:
- # GPIO_mode: GPIO_BCM or GPIO_BOARD
- # i2c_index: the i2c chip index ,
- # using '$ sudo i2cdetect -l' to check how many i2c chip
- # i2c_addr: the device address, e.g. 0x20, 0x23 , etc.
- # usging '$ sudo i2cdetect -1' to check how many devices at chip 1
- def __init__(self, GPIO_mode=None, i2c_index=None, i2c_addr=None):
- if i2c_index != None and i2c_addr != None:
- self.i2c_io = i2c_io.I2C_IO( i2c_index, i2c_addr)
- if GPIO_mode != None:
- GPIO.setmode(GPIO_mode)
- # using the 'port' parameter to determin whether it is a GPIO port
- def _is_GPIO(self, port):
- # it is the GPIO port when its type is int
- if type(port) == type(1):
- return True
- elif type(port) == type("str"):
- # it is the GPIO port when it can be translate to int,
- # because the i2c io start with 'I'
- try:
- int(port)
- return True
- except ValueError:
- return False
- else:
- error( "G_IO::_is_GPIO():Error! the type of 'port' should be str or int. now is %s. " % port)
- return None
- # get the port value from a port string like 'I2', 'I7'
- def get_port_value(self, port):
- return int( port[1:] )
- # setup the io direction, only effect to GPIO io
- def setup(self, port, direction ):
- #debug("G_IO::setup(): set direciton %d for port %s" % (direction,port) )
- if self._is_GPIO(port):
- GPIO.setup(port, direction)
- else:
- self.i2c_io.setup(port, direction)
- # output to the port
- # the value only suport the HIGH or LOW
- def output(self, port, value):
- debug("G_IO::output(): set output value %d for port %s" % (value,port) )
- if self._is_GPIO(port):
- GPIO.output(port, value)
- else:
- port = self.get_port_value(port)
- self.i2c_io.output(port,value)
- # input from a port
- def input(self, port):
- debug("G_IO::output(): get input value from port %s" % (port) )
- if self._is_GPIO(port):
- return GPIO.input(port)
- else:
- port = self.get_port_value(port)
- return self.i2c_io.input(port)
复制代码
第三个文件: L298N.py
- #!/usr/bin/python
- #coding=utf-8
- # Author: 边城量子(QQ:1502692755)
- # 树莓派通过PCF8574来控制L298N控制的例子, 包括PWM调速功能
- # 属于智能语音控制树莓派小车的车辆控制部分
- #连接方式:
- # ENDx口直接连树莓派GPIO
- # INx口 连接PCF8574的pin口, 然后PCF8574通过I2C连接到树莓派I2C接口
- #控制方式:
- # 1. 针对ENDx口的PWM调速,直接通过GPIO口下发.
- # 2. 针对INx口的电机转向控制, 通过PCF8574下发.
- # 以上两种方式的控制,都通过general_io库封装对调用者不可见, 使用统一G_IO接口:
- # general_io库会自动识别,若端口是"I2"、'I7'这种类型,则使用i2c方式设置端口,
- # 否则使用GPIO方式
- #端口标识: 其中I0,I2这种表示通过PCF8574连接到树莓派; 20,16这种方式表示直连GPIO
- '''
- use two L298N to control 4 engine
- CTRL 1:
- ENDA 1 2 3 4 ENDB
- 黄 橙 红 棕 黑 白
- BCM: P21 I0 I1 I2 I3 P20
- CTRL 2:
- ENDA 1 2 3 4 ENDB
- 黑 白 灰 紫 蓝 绿
- BCM: P12 I7 I6 I5 I4 P16
- '''
- import RPi.GPIO as GPIO # 使用GPIO常量, 例如GPIO.HIGH
- from general_io import G_IO # I2C接口和GPIO接口 统一调用库
- from log import debug, log # 日志
- import time
- CTRL1 = { 'ENDA':21, 'IN1':'I0', 'IN2':'I1', 'IN3':'I2', 'IN4':'I3', 'ENDB':20 }
- CTRL2 = { 'ENDA':12, 'IN1':'I7', 'IN2':'I6', 'IN3':'I5', 'IN4':'I4', 'ENDB':16 }
- class Moto_Ctrl:
- def __init__(self):
- #
- # GPIO_mode: GPIO.BCM or GPIO.BOARD, 若用到GPIO端口需设置
- # i2c_index: 树莓派3一般是1, 表示chip. i2cdetect -l查到
- # i2c_addr: i2c 设备的地址, 例如0x20, 此处为PCF8574的I2C地址.
- # 可使用i2cdetect -1 查到(其中1代表i2c_index的值)
- self.gio = G_IO( GPIO_mode=GPIO.BCM, i2c_index=1, i2c_addr=0x20 )
- for key in CTRL1:
- port = CTRL1[key]
- #debug("Moto_Ctrl::__init__(): set GPIO.OUT for port %s" % port )
- self.gio.setup(port,GPIO.OUT)
- for key in CTRL2:
- port = CTRL2[key]
- #debug("Moto_Ctrl::__init__(): set GPIO.OUT for port %s" % port )
- self.gio.setup(port,GPIO.OUT)
-
- # make all output port LOW
- self.stop()
- # setup the pwm for speed controller
- self.pwms = []
- # four control channels from two L198N controller
- channels =[ CTRL1["ENDA"], CTRL1["ENDB"], CTRL2["ENDA"], CTRL2["ENDB"] ]
- for channel in channels:
- debug("Moto_Ctrl::__init__(): set pwm for channel %s" % channel )
- # the frequency is set to 150HZ
- p = GPIO.PWM( channel , 150)
- # the duty cycle is init to zero
- p.start( 0 )
- self.pwms.append( p )
- def __del__(self):
- self.pwms = []
- self.gio = None
- # set the speed
- # duty_cycle: [0,100]
- def set_speed(self, duty_cycle):
- # set the speed using the duty_cycle
- for p in self.pwms:
- debug("Moto_Ctrl::set_speed(): set ducy cycle for channel %s" % p )
- p.ChangeDutyCycle( duty_cycle )
- # stop
- def stop(self):
- for key in CTRL1:
- if key.startswith("IN"):
- port = CTRL1[key]
- #debug("Moto_Ctrl::stop(): set GPIO.LOW port %s" % port )
- self.gio.output(port,GPIO.LOW)
- for key in CTRL2:
- if key.startswith("IN"):
- port = CTRL2[key]
- #debug("Moto_Ctrl::stop(): set GPIO.LOW port %s" % port )
- self.gio.output(port,GPIO.LOW)
-
- def left_forward(self):
- # left rear wheels
- self.gio.output( CTRL2["IN3"], GPIO.LOW )
- self.gio.output( CTRL2["IN4"], GPIO.HIGH )
- # lefe front wheels
- self.gio.output( CTRL2["IN1"], GPIO.HIGH )
- self.gio.output( CTRL2["IN2"], GPIO.LOW )
- def rigth_forward(self):
- # right rear wheels
- self.gio.output( CTRL1["IN1"], GPIO.HIGH )
- self.gio.output( CTRL1["IN2"], GPIO.LOW )
- # right front wheels
- self.gio.output( CTRL1["IN3"], GPIO.HIGH )
- self.gio.output( CTRL1["IN4"], GPIO.LOW )
- def right_backward(self):
- # right rear wheels
- self.gio.output( CTRL1["IN1"], GPIO.LOW )
- self.gio.output( CTRL1["IN2"], GPIO.HIGH )
- # right front wheels
- self.gio.output( CTRL1["IN3"], GPIO.LOW )
- self.gio.output( CTRL1["IN4"], GPIO.HIGH )
- def left_backward(self):
- # left rear wheels
- self.gio.output( CTRL2["IN3"], GPIO.HIGH )
- self.gio.output( CTRL2["IN4"], GPIO.LOW )
- # lefe front wheels
- self.gio.output( CTRL2["IN1"], GPIO.LOW )
- self.gio.output( CTRL2["IN2"], GPIO.HIGH )
-
- # 控制小车行为,对外暴露接口
- # action: string, 'forward','backwoard','turnleft','turnright'
- # 'back_turnright', 'back_turnleft', 'stop'
- # speed: int, range [0,100]
- # duration: 持续时间, 若不传入, 则持续时间由外部控制
- def control(self, action, speed, duration=None):
- self.set_speed( speed )
- if action == 'forward':
- debug("Moto_Ctrl::control(): forward")
- self.left_forward()
- self.rigth_forward()
- elif action == 'backward':
- debug("Moto_Ctrl::control(): backward")
- self.right_backward()
- self.left_backward()
- elif action == 'turnleft':
- debug("Moto_Ctrl::control(): turn left")
- self.stop()
- self.rigth_forward()
- elif action == 'turnright':
- debug("Moto_Ctrl::control(): turn right")
- self.stop()
- self.left_forward()
- elif action == 'back_turnright':
- debug("Moto_Ctrl::control(): back turn right")
- self.stop()
- self.left_backward()
- elif action == 'back_turnleft':
- debug("Moto_Ctrl::control(): back turn left")
- self.stop()
- self.right_backward()
- elif action == 'stop':
- debug("Moto_Ctrl::control(): turn right")
- self.stop()
- if duration != None:
- time.sleep(duration)
-
- if __name__ == '__main__':
- ctrl = Moto_Ctrl()
- ctrl.control('backward', 90, 3)
- ctrl.control('turnright', 60, 3)
- ctrl.control('turnleft', 60, 3)
- ctrl.control('forward', 60, 3)
- ctrl.control('back_turnleft', 60, 3)
- ctrl.control('back_turnright', 60, 3)
-
- GPIO.cleanup()
复制代码
|
|