存档8月 2021

多个PCA9685舵机驱动板一起执行实验(含地址修改)

    以前的实验我们实验过了单个pca9685驱动板,同时驱动多个舵机的实验;但是有些情况下,可能一个板子无法满足我们的驱动需求;我们知道一个板子最大可驱动16个舵机,但是如果我们想同时驱动 17个舵机,这个时候就遇到问题;本篇文章就实验一下如何解决这个问题。
    为了解决这个问题,先得解决地址冲突问题;因为板子默认地址都是0x40,那么我们无法辨别哪个是哪个板子,这个时候就需要修改板子的地址;如下图:
地址修改
 地址修改后,连接树莓派,执行如下命令:

i2cdetect -y 1
    可见下图,由于树莓派连接了两个pca9685驱动板,所以可见两个地址,分别为0x40和修改地址后的0x41;
地址修改后效果
#解决地址冲突后,接下来看一下树莓派如何同时接多块板子,见下图:
接线效果
    由上图可见,由于板子默认没有焊接尾部扩展针脚,因此需要我们先焊接针脚;然后用杜邦线连接第二个板子;电源部分两者共用即可。
 #实验代码:

#coding:utf-8

'''
from JiuJiang
树莓酱的操作实例
http:://www.shumeijiang.com
'''

import time
from board import SCL, SDA
import busio

from adafruit_pca9685 import PCA9685
from adafruit_motor import servo

#引入i2c
i2c = busio.I2C(SCL, SDA)

#控制第一块板子
pca = PCA9685(i2c, address=0x40)  #地址可以修改  默认0x40
pca.frequency = 50

print("执行第一块板子")

for i in [0]:

    servo_o = servo.Servo(pca.channels[i]) #i是舵机在pca9685上的编号

    #设置脉冲宽度 500到2500是正常的 这个可以自己调整 不设置默认只到135度
    servo_o.set_pulse_width_range(min_pulse=500, max_pulse=2500)

    #先0度
    servo_o.angle = 0
    time.sleep(1)

    #再执行90度
    servo_o.angle = 90
    time.sleep(1)

    #再执行180度
    servo_o.angle = 180
    time.sleep(1)

    #再恢复90度
    servo_o.angle = 90
    time.sleep(1)

pca.deinit()

#控制第二块板子
pca = PCA9685(i2c, address=0x41)
pca.frequency = 50

print("执行第二块板子")

for i in [0, 1]:
    servo_o = servo.Servo(pca.channels[i]) #i是舵机在pca9685上的编号

    #设置脉冲宽度 500到2500是正常的 这个可以自己调整 不设置默认只到135度
    servo_o.set_pulse_width_range(min_pulse=500, max_pulse=2500)

    #先0度
    servo_o.angle = 0
    time.sleep(1)

    #再执行90度
    servo_o.angle = 90
    time.sleep(1)

    #再执行180度
    servo_o.angle = 180
    time.sleep(1)

    #再恢复90度
    servo_o.angle = 90
    time.sleep(1)

pca.deinit()
#实验效果,可见三个舵机依次转动,如下图:

舵机的新驱动方式

    去年曾经写过舵机的驱动方式,文件见链接:https://www.shumeijiang.com/2020/05/06/舵机(伺服电机)驱动实验/;在那篇文章内我们采用的是外置的驱动文件,其原理还是利用的led的驱动方式;今天我们将切换另一种驱动方式,无需再每次使用时额外引入驱动程序,而是直接调用即可。
    调用之前,我们先认识一下今天的主角CircuitPythonCircuitPython初学者友好的Python开源版本,适用于称为微控制器的小型廉价计算机。微控制器是许多电子产品的大脑,包括用于构建业余项目和原型的各种开发板。

    CircuitPython是正在快速发展一种兼容标准Python的嵌入式系统编程语言,目前已支持树莓派(Raspberry Pi)、ARM Cortex-M0/M0+、 ARM Cortex-M4/M4F等;它具有统一的 Python核心API和收纳了越来越多的150多个与之配合使用的设备库和驱动程序;因此我们在驱动硬件设备时,可以直接通过命令调用它的内置驱动,从而不用再每次引入其他驱动程序。

    CircuitPython是由著名开源软件硬件商--Adafruit发起,得到全球开源社区的支持与维护,作为支持嵌入式计算机系统Python解释器的后起之秀,CircuitPython具备很多适合小体积、低功耗、有限资源系统的特征,尤其是在开放性和安全性方面。

    CircuitPython的大部分内建库完全兼容标准的Python3,因此这次调用我们也使用的是Python3。
#实验准备,开始之前需要安装依赖包,命令如下(直接执行即可):

pip3 install --upgrade throttle  adafruit-circuitpython-motor adafruit-circuitpython-pca9685 inputs smbus RPi.GPIO pick board
#实验代码如下:

#coding:utf-8

'''
from JiuJiang
树莓酱的操作实例
https:://www.shumeijiang.com
'''

import time
from board import SCL, SDA
import busio

from adafruit_pca9685 import PCA9685
from adafruit_motor import servo

#引入i2c
i2c = busio.I2C(SCL, SDA)

#实例化 此处是0x41 驱动板地址修改过导致
pca = PCA9685(i2c, address=0x41)  #地址可以修改  默认0x40
pca.frequency = 50

for i in [0,1,2]:

    servo = servo.Servo(pca.channels[i]) #i是舵机在pca9685上的编号

    #设置脉冲宽度 500到2500是正常的 这个可以自己调整 不设置默认只到135度
    servo.set_pulse_width_range(min_pulse=500, max_pulse=2500)

    #先0度
    servo.angle = 0
    time.sleep(1)

    #再执行90度
    servo.angle = 90
    time.sleep(1)

    #再执行180度
    servo.angle = 180
    time.sleep(1)

    #再恢复90度
    servo.angle = 90
    time.sleep(1)

pca.deinit()
#注意:如果报错board模块的 SCL或者SDA找不到,可以将board文件替换一下,见下面的下载文件; 文件替换地址/home/pi/.local/lib/python3.7/site-packages;地址仅做参考,楼主是这个地址。
#其中关于脉冲宽度部分不同的舵机有不同的设置,具体可参考下图:
#实验效果:
执行效果
#参考文章:https://circuitpython.readthedocs.io/projects/motor/en/latest/index.html#

树莓派和手柄-控制舵机和电机

    上一篇文章我们已经介绍过了如果获取手柄的按钮、摇杆、方向键等信息;这一篇文章我们将继续上一次的主题,尝试一下如何在获取手柄数据后,控制舵机跟随摇杆方向转动以及如何触发电机转动。
#先看组装效果:
接线效果
    如上图,可见舵机和电机小风扇由同一块树莓派控制;外接同一块电源驱动;其中舵机由pca9685驱动板驱动;电机小风扇这次只是为了效果展示,所以直接由继电器控制电源开合(接通电源小风扇即可旋转)。

*舵机的接线示例可参考文章:舵机驱动实验;
*电机的接线以及更多的扩展,比如旋转方向控制,旋转速度控制等可参考文章:直流电机驱动实验;
*继电器的接线示例可参考文章:继电器实验
#实验过程:
1、编写代码,然后保存为jiujiang.py;
2、连接手柄,可以通过蓝牙或者直接电线连,按上图接线,然后接通电源;
3、打开vnc,选择要执行的代码然后执行,执行效果见下图:
4、摆动摇杆或者按动按键,查看vnc画面是否有数值出现(有数据为正常);
5、按动R2键,可见电机小风扇转动;
6、摆动右侧摇杆,向右摆动可见舵机顺时针转动,当摇杆复位时,舵机也复位90度;当向左摆动摇杆可见舵机逆时针转动;舵机最大最小转动度数代码设置为170度和10度;这个可以自行调整。
#效果如下:
#注:其中舵机驱动部分我们采用了新的驱动方式,去掉了繁琐的驱动文件,采用CircuitPython的舵机驱动程序;这部分下一篇文章我们会详细介绍和实验。

树莓派和手柄-数据获取

    上一篇文章:树莓派和手柄-蓝牙连接,我们已经完成手柄的蓝牙连接;这篇文章我们将继续实验,尝试如何获取手柄的各个按钮、摇杆的操作数据,然后通过这些数据,方便后续的其他实验。
    实验前需要先安装Pygame,(Pygame是一个免费的开源python编程语言库,用于制作游戏等多媒体应用程序。)我们这次会用到Pygame的joystick模块,从joystick的中文含义(操纵杆)可知,它是用于与操纵杆、游戏手柄和轨迹球交互的Pygame模块。
按键区域名称设定
    由上图我们先设定了一些名称,方便跟代码对应;其中摇杆分左右两个;按钮除了右边圈内的四个,还包含share键,options键,ps键以及L1、L2和R1、R2;方向键为左边圈内上下左右四按键。
#程序执行(程序文档可参考:https://www.pygame.org/docs/ref/joystick.html
由于在多次尝试ssh登录情况下,效果无法达到预期,所以这次直接通过vnc在树莓派桌面执行程序(预估是权限问题)。vnc相关可参考文章:树莓派安装vnc
#程序代码:

#coding:utf-8

import pygame
import time

#定义显示颜色
BLACK = pygame.Color('black')
WHITE = pygame.Color('white')

#信息输出类
class TextPrint():
    def __init__(self):
        self.reset()
        self.font = pygame.font.Font(None, 20)

    def tprint(self, screen, textString):
        textBitmap = self.font.render(textString, True, BLACK)
        screen.blit(textBitmap, (self.x, self.y))
        self.y += self.line_height

    def reset(self):
        self.x = 10
        self.y = 10
        self.line_height = 15

    def indent(self):
        self.x += 10

    def unindent(self):
        self.x -= 10

pygame.init()
screen = pygame.display.set_mode((500, 700))
pygame.display.set_caption("My Game")
done = False
clock = pygame.time.Clock()

#初始化joystick
pygame.joystick.init()
textPrint = TextPrint()

#主程序
while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True #停止
        elif event.type == pygame.JOYBUTTONDOWN:
            print('joystick button pressed!')
        elif event.type == pygame.JOYBUTTONUP:
            print('joystick button released!')


    #绘图
    screen.fill(WHITE)
    textPrint.reset()

    #获取摇杆数量
    joystick_count = pygame.joystick.get_count()

    #数量信息输出
    textPrint.tprint(screen, "number of joysticks: {}".format(joystick_count))
    textPrint.indent()

    #逐个遍历按钮和摇杆
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)  #创建对象
        joystick.init()

        try:
            jid = joystick.get_instance_id()
        except AttributeError:
            jid = joystick.get_id
        textPrint.tprint(screen, "Joystick {}".format(jid))
        textPrint.indent()

        #获取名称
        name = joystick.get_name()
        textPrint.tprint(screen, "Joystick name: {}".format(name))

        try:
            guid = joystick.get_guid()
        except AttributeError:
            pass
        else:
            textPrint.tprint(screen, "GUID: {}".format(guid))

        #获取操作杆轴数
        axes = joystick.get_numaxes()
        textPrint.tprint(screen, "Number of axes:{}".format(axes))

        #获取每个轴的数值
        for i in range(axes):
            axis = joystick.get_axis(i) #获取每个轴当前的位置 数字表示 取件-1到1之间 0表示居中
            textPrint.tprint(screen, "Axis {} value is:{:>6.3f}".format(i, axis))
        textPrint.unindent()

        #获取手柄按钮数据
        buttons = joystick.get_numbuttons()
        textPrint.tprint(screen, "Number of buttons:{}".format(buttons))
        textPrint.indent()

        #获取每个按钮状态
        for i in range(buttons):
            button = joystick.get_button(i) #获取当前按钮状态 按压True 否False
            textPrint.tprint(screen, "button {:>2} value {}".format(i, button))
        textPrint.unindent()
        #获取方向键状态
        hats = joystick.get_numhats()  #获取数量
        textPrint.tprint(screen, "Number of hats:{}".format(hats))
        textPrint.indent()

        #获取每个按键的值
        for i in range(hats):
            hat = joystick.get_hat(i)
            textPrint.tprint(screen, "Hat {} value: {}".format(i, str(hat)))
        textPrint.unindent()
        textPrint.unindent()

    pygame.display.flip()
    clock.tick(20)

pygame.quit()
#执行效果
图片可能不清晰,也可以看如下图表,是对应的按钮和摇杆以及方向键的操作类型和值范围;
按键类型控制器类型值范围
左摇杆(上、下)Axis1上-1到下1
左摇杆(左、右)Axis0左-1到右1
右摇杆(上、下)Axis5上-1到下1
右摇杆(左、右)Axis2左-1到右1
按钮 △button2按压1,非按压0
按钮 ◻︎button3按压1,非按压0
按钮 Obutton1按压1,非按压0
按钮 Xbutton0按压1,非按压0
optionsbutton9按压1,非按压0
sharebutton8按压1,非按压0
ps键button10按压1,非按压0
L1button4按压1,非按压0
L2button6、Axis3按压1,非按压0,其中Axis3显示力度(-1到1,-1为非按压)
R1button5按压1,非按压0
R2button7、Axis4按压1,非按压0,其中Axis4显示力度(-1到1,-1为非按压)
方向键-上hat(A,B)的B为1返回值为(0,1)
方向键-下hat(A,B)的B为-1返回值为(0,-1)
方向键-左hat(A,B)的A为-1返回值为(-1, 0)
方向键-右hat(A,B)的A为1返回值为(1, 0)

树莓派和手柄-蓝牙连接

    生活中经常能看到遥控汽车,遥控飞机,遥控机器狗等依赖手柄遥控的玩具;今天这个实验就来说说树莓派如何无线连接手柄的;由于目前手中有一个ps4手柄,因此就以它为例。
实验物品
#实验过程:
1、开始连接前,需要将ps4手柄由休眠模式置于蓝牙配对模式;先按住share键,然后再按住ps键,当手柄灯光出现明暗闪烁时,即进入蓝牙配对模式;
2、打开命令行终端,ssh连接树莓派; (比如:ssh pi@192.168.x.xxx)
3、打开蓝牙工具,执行命令: sudo bluetoothctl
4、进入交互页面后,分别执行:
 (1) agent on
 (2) default-agent
 (3) scan on
5、第四步执行搜索设备后,可以看到如下画面:
设备搜索列表
6、如上图可见,红框内“Wireless Controller”即为我们要连接的ps4手柄;复制设备地址:“90:89:5F:1C:88:F8”;
7、复制设备地址后执行连接:connect 90:89:5F:1C:88:F8
连接成功
8、提示“connection successful”即为连接成功;这个时候也能看到ps4手柄灯光常亮;
9、为了每次都能自动连接,我们需要让树莓派记住这个地址,执行:
   trust 90:89:5f:1c:88:f8  
(如果这个时候被刷屏看不到地址,可以新开终端执行cat /proc/bus/input/devices,就可以看到设备信息,其中Uniq即为地址);
10、如果执行cat /proc/bus/input/devices,没有看到信息,则说明没有连接成功;
11、连接成功后,我们来测试一下按钮按动后系统接收的数值变化效果,新窗口执行命令:
   sudo jstest /dev/input/js0,就可以看到如下效果;
联动效果
12、如果报错“jstest module not found ***”,则需要先安装jstest命令,
    sudo apt-get install jstest-gtk
13、退出蓝牙工具操作页面执行 “quit”即可;移除设备则执行命令 
    remove  ******  (对应的设备地址) 即可。

步进电机应用-智能储物盒尝试

    由上一篇实验,我们初步解决了步进电机定位的问题;文章可见:https://www.shumeijiang.com/2021/07/25/步进电机定位问题解决尝试;今天我们将在上一篇文章的基础上,由定位衍生出寻找的策略;意思就是在已知的四个点A、B、C、D,分别灵活定义为我们需要存储的物品,然后定义名称;当我们需要的时候,再输入名称程序便可轻松帮我们找寻到物品所在的位置。
    我们定义A点,即正上角(存放“瓜子”)的位置为输出口。
#先看组装效果:
    从安装效果可见,分别在上一篇实验的基础上,将A、B、C、D点四个点分别替换为四个可储物可转动的半封闭容器;然后里面分别存放了“瓜子”,“小毛刷”,“螺丝刀”,“转换头”;当然里面的物品可自定义,也可以为空。
    接下来我们要实现的效果是,我们输入一个物品,比如“螺丝刀”,由于定义A点位输出口,所以程序应该驱动步进电机转动“螺丝刀”到输出口;等我们拿到东西后,步进电机再驱动恢复到初始化状态。
    “此处的输入可以是文字输入,也可以应用我们前面用到的语音识别,也可以是其他传感器触发”。
#实现思路:
1、程序运行,接收输入(命令行/语音输入/传感器触发等);
2、读取物品位置映射;
3、物品的位置地址计算步长N;
4、获取步长N,驱动步进电机执行;
5、输出口停顿等待物品输出;
6、步进电机反转步长N,恢复初试状态。
#视频效果如下:
#关键代码
代码示例