You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RPi Pico异步Socket通信阻塞WS2812 LED运行问题求助

Raspberry Pi Pico 艺术装置异步通信问题及解决方案

项目背景

  • 基于多台Raspberry Pi Pico搭建艺术装置,使用MicroPython开发:
    1. 一台Pico作为远程控制器,以Web服务器形式提供电位器状态数据
    2. 另外3台Pico通过Socket连接控制器获取数据,每台配备电机和级联WS2812 LED,实现色彩与运动联动
    3. 希望了解其他多客户端通信方案(蓝牙实现复杂度高,暂不考虑),或相关技术学习书籍推荐

核心问题

使用asyncio时,s.recv()代码会导致LED彩虹动画卡顿1秒,需要保证LED动画持续流畅运行。尝试过线程方案但无效,设置s.setblocking(False)后卡顿时间反而更长。

原始客户端代码

import machine
import network
import time
from secret import ssid, password
import socket
from neopixel import Neopixel
import asyncio

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected() and wlan.status() >= 0:
    print("Waiting to connect:")
    time.sleep(1)
# Should be connected and have an IP address
wlan.status() # 3 == success
wlan.ifconfig()
print(wlan.ifconfig())

async def get_data():
    while True:
        try:
            if wlan.isconnected:
                print("requesting data")
                ai = socket.getaddrinfo("192.168.0.24", 80) # Address of Web Server
                addr = ai[0][-1]
                # Create a socket and make a HTTP request
                s = socket.socket() # Open socket
                s.connect(addr)
                s.send(b"GET Data") # Send request
                ss = str(s.recv(512))  # Attempt to receive data
                s.close()          # Close socket
                await asyncio.sleep_ms(1000)    # wait
            else:
                print("no connection")
                await asyncio.sleep_ms(1000)
        except:
            s.close()
            print('no data, connection closed')
            await asyncio.sleep_ms(1000)
            
async def rainbow(): # This function runs a walking rainbow across the LED strip
    NUM_LEDS = 74
    LED_PIN = 13
    pixels = Neopixel(NUM_LEDS, 0, LED_PIN, "GRB")
    hue_offset = 1028
    brightness = 16
    while True:
      for hue in range(0, 65535, 1000):
        # Set the hues
        for led in range(NUM_LEDS):
          color = pixels.colorHSV(hue +(led * hue_offset), 255, brightness)
          pixels.set_pixel(led, color)
        pixels.show()
        print(f"rgb={color}")
        await asyncio.sleep_ms(100)
 
async def main():
    tasks = []
    tasks = [
        asyncio.create_task(get_data()),
        asyncio.create_task(rainbow()),
    ]
    await asyncio.gather(*tasks)
    
while True:
    asyncio.run(main())

原始服务器代码

# Webserver to send RGB data
# Tony Goodhew 5 July 2022
import network
import socket
import time
from machine import Pin, ADC
from secret import ssid,password
import random

potA = machine.ADC(26)
potB = machine.ADC(27)
potC = machine.ADC(28)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
       
# Wait for connect or fail
max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)

print('listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        print(request)
        # Do not unpack request
        # We reply to any request the same way
        # Generate 3 values to send back
        print(f"Potentiometer A is: {potA.read_u16()}")
        print(f"Potentiometer B is: {potB.read_u16()}")
        print(f"Potentiometer C is: {potB.read_u16()}")
        r = int((potA.read_u16()))
        g = int((potB.read_u16()))
        b = int((potC.read_u16()))
        # Join to make a simple string with commas as separators
        rgb = str(r) + "," + str(g) + ","+str(b)
        
        response = rgb # This is what we send in reply

        cl.send(response)
        print("Sent:" + rgb)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

优化后的代码

感谢jupiterbjy的解答,更新后的代码替换了阻塞式Socket调用为asyncio原生的流API,解决了卡顿问题。同时添加gc.collect()解决ENOMEM崩溃问题,但目前彩虹动画仍会在3-7秒后重置,需进一步排查。

优化后客户端代码

# The Jupiter client

import asyncio
import time
import network
from machine import Pin
from secret import ssid, password
from neopixel import Neopixel
import gc
            

NUM_LEDS = 102
LED_PIN = 17
pixels = Neopixel(NUM_LEDS, 0, LED_PIN, "GRB")
hue_offset = 1028
brightness = 16

async def connect_wlan():
    sta_if = network.WLAN(network.STA_IF)
    sta_if.active(True)
    sta_if.connect(ssid, password)
    
    while not sta_if.isconnected():
        await asyncio.sleep(1)
    
    return sta_if

def log(*args, **kwargs):
    print(f"{time.time():.1}", *args, **kwargs)

async def get_data():
    """Periodic network task"""
    while True:
        reader, writer = await asyncio.open_connection("192.168.0.24", 80)
        log(f"Connected!")

        try:
            writer.write("Big Ring".encode())
            await writer.drain()
            log(f"Request Sent!")
            data = str(await reader.read(1024))
            print(f"Received Data: {data}")
            await asyncio.sleep_ms(200)
            gc.collect()

        except:        
            gc.collect()
            writer.close()
            await writer.wait_closed()
            log(f"Disconnected!")
            await asyncio.sleep(2)

# This function runs a walking rainbow across the LED strip                       
async def rainbow(): 
    while True:
      for hue in range(0, 65535, 1000):
        # Set the hues
        for led in range(NUM_LEDS):
          color = pixels.colorHSV(hue +(led * hue_offset), 255, brightness)
          pixels.set_pixel(led, color)
        pixels.show()
        await asyncio.sleep_ms(100)

async def blinker():
    """Continuous LED blinker task"""    
    # for Pico it's 25, for Pico W/WH it's "LED"
    led = Pin("LED", Pin.OUT)    
    while True:
        led.toggle()
        log("[LED] LED", "ON" if led.value() else "off")
        await asyncio.sleep(1)

async def main():
    sta_if = await connect_wlan()
    tasks = []
    tasks = [
        asyncio.create_task(rainbow()),
        asyncio.create_task(blinker()),
        asyncio.create_task(get_data()),
    ]
    await asyncio.gather(*tasks)

while True:
    asyncio.run(main())

优化后服务器代码

# The Jupiter server

import asyncio
import time
import network
import machine
from machine import Pin
from secret import ssid, password

led = Pin("LED", Pin.OUT) 
potA = machine.ADC(26)
potB = machine.ADC(27)
potC = machine.ADC(28)

async def connect_wlan():
    #Connect to WLAN
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)

    while wlan.isconnected() == False:
        print('Waiting for connection...')
        await asyncio.sleep(1)
    ip = wlan.ifconfig()[0]
    print(f'Connected on {ip}')
    return ip
    

async def callback(
    reader: asyncio.StreamReader,
    writer: asyncio.StreamWriter,
):
    addr = writer.get_extra_info('peername')
    print(f"Connection from {addr!r}")
    
    # read from client
    client_data = await reader.read(1024)
    print(f"Client data: {client_data}")
    
    #assess potentiometers and assign to r, g, b
    r = int((potA.read_u16()))
    g = int((potB.read_u16()))
    b = int((potC.read_u16()))

    # Join to make a simple string with commas as separators
    rgb = str(r) + "," + str(g) + ","+str(b)
    
# This is what we send in reply
    response = rgb 
    print(f"sending {response}")

    # send to client
    writer.write(response.encode())
    await writer.drain()
    
    # wait for connection to close
    writer.close()
    await writer.wait_closed()
    
#    print(f"Disconnected {addr!r}")


async def main():
    ip = await connect_wlan()
    server = await asyncio.start_server(callback, ip, 80)

    async with server:
        while True:
            led.toggle()
            await asyncio.sleep(1)


asyncio.run(main())

额外需求

寻求适合3台Pico客户端的通信方案推荐(排除蓝牙),或MicroPython网络编程、异步编程相关的学习书籍推荐。


内容的提问来源于stack exchange,提问作者Space Cowboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 03:14:54