ps2摇杆控制可视化小车(一)

前段时间实验了ps2摇杆控制多舵机实验;今天尝试将ps2摇杆数据转换为四驱小车的方向控制。实验将分两部分,第一部分将叙述整个流程的概要;第二部分将具体去实现。

其中用到的知识点:
1、四驱小车驱动封装参考:https://www.shumeijiang.com/2021/09/23/四驱小车循迹实验-直流电机驱动封装;
2、PCF8591数模转换参考:https://www.shumeijiang.com/2022/10/30/pcf8591-da数模转换实验.html;
3、树莓派间socket通信参考:https://www.shumeijiang.com/2021/10/18/树莓派间通信-socket通信.html;
4、ps2摇杆数据获取参考:https://www.shumeijiang.com/2022/12/07/ps2摇杆传感器实验.html;
5、摄像头数据流参考:https://www.shumeijiang.com/2021/11/14/树莓派摄像头实验(一).html。
实验思路:
1、用到两个树莓派,一个树莓派负责获取摇杆数据;另一个树莓派负责接收指令然后驱动小车行驶;
2、第一个树莓派获取到摇杆数据后,将数据发送给PCF8591数模转换板,转换为数字信号;3、第一个树莓派然将数字信号转换为对应的方向控制,然后通过socket通信将命令发给第二个树莓派;
4、负责执行的树莓派,接收到通信数据后,驱动小车执行对应的命令。
组装示例:
第二篇文章将具体实现如何控制小车行驶起来。

ps2摇杆传感器实验

实验目的:通过实验获取摇杆传感器对应的方向以及数值。从而为摇杆应用提供支持。
传感器示例
    因为摇杆获取的是模拟信号,我们需要将模拟信号转换为数字信号;因此传感器需要配合PCF8591数模转换模块使用。
接线示例:
引脚连接引脚
摇杆GND树莓派GND
摇杆+5V树莓派5V
摇杆VRXPCF8591的AIN0
摇杆VRYPCF8591的AIN1
摇杆SWPCF8591的AIN2

代码示例:


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

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

import time      ##引入time库
import smbus     ##引入控制总线

address = 0x48    ##传感器地址
xAddress = 0x40   ##x轴使用的地址 对应的AIN0
yAddress = 0x41   ##y轴使用的地址 对应的AIN1
zAddress = 0x42   ##z轴使用的地址 对应的AIN2

bus = smbus.SMBus(1)   ##开启总线

##定义监测方法
def getStatus(n):
    if not n: return
    global address

    map = {'x':xAddress, 'y':yAddress, 'z':zAddress}
    if n not in map.keys():
        return False

    #发送一个控制字节到设备 表示要读取n通道的数据
    bus.write_byte(address, map[n])

    #空读一次 消费掉无效数据(重要)
    bus.read_byte(address)

    #返回某通道输入的模拟值A/D转换后的数字值
    return bus.read_byte(address)

##获取方位信息
def getDirection(n):
    if not n: return
    num = getStatus(n)
    num = int(num)

    mess = ''
    if n == 'x':
        if num > 150: mess = 'backward'
        if num < 125: mess = 'forward'

    if n == 'y':
        if num > 150: mess = 'left'
        if num < 125: mess = 'right'

    if n == 'z':
        if num == 0: mess = 'pressed'

    return mess
其中PCF8591数模转换模块参考:https://www.shumeijiang.com/2022/10/30/pcf8591-da数模转换实验.html

PCF8591 DA数模转换实验

实验目的:实验采用PCF8591数模转换板,实现数字信号(Digital signal)到模拟信号(Analog signal)以及模拟信号到数字信号之间的转换。其中数字信号可以理解为”有无“(高电平或低电平),模拟信号则可以理解为”多少“(可以显示信号量多少)。
#传感器如图:
各引脚说明:
引脚含义接线
SDAI2C通信接口接树莓派SDA
SCLI2C通信接口接树莓派SCL
GND接地接口接树莓派GND
VCC电源接口(3.3V~5V)接树莓派5V(重要)
AOUTDA(数模)输出接口,输出模拟信号
AIN0模拟输入接口0
AIN1模拟输入接口1
AIN2模拟输入接口2
AIN3模拟输入接口3
此处用ps2摇杆传感器测试:
1、GND接树莓派GND;
2、+5V接树莓派5V;
3、VRX接PCF8591的AIN0;
4、VRY接PCF8591的AIN1;
5、SW接PCF8591的AIN3。
实验代码:

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

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

import time      ##引入time库
import smbus     ##引入控制总线

address = 0x48    ##传感器地址
xAddress = 0x40   ##x轴使用的地址 对应的AIN0
yAddress = 0x41   ##y轴使用的地址 对应的AIN1
zAddress = 0x42   ##z轴使用的地址 对应的AIN2

bus = smbus.SMBus(1)   ##开启总线

##定义监测方法
def getStatus(n):
    if not n: return
    global address

    map = {'x':xAddress, 'y':yAddress, 'z':zAddress}
    if n not in map.keys():
        return False

    #发送一个控制字节到设备 表示要读取n通道的数据
    bus.write_byte(address, map[n])

    #空读一次 消费掉无效数据(重要)
    bus.read_byte(address)

    #返回某通道输入的模拟值A/D转换后的数字值
    return bus.read_byte(address)

#获取数值
try:
    while True:
        xN = getStatus('x') #获取x轴
        yN = getStatus('y') #获取y轴
        zN = getStatus('z') #获取z轴

        print('x num is', xN)
        print('y num is', yN)
        print('z num is', zN)
        print("\n")

        time.sleep(0.3)

except KeyboardInterrupt:
    print('stop')
执行Python3 jiujiang.py
执行效果:
注意:其中PCF8591模块是8位的数模转换芯片,所以其输出的数字量是2的8次方也就是256种;也就是0到255区间范围。然后这个区间将5V电压平均分为255份。
实验前,需要先打开树莓派I2c协议支持。

ps2摇杆传感器控制舵机实验

    ps2摇杆传感器组合PCF8591数模转换模块,从而获取方向信息,然后通过PCA9685驱动板,驱动两个方向的舵机进行转动。
组合效果如图:
组装示例

如图所示,需要用到的模块有:

模块数量
ps2摇杆传感器1
PCF8591数模转换模块1
树莓派Zero1
PCA9685舵机驱动板1
MG966R舵机3
电源(此处是充电宝)1
特别说明:

1、由于ps2摇杆和PCA9685都需要用到SCL和SDA引脚,所以需要提前焊接PCA9685舵机驱动板尾部串联引脚。(可参考文章:https://www.shumeijiang.com/2021/08/29/多个pca9685舵机驱动板一起执行实验(含地址修改).html)。

2、实验前需要手工打开树莓派I2c协议支持(可参考文章:https://www.shumeijiang.com/2019/12/08/基于命令行打开i2c协议支持.html)。

接线说明:

引脚连接引脚
摇杆GND树莓派GND
摇杆+5V树莓派5V
摇杆VRXPCF8591的AIN0
摇杆VRYPCF8591的AIN1
摇杆SWPCF8591的AIN2
PCF8591的SDAPCA9685的SDA
PCF8591的SCLPCA9685的SCL
PCF8591的VCCPCA9685的VCC
PCF8591的GNDPCA9685的GND
PCA9685的SCL树莓派SCL
PCA9685的SDA树莓派SDA
PCA9685的VCC树莓派5V
PCA9685的GND树莓派GND
视频效果:
关键代码:

#coding:utf-8

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

import time
from board import SCL, SDA
import busio
from PCF8591 import getDirection #引入读取ps2摇杆数据

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

#初始化两个舵机驱动
servo_x = servo.Servo(pca.channels[0])
servo_y = servo.Servo(pca.channels[1])

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

#获取摇杆数据
try:
    while True:
        xN = getDirection('x')
        yN = getDirection('y')
        print(xN)
        print(yN)

        #control x
        if xN == '向前':
            servo_x.angle = 60
        elif xN == '向后':
            servo_x.angle = 140
        else:
            servo_x.angle = 100

        #control y
        if yN == '向左':
            servo_y.angle = 40
        elif yN == '向右':
            servo_y.angle = 140
        else:
            servo_y.angle = 90

        time.sleep(0.2)

except KeyboardInterrupt:
    print('stop')

pca.deinit()
其他参考:
1、PCF8591数模转换实验参考文章:https://www.shumeijiang.com/2022/10/30/pcf8591-da数模转换实验.html

自制防止电脑掉线小工具

     最近家里新添加了一位吃奶小战士,所以事情比较多;经常会被打断去忙别的事情;导致电脑长时间没有动静从而出现微信等工具掉线情况;所以自制了一个小工具,在忙别的事情的时候能保证电脑不会掉链子。
     下面将通过树莓派Zero、PCA9685舵机驱动、MG996舵机组合实现鼠标的定期或随机移动,组合如下图:
组合效果
实现原理:
    树莓派连接PCA9685舵机驱动板,驱动板连接MG996舵机;舵机转轴连接一个驱动臂;将鼠标用胶带跟驱动臂连接起来;接下来写程序,让舵机每隔一段时间执行转动-复位动作,然后驱动臂顺带带动鼠标移动一下;从而实现,电脑检测鼠标在使用的情况,从而防止电脑程序不掉线的目的。
视频效果如下:
实现代码如下:

#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)

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

default_angle = {0:113, 1:0, 2:80}

#开始执行
while True:
    i = 2
    servo_o = servo.Servo(pca.channels[i]) #i是舵机在pca9685上的编号

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

    #初始化度数
    servo_o.angle = default_angle[i]
    time.sleep(1)

    #再执行移动
    servo_o.angle = 65
    time.sleep(1)

    #再恢复
    servo_o.angle = default_angle[i]  
    time.sleep(60) #一分钟执行一次  可随机数

pca.deinit()
舵机驱动部分参考文章:https://www.shumeijiang.com/2021/08/29/舵机的新驱动方式.html

卡尔曼滤波算法实现

    卡尔曼滤波(Kalman filtering)是一种利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响,所以最优估计也可看作是滤波过程。
    数据滤波是去除噪声还原真实数据的一种数据处理技术,Kalman滤波在测量方差已知的情况下能够从一系列存在测量噪声的数据中,估计动态系统的状态。(百度百科)
    实际应用中,我们陀螺仪实验的时候,会发现获取的数值抖动太大,不够平滑;这样的数值在应用时会导致很多问题,因此需要通过一定的算法将数值转换的更平滑。如下采集数据:
105.87231557, 108.0076993, 85.64052734, 84.23328308, 124.71488794, 102.91086635, 96.6229862, 93.07739765, 98.52594135, 111.79610743, 94.78393208, 105.27301268, 87.23224952, 89.71036819, 117.43724425, 64.70525616, 75.48832499, 91.99249324, 105.44355624, 84.93538347
    上面的数据是获取的陀螺仪Roll方向20次样本数据,可见数值波动很大,如84都124,89到117等。下面我们将尝试卡尔曼算法,实现数值降噪滤波。
代码示例:

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

'''
from JiuJiang
树莓酱的操作实例
https:://www.suhmeijiang.com
'''
import matplotlib.pyplot as plt

#滤波类
class kalman_filter:
    def __init__(self,Q,R):
        self.Q = Q
        self.R = R

        self.P_k_k1 = 1
        self.Kg = 0
        self.P_k1_k1 = 1
        self.x_k_k1 = 0
        self.ADC_OLD_Value = 0
        self.Z_k = 0
        self.kalman_adc_old=0

    def kalman(self,ADC_Value):

        self.Z_k = ADC_Value

        if (abs(self.kalman_adc_old-ADC_Value)>=60):
            self.x_k1_k1= ADC_Value*0.382 + self.kalman_adc_old*0.618
        else:
            self.x_k1_k1 = self.kalman_adc_old;

        self.x_k_k1 = self.x_k1_k1
        self.P_k_k1 = self.P_k1_k1 + self.Q
        self.Kg = self.P_k_k1/(self.P_k_k1 + self.R)

        kalman_adc = self.x_k_k1 + self.Kg * (self.Z_k - self.kalman_adc_old)
        self.P_k1_k1 = (1 - self.Kg)*self.P_k_k1
        self.P_k_k1 = self.P_k1_k1

        self.kalman_adc_old = kalman_adc

        return kalman_adc

if __name__ == '__main__':
    kalman_filter =  kalman_filter(0.001,0.1)

    #陀螺仪测试数据
    test_array = [105.87231557, 108.0076993, 85.64052734, 84.23328308, 124.71488794, 102.91086635, 96.6229862, 93.07739765, 98.52594135, 111.79610743, 94.78393208, 105.27301268, 87.23224952, 89.71036819, 117.43724425, 64.70525616, 75.48832499, 91.99249324, 105.44355624, 84.93538347, 105.87231557, 108.0076993, 85.64052734, 84.23328308, 124.71488794, 102.91086635, 96.6229862, 93.07739765, 98.52594135, 111.79610743, 94.78393208, 105.27301268, 87.23224952, 89.71036819, 117.43724425, 64.70525616, 75.48832499, 91.99249324, 105.44355624, 84.93538347]

    print(test_array)
    n = len(test_array)

    #卡尔曼过滤
    new_array=[]
    for i in range(n):
        new_array.append(int(kalman_filter.kalman(test_array[i])))

    #滤波后数据
    print(new_array)

    #图形生成展示
    plt.plot(new_array)
    plt.plot(test_array)
    plt.show()
效果如图:
执行效果
    上图可见,我们通过vnc进入可视化桌面,然后命令行执行程序文件,左侧是输出的数据;右侧是图表化滤波数据和原值数据,蓝色是滤波后数据,黄色是原值数据。可见,滤波后数据变的平滑很多。
    算法部分来源于大神文章:https://blog.csdn.net/moge19/article/details/82531119
    图形化展示部分,使用的是matplotlib,Python的2D绘图库;系统默认是没有安装的,需要自己安装,由于默认安装的matplotlib版本执行一直有问题,所以安装时指定了版本:

pip install matplotlib==2.0.2
    安装后执行如果报问题:Couldn't find foreign struct converter for 'cairo.Context',那么执行如下命令:

 sudo apt-get install python-gi-cairo
其他滤波算法,还没尝试,如果有兴趣可以试一下:十种滤波算法的Python实现。

陀螺仪平衡小车实验

    之前曾尝试如何获取陀螺仪的数据,文章地址:https://www.shumeijiang.com/2021/11/09/陀螺仪模块实验-获取欧拉角.html;今天则尝试给陀螺仪加上两个轮子组装成一个二轮小车;然后利用陀螺仪数据使之保持平衡。组装小车中使用到树莓派Zero,直流电机驱动板L298N,以及可显降压升高模块。
接线示例:
实现原理:
1、获取陀螺仪Roll数据(根据陀螺仪安装方向而定);
2、定义一个平衡区间,然后对比陀螺仪的值是否在区间内;
3、当数值小于左侧边界时,小车向左移动来保持平衡;
4、当数值大于右侧边界时,小车向右移动来保持平衡;
5、当在区间内时,小车停止驱动。
代码示例:

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

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

from mpu6050 import mpu6050
#import threading
import time
import MPU6050filter
import RPi.GPIO as GPIO ##引入GPIO模块

motorA1 = 17   ##定义电机A的IN1引脚
motorA2 = 18   ##定义电机A的IN2引脚
enA = 21 #引脚21接ENA,调速电机A

GPIO.setmode(GPIO.BCM)  ##此处采用BCM编码
GPIO.setup(motorA1, GPIO.OUT)  ##设置引脚为输出模式
GPIO.setup(motorA2, GPIO.OUT)
GPIO.setup(enA, GPIO.OUT)

#初始化PWM
pwm = GPIO.PWM(enA, 40)
pwm.start(60)  ##初始化占空比

#陀螺仪数据获取
def get_mpu6050():
    sensor = mpu6050(address=0x68)
    sensor.set_gyro_range(mpu6050.ACCEL_RANGE_16G)
    sensor.set_accel_range(mpu6050.GYRO_RANGE_2000DEG)
    time.sleep(0.02)

    accel_data = sensor.get_accel_data()
    gyro_data = sensor.get_gyro_data()
    rotation = MPU6050filter.IMUupdate(accel_data['x'], accel_data['y'], accel_data['z'], gyro_data['x'], gyro_data['y'], gyro_data['z'])

    return rotation

#设置正反方向
def get_direction(direction):
        if direction == 1:
            GPIO.output(motorA1, GPIO.LOW)
            GPIO.output(motorA2, GPIO.HIGH)
        else:
            GPIO.output(motorA1, GPIO.HIGH) ##设置A1 引脚为高电平
            GPIO.output(motorA2, GPIO.LOW)  ##设置A2 引脚为低电平 如此可控制电机A正转,反之电机A反转

#根据幅度决定速度
def get_duty_cycle(val, n=1):
    val = abs(val)
    if val>10:
        val = 10
    return n*(val*6+40)

def main():
    middle = -3
    left_board = -9
    right_board = 6
    while True:
        time.sleep(0.05)
        rotation = get_mpu6050()
        roll = rotation[1]
        val = roll-middle
        print("roll is", roll)

        duty = get_duty_cycle(val, 0.6)
        if val > right_board:
            get_direction(2)
            pwm.ChangeDutyCycle(duty)
        elif val < left_board:
            get_direction(1)
            pwm.ChangeDutyCycle(duty)
        else:
            #安全区域
            pwm.ChangeDutyCycle(0)

if __name__ == '__main__':
    main()
平衡效果:
实验效果:
1、执行命令 Python jiujiang.py;
2、当小车中心失衡时,出现向左向右行走以保持平衡;
3、不过也有很多问题,当偏恒幅度过大时,小车容易出现无法保持平衡的情况;
4、由于没有经过滤波,所以小车很容易出现抖动。
视频效果:
另一个视角:

L298N驱动板驱动实验

    以前曾用L298N改进板驱动直流电机,文章链接:https://www.shumeijiang.com/2020/05/04/直流电机驱动变速实验.html;现在尝试L298N驱动板如何接线以及如何使用。
驱动板注解
    其中改进型和这个板子的明显区别在于,改进型将调速直接作用于高电平引脚输出即可;这个板子,在ENA和ENB上面作用;如果安装跳帽则表示固定速度,如果连接树莓派引脚,则可以通过PWM调速。
#接线示例
#代码示例

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

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

import RPi.GPIO as GPIO ##引入GPIO模块
import time    ##引入time库

motorA1 = 17   ##定义电机A的IN1引脚
motorA2 = 18   ##定义电机A的IN2引脚
enA = 21 #引脚21接ENA,调速电机A

GPIO.setmode(GPIO.BCM)  ##此处采用BCM编码
GPIO.setup(motorA1, GPIO.OUT)  ##设置引脚为输出模式
GPIO.setup(motorA2, GPIO.OUT)
GPIO.setup(enA, GPIO.OUT)

#enA决定速度
pwm = GPIO.PWM(enA, 80)
pwm.start(70)  ##初始化占空比 即单位时间高电平的时间占比

#设置正反方向
GPIO.output(motorA1, GPIO.HIGH) ##设置A1 引脚为高电平
GPIO.output(motorA2, GPIO.LOW)  ##设置A2 引脚为低电平 如此可控制电机A正转,反之电机A反转

##通过一定时间间隔设置占空比,可见电机间隔一定时间后会发生速度变化 其中0则电机停止转动
try:
    while True:
        pwm.ChangeDutyCycle(30)
        time.sleep(2)
        pwm.ChangeDutyCycle(70)
        time.sleep(2)
        pwm.ChangeDutyCycle(0)  ##电机停止转动
        time.sleep(2)  ##持续时间

except KeyboardInterrupt:
    pass
pwm.stop()
GPIO.cleanup()
#视频效果: