完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 边城量子 于 2017-1-7 18:48 编辑
使用树莓派很容易发生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设置PWM,我理解是不行的). 因此, Echo口直接连接树莓派GPIO, Trig口可以连接到PCF8574上. 其他模块: 类似的, 只要是对端口是高低电平控制的, 都可以通过PCF8574来控制,减少对树莓派GPIO口的占用. |
|
相关推荐
|
|
本帖最后由 边城量子 于 2017-1-7 18:58 编辑
考虑到网络, 没有一次性写完, 我逐步写完. 硬件连接完毕后. 下面进入到程序部分: 写了三个程序文件: 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类. |
|
|
|
|
|
接下来是 general_io.py
#!/usr/bin/python #coding=utf-8 # Author: 边城量子(QQ:1502692755) # suport write and read for GPIO and I2C IO # 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) # test the moto work well 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() |
|
|
|
|
|
第一个文件: 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< 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() |
|
|
|
|
|
辅助文件: log.py
#!/usr/bin/python #coding=utf-8 from time import gmtime, strftime def log(level, info): stime = strftime("%d %b %Y %H:%M:%S", gmtime()) print stime,level, info # to be implemented: write to the log file def debug(info): log ( "DEBUG", info ) def error(info): log ( "ERROR", info ) |
|
|
|
|
|
为啥第二个和第三个文件无法发上来, 一直说要审核? 都是纯技术贴....
我把整个文件夹都打包放到附件中吧, 需要的下载看.
PCF8574扩展IO支持L198N的代码例子.rar
(4.1 KB, 下载次数: 38
)
|
|
|
|
|
|
只有小组成员才能发言,加入小组>>
5913 浏览 0 评论
14210 浏览 9 评论
7825 浏览 0 评论
基于Amazon Echo 和 Raspberry Pi 的自动窗帘控制
7162 浏览 1 评论
【下载】《Linux+树莓派玩转智能家居》——亲手进行树莓派应用制作
160964 浏览 374 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 00:20 , Processed in 1.113927 second(s), Total 65, Slave 56 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号