用RP2040 PIO驱动WS2812灯带:从时序原理到MicroPython实现 先聊个现象玩灯带的人基本都会撞上 WS2812 这个型号便宜、单线通讯、支持级联一条线能串几百颗灯珠效果做起来相当唬人。但真正上手去点第一颗灯的时候很多人卡在了为什么我的灯不亮、为什么颜色不对、为什么用 ESP32 没问题、换到树莓派 Pico 上就各种怪毛病这类问题上。这篇文章就是从源头把这笔账算清楚为什么 RP2040 这个芯片天生适合干这件事PIO 这个外设到底解决了什么痛点以及怎么用 MicroPython 在树莓派 Pico 上把 WS2812 跑起来。内容会涉及底层时序、PIO 状态机原理、完整的 MicroPython 代码、接线注意事项和实际调试中踩过的坑。适合手里有 Pico 或者正打算入坑彩灯控制的朋友读过之后至少能少走我之前走过的弯路。1. WS2812 灯珠到底难在哪里——先从硬件时序说起很多人第一次接触 WS2812 是去看别人代码发现就是往一个引脚上不断发数据代码看起来也不长于是觉得很简单。可真到自己写的时候用GPIO.value(1)、延时、GPIO.value(0)、延时这种方式去模拟结果灯要么不亮要么乱闪。原因很简单WS2812 的时序要求非常苛刻而 MicroPython 的纯 Python 循环根本达不到那个精度。1.1 一根信号线背后的加密通讯WS2812 是单线协议数据只有一根线既传数据又传时钟。它用不同宽度的高电平脉冲来表示 0 和 1。具体来说发送一个 bit 的时间周期是固定的 1.25 微秒μs在这个周期内逻辑 0高电平维持约 0.35 μs然后拉低逻辑 1高电平维持约 0.8 μs然后拉低当一帧数据发完后还需要一个大于 50 μs 的低电平作为复位信号RESET灯珠才会把这一帧数据锁存到内部寄存器更新显示颜色。把这三个参数放到一起看你会发现整个时序窗口非常窄。以 0 码为例实际允许的高电平范围大概是 150ns 到 500ns 之间比 1 码的窗口约 650ns 到 950ns要严格得多。常见的 MicroPythontime.sleep_us()在毫秒级别上还行但到了纳秒级就完全失控了因为解释器执行每行代码本身就有一层额外的开销再加上函数调用、变量访问的损耗实际延时和设定值偏差非常大。1.2 为什么说好驱动和难驱动的差别就在一个外设有人可能会问ESP32 平台上大家经常用 RMT 外设来驱动 WS2812一次能控制一整条灯带效果很好。RMT 是 ESP32 的专用红外遥控外设本质是一组硬件通道可以按照预配置的脉冲宽度自动发送波形CPU 只在开始和结束时参与一下中间全部由硬件完成。RP2040 上严格来说没有 RMT但它有一个更灵活、更通用的外设叫 PIOProgrammable I/O可编程输入输出。PIO 本质上是一组小型的、可编程的状态机它能按照你自己写的指令去驱动 GPIO 引脚翻转完全不受 CPU 干预也几乎不受代码执行速度影响。这就使得 RP2040 可以非常平滑地输出 WS2812 需要的纳秒级时序精度比纯软件翻转高出一大截甚至比大多数 MCU 上的定时器中断方案更统一。1.3 数据帧结构24 bit 一攒齐WS2812 的单个灯珠需要 24 bit 数据顺序是 GRB而不是很多初学者以为的 RGB。也就是说第一个字节是绿色分量第二个字节是红色分量第三个字节是蓝色分量。每个分量范围是 0 到 255数值越大对应的灯珠越亮。如果数据帧是级联的第一颗灯珠会把接收到的前 24 bit 数据锁存给自己然后把后面的数据从 DOUT 引脚重新输出给下一颗灯珠。所以一串灯的数据帧就是 n 个 24 bit 数据按顺序排列。举个例子带 5 颗灯珠的灯带数据帧总长度就是 5 × 24 120 bit。这里有个很实际的问题一旦你在代码里把颜色值顺序搞错整条灯带显示出来的颜色会非常魔幻通常是红绿互换。所以写驱动之前先确认你手里的灯珠到底是 GRB 顺序还是 RGB 顺序淘宝上有些杂牌灯珠甚至可能是 GRBW 或者 RGBW 四通道版本那数据帧结构就完全不同了得单独处理。2. 为什么偏偏是 RP2040 的 PIO 来干这活很多新手第一次看到 PIO 这个概念会觉得很高端但其实它并没有想象中那么神秘。RP2040 内部有两组 PIO 模块每组有 4 个状态机总共 8 个状态机。这些状态机可以被看成是精简版的 CPU 核心专门用来执行非常简单的 I/O 操作指令。2.1 PIO 指令集几条指令能干什么PIO 的指令集非常精简常用的就那么几条set把立即数写到引脚、引脚方向或寄存器out把发送移位寄存器OSR里的数据按位移出in把引脚上的数据按位移入接收移位寄存器ISRjmp条件跳转可以根据引脚、寄存器或移位寄存器是否为 0 来决定跳转nop空操作通常用来凑延时wait等待某个引脚或 IRQ 信号你没看错整个指令集就这么简单。它没有算术运算没有内存读写没有中断处理一切都是围绕在精确的时钟周期里操作 GPIO设计的。这种极度精简的设计换来了两个最重要的特性指令执行时间是完全确定的而且极快——每条指令只需要一个时钟周期部分带 delay 的指令可以额外插入 1 到 31 个周期延时。所以驱动 WS2812 时PIO 完全可以做到发送下一个 bit 的精确时刻我可以提前计算好并且误差不超过一个时钟周期约 12.5ns。相比之下用定时器中断方案时中断响应延迟会受到当前主程序执行位置的影响时序抖动很难压到这么低。2.2 PIO 眼中的 WS2812一个状态机的完整生命周期用 PIO 驱动 WS2812核心思路是让状态机以固定频率运行用移位寄存器往外逐位发送数据。数据从 FIFO 队列进入发送移位寄存器OSR然后每发一位就移出一位。当 OSR 里的数据全部移空时如果开启了autopullPIO 会自动从 FIFO 加载下一个 32 bit 数据不需要 CPU 干预。这里有个很容易搞混的点WS2812 每个灯珠需要 24 bit 数据但 MicroPython 的out()指令和 FIFO 都是以 32 bit 为单位操作的。所以通常会更倾向于在代码里把 24 bit 数据对齐到一个 32 bit 变量里比如左移 8 位高位对齐或者设置 pull_thresh24让PIO自动拉取24bit。后者在逻辑上更贴合WS2812的数据帧但会在有些库函数里增加额外的处理。我自己用下来最省心的方式还是把颜色数据整理成 32 bit 的高位对齐格式一个灯珠一个 32 bit 值发送端保持 32 bit 处理逻辑更清晰。2.3 PIO 和 ESP32 的 RMT 对比谁更适合 WS2812 项目RMT 和 PIO 都能驱动 WS2812但两者的设计哲学不同。RMT 是专用外设配置简单每个通道可以预先定义波形表支持硬件循环发送确实很方便。PIO 更接近 FPGA 里的可编程逻辑它的通用性更强不只是驱动 WS2812还能模拟 DHT11、红外遥控、SD 卡、甚至 VGA 视频输出。同样的外设能做很多事情这是 RP2040 一个很大的卖点。如果你本来就打算只控制灯带那买 ESP32 完全够用RMT 方案社区资料也很多。但如果你的项目里还涉及很多其他非标准协议的外设或者你想深入理解时序控制的底层原理RP2040 的 PIO 能给你更多发挥空间。而且Pico 的价格通常在 20 元上下相比带 Wi-Fi/蓝牙的 ESP32 便宜不少在上位机不依赖无线通信的场景里性价比很高。3. 硬件准备与接线别在起步阶段就埋下隐患写代码之前先把硬件环节搞定。WS2812 硬件端常出问题的地方不是协议本身而是电平、供电和接线顺序。3.1 你需要准备的东西从零开始做这个项目我建议准备以下材料树莓派 Pico 或 Pico W 一块RP2040 芯片两者在 PIO 外设上完全一致WS2812 灯带或灯环建议先买 8 到 16 颗灯珠的小尺寸型号来调试5V 电源灯珠供电用。8 颗灯珠用 USB 5V 勉强够16 颗以上建议单独上电源3.3V 转 5V 电平转换模块可靠方案后面细说杜邦线若干、面包板一块必要的电容1000μF/6.3V 电解电容一个并联在电源端100nF 陶瓷电容靠近灯带电源引脚这里特别提一下 1000μF 电容很多人忽略它。WS2812 显示白色高亮时电流可以到 60mA/颗8 颗就是接近 0.5A。如果电源线比较长线路上的寄生电感会引发电压跌落轻则颜色偏色重则芯片复位。在电源两端并联一个大电容能有效缓冲这种瞬时电流冲击这算是玩灯带的人都知道、但新手往往不知道的潜规则。3.2 接线方式与电平转换问题WS2812 的信号线是 5V 逻辑电平而 RP2040 的 GPIO 是 3.3V 逻辑电平。很多商家宣传直接用 GPIO 接 DIN 就行在大多数情况下也确实能工作因为 WS2812 的输入高电平阈值一般在 0.7 × VDD 左右按 5V 供电算是 3.5V。而 RP2040 的 GPIO 在输出高电平时大约能达到 3.3V低于 3.5V 的阈值设计上是处于灰色地带的。如果你手里的灯珠是 5V 供电的常见型号那么大概率还能亮但电压一旦有点波动、线缆长一点、干扰大一点就容易出现不稳定、闪烁、乱色的问题。所以我自己的做法是电路里加一个 3.3V 转 5V 的单向电平转换模块用 TXS0102 或者 74HCT1G125 这类芯片都很便宜几块钱一个。这样信号输出电平就是稳定的 5V时序也不会受电平偏差影响。如果你实在不想加模块也可以用 5V 供电给灯带、3.3V 直接连信号线但要在代码里做降频或者加粗信号线来降低干扰能工作但不严谨。接线顺序也比较固定Pico 的板载 3.3V 和 GND 先连接好灯带的 VCC 接 5V 电源正极GND 接 5V 电源负极GND 同时和 Pico 的 GND 共地信号线从 Pico 的 GPIO 引脚出发经电平转换模块后接灯带 DIN注意Pico 的 GND 和灯带的 GND 必须连通否则信号线没有参考电平灯带肯定不会正常工作。这是很多接线问题的根源。3.3 常见接线错误汇总根据我帮别人排查过的经验硬件问题基本逃不出下面这几类忘记共地——信号线高电平再准也没用因为参考地不一致信号线接到 DOUT 而不是 DIN——灯带尾部接输入整条灯带静默5V 电源功率不够——接上灯带后电压被拉低Pico 跟着重启在通电状态下反复插拔信号线——容易烧 DIN 引脚灯珠内部 IC 很脆弱尤其是第 3 点值得展开说。我曾经在调试一个 30 颗灯珠的项目时直接用电脑 USB 口供电结果一开灯电脑直接提示 USB 设备过流。后来换成 5V/3A 的适配器才正常。所以灯珠数量和亮度需求一定要提前估算电流30 颗全亮白光是 1.8A这不是 USB 口能扛的。正确的做法是灯带独立供电Pico 由自己的 USB 口或 5V 引脚供电两者只共地不要共用一根电源线。4. MicroPython 实现从反面教材到完整驱动代码硬件搞定之后进入代码环节。这一节我会从为什么不能用纯 Python 模拟时序讲起然后拆解手写 PIO 汇编驱动的代码最后介绍 MicroPython 官方neopixel库的用法。这样你可以既理解原理又能快速上手。4.1 反面教材纯 Python 翻转 GPIO 为什么不行先把最简单、也最容易翻车的方式写出来from machine import Pin import time DATA_PIN Pin(22, Pin.OUT) def send_bit(bit): DATA_PIN.value(1) if bit: time.sleep_us(1) # 想模拟 0.8μs 高电平 else: time.sleep_us(0) # 想模拟 0.35μs 高电平 DATA_PIN.value(0) time.sleep_us(0) while True: # 假设发一个绿色 send_bit(0) send_bit(1) # ... 后面还有 22 个 bit先省略 time.sleep_us(50)这个代码的问题非常致命time.sleep_us(1)实际上不会只睡 1 微秒因为函数调用、参数传递、变量访问都产生额外时间开销。用逻辑分析仪实测的话一个 bit 的实际周期很可能在 3 到 5 微秒远超 WS2812 允许的误差范围。灯珠可能偶尔能识别对几个 bit但整体数据一定是错乱的。这不是 MicroPython 的问题任何解释型语言在纳秒级精度的时序控制上都无能为力。所以正确思路是把时序控制交给硬件MicroPython 只负责把数据喂给硬件外设。4.2 手写 PIO 汇编驱动核心代码逐行解析RP2040 的 MicroPython 固件内置了rp2模块可以让你直接在 Python 代码里定义 PIO 程序。下面这段是我在实际项目中验证过的 WS2812 PIO 驱动以 8MHz 状态机时钟运行一个 bit 周期正好是 10 个时钟周期即 1.25μs和 WS2812 数据手册完全吻合。from machine import Pin from rp2 import PIO, StateMachine, asm_pio import time asm_pio(sideset_initPIO.OUT_LOW, out_shiftdirPIO.SHIFT_LEFT, autopullTrue, pull_thresh24) def ws2812(): T1 2 T2 5 T3 3 wrap_target() label(bitloop) out(x, 1).side(0)[T3 - 1] jmp(not_x, do_zero).side(1)[T1 - 1] jmp(bitloop).side(1)[T2 - 1] label(do_zero) nop().side(0)[T2 - 1] wrap() sm StateMachine(0, ws2812, freq8_000_000, sideset_basePin(22)) sm.active(1) def set_color(r, g, b): # WS2812 数据帧是 GRB 顺序 data (g 16) | (r 8) | b sm.put(data)逐行拆解一下asm_pio(sideset_initPIO.OUT_LOW, ...)声明状态机初始化时side-set 引脚默认输出低电平。这里没有显式指定引脚而是在创建StateMachine时通过sideset_basePin(22)指定 GPIO22 为输出引脚。out_shiftdirPIO.SHIFT_LEFT表示数据从移位寄存器的高位移出这样sm.put()传入的 32 bit 值会按高到低的顺序逐个发出最自然的 bit 顺序。out(x, 1).side(0)[T3 - 1]从 OSR 取出 1 bit 放入 x 寄存器同时 side-set 输出低电平并延时 T3 - 1 2 个周期。加上本条指令本身的 1 个周期低电平总宽度是 3 个周期也就是 375ns。这是 bit 周期中低电平阶段的开始。jmp(not_x, do_zero).side(1)[T1 - 1]如果 x 为 0即这一位是逻辑 0跳转到 do_zero否则顺序执行。side-set 输出高电平延时 T1 - 1 1 个周期。由于 jmp 指令本身占 1 个周期所以从这里开始高电平维持了 2 个周期即 250ns。这就是逻辑 0 的高电平宽度。jmp(bitloop).side(1)[T2 - 1]x 为 1 时走到这里不跳转继续循环到 bitloopside-set 保持高电平延时 T2 - 1 4 周期。加上本条指令的 1 周期从步骤 3 开始到此刻高电平共维持了 2 5 7 个周期即 875ns对应逻辑 1 的高电平宽度。nop().side(0)[T2 - 1]逻辑 0 的跳转目标。nop 占 1 个周期延时 T2 - 1 4 周期此时 side-set 是低电平。所以从步骤 3 跳转到这里后低电平宽度为 5 个周期即 625ns。加上前面 2 个周期的高电平一个 bit 周期还是 1.25μs。用表格对比一下关键时序参数时序高电平宽度低电平宽度总周期实际时长(8MHz)逻辑 02.5 周期7.5 周期101.25μs逻辑 17 周期3 周期101.25μs数据手册的标准值是 0 码高电平 0.35μs、1 码高电平 0.8μs。上面的逻辑 0 高电平稍短约 0.3125μs逻辑 1 高电平稍长约 0.875μs但都在允许范围内。这个参数组合是社区里经过大量实测的经典配置兼容性很好。4.3 用官方 neopixel 库简化日常开发如果只是想快速实现跑马灯、呼吸灯这类效果不关心底层协议细节可以直接用 MicroPython 官方提供的neopixel库from machine import Pin from neopixel import NeoPixel pin Pin(22, Pin.OUT) np NeoPixel(pin, 8) # 8 颗灯珠 # 设置第一颗灯珠为红色 np[0] (255, 0, 0) np.write() # 设置整条灯带为绿色 for i in range(8): np[i] (0, 255, 0) np.write()官方neopixel库的底层实现其实就是 PIO 状态机用的是rp2.StateMachine。它在初始化时会在 PIO 的第 0 组分配一个状态机并把指定的 GPIO 配置为 PIO 功能。np[0] (r, g, b)这个赋值操作在底层会把 RGB 数据转换成 PIO 需要的高位对齐格式写入状态机的 FIFO然后np.write()把所有数据刷新到灯带。这个库的好处是 API 非常简洁几乎不用关心时序细节。缺点是自定义程度低没法改灯珠类型、没法改数据帧顺序、也没法在同一根线上混用不同协议。但作为日常使用完全足够强烈建议新手从这里入手跑通第一个点灯程序。4.4 进阶用法跑马灯、渐变、多状态机并行控制在基础驱动跑通之后可以继续扩展一些常用的灯效。下面是一个简单跑马灯的实现import time from machine import Pin from neopixel import NeoPixel LED_COUNT 16 np NeoPixel(Pin(22, Pin.OUT), LED_COUNT) def wheel(pos): # 经典的 0-255 彩虹色轮 if pos 85: return (255 - pos * 3, pos * 3, 0) elif pos 170: pos - 85 return (0, 255 - pos * 3, pos * 3) else: pos - 170 return (pos * 3, 0, 255 - pos * 3) while True: for i in range(LED_COUNT): np.fill((0, 0, 0)) np[i] wheel((i * 8) % 256) np.write() time.sleep_ms(30)注意里面对wheel色轮做了一个小优化根据灯的索引偏移色相这样跑起来是彩虹流动效果而非单个白点视觉效果更丰富。你也可以把time.sleep_ms(30)改成变量来控制流动速度。RP2040 的 PIO 有 8 个状态机这意味着可以同时驱动 8 条独立的 WS2812 灯带每条灯带并行刷新、互不干扰。这在做矩阵灯、分区域灯控时非常有用。只需要创建多个StateMachine实例分别指定不同的 GPIO 和 FIFOCPU 只要往不同状态机里写入数据即可。5. 避坑实录从时序到调试的排查技巧这里整理了我在实际项目中踩过、以及帮别人排查过的一堆问题。有些问题不太容易遇到一旦遇到就是折腾半天的级别希望下面的总结能帮你少走点弯路。5.1 灯带只亮第一颗灯珠、后面的不亮这是最常见的现象。第一颗灯珠能亮说明 PIO 驱动、GPIO 输出、接线、时序都是通的问题出在数据帧长度或者级联逻辑上。典型原因有两个一是你只发送了一个灯珠的数据帧比如np.write()前只设置了一颗后面的灯珠自然拿不到数据二是数据长度与灯珠数量不匹配比如灯带是 16 颗你发送了 8 颗的数据第 8 颗之后的所有灯珠收不到完整数据所以全灭。排查方法很简单先用固定颜色点亮全部灯珠比如np.fill((128, 0, 0))后np.write()。如果只有第一颗红基本可以确定发送的数据量小于灯珠数。再检查代码里NeoPixel(pin, count)中的 count 是否和实际灯珠数一致。还有一个坑在购买渠道有些商家把两段 1 米灯带剪开再焊起来卖中间焊点质量差导致信号丢失也会出现只亮前面几颗的现象。这种硬件问题可以用万用表测 DI 到 DO 之间的通断来确认。5.2 颜色错乱红色变绿色、颜色偏暗如果你点亮第 0 颗灯珠设置红色(255, 0, 0)结果它显示绿色那基本可以确定是数据帧顺序问题。WS2812 是 GRB 顺序官方neopixel库已经处理好这个映射但如果你用的是自己手写的 PIO 代码很容易把 RGB 直接塞给状态机导致红绿互换。偏暗的问题通常是 PWM 刷新率不够造成的视觉问题。WS2812 内部是靠恒流源驱动 LED 的亮度由每颗灯珠的 PWM 占空比决定。如果你的 PIO 频率设置偏低比如低于 4MHz一个 bit 周期被拉长到 2.5μs 以上灯珠虽然能识别数据但是内部的逆变电路充电时间不够峰值电流上不来亮度就会下降。解决方法是把StateMachine的freq参数设置为 8MHz这是经过广泛验证的最优值。5.3 灯带闪烁或随机乱色这种现象多出现在电源供电不足或者信号线受到干扰的场景。排查步骤可以按下述顺序来先用 USB 电源单独给灯带供电Pico 单独供电验证是否是共用电源导致的问题在灯带电源端并联 1000μF 电解电容看闪烁是否缓解检查信号线长度。超过 50cm 的信号线建议用双绞线或者屏蔽线并且远离电源线如果是用 3.3V GPIO 直接驱动优先加上电平转换模块我曾经在一个项目里把信号线走在了 220V 交流线旁边灯带每隔几秒就闪一下。后来把信号线挪开、加了一个磁环问题立刻消失。用电环境是排查这类问题时最容易忽略的因素。5.4 关于树莓派 Pico 总是进入烧录模式的问题有朋友问过为什么我的 Pico 一上电就进入烧录模式BOOTSEL 盘符出现而不是正常运行程序这种情况通常发生在以下几种原因RP2040 芯片检测到 BOOTSEL 引脚GPIO15即板上的 BOOTSEL 按钮在复位时被拉低你用的 MicroPython 固件不是最新版或者固件本身损坏程序里将 GPIO15 配置为输入下拉或者输出低电平复位瞬间引起 BOOTSEL 被误触发排查方法很直接按住 BOOTSEL 按钮插入 USB重新烧录一次 MicroPython 固件。如果仍然随机进入烧录模式检查你的代码里是否占用了 GPIO15避免在程序初始化阶段操作这个引脚。还要注意Pico 在进入烧录模式时是 USB 枚举为存储设备这并不代表芯片损坏重新烧录固件一般都能解决。5.5 常见问题速查表把上面这些内容整理成一张速查表方便直接对照排查现象可能原因排查方向所有灯都不亮未共地、电源接反、DIN 接错测 GPIO 引脚电压、检查接线只亮第一颗灯数据长度不足、级联信号断确认灯珠数量、测 DO 输出颜色互换数据帧 RGB/GRB 顺序用官方 neopixel 库测试整体偏暗PIO 频率过低、供电电压不足设置 freq8_000_000加电容间歇性闪烁电源功率不足、信号干扰独立供电、接电容、走线远离干扰源随机进入烧录模式固件损坏、GPIO15 被占用重新烧录固件、检查代码5.6 调试工具推荐如果你要深入排查时序相关问题强烈建议入手一个逻辑分析仪。现在市面上几十块钱的 8 通道 24MHz 采样率的逻辑分析仪就能满足 WS2812 调试需求。把信号线接在分析仪上用 PulseView 软件能看到实时波形高电平宽度是 0.35μs 还是 0.8μs一目了然。这个投入非常值得。没有逻辑分析仪的时候你可能要花几个小时去猜问题出在哪里有了波形图之后问题几乎当场就能定位。比如上面提到的纯 Python 模拟时序问题接上分析仪一测发现一个 bit 周期已经飙到 4μs 了就会从根源上明白为什么灯不亮。6. 扩展方向PIO 还能帮你做更多事学会了 PIO 驱动 WS2812你其实已经掌握了 RP2040 的一个核心能力。PIO 的应用远不止灯带控制它还能模拟各种常见协议比如DHT11/DHT22 温湿度传感器的单总线协议DS18B20 温度传感器时序需要额外的 ROM 操作指令支持红外遥控信号的接收与解码智能卡 ISO 7816 协议甚至 VGA 视频信号输出把这些协议在 PIO 上跑通你的 Pico 就不再只是一个点灯板而是一台能同时处理多种通信任务的微型控制器。PIO 状态机之间相互独立你可以让状态机 0 驱动灯带、状态机 1 读取 DHT11、状态机 2 解码红外遥控完全并行运行。如果项目里还需要屏幕显示也很顺手。Pico 的 I2C 接口接一块 SSD1306 OLED在跑马灯效果旁边显示当前模式和亮度参数整体项目的可玩性和实用性一下就上去了。MicroPython 的ssd1306驱动库是官方内置的几行代码就能把屏幕点亮配合灯带做成一个小型桌面氛围灯控制面板是很好的练手项目。根据我自己的经验从 PIO 基础到能稳定驱动灯带再到玩出各种灯效大概需要一个下午的时间。卡住的点往往不是代码本身而是对时序、电平、供电这些硬件直觉的建立。这也是为什么我建议新手从硬件接线开始学先把环境弄靠谱了再回头看代码会顺畅得多。