
1. 项目概述当FPGA开发板成为艺术表达的媒介几年前当我第一次把PYNQ-Z2开发板从盒子里拿出来时我满脑子想的都是卷积神经网络加速、硬件加速器设计这些硬核技术话题。它是一块强大的FPGA现场可编程门阵列平台集成了ARM处理器和可编程逻辑在学术界和工业界常被用于验证算法、构建原型。然而一个偶然的念头让我开始思考当一块被设计用来执行确定逻辑、处理海量数据的“机器大脑”被赋予一种看似无目的、充满随机与隐喻的“行为”时会发生什么这就是“一个由PYNQ-Z2小车完成的行为艺术”项目的起点——一次用纯粹的技术工具去触碰非技术性表达边界的尝试。这个项目的核心是让一台搭载PYNQ-Z2的小车脱离传统的循迹、避障或目标追踪任务转而执行一套由程序定义的、具有象征意义和情感张力的行为序列。它不再是“解决问题”的工具而是成为了表达本身。PYNQ-Z2在这里扮演了双重角色其ARM Cortex-A9处理器作为“大脑”负责高级行为决策和与环境的交互而其FPGA部分则可被编程为“感官”或“本能”实现一些独特的、低延迟的硬件行为比如生成特定的光效模式、发出非周期性的声音或者控制电机做出带有“犹豫”或“冲动”感的运动。这不仅仅是编程更像是为一块硅基芯片编写具有“性格”的剧本。它适合谁我认为有三类朋友可能会感兴趣一是厌倦了常规Demo的嵌入式或FPGA开发者想寻找一个能融合技术深度与创意表达的项目二是数字艺术或交互艺术领域的创作者希望了解如何将可编程硬件作为可靠的创作媒介三是任何对科技与人文交叉点感到好奇的人这个项目展示了代码和电路如何承载超越其本身功能的意义。接下来我将完整拆解从构思、硬件搭建、行为逻辑设计到最终实现与调试的整个过程分享如何将一块冰冷的开发板变成一场有温度的行为表演者。2. 项目整体设计与艺术概念拆解2.1 核心艺术概念与行为剧本设计行为艺术的核心在于“行为”本身所传递的观念。我们的PYNQ-Z2小车不是要完成一个高效的任务而是要演绎一个故事或一种状态。我构思的核心概念是“寻光与困惑”在一个有限的空间内小车本能地被光源吸引寻光但在接近过程中又会因为程序预设的“犹豫”、“记忆”或“干扰”算法而产生徘徊、折返甚至自我冲突的行为困惑最终可能抵达也可能永远在循环中。这个概念的实现依赖于一套分层的“行为剧本”基础驱动层负责最底层的电机控制、传感器数据读取如光敏电阻、超声波测距。这部分在FPGA中实现以确保实时性和确定性。行为状态机层在ARM处理器上运行一个状态机。状态不是简单的“前进”、“后退”而是“坚定追寻”、“迟疑张望”、“记忆回溯”、“冲突旋转”等更具拟人色彩的状态。每个状态决定了驱动层的参数如速度、转向角以及FPGA“感官”的输出如LED闪烁频率、蜂鸣器音调。环境交互与随机因子层引入伪随机数或基于传感器噪声的随机因子影响状态切换的条件和持续时间让每次运行的行为轨迹都不可完全预测增加艺术的“一次性和独特性”。为什么选择PYNQ-Z2来实现普通单片机如STM32也能控制小车做复杂运动但PYNQ-Z2的独特优势在于其软硬协同能力。FPGA可以并行处理多个传感器输入并生成复杂的脉冲控制信号让电机的启停、加减速带有一种“模拟感”而非数字台阶感这对于表现“犹豫”这样的细微动作至关重要。同时PYNQ上的Python环境PYNQ之名即源于PythonZynq让我们能快速原型化复杂的行为逻辑并用Jupyter Notebook实时调整“剧本”就像导演在现场说戏一样方便。2.2 硬件平台搭建与选型考量小车底盘是行为的载体其选择直接影响艺术表达的质感。底盘与驱动模块 我选择了一个结构简单的两轮差分驱动底盘。四轮小车太稳定、太“工程化”而两轮结构在转向时会有更明显的轴心变化和轻微晃动这种不稳定性恰好能强化“困惑”和“不确定”的视觉感受。电机我选了带有编码器的直流减速电机。编码器并非用于精确的里程计Odometry而是用来感知“自身努力的程度”——例如在“记忆回溯”状态程序会命令电机反转但通过编码器反馈判断是否真的回到了记忆中的某个位置这种“试图回到过去但总有偏差”的意象是行为艺术的一部分。传感器配置光敏电阻阵列这是“寻光”行为的核心。我在小车前方布置了三个光敏电阻分别指向左、中、右方向。FPGA负责高速采集它们的模拟电压值并进行比较将“最强光方向”这个结果传递给ARM处理器。关键在于我并没有做复杂的滤波而是保留了部分噪声让光源方向的判断偶尔出现“跳动”模拟注意力不集中。超声波测距模块用于感知障碍但在这里障碍物被赋予了新的含义。它可能代表“无法逾越的过去”或“内心的边界”。当靠近障碍物时触发的不是简单的避障而是可能进入“冲突旋转”或“长时间停滞”状态。惯性测量单元IMUMPU6050模块用于感知小车自身的姿态。剧烈的转向或长时间的原地旋转会导致姿态角变化这可能作为触发状态切换的条件之一例如“眩晕后的静止”。PYNQ-Z2的接口与扩展 PYNQ-Z2的Arduino接口和RPI接口用于连接上述传感器和电机驱动板如L298N或TB6612。电机驱动板的PWM控制信号直接由PYNQ-Z2的FPGA部分产生因为FPGA可以生成非常平滑且可动态调整的PWM波控制电机的“情绪”。LED灯带和蜂鸣器也连接到FPGA的GPIO上由硬件逻辑直接驱动确保光效和声音与行为同步无软件延迟。注意电源管理是艺术项目稳定的基石。电机启动瞬间电流很大务必为电机驱动部分单独供电并与PYNQ-Z2的逻辑电源隔离。我使用了两块独立的锂电池并通过共地连接。一个因为电机干扰导致的系统重启会彻底毁掉一场正在进行中的“行为表演”。3. 核心细节解析软硬协同的行为生成引擎3.1 FPGA硬件逻辑设计赋予小车“本能”与“感官”在PYNQ上FPGA逻辑通常通过Overlay比特流文件加载。我使用Vivado HLS和Block Design设计了一个自定义的IP核它包含以下几个关键模块多通道PWM控制器不同于简单的占空比设置我设计了一个带缓存的PWM发生器。ARM处理器可以通过AXI-Lite总线发送一个目标占空比和一个“变化斜率”参数。FPGA硬件会在每个时钟周期平滑地将当前占空比调整至目标值。例如当状态切换到“迟疑张望”时ARM会发送一个较低的目标速度和较大的斜率缓慢加速电机就会表现出“慵懒”的启动特性。模拟传感器采集与预处理光敏电阻和超声波模块的回响信号需简单电路处理接入PYNQ的XADC模拟数字转换器引脚。我在FPGA里实现了一个轻量级的窗口平均滤波器但故意设置了一个较小的窗口让输出值保留了一定的实时波动。这个波动的数据会连同平均值一起打包发送给ARM为“不确定性”提供数据源。灯光与声音效果发生器我定义了几种硬件光效模式如“呼吸”、“急促闪烁”、“随机星光”。ARM只需发送一个模式代码和基础参数FPGA就会自动生成相应的GPIO时序。例如“冲突旋转”状态对应着“急促闪烁”的红色灯光由硬件并行执行完全不占用CPU资源效果极其流畅。关键技巧利用AXI-Stream接口传递“情绪流”。除了控制参数我还设计了一条从FPGA到ARM的AXI-Stream数据流它不断发送一组经过处理的传感器原始数据摘要如光强波动方差、最近几次电机命令的执行误差。ARM端的程序可以将此视为小车的“潜意识流”或“情绪指标”并据此影响高层行为决策实现从硬件“感官”到软件“意识”的闭环。3.2 ARM端Python行为状态机实现在PYNQ的PS处理系统端我们使用Python编写主控程序。Jupyter Notebook提供了完美的交互式调试环境。# 行为状态枚举简化示例 class BehaviorState: IDLE 0 # 静止观察 PURSUIT 1 # 坚定追寻 HESITATE 2 # 迟疑张望 CONFLICT 3 # 冲突旋转 MEMORY 4 # 记忆回溯 # 状态机核心类 class ArtCarBehaviorEngine: def __init__(self, overlay): self.overlay overlay # 加载的FPGA Overlay self.current_state BehaviorState.IDLE self.light_history [] # 用于“记忆” self.conflict_timer 0 def update_sensors(self): # 从FPGA读取传感器数据包括“情绪流” light_data self.overlay.axi_stream_sensor.read() self.current_light_dir light_data[primary_dir] self.light_volatility light_data[volatility] # 光强波动性作为“困惑度”指标 self.distance self.overlay.ultrasonic.read() def state_transition(self): # 基于传感器数据和内部状态决定下一个状态 volatility_threshold 0.3 distance_threshold 20 # 厘米 if self.current_state BehaviorState.PURSUIT: if self.light_volatility volatility_threshold: return BehaviorState.HESITATE # 光不稳定开始犹豫 if self.distance distance_threshold: return BehaviorState.CONFLICT # 接近障碍边界产生冲突 # ... 其他条件 elif self.current_state BehaviorState.HESITATE: if random.random() 0.1: # 引入10%的随机性 return BehaviorState.MEMORY # 随机陷入回忆 # ... 其他条件 # ... 其他状态判断 def execute_state(self, new_state): self.current_state new_state # 根据新状态通过AXI-Lite配置FPGA中的各个硬件模块 if new_state BehaviorState.PURSUIT: self.overlay.pwm_controller.write_target_speed(80) # 目标速度80% self.overlay.pwm_controller.write_slope(5) # 平滑变化斜率 self.overlay.led_controller.write_mode(0x01) # 模式1稳定白光 # 同时向“坚定追寻”的光源方向转向 self.overlay.servo_controller.write_angle(self.current_light_dir) elif new_state BehaviorState.HESITATE: self.overlay.pwm_controller.write_target_speed(30) self.overlay.pwm_controller.write_slope(20) # 更慢的斜率显得拖沓 self.overlay.led_controller.write_mode(0x02) # 模式2缓慢呼吸黄光 # 让转向在一个小范围内随机摆动 sway_angle self.current_light_dir (random.randint(-30, 30)) self.overlay.servo_controller.write_angle(sway_angle) elif new_state BehaviorState.CONFLICT: self.overlay.pwm_controller.write_target_speed(0) # 停止 self.overlay.pwm_controller.write_slope(1) # 急停 self.overlay.led_controller.write_mode(0x03) # 模式3急促闪烁红光 self.overlay.buzzer_controller.play_tone(500, 200) # 发出短促鸣响 self.conflict_timer time.time() # ... 其他状态执行 def main_loop(self): while True: self.update_sensors() next_state self.state_transition() if next_state ! self.current_state: self.execute_state(next_state) time.sleep(0.1) # 100ms的决策周期这个状态机的精髓在于转移条件的非确定性。它不仅仅依赖于精确的传感器阈值还混合了随机数、历史状态持续时间、甚至传感器数据的噪声light_volatility。这使得小车的行为无法被完全预测每次运行都是一次独特的“演出”。3.3 环境布置与交互设计行为艺术发生在特定的“舞台”。我布置了一个约2m x 2m的黑色场地边缘放置了一些低矮的白色障碍物作为“边界”。场地中央悬挂一个可调光度的主光源侧方还有一个可移动的次要光源用手电筒模拟。光源作为“诱惑”与“目标”主光源的亮度会由另一个独立的微控制器缓慢地、随机地调节。小车对亮度变化的反应被编程为非线性轻微变亮可能引发“追寻”剧烈变亮反而可能触发“退缩”模拟无法承受的强烈期望。障碍物作为“规则”与“限制”白色障碍物被超声波清晰地检测到。当小车进入“冲突”状态并接触到边界时它会长时间停在那里只有LED灯在闪烁仿佛在“思考”或“对抗”。直到随机条件满足它才会“放弃”并转入“记忆回溯”状态试图沿原路返回。观众的影响可选扩展可以通过在场地周围放置光敏传感器或摄像头连接PYNQ将观众的移动或声音幅度作为输入影响小车行为状态的概率。例如当观众过于安静时小车更容易陷入“迟疑”当周围有突然的声响时可能触发“冲突”。这便将单向表演升级为了一种互动装置艺术。4. 实操过程从零构建艺术小车4.1 硬件组装与电气连接底盘与电机安装将两个带编码器的直流电机安装到底盘上连接轮子。确保两个轮子对称空载转动顺畅。电机驱动板连接将电机驱动板如TB6612的电机输出端A、B分别连接左右电机。将驱动板的控制输入端PWMA, AIN1, AIN2, PWMB, BIN1, BIN2连接到PYNQ-Z2的PMOD或RPI接口的GPIO引脚上。务必查阅PYNQ-Z2引脚映射图确保选择的引脚在设计中已被包含在FPGA的Overlay里。PYNQ-Z2固定与供电使用铜柱和螺丝将PYNQ-Z2开发板稳妥地固定在小车底盘上方。使用一根Micro USB线为其提供5V电源用于PS系统运行或者通过其DC接口供电。电机驱动板的电源VM必须使用另一块大容量锂电池如7.4V单独供电两地GND必须在驱动板附近连接在一起。传感器集成光敏电阻搭建分压电路将三个光敏电阻的输出分别连接到PYNQ-Z2的XADC专用模拟输入引脚如VP_0, VN_0等。超声波模块Trig引脚连接FPGA GPIO作为输出Echo引脚连接FPGA GPIO作为输入注意Echo是5V电平可能需要电平转换或分压。IMU模块MPU6050通过I2C接口连接至PYNQ的I2C引脚。灯光与声音将LED灯带的数据线连接至一个FPGA GPIO。蜂鸣器通过一个三极管驱动电路连接至另一个GPIO。实操心得布线即美学。对于这样一个强调表达的项目杂乱的飞线会破坏观感。我使用了硅胶导线和热缩管尽量将线缆捆扎整齐并沿着底盘结构走线。电源线特别是电机电源与信号线如I2C、传感器线分开走减少干扰。一个整洁的“身体”能让“行为”更聚焦。4.2 FPGA Overlay开发与生成创建Vivado项目选择PYNQ-Z2开发板。使用IP Integrator创建Block Design。添加并配置IP核从PYNQ库中添加ZYNQ7 Processing SystemIP运行Run Block Automation应用预设配置。添加AXI GPIOIP用于控制LED、蜂鸣器等简单外设。添加AXI Timer或自定义PWM IP用于电机控制。添加XADCIP用于采集模拟光敏信号。对于自定义的复杂逻辑如行为效果发生器使用Create and Package New IP功能用VHDL/Verilog或HLS编写代码并将其添加到设计中。连接与地址分配将所有用户IP通过AXI Interconnect连接到Zynq的GP AXI Master端口。为每个IP分配唯一的地址空间。连接时钟、复位以及外部端口到FPGA引脚。引脚约束根据实际硬件连接编写XDC约束文件将FPGA的逻辑端口映射到具体的物理引脚如RPI或Arduino接口的引脚。生成比特流验证设计、综合、实现、生成比特流.bit文件和硬件描述文件.hwh文件。4.3 Python环境搭建与行为程序部署启动PYNQ为PYNQ-Z2烧录最新版本的PYNQ镜像如v2.7通过网线或Wi-Fi连接到网络。上传Overlay文件将生成的.bit和.hwh文件通过Jupyter界面或SCP上传到PYNQ板卡的/home/xilinx/pynq/overlays/目录下或自定义目录。安装额外Python库通过终端使用pip安装可能需要的库如numpy用于简单计算、matplotlib用于实时可视化传感器数据辅助调试。编写并调试行为脚本在Jupyter Notebook中新建一个笔记本导入pynq库加载Overlay。然后将前面章节描述的ArtCarBehaviorEngine类代码写入并实例化运行。Jupyter的交互特性允许你随时修改变量如状态转移阈值、调用函数手动控制小车观察打印的日志实时调整“剧本”。从Notebook到独立运行调试满意后将核心的main_loop循环和相关类封装到一个独立的Python脚本如art_car_main.py中。可以设置该脚本在PYNQ启动时自动运行例如通过systemd服务或rc.local这样小车一上电就开始它的“行为艺术表演”无需连接电脑。5. 调试、优化与问题排查实录5.1 常见问题与解决方案在实际制作中我遇到了不少典型问题以下是排查记录问题现象可能原因排查步骤与解决方案小车行为抽搐电机时转时停1. 电源功率不足。2. 电机驱动逻辑电平不匹配。3. PWM频率不合适。1.测量电压在电机启动时用万用表测量电机驱动板输入电压若大幅跌落需更换容量更大、放电倍率更高的电池。2.检查电平确认PYNQ GPIO输出高电平通常3.3V能达到电机驱动板逻辑高电平的最小要求查看驱动板手册。若不能需加电平转换模块。3.调整PWM频率直流电机PWM频率通常在1kHz-20kHz。频率太低会听到啸叫且运行不平稳频率太高可能超出驱动芯片响应能力。在FPGA代码中尝试调整时钟分频。光敏传感器读数不稳定导致行为狂躁1. 环境光干扰如日光灯频闪。2. 模拟信号线引入噪声。3. XADC参考电压不稳。1.使用稳定光源表演时使用直流供电的LED灯作为光源避免交流光源。2.硬件滤波在光敏电阻分压电路输出端并联一个0.1uF~1uF的电容到地进行简单滤波。3.软件滤波在FPGA采集逻辑中增加数字滤波器的阶数或窗口大小。但注意本项目需要保留一定噪声因此滤波强度要取得一个平衡只滤除高频毛刺保留低频的真实波动。状态切换过于频繁行为缺乏“节奏感”状态机转移条件阈值设置不合理或传感器噪声被误判为状态变化。1.引入状态保持时间为每个状态设置一个最小持续时间例如“冲突”状态至少持续5秒在此期间即使触发条件满足也不立即切换。2.使用迟滞比较对于基于传感器阈值如距离的转移使用带有迟滞的阈值。例如从“追寻”进入“冲突”的距离阈值是20cm但从“冲突”回到其他状态的距离阈值需要大于25cm避免在边界附近反复横跳。3.对传感器数据进行平滑处理使用一阶低通滤波公式filtered_value alpha * raw_value (1 - alpha) * previous_filtered_value其中alpha取较小值如0.1让数据变化更平缓。FPGA Overlay加载后部分外设不工作1. 引脚约束文件XDC错误。2. Block Design中IP的时钟或复位连接错误。3. Python中访问的IP地址或寄存器偏移量错误。1.双重检查XDC逐一核对每个外部端口如led_pin,motor_pwm是否与硬件连接完全一致包括引脚编号和I/O标准如LVCMOS33。2.在Vivado中验证设计使用Validate Design功能检查Block Design的连接。确保所有用户IP的时钟和复位都正确连接到系统时钟和复位网络。3.使用overlay?命令在Jupyter中加载Overlay后输入overlay?假设变量名为overlay可以打印出Overlay中所有IP的地址映射与Python代码中的访问地址进行比对。行为缺乏“艺术感”看起来仍像普通机器人逻辑过于确定缺乏随机性和“人性化”的瑕疵。1.引入多种随机源不仅使用random库还可以将传感器读数的最低有效位、系统运行时间的小数部分等作为随机种子增加行为的不可预测性。2.设计“非最优”行为例如在“追寻”状态中偶尔加入一个短暂的、小幅度的错误转向在“静止”时让LED灯产生看似无规律的轻微明暗变化。3.创建内部“变量”为小车设计一些内部变量如“能量水平”、“好奇心指数”。这些变量随时间缓慢变化并影响状态转移的概率。例如“能量低”时更易进入“迟疑”状态。5.2 性能优化与表达强化FPGA资源优化行为艺术对实时性要求高但对逻辑规模不一定。如果Overlay太大导致资源紧张可以审视自定义IP。例如将一些复杂的计算如随机数生成、历史数据平均放到ARM端的Python去做FPGA只负责最实时、最并行的部分PWM生成、传感器采样、简单滤波。Python循环效率主循环中的time.sleep(0.1)决定了行为决策的频率。10Hz对于这个项目通常足够。但如果状态逻辑变得非常复杂导致一次循环执行时间超过100ms就会拖慢响应。此时需要优化Python代码或将部分判断逻辑下放到FPGA作为硬件中断触发。增强表达维度声音不要只使用单一的蜂鸣器哔哔声。可以利用PYNQ的PL部分生成PWM调制不同频率产生简单的旋律或白噪声甚至通过I2S接口连接一个音频解码芯片播放预存的音频片段如环境音、心跳声。机械扩展在小车上增加一个简单的舵机云台上面可以放置一面小镜子或一个微型屏幕。在不同的行为状态下云台转动反射环境光或显示简单的图案增加视觉表达的层次。完成这个项目后我最大的体会是技术的边界往往是我们自己设定的。PYNQ-Z2作为一款工程教育平台其潜力远不止于实现一个功能。当你开始用程序去定义“性格”用电路去模拟“情绪”用传感器数据去触发“抉择”时你就在进行一种创作。调试过程中看着小车因为一个随机数的不同而走出截然不同的轨迹时而坚定时而迷茫那种感觉非常奇妙——你既是它的造物主又是它行为的观察者。这种从“实现功能”到“塑造行为”的思维转变或许是这个项目带给我的比任何具体技术细节都更宝贵的收获。如果你也想尝试不妨从定义一个最简单的“性格”开始比如“内向的探索者”或“乐观的追逐者”然后看看你的硬件会如何将它演绎出来。