news 2026/7/29 5:32:14

FPGA串口发送模块Verilog实现:从原理到工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA串口发送模块Verilog实现:从原理到工程实践

1. 项目概述与核心价值

最近在整理手头的FPGA项目,发现串口通信这块,很多刚入门的朋友总是卡在发送逻辑的实现上。要么是时序对不上,要么是数据发出去对方收不到,调试起来一头雾水。今天我就结合“小梅哥Xilinx FPGA学习笔记10——串口通信发送”这个主题,把FPGA实现串口发送的里里外外彻底拆解一遍。这不仅仅是照搬一个代码,更重要的是理解从并行数据到串行比特流的完整转换过程,以及如何用Verilog硬件描述语言精准地控制每一个时间点。串口(UART)作为最古老、最简单、也最常用的异步通信接口之一,在FPGA与上位机(如电脑)、单片机、传感器模块的调试和数据交换中扮演着核心角色。掌握它,就等于打通了FPGA与外界对话的第一道关卡。无论你是正在学习小梅哥教程的学生,还是工作中需要快速实现通信功能的工程师,这篇笔记都能帮你绕过我当年踩过的那些坑,直接构建一个稳定、可靠的串口发送引擎。

2. 串口通信发送的整体设计与思路拆解

2.1 异步串口通信的核心原理

在动手写代码之前,我们必须先抛开FPGA,搞清楚串口通信本身是怎么一回事。串口通信是异步的,这意味着通信双方没有统一的时钟线来同步数据。那如何保证接收方能在正确的时间点采样到正确的数据呢?秘诀就在于波特率数据帧格式的严格约定。

一个标准的串口数据帧(以最常见的8N1格式为例)包含以下部分:

  1. 起始位:固定为1个比特时间的低电平(逻辑0)。这是帧开始的标志,接收端检测到这个下降沿,就启动一个数据帧的接收流程。
  2. 数据位:紧接着起始位之后,通常是5-9位数据,我们最常用的是8位,即一个字节。数据位是实际要传输的信息,从最低位(LSB)开始发送。
  3. 校验位(可选):用于简单的错误检测,比如奇校验或偶校验。在8N1格式中,“N”就代表无校验位。
  4. 停止位:固定为1个(或1.5个、2个)比特时间的高电平(逻辑1)。标志着数据帧的结束,并为下一帧的起始位下降沿提供必要的空闲状态。

整个通信过程建立在双方预先约定好的波特率上,例如9600 bps。波特率表示每秒传输的符号数,在这里1个符号就是1个比特。因此,每个比特的持续时间(即比特时间)是波特率的倒数,对于9600波特率,比特时间约为104.17微秒。发送端和接收端各自使用一个精度足够的本地时钟(通常是波特率的16倍或更高),来度量这个比特时间,从而实现“自同步”。

注意:异步通信的关键在于“约定大于同步”。只要双方波特率误差在可接受范围内(通常<3%),且严格按照帧格式收发,通信就能成功。FPGA实现的核心,就是生成一个精准的本地波特率时钟,并严格按照帧格式控制TX引脚的电平变化。

2.2 FPGA实现发送功能的模块化设计思路

用FPGA实现串口发送,本质上是一个并串转换精确时序控制的过程。我们不能像单片机那样用一个UART外设,而是要自己用逻辑门和寄存器把这个外设“造”出来。一个稳健的发送模块(通常命名为uart_tx)应该包含以下几个关键部分:

  1. 波特率时钟生成器:这是发送时序的“心跳”。FPGA的主时钟频率(如50MHz)远高于波特率(如9600Hz)。我们需要通过分频或更精确的时钟管理,产生一个与目标波特率同步的脉冲信号(baud_clkbaud_en)。这个脉冲的周期等于一个比特时间,每个脉冲到来,就意味着需要输出下一个比特。
  2. 发送状态机:这是模块的“大脑”。它定义了发送过程的各个阶段:空闲(IDLE)、发送起始位(START)、发送数据位(DATA)、发送停止位(STOP)。状态机根据波特率时钟和当前状态,决定下一个状态以及输出到TX引脚的电平。
  3. 数据移位寄存器:这是数据的“搬运工”。在开始发送时,它将待发送的并行数据(如8位)加载进来。每发送一个数据位,寄存器就右移一位,将下一个要发送的位送到最低位,方便输出。
  4. 比特计数器:这是进度的“标尺”。它记录当前已经发送了多少个比特(起始位、数据位、停止位),当计数达到一帧的总比特数时,通知状态机可以结束当前帧,回到空闲状态。

这样的模块化设计,使得代码结构清晰,各司其职,易于调试和维护。发送模块的对外接口通常很简单:一个时钟输入clk,一个复位输入rst_n,一个数据有效信号data_valid,一个8位并行数据输入data_in,以及一个串行数据输出tx

3. 核心细节解析与实操要点

3.1 波特率时钟的精准生成:分频计数法

生成波特率时钟最直接的方法是计数器分频。假设系统时钟clk为50MHz(周期20ns),目标波特率为9600bps。

  • 比特时间 = 1 / 9600 ≈ 104166.67 ns。
  • 需要计数的系统时钟周期数 = 比特时间 / 系统时钟周期 = 104166.67 ns / 20 ns ≈ 5208.33。

这不是一个整数。如果我们简单取整为5208,那么实际生成的波特率将是50e6 / 5208 ≈ 9600.61 bps,误差约为0.006%,完全在允许范围内。因此,我们可以设置一个计数器,从0计数到5207(共5208个周期),在每个计数周期结束时产生一个高脉冲作为波特率使能信号baud_en。这个baud_en脉冲的宽度就是一个系统时钟周期,它用于驱动状态机和移位寄存器。

Verilog代码片段示例:

parameter CLK_FREQ = 50_000_000; // 50MHz parameter BAUD_RATE = 9600; localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE - 1; // 计算分频系数 reg [12:0] baud_cnt; // 需要13位才能容纳5208 reg baud_en; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin baud_cnt <= 0; baud_en <= 1'b0; end else begin baud_en <= 1'b0; // 默认拉低 if(baud_cnt == BAUD_CNT_MAX) begin baud_cnt <= 0; baud_en <= 1'b1; // 计数满,产生一个时钟周期的使能脉冲 end else begin baud_cnt <= baud_cnt + 1'b1; end end end

实操心得:对于更高的系统时钟或更低的波特率,分频系数会很大,计数器位宽要设够。另外,这种方法的误差对于标准波特率(如9600, 115200)通常可以接受。但如果追求极致精度,尤其是在高波特率下,可以考虑使用FPGA内部的锁相环(PLL)或时钟管理器(MMCM)来直接生成所需的波特率时钟,或者使用分数分频算法。

3.2 发送状态机的精妙设计

状态机设计是串口发送的核心逻辑。一个典型的状态机包含4个状态,其转换完全由baud_en信号驱动。

  1. IDLE(空闲状态)

    • 输出tx引脚保持高电平(代表线路空闲)。
    • 转换条件:当外部输入的data_valid信号有效(通常为高脉冲)时,表示有新的数据要发送。状态机在下一个baud_en到来时,跳转到START状态,同时将输入数据锁存到移位寄存器中。
  2. START(发送起始位)

    • 输出tx引脚输出低电平,持续一个比特时间。
    • 转换条件:当在此状态下检测到baud_en脉冲时,表示起始位发送完毕。状态机跳转到DATA状态,并复位比特计数器。
  3. DATA(发送数据位)

    • 输出tx引脚输出移位寄存器的最低位(shift_reg[0])。每来一个baud_en脉冲,就右移一位,将新的最低位输出。
    • 转换条件:比特计数器随着每个baud_en递增。当计数器指示所有数据位(例如8位)都已发送完毕时,状态机跳转到STOP状态。
  4. STOP(发送停止位)

    • 输出tx引脚输出高电平,持续一个(或两个)比特时间。
    • 转换条件:当在此状态下检测到baud_en脉冲(对于1个停止位,来一次就跳转;对于2个停止位,来两次才跳转),表示一帧数据发送完成。状态机跳转回IDLE状态,并可以输出一个“发送完成”信号(tx_done),通知外部逻辑可以准备下一个数据。

状态机设计的关键点

  • 状态编码:可以使用可读性好的参数定义(localparam),也可以使用独热码(One-Hot)以提高性能(但消耗更多触发器)。
  • 输出逻辑:可以采用组合逻辑输出(与状态和当前数据有关),也可以采用时序逻辑输出(在baud_en边沿更新)。后者更能保证输出信号的稳定性,避免毛刺。
  • 比特计数器:它是一个独立的计数器,但在DATA状态下才有效。其最大值等于数据位宽。

3.3 数据缓冲与流控的简单实现

基础的发送模块在tx_done信号有效前,无法接收新的数据。在实际应用中,数据可能连续到来。为了避免数据丢失,我们可以在发送模块前增加一个FIFO(先入先出)缓冲区

  • 工作原理:外部逻辑将数据写入FIFO。发送模块在空闲(IDLE)状态且FIFO非空时,自动从FIFO读取一个数据,启动发送过程。这样,即使外部数据突发到达,也能被缓冲起来,平滑地发送出去。
  • FPGA实现:Xilinx FPGA的IP Catalog中提供了FIFO Generator IP核,可以非常方便地配置一个同步或异步FIFO。我们只需要例化这个IP,将它的写端口连接数据源,读端口连接uart_tx模块的data_indata_valid即可。
  • 流控:更复杂的系统可能需要硬件流控(RTS/CTS),但对于大多数简单应用,FIFO缓冲已经足够。我们可以通过监控FIFO的“几乎满”信号来通知数据发送方暂停发送,实现简单的流控。

4. 实操过程与核心环节实现

4.1 完整的Verilog发送模块代码实现

下面是一个针对Xilinx FPGA平台,基于状态机设计的、包含FIFO接口提示的完整uart_tx模块代码。我们假设使用8位数据、无校验、1位停止位(8N1),系统时钟50MHz,波特率9600。

module uart_tx #( parameter CLK_FREQ = 50_000_000, // 输入时钟频率 parameter BAUD_RATE = 9600, // 波特率 parameter DATA_WIDTH = 8, // 数据位宽 parameter STOP_BIT_NUM = 1 // 停止位个数 )( input wire clk, // 系统时钟 input wire rst_n, // 低电平复位 // 用户接口 input wire [DATA_WIDTH-1:0] data_in, // 待发送数据 input wire data_valid, // 数据有效脉冲,高有效 output reg tx_done, // 单字节发送完成脉冲 output wire tx_busy, // 发送忙标志 // UART物理接口 output reg tx // 串行输出 ); // 波特率分频计数 localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE - 1; reg [12:0] baud_cnt; reg baud_en; // 发送状态机定义 localparam IDLE = 2'b00; localparam START = 2'b01; localparam DATA = 2'b10; localparam STOP = 2'b11; reg [1:0] state, next_state; // 数据移位寄存器与比特计数器 reg [DATA_WIDTH-1:0] shift_reg; reg [3:0] bit_cnt; // 计数数据位,4位足够计到15 // 波特率时钟生成逻辑 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin baud_cnt <= 0; baud_en <= 1'b0; end else begin baud_en <= 1'b0; if (baud_cnt == BAUD_CNT_MAX) begin baud_cnt <= 0; baud_en <= 1'b1; end else begin baud_cnt <= baud_cnt + 1'b1; end end end // 状态机第一段:时序逻辑,状态寄存器 always @(posedge clk or negedge rst_n) begin if (!rst_n) state <= IDLE; else state <= next_state; end // 状态机第二段:组合逻辑,状态转移 always @(*) begin next_state = state; // 默认保持状态 case (state) IDLE: begin if (data_valid) // 有数据要发送 next_state = START; end START: begin if (baud_en) next_state = DATA; end DATA: begin if (baud_en && (bit_cnt == DATA_WIDTH - 1)) // 数据位发完 next_state = STOP; end STOP: begin if (baud_en) // 停止位发完,回到空闲 next_state = IDLE; end default: next_state = IDLE; endcase end // 状态机第三段:时序逻辑,输出与寄存器更新 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin tx <= 1'b1; // 空闲时为高电平 shift_reg <= 0; bit_cnt <= 0; tx_done <= 1'b0; end else begin tx_done <= 1'b0; // 默认拉低 case (state) IDLE: begin tx <= 1'b1; // 保持高电平 if (data_valid) begin // 锁存数据 shift_reg <= data_in; end end START: begin if (baud_en) begin tx <= 1'b0; // 输出起始位 bit_cnt <= 0; // 复位比特计数器 end end DATA: begin if (baud_en) begin tx <= shift_reg[0]; // 输出最低位 shift_reg <= {1'b0, shift_reg[DATA_WIDTH-1:1]}; // 右移 bit_cnt <= bit_cnt + 1'b1; // 计数加1 end end STOP: begin if (baud_en) begin tx <= 1'b1; // 输出停止位 if (bit_cnt == STOP_BIT_NUM - 1) begin // 停止位发够个数 tx_done <= 1'b1; // 产生完成脉冲 end else begin bit_cnt <= bit_cnt + 1'b1; end end end endcase end end // 发送忙信号:只要不在IDLE状态,就是忙 assign tx_busy = (state != IDLE); endmodule

4.2 测试平台搭建与仿真验证

代码写好了,但绝不能直接上板子。仿真(Simulation)是保证设计正确的关键一步。我们需要编写一个测试平台(Testbench)来模拟真实环境。

测试平台的核心任务:

  1. 生成时钟和复位信号
  2. 实例化(Instantiate)待测试的uart_tx模块
  3. 编写激励(Stimulus):模拟上游逻辑,在特定时间向uart_tx模块发送有效数据(data_valid脉冲和data_in)。
  4. 观察输出:在仿真波形中查看tx引脚上的波形,检查其是否符合8N1格式,数据是否正确。也可以将tx信号连接到一个虚拟的接收器模型,进行自动比对。

一个简单的测试平台示例框架:

`timescale 1ns / 1ps module tb_uart_tx(); reg clk; reg rst_n; reg [7:0] data_to_send; reg send_en; wire tx; wire tx_done; wire busy; // 实例化被测模块 uart_tx u_uart_tx ( .clk(clk), .rst_n(rst_n), .data_in(data_to_send), .data_valid(send_en), .tx_done(tx_done), .tx_busy(busy), .tx(tx) ); // 生成50MHz时钟 initial clk = 0; always #10 clk = ~clk; // 20ns周期,即50MHz // 测试过程 initial begin // 初始化 rst_n = 0; data_to_send = 8'h00; send_en = 0; #100; // 复位一段时间 rst_n = 1; #1000; // 测试用例1:发送数据 0x55 (01010101) @(posedge clk); data_to_send = 8'h55; send_en = 1; @(posedge clk); send_en = 0; // 产生一个时钟周期的高脉冲 // 等待发送完成 wait(tx_done == 1); #10000; // 等待一段时间 // 测试用例2:发送数据 0xAA (10101010) @(posedge clk); data_to_send = 8'hAA; send_en = 1; @(posedge clk); send_en = 0; // 等待发送完成 wait(tx_done == 1); #10000; $finish; // 结束仿真 end endmodule

在仿真工具(如Vivado Simulator、ModelSim)中运行此测试平台,观察波形。你应该能看到tx信号上出现清晰的帧结构:先变低(起始位),然后是8个数据比特(0x55的LSB在前,所以波形是10101010),最后变高(停止位)。通过测量高低电平的持续时间,可以验证其是否符合104us左右的比特时间。

4.3 上板实测与调试技巧

仿真通过后,就可以进行上板实测了。你需要:

  1. 分配引脚:在Xilinx Vivado的约束文件(.xdc)中,将tx信号分配到FPGA开发板上UART接口对应的物理引脚。
  2. 连接硬件:用USB转串口线将FPGA开发板的UART接口与电脑连接。
  3. 使用串口调试助手:在电脑上打开串口调试助手(如SecureCRT、Putty、或者小梅哥资料包里常带的调试工具),设置正确的COM口、波特率(9600)、数据格式(8N1)。
  4. 编写顶层测试代码:创建一个顶层模块,例化uart_tx,并编写一个简单的逻辑,例如每隔1秒发送一个递增的字符或固定的字符串“Hello FPGA!”。
  5. 下载比特流:将综合实现后生成的.bit文件下载到FPGA中。

调试常见问题与技巧:

  • 电脑收不到任何数据
    • 检查引脚分配:确认.tx引脚是否分配正确,是否与原理图一致。
    • 检查波特率:确认FPGA代码中的波特率与串口助手设置的完全一致。计算分频系数时检查是否溢出或计算错误。
    • 检查线序:USB转串口线通常只需要连接TX、RX和GND三根线。确保FPGA的TX连接到了USB线的RX,FPGA的RX连接到了USB线的TX(交叉连接)。
    • 用示波器或逻辑分析仪抓取波形:这是最直接的调试手段。观察FPGA的TX引脚是否有波形输出,波形是否符合串口帧格式,比特时间是否正确。
  • 收到乱码
    • 最常见原因:波特率不匹配。即使计算值只有微小误差,在长时间传输后也可能造成累积错误。仔细核对计算过程,或尝试使用PLL生成更精确的时钟。
    • 数据位序错误:确认发送模块是从LSB开始发送。串口调试助手也要设置为LSB first(通常默认就是)。
    • 停止位设置:确认代码中停止位个数与串口助手设置一致。
  • 发送不连续或丢数据
    • 检查tx_busy信号:确保在发送模块忙时,没有新的data_valid信号进来。或者如前所述,加入FIFO进行缓冲。
    • 检查时序约束:确保系统时钟的时序约束正确,没有建立/保持时间违规。

实操心得:上板调试时,先从一个最简单的功能开始——固定发送一个字节,比如0x55或0xAA。因为它们的比特模式是交替的(01010101或10101010),在示波器上看起来是标准的方波,非常容易判断波特率是否正确。成功后再测试发送连续数据。

5. 性能优化与高级应用扩展

5.1 使用PLL精确生成波特率时钟

当系统时钟频率不是波特率的整数倍时,分频计数法会引入误差。对于高波特率(如115200、921600)或对时钟精度要求极高的应用(如长距离通信),可以使用FPGA内部的PLL来生成一个频率正好是波特率16倍或更高的时钟。

例如,要生成精确的115200 Hz波特率时钟,可以配置PLL输入50MHz,输出1.8432 MHz(115200 * 16)。这个1.8432 MHz的时钟再通过一个简单的16分频计数器,就能得到精准的115200 Hz波特率使能信号。Vivado中的Clock Wizard IP可以轻松配置PLL。

5.2 添加FIFO实现连续发送

如前所述,添加FIFO是提升模块实用性的关键。下面简要说明集成FIFO的顶层设计:

  1. 例化FIFO IP核:配置为同步FIFO,宽度8位(或更宽),深度根据数据吞吐量设定(如512、1024)。启用“Almost Full”标志。
  2. 修改顶层逻辑
    • 外部数据写入FIFO(wr_endin)。
    • uart_tx模块的data_valid信号不再由外部直接控制,而是由FIFO非空(!empty)uart_tx空闲(!tx_busy)的条件触发。
    • 当上述条件满足时,从FIFO读取一个数据(rd_en拉高)送到uart_txdata_in,同时产生一个data_valid脉冲。
    • 可以将FIFO的“Almost Full”信号输出给数据产生模块,作为反压(Back Pressure)信号,防止FIFO溢出。

这样,只要FIFO里有数据,发送模块就会自动、连续地发送,直到FIFO为空,实现了简单的流量控制。

5.3 自定义协议帧封装

单纯的字节发送能力是基础。在实际项目中,我们通常需要传输有意义的命令或数据包。这就需要定义简单的通信协议。一个非常常用的轻量级协议帧格式是:帧头 + 长度 + 命令/数据 + 校验和

  • 帧头:1-2个特殊的字节(如0xAA、0x55),用于标识一帧的开始,帮助接收方进行帧同步。
  • 长度:1个字节,指示后面“命令/数据”字段的字节数。
  • 命令/数据:可变长度的有效载荷。
  • 校验和:1个字节,可以是前面所有字节的累加和(取低8位),或者异或和,用于检测传输错误。

在FPGA中,我们可以编写一个协议封装模块。该模块接收需要发送的有效载荷,自动为其添加帧头、长度和校验和,然后将完整的帧字节流写入到UART发送模块(或前面的FIFO)中。接收端(通常是PC软件或单片机)按照相同的协议进行解析,从而实现了可靠的结构化数据传输。

6. 常见问题与排查技巧实录

在多年的FPGA开发和教学实践中,我总结了以下几个最常遇到的串口发送问题及其排查思路,希望能帮你快速定位问题。

问题现象可能原因排查方法与解决思路
完全无数据输出1. 引脚分配错误或约束文件未生效。
2. 顶层模块未正确例化或信号未连接。
3. 复位信号一直有效,逻辑未启动。
4. 波特率生成逻辑错误,baud_en永远为低。
1. 检查.xdc文件,确认端口名、引脚号、电平标准正确。上板后可用万用表测量引脚电压。
2. 检查综合后的原理图(RTL Schematic),看模块是否被优化掉,信号连接是否正确。
3. 在代码中暂时注释掉复位逻辑,或确保上电后复位信号已释放。仿真时检查复位时序。
4. 仿真时观察baud_cntbaud_en的波形,看计数器是否正常循环,使能脉冲是否产生。
有波形输出,但电脑端乱码1.波特率不匹配(最常见)
2. 数据位、停止位、校验位设置与串口助手不一致。
3. 比特顺序(LSB/MSB)错误。
4. 信号毛刺或抖动严重。
1.用示波器测量比特时间。发送0x55(01010101),测量一个脉冲周期(高或低电平)。对于9600波特率,应为104us左右。偏差过大则调整分频系数。
2. 核对代码中的帧格式定义与串口助手设置,必须完全一致。
3. 确认发送代码是从shift_reg[0](LSB)开始输出。串口助手通常默认LSB first。
4. 检查PCB走线,过长或干扰大可能引起波形畸变。可在FPGA输出端串联一个小电阻(如22欧姆)以减少反射。
发送单个字节正常,连续发送出错1. 状态机未能正确回到IDLE状态,或tx_done信号有问题。
2. 连续发送间隔太短,新数据冲掉了正在发送的数据。
3. FIFO逻辑有误,导致读写出错。
1. 仿真连续发送多个字节,仔细观察状态机转换和tx_done信号。确保发送完一帧后,tx_busy信号拉低,且模块准备好接收新数据。
2.必须等待tx_done!tx_busy信号有效后,再送入下一个data_valid。或者引入FIFO缓冲。
3. 仿真FIFO的读写逻辑,检查空满标志、读写使能时序是否正确。
高波特率下(如115200)不稳定1. 系统时钟频率不够高,分频后波特率时钟误差大。
2. 时序违例,逻辑速度跟不上。
3. 串口线或接口质量差,不支持高波特率。
1. 使用PLL生成更精准的时钟。计算理论误差,115200要求较高,50MHz系统时钟分频误差很小,但921600可能就需要PLL了。
2. 在Vivado中运行时序报告(Timing Report),检查是否有建立/保持时间违规。优化关键路径逻辑,或对baud_en等信号使用时钟使能(CE)寄存器打拍。
3. 更换质量好的USB转串口模块(如FTDI、CP2102芯片的模块)。

一个关键的调试习惯:分层验证。不要试图一次性调通整个复杂系统。先从波特率生成模块开始,仿真看baud_en的周期对不对。然后验证状态机,看状态转移是否符合预期。接着用仿真发送固定数据,看tx波形。最后再上板用示波器看实际波形。每一步都确认无误,最终的问题往往就迎刃而解了。

FPGA的串口发送实现,是一个非常好的同步时序逻辑设计练习。它涵盖了时钟分频、状态机、计数器、移位寄存器等数字逻辑的核心要素。吃透这个模块,不仅能让你轻松应对各种串口通信需求,更能深刻理解FPGA如何用硬件思维来实现软件工程师看来很简单的功能。当你看到自己编写的代码在示波器上呈现出完美的串口波形,在电脑终端上显示出预期的字符时,那种成就感就是学习硬件描述语言最大的乐趣所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 5:31:46

基于树莓派PICO W的FPV小车:低成本嵌入式开发与实时图传实践

1. 项目概述&#xff1a;当树莓派PICO遇上FPV小车如果你玩过航模或者穿越机&#xff0c;肯定对FPV&#xff08;第一人称视角&#xff09;那种沉浸式的操控感念念不忘。但把这种体验从天上搬到地上&#xff0c;用一辆自己能亲手打造的小车来实现&#xff0c;乐趣和挑战都会翻倍。…

作者头像 李华
网站建设 2026/7/29 5:27:31

手把手教你用WorkBuddy做HTML视频

大家有没有刷到一些讲解类视频&#xff0c;整体画面像是一套能动的 HTML 版PPT&#xff1f; 刚好这段时间我们在研究 HyperFrames 玩法&#xff0c;它的原理也是用 HTML生成画面然后组合成视频。于是我们想能不能让 HTML 负责前端画面&#xff0c;HyperFrames 负责时间轴和渲染…

作者头像 李华
网站建设 2026/7/29 5:23:11

Web安全实战:文件上传漏洞的深度挖掘与防护指南

1. 项目概述&#xff1a;一次源于真实业务场景的“意外”发现那天下午&#xff0c;我正像往常一样&#xff0c;对客户的一个新上线的Web应用进行常规的安全审计。这个应用有一个非常标准的功能模块&#xff1a;用户头像上传。界面设计得很现代&#xff0c;前端有漂亮的裁剪和预…

作者头像 李华
网站建设 2026/7/29 5:23:05

省出百元:2026年3款小米录音总结哪个好,高性价比结论清晰明了

先回答用户真正关心的问题 作为长期测试AI效率工具的博主&#xff0c;结合我2026年2月对听脑AI、网易见外工作台、Sonix三款工具的亲测&#xff0c;针对学生党小米录音的课堂记录、论文访谈整理需求&#xff0c;追求高性价比省百元的话&#xff0c;结论很清晰&#xff1a;仅需…

作者头像 李华
网站建设 2026/7/29 5:22:31

RT-Thread FinSH组件:嵌入式开发的命令行调试利器与配置实战

1. 项目概述&#xff1a;RT-Thread FinSH组件&#xff0c;嵌入式开发的“瑞士军刀”在嵌入式开发的世界里&#xff0c;调试和系统状态监控一直是开发者绕不开的痛点。想象一下&#xff0c;你的设备已经部署在千里之外&#xff0c;或者正运行在一个没有屏幕、没有键盘的“黑盒子…

作者头像 李华