树莓派使用PCF8574扩展GPIO口成功实践和代码共享
本帖最后由 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: %sto 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 like12, 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 )
# 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 I1I2 I3 P20
CTRL 2:
ENDA 1 2 3 4 ENDB
黑 白 灰 紫 蓝 绿
BCM:P12I7 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
#debug("Moto_Ctrl::__init__(): set GPIO.OUT for port %s" % port )
self.gio.setup(port,GPIO.OUT)
for key in CTRL2:
port = CTRL2
#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:
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
#debug("Moto_Ctrl::stop(): set GPIO.LOWport %s" % port )
self.gio.output(port,GPIO.LOW)
for key in CTRL2:
if key.startswith("IN"):
port = CTRL2
#debug("Moto_Ctrl::stop(): set GPIO.LOWport %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
# 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()
ifduration != 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()
谢谢分享! 谢谢分享! 谢谢分享。。。 IIC速度没有SPI快 gmyu 发表于 2017-1-20 09:41
IIC速度没有SPI快
兄台有SPI扩展IO的方案? I3C一出来,就更屌了 avr-arm 发表于 2017-1-20 20:34
I3C一出来,就更屌了
哥们你是在开玩笑,还是我太孤陋寡闻了,还有I3C??? kevin_me 发表于 2017-1-20 22:47
哥们你是在开玩笑,还是我太孤陋寡闻了,还有I3C???
有的啊,不信你去查查
页:
[1]