分类目录动手就动手

四驱小车循迹实验-循迹实现

    前两篇文章已经介绍了如何组装四驱小车以及小车的驱动类封装;接下来这篇文章将尝试驱动类调用以及小车循迹测试。
    小车组装文章:https://www.shumeijiang.com/2021/09/23/四驱小车循迹实验-小车组装/。
    小车驱动类封装文章:https://www.shumeijiang.com/2021/09/23/四驱小车循迹实验-直流电机驱动封装/
#循迹实验效果
#实现效果:
    从上面gif图以及视频可看到,小车遇到直线会直行前进,由于手工贴的胶带不是很直所以小车会实时调整前进的角度;当遇到转弯时,同样会一点点进行转弯直到寻找到黑线线条;
传感器示例
    上图模拟小车前面并排的三个红外避障传感器,分别是ONE,TWO,THREEE;默认黑线位于TWO传感器正下方为直行;由于小车要不停向前移动,但是黑线有弯曲或者转弯的情况,所以小车会出现三个传感器分别接触黑线的情况;通过不同的传感器被触发事件,从而得出小车现在的行走方向以及判断是否要做方向调整,具体原理如下(1为检测到黑线触发事件,0为未检测到黑线):

1、ONE=1,THREE=0 说明黑线偏转右侧,此时小车应向右转向;
2、ONE=0,THREE=1 说明黑线偏转左侧,此时小车应向左转向;
3、ONE=0,TWO=1,THREE=0 说明黑线在中心方向,小车需直行;
4、ONE=1,TWO=1,THREE=1 说明遇到横线,小车需停顿;
5、ONE=0,TWO=0,THREE=0 说明小车失去黑线,此时需寻找或者停止。
#实现代码

#!/usr/bin/env python
#coding:utf-8

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

import RPi.GPIO as GPIO
import time

GPIO.setwarnings(False)

#引入驱动类
from baseAct import baseAct
#实例化驱动类并赋值四个电机所占引脚值
act = baseAct(17, 16, 13, 12, 19, 18, 21, 20)

#三个传感器检测黑线
senseOne = 22
senseTwo = 23
senseThree = 24

#一个检测障碍物
checkPin = 25

GPIO.setmode(GPIO.BCM)
GPIO.setup(senseOne, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(senseTwo, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(senseThree, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(checkPin, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#事件注册 注意不同传感器触发事件不同
GPIO.add_event_detect(senseOne, GPIO.RISING, bouncetime=200)
GPIO.add_event_detect(senseTwo, GPIO.FALLING, bouncetime=200)
GPIO.add_event_detect(senseThree, GPIO.RISING, bouncetime=200)
GPIO.add_event_detect(checkPin, GPIO.FALLING, bouncetime=200)

#检测事件
def check_event(pin):
    status = GPIO.event_detected(pin)
    return status

#检测状态
def check_status(pin):
    status = GPIO.input(pin)
    return status

#find
done = False
try:
    while not done:
   
        #障碍物检测
        front_status = check_event(checkPin)
        print("front status is %d"% front_status)
        if front_status == 1:
            print('有障碍物 停止')
            act.act_backward(0.5, 50, 50)
            done = True
            break

        #检测各传感器状态
        #status = find_way(senseOne, senseTwo, senseThree)
        sOne = check_event(senseOne)
        sTwo = check_event(senseTwo)
        sThree = check_event(senseThree)

        print('sOne is %d'% sOne)
        print('sTwo is %d'% sTwo)
        print('sThreee is %d' % sThree)
       
        way_type = 'forward'
        #继续前行
        if sOne == 0 and sThree==0:
            act.act_forward(0.15, 50, 40, True)
            print('forward')

        #遇到横线停顿
        elif sOne == 1 and sThree == 1:
            time.sleep(0.5)
            print('wait')

        #向右侧转弯调整
        elif sOne == 1 and sThree == 0:
            #act.turn_right(0.15, 50, 40)

            #检测是否居中
            find = False
            while not find:
                oneStatus = check_event(senseOne)
                twoStatus = check_event(senseTwo)
                threeStatus = check_event(senseThree)
                print('run right---->>')
                print('one status is %d'% oneStatus)
                print('two status is %d'% twoStatus)
                print('three status is %d'% threeStatus)

                if oneStatus==0 and twoStatus==1 and threeStatus==0:
                    find = True
                    break

                act.turn_right(0.15, 55, 45, True)
                time.sleep(0.2)
                   
            print('right')
            way_type = 'right'

        #向左侧转弯调整
        elif sOne == 0 and sThree == 1:

            find = False
            while not find:
                oneStatus = check_event(senseOne)
                twoStatus = check_event(senseTwo)
                threeStatus = check_event(senseThree)
                print('run left<<----')
                print('one status is %d'% oneStatus)
                print('two status is %d'% twoStatus)
                print('three status is %d'% threeStatus)

                if oneStatus==0 and twoStatus==1 and threeStatus==0:
                    find = True
                    break

                act.turn_left(0.15, 55, 45, True)
                time.sleep(0.2)

            print('left')
            way_type = 'left'
        else:
            pass

        print('once')
        #time.sleep(0.2)

        #if sOne==0 and sTwo==0 and sThree==0:
            #act.act_break()
            #print('stop!!!!')
            #break

except KeyboardInterrupt:
    pass

GPIO.cleanup()
从上面的代码可以看到循迹的策略和流程如下:

1、首先注册one和three分别一个RISING(低变高)事件,因为默认情况下这两个传感器没有遇到黑线而是地板,所以触发低电平事件(传感器低电平触发检测);

2、two注册一个FALLING(高变低)事件,因为默认two传感器红外被黑色线条吸收,从而不产生障碍物检测,所以检测状态为高电平;

3、checkPin为小车前方障碍物检测,如果遇到前方障碍物,小车停车并后退,这个不是重点不做详细介绍;

4、one、two、three传感器的防抖时间bouncetime,异或说检测间隔为200毫秒,这个可以自己调节,主要用于探测黑线的频率;

5、紧接着进入一个while循环,每次都检测三个传感器状态,当发现:
(1)one和three都没有触发RISING事件,我们认为此时黑线还在中间,所以直行;
(2)one和three都触发RISING事件,说明遇到横线,所以停顿;
(3)one触发,three没有触发,说明直行后黑线弯曲,触发了右侧one传感器;为了黑线居中,这个时候小车就需要右转,但是由于不知道黑线弯曲程度(后续可实验图像识别),所以小车进入一个找线环节,以0.15秒的步伐,一点点右转,同时占空比调为55,赫兹设置为45,降低右转速度;当two传感器触发FALLING事件,说明小车归正,则继续触发直行策略,以此类推;
(4)one没有触发,three触发,说明直行后黑线向左弯曲,触发左转动作,具体细节同右转相似;

6、如此,小车便会直行、停顿、左转和右转;当然程序还有很多不足的地方,后续还会继续优化。
#事件检测(边沿检测)部分可参考文章:https://www.shumeijiang.com/2020/03/15/gpio之event_detected系列函数/

四驱小车循迹实验-直流电机驱动封装

    上篇文章已经介绍了四驱小车如何组装,这篇文章我们将继续介绍如何驱动小车前进后退以及左转右转;因为小车循迹的过程中,会遇到左转线、直线、右转线以及停顿标示;这个时候就需要小车能实现左转、右转、直行、停车、后退等动作。
#直流电机驱动板L298N驱动原理:
直流电机转动效果IN1IN2IN3IN4
MOTOR-A正向(调速)高电平/PWM低电平
反向(调速)低电平高电平/PWM
停止低电平低电平
刹车高电平高电平
MOTOR-B正向(调速)高电平/PWM低电平
反向(调速)低电平高电平/PWM
停止低电平低电平
刹车高电平高电平
    从上面表格可见,motorA连接引脚IN1和IN2,当IN1高电平IN2低电平的时候,电机正向转动,其中IN1输入PWM进行调速和扭矩变化;反之IN1低电平和IN2高电平的时候电机反向转动,从而实现倒车;其他电机以此类推。
#其中PWM控制转速和扭矩,示例如下:
1、创建PWM实例:p = GPIO.PWM(channel, frequency);
2、开始PWM:p.start(duty_cycle)  #duty_cycle为占空比;
3、更改频率,更改扭矩:p.ChangeFrequency(frequency);
4、更改占空比,更改转速:p.ChangeDutyCycle(duty_cycle);
5、PWM停止:p.stop()
#代码示例

#!/usr/bin/env python
#coding:utf-8

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

import RPi.GPIO as GPIO ##引入GPIO模块
import time    ##引入time库
GPIO.setmode(GPIO.BCM)

#驱动类
class baseAct:
    def __init__(self, ain1, ain2, ain3, ain4, bin1, bin2, bin3, bin4):
        self.ain1 = ain1
        self.ain2 = ain2
        self.ain3 = ain3
        self.ain4 = ain4
        self.bin1 = bin1
        self.bin2 = bin2
        self.bin3 = bin3
        self.bin4 = bin4
        GPIO.setup(self.ain1, GPIO.OUT)
        GPIO.setup(self.ain2, GPIO.OUT)
        GPIO.setup(self.ain3, GPIO.OUT)
        GPIO.setup(self.ain4, GPIO.OUT)
        GPIO.setup(self.bin1, GPIO.OUT)
        GPIO.setup(self.bin2, GPIO.OUT)
        GPIO.setup(self.bin3, GPIO.OUT)
        GPIO.setup(self.bin4, GPIO.OUT)

    #结束动作
    def act_stop(self, duration, slow_time=0.15):

        #执行时间
        duration = float(duration)-slow_time
        time.sleep(duration)
   
        #self.init_move('forward', duration, 50, 50)

        if slow_time:
            #执行缓冲慢行
            self.apwm1.ChangeDutyCycle(20)
            self.apwm3.ChangeDutyCycle(20)
            self.bpwm1.ChangeDutyCycle(20)
            self.bpwm3.ChangeDutyCycle(20)
            time.sleep(slow_time)

        #结束动作
        self.apwm1.stop()
        self.apwm3.stop()
        self.bpwm1.stop()
        self.bpwm3.stop()
        GPIO.output(self.ain2, GPIO.LOW)
        GPIO.output(self.ain4, GPIO.LOW)
        GPIO.output(self.bin2, GPIO.LOW)
        GPIO.output(self.bin4, GPIO.LOW)

    ##PWM初始化
    def init_move(self, act_type, duration, duty_ratio, hz_num):

        if act_type == 'forward':
            hpin1 = self.ain1
            lpin1 = self.ain2
            hpin2 = self.ain3
            lpin2 = self.ain4
            hpin3 = self.bin1
            lpin3 = self.bin2
            hpin4 = self.bin3
            lpin4 = self.bin4
        elif act_type == 'backward':
            hpin1 = self.ain2
            lpin1 = self.ain1
            hpin2 = self.ain4
            lpin2 = self.ain3
            hpin3 = self.bin2
            lpin3 = self.bin1
            hpin4 = self.bin4
            lpin4 = self.bin3
        elif act_type == 'turn_left':
            hpin1 = self.ain1  
            lpin1 = self.ain2  #右前电机前进 pin2低电平则前进
            hpin2 = self.ain3
            lpin2 = self.ain4  #右后电机前进
            hpin3 = self.bin2
            lpin3 = self.bin1  #左前电机后退 pin2高电平则后退
            hpin4 = self.bin4
            lpin4 = self.bin3  #左后电机后退
        elif act_type == 'turn_right':
            hpin1 = self.ain2
            lpin1 = self.ain1  #右前电机后退  pin2高电平则后退
            hpin2 = self.ain4
            lpin2 = self.ain3  #右后电机后退
            hpin3 = self.bin1
            lpin3 = self.bin2  #左前电机前进  pin2高电平则前进
            hpin4 = self.bin3
            lpin4 = self.bin4  #左后电机前进
        else:
            print "不支持的类型"
            pass

        #挨个实例化每个电机的PWM实例
        self.apwm1 = GPIO.PWM(hpin1, hz_num) #实例PWM 并可自定义hz
        self.apwm1.start(0)           #初始占空比为0
        GPIO.output(hpin1, GPIO.HIGH) #设置单个电机一个高电压
        GPIO.output(lpin1, GPIO.LOW)  #另一个低电压
        self.apwm1.ChangeDutyCycle(duty_ratio) #设置用户自定义占空比

        #驱动第二个电机
        self.apwm3 = GPIO.PWM(hpin2, hz_num)
        self.apwm3.start(0)
        GPIO.output(hpin2, GPIO.HIGH)
        GPIO.output(lpin2, GPIO.LOW)
        self.apwm3.ChangeDutyCycle(duty_ratio)

        #驱动第三个电机
        self.bpwm1 = GPIO.PWM(hpin3, hz_num)
        self.bpwm1.start(0)
        GPIO.output(hpin3, GPIO.HIGH)
        GPIO.output(lpin3, GPIO.LOW)
        self.bpwm1.ChangeDutyCycle(duty_ratio)

        #驱动第四个电机
        self.bpwm3 = GPIO.PWM(hpin4, hz_num)
        self.bpwm3.start(0)
        GPIO.output(hpin4, GPIO.HIGH)
        GPIO.output(lpin4, GPIO.LOW)
        self.bpwm3.ChangeDutyCycle(duty_ratio)

    ##前进
    def act_forward(self, duration, duty_ratio, hz_num=50, close_slow=False):
        self.init_move('forward', duration, duty_ratio, hz_num)

        if close_slow:
            slow_time = 0
        else:
            slow_time = 0.15

        #停止动作 并执行缓冲
        self.act_stop(duration, slow_time)

    ##后退
    def act_backward(self, duration, duty_ratio, hz_num):
        self.init_move('backward', duration, duty_ratio, hz_num)
        #停止动作 并缓冲执行
        self.act_stop(duration)

    ##向左转
    def turn_left(self, duration, duty_ratio, hz_num, serial=False):
        self.init_move('turn_left', duration, duty_ratio, hz_num) #左转执行
        slow_time = 0.05  #减速时间
        if serial==True:
            slow_time = 0
        self.act_stop(duration, slow_time)  #动作停止

    ##向右转
    def turn_right(self, duration, duty_ratio, hz_num, serial=False):
        self.init_move('turn_right', duration, duty_ratio, hz_num) #右转执行
        slow_time = 0.05  #减速时间
        if serial==True:
            slow_time = 0 #连续不减速
        self.act_stop(duration, slow_time)  #动作停止

    ##刹车
    def act_break(self):
        GPIO.output(self.ain1, GPIO.HIGH)
        GPIO.output(self.ain2, GPIO.HIGH)
        GPIO.output(self.ain3, GPIO.HIGH)
        GPIO.output(self.ain4, GPIO.HIGH)

        GPIO.output(self.bin1, GPIO.HIGH)
        GPIO.output(self.bin2, GPIO.HIGH)
        GPIO.output(self.bin3, GPIO.HIGH)
        GPIO.output(self.bin4, GPIO.HIGH)
    如上代码可见,封装了直流电机驱动类baseAct,以及直行、后退、左转、右转和刹车等方法;实例化类后便可直接调用,其中ain1、ain2、ain3、ain4为左侧电机驱动板引脚,bin1、bin2、bin3、bin4为右侧电机驱动板引脚,分别对应即可。
#参考文章:https://www.shumeijiang.com/2020/05/04/直流电机驱动变速实验/

四驱小车循迹实验-小车组装

    从本篇文章开始,我们将进行四驱小车的循迹实验;因为曾经看到过相关的比赛,所以想着自己能不能组装一个可以循迹的四驱小车,花了中秋假期三天的时间,从组装到代码开发调试,小车终于可以循迹跑起来了;下面先从组装开始说起。
#材料准备
1、四驱小车底盘,网上都有卖,包含四个直流电机以及一个亚克力板;
2、树莓派3B+一个,这个不做限制,只要能跑代码即可;
3、四路红外避障传感器,当然也可以用其他循迹传感器;原理是一样,都是利用红外线遇到黑线,红外线被吸收,从而不产生反射的原理。
4、两个L298N直流电机驱动模块,用于四个电机的驱动;
5、两个供电充电宝,分别给树莓派和直流电机供电;
6、一个数显降压稳压模块,用于电机电压的控制;
7、一个船型开关,用于小车电源的开关(非必须);
8、一个降温小风扇,一方面用于给树莓派降温,另一方面防止充电宝供电中断;
9、一卷黑色胶带,后续循迹实验用于铺设循迹轨道;
10、雪糕棒以及热熔枪用于粘合;
11、扎带用于固定充电宝以及其他物品;
#组装效果
前面
侧面
后面
上面
#其中第一张图,三个并列的红外传感器,需要注意间距和高低位置;高低校准是放置在地面检测灯亮起,遇到黑线红灯熄灭即可,如果发现红灯不亮或者不灭可以调节可调电阻调整探测距离;彼此间距,校准是当黑线不在中间探测器下方时,左右传感器在N个移动周期内能检测到黑线即可。
#接线部分:
1、左侧电机L298N驱动板
(1)IN1接18;
(2)IN2接19;
(3)IN3接20;
(4)IN4接21;
2、右侧电机L298N驱动板
(1)IN1接17;
(2)IN2接16;
(3)IN3接13;
(4)IN4接12;
3、四路红外避障传感器
(1)IN1接22;
(2)IN2接23;
(3)IN3接24;
(4)IN4接25;
(5)GND接GND;
(6)VCC接3.3V;
组装和接线完成后,下一篇文章会讲解我自己封装的驱动程序。
#传感器参考文章:https://www.shumeijiang.com/2021/09/02/四路红外避障传感器实验/

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

    上一篇文章我们已经介绍过了如果获取手柄的按钮、摇杆、方向键等信息;这一篇文章我们将继续上一次的主题,尝试一下如何在获取手柄数据后,控制舵机跟随摇杆方向转动以及如何触发电机转动。
#先看组装效果:
接线效果
    如上图,可见舵机和电机小风扇由同一块树莓派控制;外接同一块电源驱动;其中舵机由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,恢复初试状态。
#视频效果如下:
#关键代码
代码示例

步进电机定位问题解决尝试

    有关步进电机前面我们已经做过实验,主要是执行给定的步数;然后步进电机按照设置的频率执行;这部分可见文章:步进电机驱动实验;但是有时候我们会遇到一些场景,比如我们希望步进电机在指定的旋转位置停止;这时候如果只是计算设置频率的步数会容易出现不精确的现象;因此我们想到一个方案,在需要停止的地方加上一个传感器作为检测反馈;就像我们常用的倒车雷达当有障碍物时变会及时提醒我们,然后我们做出减速设置刹车的决定。
    在本实验,这个充当检测反馈的传感器就是U型光电传感器;关于U型光电传感器的使用方法可以见前面的实验:U型光电传感器实验,以及U型光电传感器测速实验;实验用到的3D打印件,以及接线示例见下图:
组装效果
组装效果
接线示例
    具体的接线部分可分别查看文章:步进电机驱动实验以及U型光电传感器实验;其中供电部分我们采用的是家用的6节5号电池;这部分可自由搭配,大于5V即可;同时接线的时候如果有GND,建议先接GND,防止带电操作损坏传感器;
#实验效果
    由第一张图注解可以看到,转盘我们定义了4个点,4个点不是固定的可以随意定义多个点;但是需要注意每个点后面需要有一个触发板,用于U型光电传感器的感知;我们默认A点位初试点,然后定义比如从A点到B点为一个步长,A点到C点为两个步长,然后以此类推;最后达到我们输入指定的步长然后步进电机能够精准停止在指定的点上的效果。先看效果图:
执行效果
    由于视频压缩厉害,可能看不清输入的信息;第一个输入是2,就是执行2个步长;可见步进电机开始转动,每经过一个位置点就能看到背面光亮从亮到灭的过程,这就是U型光电传感器被触发了一次,我们记做一个步长;当步长等于我们录入的指令时,步进电机停止准确停止在C点;待停顿一秒后,然后又恢复到初试位置A点;等待下次执行。接下来看一下代码实现;

#coding:utf-8

'''
from JiuJiang
树莓酱的操作实例
https:://www.shumeijiang.com
'''
import sys
import RPi.GPIO as GPIO ##引入GPIO模块
import time    ##引入time库
from classes.motorAct import motorAct  #引入动作类
GPIO.setwarnings(False)

#u型光电传感器
uPin = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(uPin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) #初始化u型光电低电压

#动作实例化
motor = motorAct()

#定义多步操作
def walk(step, direction):
    if not step:
        return ;
    if not direction:
        direction = 'forward'

    run = 0
    roundStep = 600 #圈长 大于一圈的长度
    for i in range(0, roundStep):
        tect = GPIO.event_detected(uPin)
        status = GPIO.input(uPin)

        #发现事件且处于高电平
        if tect == 1 and status == GPIO.HIGH:
            run += 1
        if direction == 'backward':
            motor.oneStepBack(0.003)
        elif direction == 'forward':
            motor.oneStep(0.003)

        #检测停止条件
        if run >= step:
            motor.stopMoving()
            break

        time.sleep(0.005)  ##每步间的间隔
        #print("第%d步" % i)
    return run

#执行步进
if __name__ == '__main__':
    try:
        step = raw_input("走几步")
        step = int(step)

        #最大步长
        max_step = 4
        if step > max_step:
            print('bad step')
            sys.exit()

        #注册一个电平RISING事件 设置防抖时间
        GPIO.add_event_detect(uPin, GPIO.RISING, bouncetime=1000)

        #动作执行
        ret = walk(step, 'forward')  ##正向走对应步数
        time.sleep(1)
        walk(step, 'backward') ##反向走对应的步数
    except KeyboardInterrupt:
        pass

    GPIO.cleanup()
    其中U型光电检测涉及到传感器抖动问题的解决,这部分可以参考文章:树莓派之传感器防抖GPIO边缘检测函数等;由上面的代码可以看到,我们先注册一个U型光电传感器的RISING事件,即电压升高事件,然后接收要执行的步长进行执行,当检测U型光电被触发的次数等于步长时则停止;停止1秒后,再恢复原点;当然也可以不恢复原点,但是需要记录执行后的原点位置,这样下次执行时才知道要执行到某一点是从哪里开始。
3D打印件stl文件,可以通过发送邮件lee.chuke@foxmail.com获取。