[手滑没写完就发出来了,稍等] 请教树莓派利用 curses 库和红外传感器实现 2.4g 无线键盘遥控和避障功能实现逻辑的一个疑问(内含具体源码和详细解释,逻辑介绍和遇到的问题)

2020-04-19 21:35:29 +08:00
 mylovesaber

py 新人买了个树莓派练习,目的是实现一个 2.4g 键盘遥控的小车(已经实现的其他功能后面都不提),同时具有针对延迟问题的避障处理,目前基本都实现了,就差一个避障逻辑没弄明白只实现了一半,下面分三个部分介绍下目前我的情况,希望有前辈能指点一下:

基本控制源码 demo

关于 curses 库的官方文档和更详细的文档: https://docs.python.org/zh-cn/3/howto/curses.html http://doc.codingdict.com/python_352/library/curses.html

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import curses
import subprocess
import RPi.GPIO as io

# 定义各种硬件对应的 GPIO 引脚编号
# BCM 编码是需要查询官方引脚图定义的
# 面包板使用的扩展板有详细标记(例):GPIO xx

io.setmode(io.BCM)
rt1 = 16        # rt1/rt0:右前后轮正负极,1 正 0 负,r 右 l 左后面全都这么设置的
rt0 = 20
lt1 = 21        # lt1/lt0:左前后轮正负极
lt0 = 26
ena = 12        # a 右 b 左使能引脚
enb = 19
disb = 25       # 红外模块引脚
freq = 50       #频率 50

#进行 curses 库的初始化设置
screen = curses.initscr()
curses.noecho()
curses.cbreak()
screen.keypad(True)
io.setwarnings(False)

# 除了避障模块为读取信号,其余均为输出信号
io.setup(rt1,io.OUT)
io.setup(rt0,io.OUT)
io.setup(lt1,io.OUT)
io.setup(lt0,io.OUT)
io.setup(disb,io.IN)                  #模块检测距离不够输出 0 给树莓派,距离正常输出 1 给树莓派
io.setup(ena,io.OUT)
io.setup(enb,io.OUT)

# 设置,初始化并启用舵机和马达 pwm 调速
enr = io.PWM(ena,50)
enl = io.PWM(enb,50)
enr.start(0)
enl.start(0)

# 小车前进( 20 占空比)
def forward():
    io.output(rt1,io.HIGH)           # 正高电平,负低电平,pwm 设置慢速前进
    io.output(rt0,io.LOW)
    io.output(lt1,io.HIGH)
    io.output(lt0,io.LOW)
    enr.ChangeDutyCycle(20)
    enl.ChangeDutyCycle(20)

# 小车后退( 20 占空比)
def backward():
    io.output(rt1,io.LOW)           # 正低电平,负高电平,pwm 设置慢速后退
    io.output(rt0,io.HIGH)
    io.output(lt1,io.LOW)
    io.output(lt0,io.HIGH)
    enr.ChangeDutyCycle(20)
    enl.ChangeDutyCycle(20)

# 停止舵机和小车
def stop():
    io.output(rt1,io.LOW)           # 四个轮子重置为低电平
    io.output(rt0,io.LOW)
    io.output(lt1,io.LOW)
    io.output(lt0,io.LOW)
    enr.ChangeDutyCycle(0)          # enr/enl 左右车轮占空比重置为 0
    enl.ChangeDutyCycle(0)

# 清理
def clean():
    curses.nocbreak()
    screen.keypad(0)
    curses.echo()
    curses.endwin()
    pwmud.stop()
    pwmlr.stop()
    enr.stop()
    enl.stop()
    io.cleanup()

try:
    while True:
        char = screen.getch()                             #等待键盘按下按键
        if char == ord('k'):                                 #按键 k 会让整个程序退出
            break
        elif char == 10:                #我的蓝牙键盘方向键中间是 ok 键,按下就是车轮停止工作
            stop()
        elif char == curses.KEY_UP:      #按方向上键,先进行判断红外传感器如果读到 0 就让按键不工作
            if io.input(disb) == 0:                        #就是前面有障碍就让上键不能让小车前进
                stop()                                           
            else:
                while True:                                   #否则进入让小车停止的判断
                    forward()
                    #screen = curses.initscr()
                    #char = screen.getch()
                    if char == 10:             #如果前方无障碍物,按下 ok 键,小车停止前进跳出循环
                        stop()                                   #之后可以用方向下键后退
                        break
                    if io.input(disb) == 0:                #如果按过前进按键后不操作直到快撞上
                        stop()                                  #红外距离不够,自动停车
                        break
                        #if char == 10:                          #后面 7 行是测试的,好像没用
                        #    stop()
                        #    enr.ChangeDutyCycle(0)
                        #    enl.ChangeDutyCycle(0)
                    #elif char == 10:
                        #stop()
                        #break

        elif char == curses.KEY_DOWN:                  #如果按下后退键,小车就后退
            backward()

finally:
    clean()
    print("已释放 GPIO 口")
2294 次点击
所在节点    Python
7 条回复
mylovesaber
2020-04-19 22:01:53 +08:00
问题来源于子循环里面 screen = curses.initscr()和 char = screen.getch(),在同样“小车前进时没有障碍物”的条件下出现:
1. 这俩行注释了,小车在按下 ok 键无反映,只能等快撞上由红外强行停止
2. 这俩行不注释,小车可以通过 ok 键停止,但红外防撞功能失效

另外发现这个 getch()好像会累计你的按键,比如我使用问题 1,我按上,ok,下键,没反映,但靠近红外模块后小车立马停车后紧接着后退

不知道我这逻辑写法哪里出错了,各位前辈如果能看懂的话可否介绍下经验呢?感谢!
wangkai0351
2020-04-19 23:11:09 +08:00
软硬件联调的活儿是吃经验且累人的,留给楼下大佬来帮帮孩子吧
mylovesaber
2020-04-19 23:46:47 +08:00
@wangkai0351 谢谢,其实键值不是问题,都是测试能用的,而且这个 curses 库识别键值如果是英文字母直接就是它本身,根本不用考虑,只是这个在子循环加上读取键值就红外失效,不加上按键失效就有点尴尬。。。
no1xsyzy
2020-04-20 12:40:28 +08:00
首先,两重死循环必定是程序写错了。
请先画 FSM 。
mylovesaber
2020-04-22 00:38:06 +08:00
@no1xsyzy @wangkai0351
![避障逻辑图]( https://s1.ax1x.com/2020/04/22/JYEuKU.png)
我重读了 curses 的文档,是我疏忽了 getch()函数的特性,
即: `char = screen.getch()` 这行代码功能是等待读取键值

进入这行代码的执行的时候,读取到按键之前一定会出现整个程序的暂停,所以出现一旦我取消注释,一楼程序就进入等待按键的阶段,后面的红外测距判断就不会被执行的情况,但现在发现问题需要解决的逻辑我好像没想出来。。

再说明白点就是这两行代码是先后绑定在一起的,避障生效只能是在运行这两行代码之前,但避障使用的这个红外一直在读取到信息,也就是不停读取非 0 即 1,直接进行 if-else 进行判断的话,一旦小车动起来第一时间树莓派没有读取到 0 的值时就进入了等待输入 ok 键的阶段导致这个避障功能不生效,所以我第一反应是使用一个循环,但后面怎么改善我蒙逼了:
```python
char = screen.getch()
print("这段话绝对不会被打印在屏幕上")
if char == 10:
print("读取到 ok 键被按下这段话就会被输出到屏幕上")
```
请教下二位有没有什么可能的思路来解决呢?
no1xsyzy
2020-04-22 01:17:13 +08:00
@mylovesaber #5 非正交多输入全部进 FSM 最方便。
FSM 不是指流程图(控制流图),而是 “有限状态机”,大致上可以实现为 Moore Machine 和 Mealy Machine,特点就是根据当前状态和输入决定下一状态(状态转移),而状态或者状态转移决定输出。

你同时需要等待停止后退等按钮输入,又需要等待红外的输入,你觉得阻塞方式可能做到吗?
mylovesaber
2020-04-23 14:09:20 +08:00
文档中有个 window.timeout(delay)函数,能以非阻塞方式轮询

import time
import curses

screen = curses.initscr()
curses.noecho()
screen.timeout(0) # 设置 screen 为非阻塞读取(无延迟模式)

while True:
char = screen.getch() # 在无延迟模式下,getch 会立即返回不管有没有输入
if char == -1: # 如果没有输入,getch 会返回 -1
print("没有输入")
else:
print("输入了 {} ({})".format(chr(char), char))
time.sleep(0.2) # sleep 避免轮询过快


我滴妈,我的问题终于解决了
网上其他所谓键盘遥控自动避障小车项目好像都没有用到这个函数所以都不完整
此贴终结

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/664074

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX