搜索
bottom↓
回复: 8

树莓派使用PCF8574扩展GPIO口成功实践和代码共享

[复制链接]

出0入0汤圆

发表于 2017-1-17 19:34:41 | 显示全部楼层 |阅读模式
本帖最后由 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

  1. #!/usr/bin/python
  2. #coding=utf-8

  3. # Author: 边城量子(QQ:1502692755)

  4. # write and read the pin of PCF8574 which connect to raspberry pi over the i2c bus.
  5. #  only support the write and read of the pin;
  6. #  will support the event in the future

  7. import smbus
  8. from log import debug, log


  9. class I2C_IO:
  10.     # i2c_index:  the i2c chip index ,
  11.     #    using '$ sudo i2cdetect -l' to check how many i2c chip
  12.     # i2c_addr: the device address, e.g. 0x20, 0x23 , etc.
  13.     #    usging '$ sudo i2cdetect -1' to check how many devices at chip 1
  14.     def __init__(self, i2c_index, i2c_addr):
  15.         self.index= i2c_index
  16.         self.addr = i2c_addr
  17.         self.bus = smbus.SMBus(i2c_index)

  18.     # pin: pin number,  from 0~7
  19.     # value: 0/1 or False/True or LOW/HIGH
  20.     #  only modify the bit according to the pin
  21.     def output(self, pin, value):
  22.         data = self.bus.read_byte( self.addr )
  23.         if value == 1:
  24.             value = (1 << pin) | data
  25.         else:
  26.             value = (~(1 << pin)) & data
  27.         debug("I2C_IO::output(): old:%s,  new: %s  to pin %s" % (bin(data), bin(value),pin) )
  28.         return self.bus.write_byte( self.addr, value)

  29.     # pin: pin number,  from 0~7
  30.     # return:
  31.     #     the state of the pin, HIGH or LOW
  32.     #     only return the bit according to the pin
  33.     def input(self, pin):
  34.         data = self.read_byte(self.addr)
  35.         mask = i<<pin
  36.         if mask & data == mask:
  37.             return 1
  38.         else:
  39.             return 0

  40.     def setup(self, pin, direction):
  41.         pass
复制代码


第二个文件:  general_io.py


  1. #!/usr/bin/python
  2. #coding=utf-8

  3. # Author: 边城量子(QQ:1502692755)
  4. # suport write and read for GPIO and I2C IO using the unify class and functions
  5. #  the GPIO port like  12, 26, or "12",  "26"
  6. #  the I2C port like  "I7",  "I3", must starts with "I"


  7. import i2c_io
  8. import RPi.GPIO as GPIO
  9. from log import debug, log


  10. class G_IO:
  11.         # GPIO_mode:  GPIO_BCM or GPIO_BOARD
  12.         # i2c_index:  the i2c chip index ,
  13.         #    using '$ sudo i2cdetect -l' to check how many i2c chip
  14.         # i2c_addr: the device address, e.g. 0x20, 0x23 , etc.
  15.         #    usging '$ sudo i2cdetect -1' to check how many devices at chip 1
  16.         def __init__(self, GPIO_mode=None, i2c_index=None, i2c_addr=None):
  17.                 if i2c_index != None and i2c_addr != None:
  18.                         self.i2c_io = i2c_io.I2C_IO( i2c_index, i2c_addr)
  19.                 if GPIO_mode != None:
  20.                         GPIO.setmode(GPIO_mode)

  21.         # using the 'port' parameter to determin whether it is a GPIO port
  22.         def _is_GPIO(self, port):
  23.                 # it is the GPIO port when its type is int
  24.                 if type(port) == type(1):
  25.                         return True
  26.                 elif type(port) == type("str"):
  27.                         # it is the GPIO port when it can be translate to int,
  28.                         #  because the i2c io start with 'I'
  29.                         try:
  30.                                 int(port)
  31.                                 return True
  32.                         except ValueError:
  33.                                 return False
  34.                 else:
  35.                         error( "G_IO::_is_GPIO():Error! the type of 'port' should be str or int. now is %s. " % port)
  36.                         return None

  37.         # get the port value from a port string like 'I2', 'I7'
  38.         def get_port_value(self, port):
  39.                 return int( port[1:] )


  40.         # setup the io direction, only effect to GPIO io
  41.         def setup(self, port, direction ):
  42.                 #debug("G_IO::setup(): set direciton %d for port %s" % (direction,port) )
  43.                 if self._is_GPIO(port):
  44.                         GPIO.setup(port, direction)
  45.                 else:
  46.                         self.i2c_io.setup(port, direction)

  47.         # output to the port
  48.         # the value only suport the HIGH or LOW
  49.         def output(self, port, value):
  50.                 debug("G_IO::output(): set output value %d for port %s" % (value,port) )
  51.                 if self._is_GPIO(port):
  52.                         GPIO.output(port, value)
  53.                 else:
  54.                         port = self.get_port_value(port)
  55.                         self.i2c_io.output(port,value)

  56.         # input from a port
  57.         def input(self, port):
  58.                 debug("G_IO::output(): get input value from port %s" % (port) )
  59.                 if self._is_GPIO(port):
  60.                         return GPIO.input(port)
  61.                 else:
  62.                         port = self.get_port_value(port)
  63.                         return self.i2c_io.input(port)
复制代码


第三个文件:  L298N.py

  1. #!/usr/bin/python
  2. #coding=utf-8

  3. # Author: 边城量子(QQ:1502692755)
  4. # 树莓派通过PCF8574来控制L298N控制的例子, 包括PWM调速功能
  5. # 属于智能语音控制树莓派小车的车辆控制部分

  6. #连接方式:
  7. #  ENDx口直接连树莓派GPIO
  8. #  INx口 连接PCF8574的pin口, 然后PCF8574通过I2C连接到树莓派I2C接口

  9. #控制方式:
  10. #  1. 针对ENDx口的PWM调速,直接通过GPIO口下发.
  11. #  2. 针对INx口的电机转向控制, 通过PCF8574下发.
  12. #  以上两种方式的控制,都通过general_io库封装对调用者不可见, 使用统一G_IO接口:
  13. #     general_io库会自动识别,若端口是"I2"、'I7'这种类型,则使用i2c方式设置端口,
  14. #     否则使用GPIO方式


  15. #端口标识: 其中I0,I2这种表示通过PCF8574连接到树莓派; 20,16这种方式表示直连GPIO

  16. '''
  17. use two L298N to control 4 engine
  18. CTRL 1:
  19.           ENDA   1    2    3    4    ENDB
  20.           黄     橙   红  棕   黑    白
  21. BCM:  P21    I0   I1  I2   I3    P20  

  22. CTRL 2:
  23.           ENDA 1    2    3    4    ENDB
  24.           黑   白   灰   紫   蓝   绿
  25. BCM:  P12  I7   I6   I5   I4   P16

  26. '''


  27. import RPi.GPIO as GPIO   # 使用GPIO常量, 例如GPIO.HIGH
  28. from general_io import G_IO  # I2C接口和GPIO接口 统一调用库
  29. from log import debug, log  # 日志
  30. import time


  31. CTRL1 = { 'ENDA':21, 'IN1':'I0', 'IN2':'I1', 'IN3':'I2', 'IN4':'I3', 'ENDB':20   }
  32. CTRL2 = { 'ENDA':12, 'IN1':'I7', 'IN2':'I6', 'IN3':'I5', 'IN4':'I4', 'ENDB':16   }


  33. class Moto_Ctrl:
  34.         def __init__(self):
  35.                 #
  36.                 # GPIO_mode: GPIO.BCM or GPIO.BOARD, 若用到GPIO端口需设置
  37.                 # i2c_index: 树莓派3一般是1, 表示chip. i2cdetect -l查到
  38.                 # i2c_addr: i2c 设备的地址, 例如0x20, 此处为PCF8574的I2C地址.
  39.                 #           可使用i2cdetect -1 查到(其中1代表i2c_index的值)
  40.                 self.gio = G_IO( GPIO_mode=GPIO.BCM, i2c_index=1, i2c_addr=0x20  )
  41.                 for key in CTRL1:
  42.                         port = CTRL1[key]
  43.                         #debug("Moto_Ctrl::__init__(): set GPIO.OUT for port %s" % port )
  44.                         self.gio.setup(port,GPIO.OUT)

  45.                 for key in CTRL2:
  46.                         port = CTRL2[key]
  47.                         #debug("Moto_Ctrl::__init__(): set GPIO.OUT for port %s" % port )
  48.                         self.gio.setup(port,GPIO.OUT)
  49.         
  50.                 # make all output port LOW
  51.                 self.stop()

  52.                 # setup the pwm for speed controller
  53.                 self.pwms = []
  54.                 # four control channels from two L198N controller
  55.                 channels =[ CTRL1["ENDA"], CTRL1["ENDB"], CTRL2["ENDA"], CTRL2["ENDB"] ]        
  56.                 for channel in channels:
  57.                         debug("Moto_Ctrl::__init__(): set pwm for channel %s" % channel )
  58.                         # the frequency is set to 150HZ
  59.                         p = GPIO.PWM( channel , 150)
  60.                         # the duty cycle is init to zero
  61.                         p.start( 0 )
  62.                         self.pwms.append( p )

  63.         def __del__(self):  
  64.                 self.pwms = []
  65.                 self.gio = None

  66.         # set the speed
  67.         # duty_cycle:  [0,100]
  68.         def set_speed(self, duty_cycle):
  69.                 # set the speed using the duty_cycle
  70.                 for p in self.pwms:
  71.                         debug("Moto_Ctrl::set_speed(): set ducy cycle for channel %s" % p )
  72.                         p.ChangeDutyCycle( duty_cycle )

  73.         #  stop
  74.         def stop(self):
  75.                 for key in CTRL1:
  76.                         if key.startswith("IN"):
  77.                                 port = CTRL1[key]
  78.                                 #debug("Moto_Ctrl::stop(): set GPIO.LOW  port %s" % port )
  79.                                 self.gio.output(port,GPIO.LOW)

  80.                 for key in CTRL2:
  81.                         if key.startswith("IN"):
  82.                                 port = CTRL2[key]
  83.                                 #debug("Moto_Ctrl::stop(): set GPIO.LOW  port %s" % port )
  84.                                 self.gio.output(port,GPIO.LOW)        

  85.         
  86.         def left_forward(self):
  87.                 # left rear wheels
  88.                 self.gio.output( CTRL2["IN3"], GPIO.LOW )
  89.                 self.gio.output( CTRL2["IN4"], GPIO.HIGH )
  90.                 # lefe front wheels
  91.                 self.gio.output( CTRL2["IN1"], GPIO.HIGH )
  92.                 self.gio.output( CTRL2["IN2"], GPIO.LOW )

  93.         def rigth_forward(self):
  94.                 # right rear wheels
  95.                 self.gio.output( CTRL1["IN1"], GPIO.HIGH )
  96.                 self.gio.output( CTRL1["IN2"], GPIO.LOW )
  97.                 # right front wheels
  98.                 self.gio.output( CTRL1["IN3"], GPIO.HIGH )
  99.                 self.gio.output( CTRL1["IN4"], GPIO.LOW )

  100.         def right_backward(self):
  101.                 # right rear wheels
  102.                 self.gio.output( CTRL1["IN1"], GPIO.LOW )
  103.                 self.gio.output( CTRL1["IN2"], GPIO.HIGH )
  104.                 # right front wheels
  105.                 self.gio.output( CTRL1["IN3"], GPIO.LOW )
  106.                 self.gio.output( CTRL1["IN4"], GPIO.HIGH )

  107.         def left_backward(self):
  108.                 # left rear wheels
  109.                 self.gio.output( CTRL2["IN3"], GPIO.HIGH )
  110.                 self.gio.output( CTRL2["IN4"], GPIO.LOW )
  111.                 # lefe front wheels
  112.                 self.gio.output( CTRL2["IN1"], GPIO.LOW )
  113.                 self.gio.output( CTRL2["IN2"], GPIO.HIGH )
  114.         
  115.         # 控制小车行为,对外暴露接口
  116.         # action: string, 'forward','backwoard','turnleft','turnright'
  117.         #         'back_turnright', 'back_turnleft', 'stop'
  118.         # speed: int, range [0,100]
  119.         # duration: 持续时间, 若不传入, 则持续时间由外部控制
  120.         def control(self, action, speed, duration=None):
  121.                 self.set_speed( speed )
  122.                 if action == 'forward':
  123.                         debug("Moto_Ctrl::control(): forward")
  124.                         self.left_forward()
  125.                         self.rigth_forward()
  126.                 elif action == 'backward':
  127.                         debug("Moto_Ctrl::control(): backward")
  128.                         self.right_backward()
  129.                         self.left_backward()
  130.                 elif action == 'turnleft':
  131.                         debug("Moto_Ctrl::control(): turn left")
  132.                         self.stop()
  133.                         self.rigth_forward()
  134.                 elif action == 'turnright':
  135.                         debug("Moto_Ctrl::control(): turn right")
  136.                         self.stop()
  137.                         self.left_forward()
  138.                 elif action == 'back_turnright':
  139.                         debug("Moto_Ctrl::control(): back turn right")
  140.                         self.stop()
  141.                         self.left_backward()
  142.                 elif action == 'back_turnleft':
  143.                         debug("Moto_Ctrl::control(): back turn left")
  144.                         self.stop()
  145.                         self.right_backward()                        
  146.                 elif action == 'stop':
  147.                         debug("Moto_Ctrl::control(): turn right")
  148.                         self.stop()
  149.                 if  duration != None:
  150.                         time.sleep(duration)
  151.                
  152. if __name__ == '__main__':
  153.         ctrl = Moto_Ctrl()
  154.         ctrl.control('backward', 90, 3)

  155.         ctrl.control('turnright', 60, 3)

  156.         ctrl.control('turnleft', 60, 3)

  157.         ctrl.control('forward', 60, 3)

  158.         ctrl.control('back_turnleft', 60, 3)

  159.         ctrl.control('back_turnright', 60, 3)

  160.         
  161.         GPIO.cleanup()
复制代码


出0入0汤圆

发表于 2017-1-18 00:12:32 来自手机 | 显示全部楼层
谢谢分享!

出0入0汤圆

发表于 2017-1-19 11:46:46 | 显示全部楼层
谢谢分享!

出0入0汤圆

发表于 2017-1-20 09:01:56 来自手机 | 显示全部楼层
谢谢分享。。。

出0入4汤圆

发表于 2017-1-20 09:41:19 | 显示全部楼层
IIC速度没有SPI快

出0入0汤圆

发表于 2017-1-20 10:18:43 | 显示全部楼层
gmyu 发表于 2017-1-20 09:41
IIC速度没有SPI快

兄台有SPI扩展IO的方案?

出0入0汤圆

发表于 2017-1-20 20:34:11 来自手机 | 显示全部楼层
I3C一出来,就更屌了

出5入42汤圆

发表于 2017-1-20 22:47:56 来自手机 | 显示全部楼层
avr-arm 发表于 2017-1-20 20:34
I3C一出来,就更屌了

哥们你是在开玩笑,还是我太孤陋寡闻了,还有I3C???

出0入0汤圆

发表于 2017-1-21 09:44:09 | 显示全部楼层
kevin_me 发表于 2017-1-20 22:47
哥们你是在开玩笑,还是我太孤陋寡闻了,还有I3C???

有的啊,不信你去查查
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-18 21:17

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表