亚博智能论坛  
  
查看: 1061|回复: 2

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

  [复制链接]
  • TA的每日心情
    慵懒
    2017-7-29 14:25
  • 121

    主题

    161

    帖子

    576

    积分

    超级版主

    Rank: 9Rank: 9Rank: 9

    积分
    576
    发表于 2017-6-22 18:09:24 | 显示全部楼层 |阅读模式
    使用树莓派很容易发生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()
    复制代码


    回复

    使用道具 举报

    该用户从未签到

    5

    主题

    21

    帖子

    43

    积分

    新手上路

    Rank: 1

    积分
    43
    发表于 2017-6-22 18:10:14 | 显示全部楼层
    感谢分享!!!!!!!!!!
    回复

    使用道具 举报

    该用户从未签到

    0

    主题

    43

    帖子

    46

    积分

    新手上路

    Rank: 1

    积分
    46
    发表于 2018-3-19 01:56:12 | 显示全部楼层
    分享了,谢谢了
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

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