
1. 从“蔡徐坤”到“行空板”点阵屏的“文艺复兴”最近在B站和电子爱好者论坛上一个看似“古老”的技术——点阵屏又火了起来。起因是有人用51单片机驱动一块8x8的点阵屏循环播放“蔡徐坤打篮球”的像素动画视频播放量轻松破百万。这个现象很有意思它背后反映的其实是硬件DIY圈子里一种“复古”的创作热情。大家厌倦了动辄4K、OLED的精致显示反而开始迷恋这种由一个个发光二极管LED组成的、像素感十足的原始显示方式。它不完美有锯齿但恰恰是这种“粗糙”和“可编程”的特性让每个像素的亮灭都充满了创造的乐趣。而“行空板”的出现为这股热情添了一把更猛的火。如果说51单片机是“手工作坊”那行空板就是“现代化车间”。它本质上是一块集成了高性能处理器通常是ARM Cortex-A系列、丰富外设接口GPIO、I2C、SPI等和完整Linux操作系统的单板计算机。这意味着你不再需要像用51单片机那样从零开始写底层驱动、处理繁琐的时序你可以用更高级的语言比如Python调用现成的库像搭积木一样快速实现复杂的逻辑和动画。把“行空板”和“手写点阵屏”结合起来就是一次“降维打击”用现代计算平台的强大算力和易用性去驱动和控制这种充满复古魅力的显示设备实现更复杂、更流畅、更有创意的视觉效果。所以这篇内容我想和你聊聊怎么用行空板这块“现代化车间”去玩转点阵屏这块“复古画布”。我会从最基础的硬件连接、驱动原理讲起一直深入到如何手写代码实现动态效果、图像处理并分享一些我实际调试中遇到的坑和解决思路。无论你是被“蔡徐坤动画”吸引来的新手还是想寻找更高效点阵屏开发方案的老手相信都能在这里找到实用的东西。2. 硬件选型与连接点阵屏的“五脏六腑”玩点阵屏第一步是认识你的“画布”。市面上常见的点阵屏模块主要有两种驱动芯片MAX7219和它的兼容芯片如TM1640等以及更专业的行列驱动芯片组合如74HC595配合行驱动芯片。网络热词里提到的RU595DSP我查证了一下它并非一个标准的通用驱动芯片型号很可能是一个特定厂家对基于74HC595方案的点阵屏模块的命名或型号。RU可能是厂家代号595指代74HC595移位寄存器DSP可能指“Display”显示。所以我们可以把它理解为一种使用74HC595作为列驱动芯片的点阵屏模块。这里我强烈建议初学者从MAX7219驱动的8x8点阵屏模块开始。理由很简单集成度高MAX7219一颗芯片就解决了扫描、亮度控制、多位数码管或点阵驱动我们只需要通过3根线DIN CLK CS发送数据它内部会完成所有复杂的行列扫描工作。库支持完善在Arduino、MicroPython乃至行空板的Python环境下都有非常成熟稳定的库如luma.led_matrix库的相应组件几乎不需要关心底层时序。接线简单通常只需要连接VCC GND DIN CLK CS五根线不易出错。而行空板我们以常见的基于全志H3/H5等芯片的板子为例它提供了丰富的GPIO口。我们需要将点阵屏的这三根数据线连接到行空板的任意GPIO上。例如点阵屏VCC- 行空板3.3V或5V注意查看模块电压MAX7219模块通常兼容5V和3.3V但为保险起见接3.3V更安全点阵屏GND- 行空板GND点阵屏DIN- 行空板GPIO10(MOSI功能方便后续使用硬件SPI速度更快)点阵屏CLK- 行空板GPIO11(SCLK功能)点阵屏CS- 行空板GPIO8(可任意指定一个GPIO)注意有些MAX7219模块标注的是DINCLKCS/LOAD有些则标注为DATACLKCS。本质是一样的。另外如果你的点阵屏是RU595DSP这类基于74HC595的模块接线和驱动方式会完全不同通常需要连接更多的线如数据、时钟、锁存可能还需要独立的行选择线且需要自己编写扫描程序。这属于更底层的玩法我们后续再讨论。硬件连接好后一个关键步骤是确保行空板系统内已经启用了SPI接口。因为我们将DIN和CLK接到了硬件SPI引脚上利用硬件SPI通信效率远高于软件模拟。可以通过行空板的Web管理界面或SSH连接后使用raspi-config如果行空板基于Raspbian或查看/boot/config.txt等文件来启用SPI。通常主流行空板镜像默认已开启常用外设接口。3. 软件环境搭建与基础驱动行空板通常运行着Debian或Ubuntu等Linux发行版我们可以直接使用Python进行开发。这里我们使用一个非常强大的库luma.led_matrix。它是luma.core生态系统的一部分专门用于驱动各种LED显示设备对MAX7219点阵屏的支持非常好。首先通过SSH或终端登录到你的行空板安装必要的库sudo apt update sudo apt install python3-pip python3-pil -y sudo pip3 install luma.led_matrix安装完成后我们就可以开始写第一个点亮程序了。创建一个Python文件比如test_matrix.py#!/usr/bin/env python3 from luma.led_matrix.device import max7219 from luma.core.interface.serial import spi, noop from luma.core.render import canvas from luma.core.legacy import text, show_message from luma.core.legacy.font import proportional, CP437_FONT, TINY_FONT # 1. 初始化SPI接口 # 参数说明port0, device0 表示使用SPI0总线CS接在CE0上。 # 如果你的CS接在其他GPIO如我们之前举例的GPIO8则需要使用gpio模式稍复杂。 # 这里假设CS接在了行空板的SPI0 CE0通常是GPIO8上这是最简单的情况。 serial spi(port0, device0, gpionoop()) # 2. 创建设备对象这里指定是8x8的点阵级联数为1一块屏 device max7219(serial, cascaded1, block_orientation0, rotate0) # 3. 使用canvas进行绘制 with canvas(device) as draw: # 在坐标(0, 0)处画一个点 draw.point((0, 0), fillwhite) # 画一条从(2,2)到(6,6)的线 draw.line([(2, 2), (6, 6)], fillwhite) # 画一个矩形框 draw.rectangle([(1, 1), (7, 7)], outlinewhite) # 让显示保持一段时间 import time time.sleep(5) # 4. 显示滚动文字致敬热词 try: show_message(device, IKUN, fillwhite, fontproportional(CP437_FONT), scroll_delay0.06) except KeyboardInterrupt: device.cleanup()运行这个脚本python3 test_matrix.py你应该能看到点阵屏上出现一个点、一条线、一个框然后“IKUN”字样从右向左滚动显示。恭喜你的行空板已经成功驱动了点阵屏关键点解析与避坑spi(port0, device0, ...)这里的port和device参数需要根据行空板的具体SPI总线来定。大部分行空板的主SPI是port0。device0对应CE0Chip Enable 0device1对应CE1。最常遇到的坑就是CS线接错了SPI的片选。如果你像前文举例那样把CS接到了GPIO8这通常是CE0那么用device0就对了。如果不确定可以查看行空板的引脚定义图。gpionoop()这个参数告诉库我们不使用额外的GPIO来控制硬件复位之类的功能。保持默认即可。cascaded1如果你只有一块8x8屏这里就是1。如果你将多块MAX7219模块串联起来组成更大的屏幕比如16x16就需要修改这个参数并正确连接模块间的DOUT到下一块的DIN。block_orientation和rotate这两个参数用于调整显示方向。如果你的屏幕安装方向是90度或180度或者显示的内容上下左右反了调整这两个参数尝试0 -90 90 180就能解决。draw对象它来自PIL库Python Imaging Library提供了丰富的2D绘图API如pointlinerectangletext等。这意味着你可以用处理图像的方式来“思考”点阵屏的显示非常直观。4. 实现“手写”动画与图像处理基础的图形显示完成了但我们的目标是“手写”这意味着要能动态地、按我们设计的轨迹来点亮像素。这可以分为两个层面一是编程绘制动态图案如逐帧动画二是将外部图像甚至手绘草图转换为点阵屏可以显示的数据。4.1 编程绘制动态图案心跳动画示例我们来实现一个简单的心跳动画。思路是预先定义好几帧“心跳”的位图用0和1的二维列表表示然后循环显示它们。#!/usr/bin/env python3 from luma.led_matrix.device import max7219 from luma.core.interface.serial import spi, noop from luma.core.render import canvas import time serial spi(port0, device0, gpionoop()) device max7219(serial, cascaded1, block_orientation0, rotate0) # 定义心跳动画的4帧每帧是一个8x8的二维列表1表示亮0表示灭 heart_frames [ [ [0,0,0,0,0,0,0,0], [0,1,1,0,0,1,1,0], [1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1], [0,1,1,1,1,1,1,0], [0,0,1,1,1,1,0,0], [0,0,0,1,1,0,0,0], [0,0,0,0,0,0,0,0] ], [ [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], [0,0,1,0,0,1,0,0], [0,1,1,1,1,1,1,0], [0,1,1,1,1,1,1,0], [0,0,1,1,1,1,0,0], [0,0,0,1,1,0,0,0], [0,0,0,0,0,0,0,0] ], # ... 可以定义更多中间帧让动画更平滑 ] def display_frame(frame_data): 将二维列表数据显示到点阵屏上 with canvas(device) as draw: for y in range(8): # 行 for x in range(8): # 列 if frame_data[y][x]: draw.point((x, y), fillwhite) try: while True: for frame in heart_frames: display_frame(frame) time.sleep(0.2) # 控制动画速度 except KeyboardInterrupt: device.cleanup()这个方法非常适合制作简单的图标动画、表情变化。你可以发挥创意设计任何8x8像素的逐帧动画。4.2 图像转换把任何图片变成点阵图案更酷的是我们可以让行空板读取一张图片然后自动将其“像素化”并显示在点阵屏上。这里我们需要用到PIL库的图像处理功能。假设我们有一张favicon.png的图片想把它显示在8x8的点阵屏上。#!/usr/bin/env python3 from luma.led_matrix.device import max7219 from luma.core.interface.serial import spi, noop from luma.core.render import canvas from PIL import Image import time serial spi(port0, device0, gpionoop()) device max7219(serial, cascaded1, block_orientation0, rotate0) # 1. 打开并处理图片 image_path favicon.png img Image.open(image_path).convert(1) # 转换为1位黑白图二值化 img img.resize((8, 8), Image.Resampling.NEAREST) # 缩放到8x8像素使用最近邻算法保持硬边缘 # 2. 将图像数据转换为二维列表 pixels list(img.getdata()) # 获取所有像素值0或255 width, height img.size # 将一维列表转换为8x8的二维列表并判断是否点亮像素值128视为点亮 frame_data [] for y in range(height): row [] for x in range(width): # 注意getdata()返回的行优先顺序。对于1位图像0是黑关非0是白开。 # 我们这里假设图片背景是黑色图案是白色。如果相反需要取反。 row.append(1 if pixels[y * width x] 0 else 0) frame_data.append(row) # 3. 显示 with canvas(device) as draw: for y in range(8): for x in range(8): if frame_data[y][x]: draw.point((x, y), fillwhite) time.sleep(5) device.cleanup()实操心得convert(1)这一步是二值化阈值处理将彩色或灰度图转换为纯黑白。默认阈值是128。如果转换后效果不好细节丢失可以先转换成灰度图L然后手动调整阈值或者使用Image.point()函数进行更灵活的处理。resize((8,8), Image.NEAREST)使用最近邻插值法至关重要。如果使用双线性或双立方插值缩放后的8x8图像会变得模糊丢失清晰的像素边界转换到点阵屏上效果很差。像素值判断getdata()对于模式1的图像返回的是0或255。但有时候也可能是0或1或者True/False。最稳妥的方法是先查看img.mode并根据实际情况写判断逻辑。一个通用的方法是判断像素是否“非全黑”。取反显示有时候我们希望亮的像素对应图片中黑色的部分。这时只需要将判断条件取反即可row.append(0 if pixels[y * width x] 0 else 1)。5. 进阶玩法与性能优化当你掌握了基础显示后可以尝试一些更进阶的玩法这些玩法充分利用了行空板作为“微型电脑”的计算优势。5.1 多屏级联与更大画布单个8x8点阵屏能显示的内容有限。MAX7219支持硬件级联。你可以将多块模块的DOUT接到下一块的DIN上所有模块共享CLK和CS线。在软件上只需修改cascaded参数。例如将4块8x8屏排成2x2的阵列组成一个16x16的点阵屏。# 假设4块屏级联物理排列为2行2列 serial spi(port0, device0, gpionoop()) # cascaded4 表示总共4块MAX7219芯片 device max7219(serial, cascaded4, block_orientation0, rotate0, width16, height16)现在你的canvas尺寸就变成了16x16。绘制图形和文字时坐标范围也相应扩大。这可以用来显示更复杂的图案、更长的滚动文字甚至简单的游戏如贪吃蛇、像素小鸟。5.2 使用硬件SPI提升刷新率在前面的例子中我们初始化时使用了spi()默认就尝试使用硬件SPI。硬件SPI由芯片组专门硬件处理速度极快且不占用CPU。这是点阵屏实现流畅动画尤其是多屏级联时的关键。确保你的DIN和CLK接在了行空板的硬件SPI引脚上通常是MOSI和SCLK并且在系统层面启用了SPI驱动。如果因为引脚冲突等原因无法使用硬件SPIluma库也支持bitbang软件SPI但刷新率会低很多动画可能出现闪烁。# 软件SPI不推荐仅作备用 from luma.core.interface.serial import bitbang serial bitbang(SCLK11, MOSI10, CE8) # 需要指定具体的GPIO编号5.3 驱动“RU595DSP”类点阵屏如果你手头是那种需要自己扫描的74HC595驱动点阵屏驱动方式就完全不同了。你需要理解“行列扫描”的原理点阵屏的LED阴极按行连接阳极按列连接或反之。要点亮某个点(x, y)需要给对应的行输出低电平同时给对应的列输出高电平。由于行空板有Linux系统直接进行精确的微秒级延时循环扫描可能会被系统调度打断导致显示闪烁。一个更可靠的方法是使用硬件PWM或定时器中断但行空板上的Python环境直接操作硬件中断比较麻烦。更实用的方案是使用“多线程”“RPi.GPIO”或“gpiod”库。一个线程专门负责以固定的频率比如100Hz进行扫描刷新。import threading import time import RPi.GPIO as GPIO # 假设连接方式74HC595的数据DS接GPIO17时钟SHCP接GPIO27锁存STCP接GPIO22。 # 行选择线假设共阴极4根线接GPIO5 GPIO6 GPIO7 GPIO8假设是4x 8x8屏的行选实际需根据屏规格调整 DATA_PIN 17 CLOCK_PIN 27 LATCH_PIN 22 ROW_PINS [5, 6, 7, 8] GPIO.setmode(GPIO.BCM) GPIO.setup([DATA_PIN, CLOCK_PIN, LATCH_PIN] ROW_PINS, GPIO.OUT) # 显示缓冲区一个二维列表表示整个屏幕的点亮状态 frame_buffer [[0 for _ in range(32)] for _ in range(16)] # 假设是16行32列 def shift_out(data_byte): 向74HC595移位寄存器发送一个字节数据 for i in range(7, -1, -1): # 高位先出 bit (data_byte i) 1 GPIO.output(DATA_PIN, bit) GPIO.output(CLOCK_PIN, GPIO.HIGH) time.sleep(0.000001) # 短暂延时模拟时钟信号 GPIO.output(CLOCK_PIN, GPIO.LOW) time.sleep(0.000001) def scan_display(): 扫描显示线程函数 current_row 0 while True: # 1. 关闭所有行消隐防止鬼影 for pin in ROW_PINS: GPIO.output(pin, GPIO.HIGH) # 假设行选是高电平有效 # 2. 准备当前行的列数据 row_data frame_buffer[current_row] # 将行数据按8位一组拆分发送给多个级联的74HC595 # 这里简化处理假设只有一组8列 column_byte 0 for col in range(8): if row_data[col]: column_byte | (1 (7 - col)) # 根据硬件连接决定位顺序 # 3. 将列数据锁存到595的输出寄存器 shift_out(column_byte) GPIO.output(LATCH_PIN, GPIO.HIGH) time.sleep(0.000001) GPIO.output(LATCH_PIN, GPIO.LOW) # 4. 开启当前行 GPIO.output(ROW_PINS[current_row % len(ROW_PINS)], GPIO.LOW) # 假设低电平选通行 # 5. 延时一段时间保持显示 time.sleep(0.001) # 扫描一行的持续时间影响亮度和刷新率 # 6. 切换到下一行 current_row (current_row 1) % len(frame_buffer) # 启动扫描线程 scan_thread threading.Thread(targetscan_display, daemonTrue) scan_thread.start() # 主线程可以更新frame_buffer try: while True: # 在这里更新frame_buffer的内容例如画一个移动的点 # ... (更新逻辑) time.sleep(0.1) except KeyboardInterrupt: GPIO.cleanup()重要提示驱动这类屏非常底层需要你清楚屏的电路原理共阴/共阳行列对应关系并且代码的时序控制要精确。上面的代码只是一个原理性示例实际应用中需要根据具体屏的规格书调整延时、电平逻辑并可能需要对frame_buffer的更新加锁以防止线程冲突。对于新手强烈建议先从MAX7219模块玩起。6. 项目构思与创意拓展掌握了核心技术后点阵屏可以成为很多有趣项目的显示终端。结合行空板强大的网络、传感器和计算能力这里有几个创意方向网络信息显示器让行空板连接Wi-Fi通过API获取天气、股票、B站粉丝数、快递信息等滚动显示在点阵屏上。可以使用requests库获取数据PIL生成图像。传感器数据可视化连接温湿度传感器如DHT11/DHT22、光线传感器将实时数据以动态图表如柱状图、波形图的形式显示在点阵屏上。迷你游戏机配合几个按钮就可以在16x16甚至更大的点阵屏上玩贪吃蛇、俄罗斯方块、Flappy Bird等经典像素游戏。行空板的性能完全足够处理这些游戏的逻辑。音乐频谱可视化通过麦克风或音频输入接口采集声音信号进行FFT快速傅里叶变换分析将频谱以动态柱状图的形式在点阵屏上显示出来。这需要用到numpy等库进行数学运算对行空板是个不错的性能挑战。“手写”输入与互动配合一个摇杆或光敏传感器阵列实现简单的绘图板功能。你可以控制一个像素点在屏幕上移动并点亮路径实现真正意义上的“手写”。我个人在尝试音乐频谱可视化项目时遇到过一个典型的性能坑最初我用Python的纯循环进行FFT计算和屏幕刷新发现刷新率很低动画卡顿。后来我将FFT计算和显示刷新放在两个独立的线程中并使用了numpy的FFT函数替代手动实现的DFT同时将点阵屏的刷新部分用luma库的display函数替代了逐点绘制的canvas性能得到了巨大提升。这提醒我们在行空板这样的资源受限平台上合理利用库、多线程和高效算法至关重要。点阵屏的魅力在于它的简单和直接。每一个发光的点都是你代码逻辑的直接映射。从最初的“Hello World”到一个复杂的互动项目这个过程充满了硬件与软件结合的乐趣。行空板降低了嵌入式开发的门槛让你可以更专注于创意本身。希望这篇内容能帮你打开思路用这块复古的“像素画布”创作出属于你自己的光绘作品。