767浏览
查看: 767|回复: 1

[M10项目] 行空板扩展板冷门项目:异步控制灯带

[复制链接]
本帖最后由 燃烧吧小宇宙X 于 2024-9-2 17:41 编辑

       作为一个长期对开发板感兴趣的非专业玩家(也就用过ESP8266/32、stm32、树莓派),很感谢官方给予的这次试用机会,也终于结束多年潜水,发了这个帖子,对或不对的地方,希望大家指教。              这次试用的应该是行空板首个专用扩展板(不用再用micro:bit的扩展板了),官方名称是行空板双路电机驱动I/O扩展板,很漂亮,看官方美图:
       功能和参数见官方产品维库,在这就不浪费篇幅了。

       由于这块扩展板板载有RGB LED和红外收发模块,这次试用得到了尝试灯带和红外控制的机会,同时看到试用群里有讨论单线程的问题,于是决定做个异步控制灯带的小项目,作为这次的试用报告。
      一、板载灯带控制
       扩展板板载有三颗WS2812LED灯珠。WS2812也被称为NeoPixel,是单总线串联控制的LED灯带,每个灯珠有一个唯一地址,可独立控制颜色、亮度,组合起来可实现各种基于色彩变化的动态效果,常用于灯带、灯盘、灯板,一般长这样:

       因为一直没想到什么好项目,虽想玩但一直没有下手,这次正好可以试试。根据官网资料,行空板通过pingpong库中的NeoPixel模块对WS2812进行控制,既可同时控制所有灯珠,也可对单个灯珠进行控制。扩展板上的WS2812与行空板引脚P13连接,示意控制代码如下:
  1. from pinpong.board import Board, Pin, NeoPixel
  2. Board().begin()
  3. np = NeoPixel(Pin((Pin.P13)), 3)    # 定义引脚和数量
  4. np.range_color(0, 3, 0xFF0000)     # 控制所有灯珠
  5. np[1] = (0, 255, 0)                         # 控制单个灯珠
  6. np.clear()                                       # 清除所有灯珠
复制代码
据此可实现一些控制灯带的方法,如随机闪烁、呼吸灯、跑马灯等,为方便调用,封装了一个继承自NeoPixel的类:
  1. class NeoPixelCustom(NeoPixel):
  2.     def __init__(self, pin, num):
  3.         super().__init__(pin, num)
  4.         self.num = num
  5.     ...
  6.     # 往复跑马
  7.     def marquee_pingpong(self, sleep_time=.2):
  8.         self.clear()
  9.         color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
  10.         while True:
  11.             for i in range(self.num):
  12.                 self.clear()
  13.                 self[i] = color
  14.                 time.sleep(sleep_time)
  15.             for i in range(1, self.num-1):
  16.                 self.clear()
  17.                 self[self.num-1-i] = color
  18.                 time.sleep(sleep_time)
  19.     ...
复制代码
可直接调用测试:
  1. from NeoPixelCustomAsyn import NeoPixelCustom
  2. npc = NeoPixelCustom(Pin((Pin.P13)), 3)                      # 定义引脚号和灯珠数量
  3. npc.blink_random()
复制代码
      二、异步控制
       在完成多个基于时间的灯带效果后,一个自然的想法是通过某种方法切换不同的效果。行空板自带的GUI库可操作板载的两个按键,因此可以用按键对程序进行控制:
  1. from unihiker import GUI
  2. gui = GUI()
  3. ...
  4. counter = 0
  5. def change_mode():
  6.     global counter
  7.     if counter % 2 == 0:
  8.         npc.clear()
  9.         npc.range_color(0, 3, 0xFF0000)
  10.     elif counter % 2 == 1:
  11.         npc.clear()
  12.         npc.range_color(0, 3, 0x0000FF)
  13.     counter += 1
  14. gui.on_a_click(change_mode)
  15. def button_quit():
  16.     npc.clear()
  17.     sys.exit(0)
  18. gui.on_b_click(button_quit)
  19. while True:
  20.     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编写异步代码:
  1. import asyncio
  2. async def async_function():
  3.     await asyncio.sleep(1)
  4.     print("Async function executed")
  5. asyncio.run(async_function())
复制代码
上述代码中,使用`async`关键字定义异步函数,`await`关键字暂停异步函数的执行,等待另一个异步任务完成。  
在了解了使用协程编写异步程序的方法后,将按键控制功能切换的代码改成了异步版本,【以下代码基于个人理解,仅供参考】:
  1. import asyncio
  2. ...
  3. loop = asyncio.get_event_loop()
  4. current_task = None
  5. def change_mode():
  6.     global counter, current_task
  7.     if current_task:
  8.         current_task.cancel()
  9.     counter += 1
  10.     if counter  == 1:
  11.         current_task = loop.create_task(npc.blink_random())
  12.     ...
  13.     elif counter == 5:
  14.         current_task = loop.create_task(npc.hue_change())
  15.         counter = 0
  16. gui.on_a_click(change_mode)
复制代码
在上述代码中,在A键按下后,功能函数以异步方式执行,通过`await`接受事件循环的调度,从而可以回到主循环进行事件监听完成效果的切换。  
实现的效果如下:
行空板扩展板冷门项目:异步控制灯带图1
几处需要注意的是:
1. 自定义类中相应的功能函数需添加`async`、`await`关键字,使异步函数得以暂停恢复。  
2. 一般使用`asyncio.run(main_asyn()) `和`asyncio.create_task()`进行异步程序的运行和协程的创建。但是在这个项目中这样写灯带效果不能同步切换,报运行时错误`no running event loop`,判断和行空板CPU+MCU的架构有关,在调试时shell处于不同的线程,因此做如下更改:
  1. loop = asyncio.get_event_loop()                # 1. 获取当前的事件循环
  2. loop.run_until_complete(main_asyn())       # 2. 用该事件循环调度异步程序
  3. asyncio.events._set_running_loop(loop)    # 3. 手工设置当前的事件循环
复制代码
三、红外遥控
       由于扩展板板载了红外收发,手上也正好有个使用NEC协议的红外遥控器,因此无需额外的接收器就可将上述代码微调为红外遥控板本,根据官方文档,板载红外接收模板与行空板引脚14连接,红外信号数据中的按键代码可测试得到,主要代码如下:
  1. <blockquote>from pinpong.board import Board, Pin, IRRecv
复制代码
红外遥控版本的灯带控制效果如下:
行空板扩展板冷门项目:异步控制灯带图2
四、手势控制   
       行空板比起其他MCU的主要优势是拥有完整的Python生态,同时USB-A口方便摄像头的接入给基于视觉的智能项目带来方便。因此项目进行到这里,决定再往前一步,尝试用摄像头捕捉手势进行控制。
       这里也要提一下扩展板,虽然CV项目行空板+摄像头就OK了,但5V的供电总感觉不太稳定,至少我在调试时多次出现SSH意外断开或是死机的情况,接上扩展板的DC供电后没再出过问题了。  
       手势识别直接使用了HandTrackingModule库,需安装MediaPipe和cvzone,MediaPipe是Google开源的多媒体机器学习模型应用框架,CVZone则提供了丰富的深度学习功能。多说一句MediaPipe支持pip安装的最低版本是Python3.8,我这块行空板的版本是3.7.3,只能手工安装whl包。程序主要代码如下:
  1. <blockquote>import cv2
复制代码
上述代码中用数字1-5代表1-5的手势,调用对应用灯带效果。握拳用0表示,用于熄灭所有灯珠。  
这样就完成了一个简单的基于摄像头手势控制的项目(智能部分写得很不智能,请见谅)。

行空板扩展板冷门项目:异步控制灯带图3


       这是我提交的关于行空板双路电机驱动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用呢,引脚电气部分应该也是可以的吧,希望谁能告知我。

       开发板的乐趣就在于对各种技术的组合和折腾,希望大家折腾得开心~  
       最后,再次感谢官方的这次试用机会,也希望未来参与到更多有趣的实践和讨论中。




auroraAA  管理员

发表于 2024-9-3 08:55:59

学习!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
关于楼主
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail