行空板扩展板冷门项目:异步控制灯带
本帖最后由 燃烧吧小宇宙X 于 2024-9-2 17:41 编辑作为一个长期对开发板感兴趣的非专业玩家(也就用过ESP8266/32、stm32、树莓派),很感谢官方给予的这次试用机会,也终于结束多年潜水,发了这个帖子,对或不对的地方,希望大家指教。 这次试用的应该是行空板首个专用扩展板(不用再用micro:bit的扩展板了),官方名称是行空板双路电机驱动I/O扩展板,很漂亮,看官方美图:https://ws.dfrobot.com.cn/FobE1C8MeMuVBsvgKxbOpCbGbKpr
功能和参数见官方产品维库,在这就不浪费篇幅了。
由于这块扩展板板载有RGB LED和红外收发模块,这次试用得到了尝试灯带和红外控制的机会,同时看到试用群里有讨论单线程的问题,于是决定做个异步控制灯带的小项目,作为这次的试用报告。
一、板载灯带控制
扩展板板载有三颗WS2812LED灯珠。WS2812也被称为NeoPixel,是单总线串联控制的LED灯带,每个灯珠有一个唯一地址,可独立控制颜色、亮度,组合起来可实现各种基于色彩变化的动态效果,常用于灯带、灯盘、灯板,一般长这样:
https://ws.dfrobot.com.cn/Fi9nyx_sGye1Tf6pgViqvP9rQqxQ?imageView2/1/w/486/h/486
因为一直没想到什么好项目,虽想玩但一直没有下手,这次正好可以试试。根据官网资料,行空板通过pingpong库中的NeoPixel模块对WS2812进行控制,既可同时控制所有灯珠,也可对单个灯珠进行控制。扩展板上的WS2812与行空板引脚P13连接,示意控制代码如下:from pinpong.board import Board, Pin, NeoPixel
Board().begin()
np = NeoPixel(Pin((Pin.P13)), 3) # 定义引脚和数量
np.range_color(0, 3, 0xFF0000) # 控制所有灯珠
np = (0, 255, 0) # 控制单个灯珠
np.clear() # 清除所有灯珠据此可实现一些控制灯带的方法,如随机闪烁、呼吸灯、跑马灯等,为方便调用,封装了一个继承自NeoPixel的类:class NeoPixelCustom(NeoPixel):
def __init__(self, pin, num):
super().__init__(pin, num)
self.num = num
...
# 往复跑马
def marquee_pingpong(self, sleep_time=.2):
self.clear()
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
while True:
for i in range(self.num):
self.clear()
self = color
time.sleep(sleep_time)
for i in range(1, self.num-1):
self.clear()
self = color
time.sleep(sleep_time)
...可直接调用测试:
from NeoPixelCustomAsyn import NeoPixelCustom
npc = NeoPixelCustom(Pin((Pin.P13)), 3) # 定义引脚号和灯珠数量
npc.blink_random() 二、异步控制
在完成多个基于时间的灯带效果后,一个自然的想法是通过某种方法切换不同的效果。行空板自带的GUI库可操作板载的两个按键,因此可以用按键对程序进行控制:
from unihiker import GUI
gui = GUI()
...
counter = 0
def change_mode():
global counter
if counter % 2 == 0:
npc.clear()
npc.range_color(0, 3, 0xFF0000)
elif counter % 2 == 1:
npc.clear()
npc.range_color(0, 3, 0x0000FF)
counter += 1
gui.on_a_click(change_mode)
def button_quit():
npc.clear()
sys.exit(0)
gui.on_b_click(button_quit)
while True:
time.sleep(0) 上述代码实现了用A键切换WS2812状态,用B键退出程序。
然而,在将上述程序中切换的功能换为自定义类中基于时间的灯带效果时,就会发现切换功能失效了。
究其原因是就程序的单线程运行。程序在主循环中监听事件并执行回调函数,但当执行的回调函数中含有无限循环`while True`时(如blink),将无法再跳至主循环执行监听等操作。这时可考虑使用异步程序。
【这部分整理自网上资料,供参考】异步程序允许程序在等待某些操作完成的同时继续执行其他任务,而不会被阻塞,常用于提高程序性能、提升用户体验、简化程序模型。
在python中,可使用协程(Coroutines)实现异步程序。协程是一种特殊的程序构造,可以在执行过程中暂停并恢复,而不会丢失当前程序的上下文。协程在单个线程中运行,通过调度机制实现并发,降低了上下文切换的开销,提高了程序的执行效率。协程与多线程/多进程有以下区别:
[*]多线程:线程是操作系统层面的并行执行单位,线程间通信需要锁等同步机制,上下文切换开销大,适合CPU密集型任务。
[*]多进程:进程是独立的执行环境,拥有自己的内存空间,适合I/O密集型任务,但创建和销毁进程开销大。
[*]协程:协程在单线程中通过控制流切换实现并发,没有线程切换开销,但资源占用相对较少,适合I/O等待任务。
协程的实现原理包括以下几个关键点:
[*]异步函数定义:使用`async def`定义的函数可以在函数内部使用`await`关键字来挂起函数的执行,等待异步操作完成。
[*]事件循环:异步编程通常需要一个事件循环来调度协程的执行,Python中的asyncio库提供了事件循环的支持。
[*]协程调度:使用事件循环(Event Loop)来调度协程的执行,使得程序能够在不同的协程之间切换执行,实现异步编程的效果。
Python中可使用标准库asyncio编写异步代码:
import asyncio
async def async_function():
await asyncio.sleep(1)
print("Async function executed")
asyncio.run(async_function())上述代码中,使用`async`关键字定义异步函数,`await`关键字暂停异步函数的执行,等待另一个异步任务完成。
在了解了使用协程编写异步程序的方法后,将按键控制功能切换的代码改成了异步版本,【以下代码基于个人理解,仅供参考】:
import asyncio
...
loop = asyncio.get_event_loop()
current_task = None
def change_mode():
global counter, current_task
if current_task:
current_task.cancel()
counter += 1
if counter== 1:
current_task = loop.create_task(npc.blink_random())
...
elif counter == 5:
current_task = loop.create_task(npc.hue_change())
counter = 0
gui.on_a_click(change_mode)在上述代码中,在A键按下后,功能函数以异步方式执行,通过`await`接受事件循环的调度,从而可以回到主循环进行事件监听完成效果的切换。
实现的效果如下:
几处需要注意的是:
1. 自定义类中相应的功能函数需添加`async`、`await`关键字,使异步函数得以暂停恢复。
2. 一般使用`asyncio.run(main_asyn()) `和`asyncio.create_task()`进行异步程序的运行和协程的创建。但是在这个项目中这样写灯带效果不能同步切换,报运行时错误`no running event loop`,判断和行空板CPU+MCU的架构有关,在调试时shell处于不同的线程,因此做如下更改:loop = asyncio.get_event_loop() # 1. 获取当前的事件循环
loop.run_until_complete(main_asyn()) # 2. 用该事件循环调度异步程序
asyncio.events._set_running_loop(loop) # 3. 手工设置当前的事件循环三、红外遥控
由于扩展板板载了红外收发,手上也正好有个使用NEC协议的红外遥控器,因此无需额外的接收器就可将上述代码微调为红外遥控板本,根据官方文档,板载红外接收模板与行空板引脚14连接,红外信号数据中的按键代码可测试得到,主要代码如下:
<blockquote>from pinpong.board import Board, Pin, IRRecv红外遥控版本的灯带控制效果如下:
四、手势控制
行空板比起其他MCU的主要优势是拥有完整的Python生态,同时USB-A口方便摄像头的接入给基于视觉的智能项目带来方便。因此项目进行到这里,决定再往前一步,尝试用摄像头捕捉手势进行控制。
这里也要提一下扩展板,虽然CV项目行空板+摄像头就OK了,但5V的供电总感觉不太稳定,至少我在调试时多次出现SSH意外断开或是死机的情况,接上扩展板的DC供电后没再出过问题了。
手势识别直接使用了HandTrackingModule库,需安装MediaPipe和cvzone,MediaPipe是Google开源的多媒体机器学习模型应用框架,CVZone则提供了丰富的深度学习功能。多说一句MediaPipe支持pip安装的最低版本是Python3.8,我这块行空板的版本是3.7.3,只能手工安装whl包。程序主要代码如下:
<blockquote>import cv2上述代码中用数字1-5代表1-5的手势,调用对应用灯带效果。握拳用0表示,用于熄灭所有灯珠。
这样就完成了一个简单的基于摄像头手势控制的项目(智能部分写得很不智能,请见谅)。
这是我提交的关于行空板双路电机驱动I/O扩展板的试用报告(虽然没来及测试双路电机控制),也借这次试用交流一下关于异步编程的个人理解(主要是最近太忙了,怕来不及交作业。后面有空的话,会争取写写小车机械臂的文章,但是小车的文会很多不是吗)。
总结这次试用,加上用了阵行空板,有几点感受:
1. 行空板带触屏、有按键,可方便也通过Home菜单切换想运行的程序,很是方便。
2. 行空板使用完整python生态,忽略性能,啥都能干。
3. 板载元件带来方便,尤其是红外收发,给无线控制带来方便。
4. 扩展板带来更强的供电,且可边接着USB调试,同时DC口取电,不用担心共地问题。另外WS2812需要5V电压,电机往往需要更高的电压,有扩展板就不用额外考虑供电问题了。
5. 两路带调速的电机控制和3.3V的PWM舵机控制,一般够用。好像听说是软件PWM?效果怎样待测。
6. 做工不说了,棒棒的!
几个小建议(可能不对):
1. 关于倾斜设计。这是一个很新颖且在不同场景下见仁见智的设计,有版友表示倾斜的安装给行空板屏幕带来更好的可视角度,但是缺点呢就是在拼搭的时候占更大的地方,另外比起立式或是卧式总觉得会不牢会折断,拿起放下都是小心翼翼,希望只是感觉上。。。
2. 关于固定孔位。基于近期的兴趣,能否考虑一些兼容乐高的固定孔位。
3. 关于python版本。我当前的版本是3.7.3,当前大多数人工智能相关的库都要3.10,甚至3.12以上,而手工升级或安装多个python对于新手又不是件轻松事,行空板作为一个(也)面向初学者,又以智能化为重要方向的开发板,建议升级出厂默认python版本。
4. 关于物理开关。调试时行空板当然可以通过`shutdown`关闭系统,但单独使用中就只能粗暴断电了,使用扩展板时有个物理开关感受上就好了很多,当然也就是感受上。。。
5. 另外,不知道这块扩展板能不能反过来给micro:bit用呢,引脚电气部分应该也是可以的吧,希望谁能告知我。
开发板的乐趣就在于对各种技术的组合和折腾,希望大家折腾得开心~
最后,再次感谢官方的这次试用机会,也希望未来参与到更多有趣的实践和讨论中。
学习!{:6_202:}
页:
[1]