news 2026/9/7 1:14:52

FPGA实战:手写UART串口通信模块的完整设计思路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA实战:手写UART串口通信模块的完整设计思路

做了这么多年FPGA开发,我越来越觉得UART串口通信是入门学习里最值得手写一遍的模块。它不像SPI、I2C那样带时钟线,时序上更考验对异步信号的判断;也不像PCIe、DDR那样复杂到必须依赖IP核,自己用Verilog写一个稳定可用的UART收发器完全可行。很多朋友问我FPGA该怎么入门,我的回答基本都是先别碰那些花哨的图像处理和高速接口,先把UART调通,串口助手能稳定收到数据,你就算正式踏入数字逻辑设计的大门了。

这篇博文我就根据自己的实际工程经验,把UART在FPGA上的完整实现思路拆开揉碎讲清楚,从协议本质、RTL代码设计到仿真上板验证,再到我踩过的那些坑,全部整理出来。内容面向的是已经掌握基本Verilog语法、想动手做第一个通信项目的同学,也适合工作中需要自己写串口模块的工程师做参考。我会尽量讲得直白,把“为什么这么设计”讲透,而不是简单丢一份代码让你抄。

1. 先想明白UART到底在传什么东西

1.1 一次串口传输,其实就是一串带“起止标记”的脉冲

UART全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。很多人一上来就背协议,什么起始位1位、数据位8位、停止位1位,背完还是写不出代码。我建议换一个视角:你拿着一台示波器去看TX引脚,看到的是一串随时间变化的高电平和低电平,UART协议就是规定这串电平怎么切分、怎么解释。

线路空闲时保持高电平,这是RS232时代就定下的规矩,TTL电平的UART也沿用了这个习惯。发送方要发数据时,先拉低电平一个位时间,这叫起始位,硬件接收方看到这个从高到低的跳变,就知道“对方要开口说话了”。接着按约定好的波特率,一位一位输出数据,低位在前,然后是校验位(可省略),最后拉高一个位时间甚至更长,这叫停止位。数据位、停止位这些名词不用死记,你把它想成快递包裹:起始位是面单上的“收件人电话”,停止位是“签收确认”,数据位才是包裹里的实际货物。

这里有个关键点:UART传输的是字节的低位在前。比如你要发送0x53,二进制是0101 0011,那么线上先出现的不是最高位,而是最低位1,再是1、0、0、1、0、1、0。我早期调试时经常在逻辑分析仪里看到数据对不上,后来才反应过来是自己把位序搞反了。

1.2 波特率决定了每个位在线上“占多宽”

波特率是UART最核心的参数,它表示每秒传输多少个码元。对UART来说,一个码元就是一个bit,所以9600波特率就是每秒传9600个bit。一个bit在线上停留的时间就是波特率的倒数,约104.17微秒。这个时间宽度,就是整个设计的“心跳”。

FPGA内部用的是MHz级别的系统时钟,常见的有50MHz、100MHz,一个时钟周期只有20纳秒或10纳秒,比bit时间短得多。因此我们必须对系统时钟计数分频,产生一个周期近似等于bit时间的节拍信号,这个节拍就是发送和接收的基准。例如系统时钟50MHz,波特率9600,主频50_000_000除以9_600等于5208.33,所以每计数5208个系统时钟就翻转一次采样节拍,这样生成的节拍跟理想波特率只差3235ppm,即0.32%。这个误差远小于UART协议容忍的2%到3%,完全够用。

1.3 电平标准要分清:TTL还是RS232

FPGA开发板上的UART多半是TTL电平,高电平3.3V表示1,低电平0V表示0。但如果要跟电脑的串口调试助手通信,通常需要经过USB转串口芯片,比如CP2102、CH340、FT232这些。有些板子直接集成了USB转串口电路,插上USB线就能在电脑上看到COM口,这种情况板子上走的还是TTL信号,只是USB芯片帮你完成了TTL和USB之间的转换。

如果是跟老式设备通信,可能需要RS232电平,它的逻辑1是-3V到-15V,逻辑0是+3V到+15V,属于负逻辑。这种情况下需要加MAX3232之类的电平转换芯片。我的建议是:不管做板子还是调试,先确认对方接口的电平标准,TTL直接接TTL,RS232必须先转换,否则轻则收不到数据,重则烧引脚。我见过不止一位朋友把5V的RS232信号直接接到3.3V的FPGA引脚上,运气好的没烧,运气差的那一路引脚直接报废。

2. 设计前的思维转换:为什么接收比发送难

2.1 发送器像广播,接收器像在嘈杂现场听人说话

发送数据其实很“傻瓜”:只要按固定节奏把每一位放到TX引脚上就行。因为节奏是你自己控制的,时钟分频倍率一致,数据就一定能按预期发出去,这本质上是一个计数器加移位寄存器的体力活。

接收就麻烦多了。接收端看到的是一个没有时钟线伴随的异步信号,你只知道波特率,但不知道对方什么时候开始说话,也不知道当前线上这个电平到底是数据里的第几位。想要正确解码,需要自己从信号变化中“猜”出节奏,还要在噪声和误差中做出判断。这就是为什么很多教程里发送模块很简单,接收模块却要花大篇幅去讲采样窗口、起始位确认。

2.2 用“中点采样”对抗边沿误差

如果发送方和接收方的时钟频率存在误差,或者信号在传输中有抖动,bit边沿不会精确落在理想位置。如果我们在bit刚开始的地方采样,很容易采到前一个bit的尾巴或者边沿的毛刺。正确的思路是:在已知的bit中心附近采样,给边沿误差留出容错空间。

具体实现方法是,检测到起始位的下降沿之后,不从下降沿立刻采样,而是等一个半bit时间。比如系统时钟50MHz、波特率9600,一个bit是5208个时钟周期,那么先等5208个周期确认起始位确实是低电平(防止毛刺误触发),再等2604个周期到达第一个数据位的中心点,从那里开始每5208个周期采样一次,正好采在每一位的中间位置。这个“中点采样”原则,是整个UART接收设计里最值得记住的一句话。

2.3 过采样是最简单的滤波手段

有些工程为了提高可靠性,会采用16倍过采样,也就是每个bit时间采样16次,然后取中间几位的多数判决结果。这在FPGA里并不复杂,只要把波特率分频器改成产生更高速率的采样时钟就行。但我个人建议,入门阶段先别上过采样,用单点中点采样加简单的边沿消抖就足够了。等把基础逻辑调通,再回头加过采样和FIFO,那时你会更清楚每一步在解决什么问题。

3. RTL代码实现:发送器和接收器的逐段拆解

3.1 发送器:分频计数加上状态机就够用

发送模块我习惯写成三段式状态机,空闲态(IDLE)、发送态(SEND)和停止态(STOP)三个状态。空闲时TX线保持高电平;检测到发送使能信号后进入发送态,先输出起始位低电平,然后移位寄存器按位输出数据;数据位发完进入停止态,拉高电平一个bit时间,最后回到空闲态。

这里有三个细节值得注意。第一,计数器的参考点是bit时间,不是系统时钟本身。我一般用一个baud_cnt计数器,计数到预设的分频值就归零,并产生一个baud_pulse单周期脉冲,所有位的输出动作都对齐到这个脉冲上。第二,数据位个数要是可配置的,用参数DATA_WIDTH控制,而不是写死在8位。第三,发送完一个字节后不要立刻允许发送下一个,最好等停止位结束再拉高busy信号,外部逻辑看到busy拉低才算发送完成。

以下是发送器的完整代码示例,这个版本我实际在板上跑过,直接可综合:

module uart_tx #( parameter CLK_FREQ = 50_000_000, parameter BAUD_RATE = 9600, parameter DATA_WIDTH = 8 )( input wire clk, input wire rst_n, input wire tx_start, input wire [DATA_WIDTH-1:0] tx_data, output reg tx, output reg tx_busy ); localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE - 1; localparam HALF_CNT_MAX = CLK_FREQ / BAUD_RATE / 2 - 1; reg [15:0] baud_cnt; reg baud_pulse; reg [3:0] bit_cnt; reg [DATA_WIDTH-1:0] tx_shift; reg [1:0] state; localparam S_IDLE = 2'd0; localparam S_START = 2'd1; localparam S_DATA = 2'd2; localparam S_STOP = 2'd3; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin baud_cnt <= 16'd0; baud_pulse <= 1'b0; end else begin if (baud_cnt == BAUD_CNT_MAX) begin baud_cnt <= 16'd0; baud_pulse <= 1'b1; end else begin baud_cnt <= baud_cnt + 1'b1; baud_pulse <= 1'b0; end end end always @(posedge clk or negedge rst_n) begin if (!rst_n) begin tx <= 1'b1; tx_busy <= 1'b0; bit_cnt <= 4'd0; tx_shift <= {DATA_WIDTH{1'b0}}; state <= S_IDLE; end else begin case (state) S_IDLE: begin tx <= 1'b1; tx_busy <= 1'b0; if (tx_start) begin state <= S_START; tx_shift <= tx_data; tx_busy <= 1'b1; bit_cnt <= 4'd0; end end S_START: begin tx <= 1'b0; if (baud_pulse) state <= S_DATA; end S_DATA: begin if (baud_pulse) begin tx <= tx_shift[0]; tx_shift <= tx_shift >> 1; if (bit_cnt == DATA_WIDTH - 1) begin bit_cnt <= 4'd0; state <= S_STOP; end else begin bit_cnt <= bit_cnt + 1'b1; end end end S_STOP: begin tx <= 1'b1; if (baud_pulse) state <= S_IDLE; end default: state <= S_IDLE; endcase end end endmodule

这段代码的核心逻辑就一句话:空闲高电平,起始拉低,数据逐位移出,停止拉高。baud_cnt计数器全局只有一个,所有状态的切换都靠baud_pulse对齐,这样能保证每个bit持续的时间完全一致,不会出现累积误差。

3.2 接收器:起始位确认和位中心采样是关键

接收模块的逻辑复杂度明显更高。我的习惯是先把接收数据线rx打两拍,第一拍同步到系统时钟域,第二拍做边沿检测。这样做的好处是可以消除一部分亚稳态,同时在起始位检测时不容易被毛刺干扰。

起始位检测的逻辑是:采样到rx从高变低的跳变后,不立即判定为起始位,而是等一个半bit时间,也就是HALF_BAUD_CNT,再去采一次rx,如果这时候还是低电平,才确认是真正的起始位。如果在半个bit时间后采到的是高电平,那就说明刚才的低脉冲只是干扰,状态机回到空闲,继续等待。这个机制非常重要,没有它,接收机很容易被一两个时钟周期的毛刺触发,然后解码出一堆乱码。

确认起始位之后,每过一个完整bit时间采样一次rx,采到的电平就是当前数据位的值。用移位寄存器把采样结果串起来,等采满DATA_WIDTH位,就拼成一个完整的字节。最终在停止位阶段输出接收完成脉冲和并行数据。

以下是接收器代码的核心部分:

module uart_rx #( parameter CLK_FREQ = 50_000_000, parameter BAUD_RATE = 9600, parameter DATA_WIDTH = 8 )( input wire clk, input wire rst_n, input wire rx, output reg [DATA_WIDTH-1:0] rx_data, output reg rx_done ); localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE - 1; localparam HALF_CNT_MAX = CLK_FREQ / BAUD_RATE / 2 - 1; reg rx_r0, rx_r1; wire rx_negedge; reg baud_cnt_en; reg [15:0] baud_cnt; reg baud_mid_pulse; reg baud_sample_pulse; reg [3:0] bit_cnt; reg [DATA_WIDTH-1:0] rx_shift; reg [1:0] state; localparam S_IDLE = 2'd0; localparam S_START = 2'd1; localparam S_DATA = 2'd2; localparam S_STOP = 2'd3; // 打拍同步和下降沿检测 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin rx_r0 <= 1'b1; rx_r1 <= 1'b1; end else begin rx_r0 <= rx; rx_r1 <= rx_r0; end end assign rx_negedge = rx_r1 & (~rx_r0); // 可控制的波特率计数 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin baud_cnt <= 16'd0; end else if (!baud_cnt_en) begin baud_cnt <= 16'd0; end else if (baud_cnt == BAUD_CNT_MAX) begin baud_cnt <= 16'd0; end else begin baud_cnt <= baud_cnt + 1'b1; end end // 中点采样脉冲和常规采样脉冲 always @(*) begin baud_mid_pulse = 1'b0; baud_sample_pulse = 1'b0; if (baud_cnt_en) begin if (baud_cnt == HALF_CNT_MAX) baud_mid_pulse = 1'b1; if (baud_cnt == BAUD_CNT_MAX) baud_sample_pulse = 1'b1; end end // 状态机 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= S_IDLE; bit_cnt <= 4'd0; rx_shift <= {DATA_WIDTH{1'b0}}; rx_data <= {DATA_WIDTH{1'b0}}; rx_done <= 1'b0; baud_cnt_en <= 1'b0; end else begin case (state) S_IDLE: begin rx_done <= 1'b0; if (rx_negedge) begin state <= S_START; baud_cnt_en <= 1'b1; baud_cnt <= 16'd0; end end S_START: begin if (baud_mid_pulse) begin if (!rx_r0) begin state <= S_DATA; bit_cnt <= 4'd0; end else begin state <= S_IDLE; baud_cnt_en <= 1'b0; end end end S_DATA: begin if (baud_sample_pulse) begin rx_shift <= {rx_r0, rx_shift[DATA_WIDTH-1:1]}; if (bit_cnt == DATA_WIDTH - 1) begin bit_cnt <= 4'd0; state <= S_STOP; end else begin bit_cnt <= bit_cnt + 1'b1; end end end S_STOP: begin if (baud_sample_pulse) begin state <= S_IDLE; baud_cnt_en <= 1'b0; rx_data <= rx_shift; rx_done <= 1'b1; end end default: state <= S_IDLE; endcase end end endmodule

有两点要提醒。第一,接收器的移位方向跟发送器相反,发送器是右移把低位先发出去,接收器则要把后采到的高位往高位移,比如用{rx_r0, rx_shift[DATA_WIDTH-1:1]}把新采到的位放到最高位。第二,rx_done信号只拉高一个周期,外部模块要用它作为读使能,把rx_data取走。如果接收端接的是FIFO,就把{ rx_done }当成写使能,rx_data当成写入数据。

3.3 顶层模块和跨时钟域的思考

正常情况下,发送端由内部逻辑产生发送数据,接收端要把收到的数据交给其他模块处理。我的做法是例化两个独立模块,tx和rx之间没有直接联系,只是共用同一个系统时钟和复位。如果系统时钟不是50MHz,记得重新计算分频参数。如果时钟频率过高,比如200MHz,16位的baud_cnt可能不够用,需要扩位,计算方式保持不变。

还有个容易被忽略的问题:如果TX和RX模块要接到一个AXI总线或自定义总线上,接收完成信号rx_done可能需要打一拍转换成电平使能,或者再加一个简单握手。我的建议是第一版先不加FIFO,直接在顶层模块里把rx_done连到一个LED或者把rx_data连到数码管上,先证明数据能正确接收,再考虑加FIFO缓冲。

4. 从仿真到上板:调通一个完整回环

4.1 仿真Testbench的编写思路

写testbench的时候,我的习惯是把两个被测模块的TX和RX交叉连接,形成自发自收的回环,这样不需要外部设备也能验证逻辑正确性。比如顶层例化uart_tx和uart_rx,把tx模块的tx引脚直接连到rx模块的rx引脚,用testbench给tx模块发送一个字节0xA5(二进制1010 0101,因为高低电平交替明显,容易在波形里观察),然后检查rx模块输出的rx_data是不是0xA5。

仿真时要把baud_rate参数设得小一点,比如50MHz时钟配9600波特率,仿真时间会比较长,一个字节要大约11个bit时间,接近10毫秒,肉眼等得着急。可以把参数改成仿真专用参数,比如把CLK_FREQ设成1000,BAUD_RATE设成100,这样10个时钟周期就是一个bit,仿真速度极快。等仿真通了再改回真实参数上板,这个技巧能节省大量仿真时间。

以下是testbench的关键部分:

module tb_uart_loopback(); parameter CLK_FREQ = 1_000_000; parameter BAUD_RATE = 100; reg clk = 0; reg rst_n = 0; wire tx_line; wire [7:0] rx_data; wire rx_done; // 例化发送和接收模块 uart_tx #( .CLK_FREQ(CLK_FREQ), .BAUD_RATE(BAUD_RATE) ) u_tx ( .clk(clk), .rst_n(rst_n), .tx_start(tx_start), .tx_data(tx_data), .tx(tx_line), .tx_busy(tx_busy) ); uart_rx #( .CLK_FREQ(CLK_FREQ), .BAUD_RATE(BAUD_RATE) ) u_rx ( .clk(clk), .rst_n(rst_n), .rx(tx_line), .rx_data(rx_data), .rx_done(rx_done) ); always #5 clk = ~clk; integer i; initial begin rst_n = 0; tx_start = 0; tx_data = 8'h00; #100; rst_n = 1; // 发送两个字节 0xA5 和 0x5A for (i = 0; i < 2; i = i + 1) begin @(posedge clk); tx_data = (i == 0) ? 8'hA5 : 8'h5A; tx_start = 1; @(posedge clk); tx_start = 0; wait (tx_busy == 0); end #1000; $finish; end endmodule

仿真波形里重点检查三处:起始位出现后,rx的中位采样是否落在低电平上;数据位采样得到的电平是否与tx输出的电平一致;停止位结束后rx_done是否出现单周期脉冲,rx_data是否等于发送值。只要这三处都对,逻辑基本就稳了。

4.2 上板验证:串口助手常见的三个坑

仿真通过只是第一步,上板才会遇到真正的“现实问题”。我的上板调试流程是这样的:用开发板上的USB转串口芯片连接电脑,把FPGA的TX接到USB芯片的RX,FPGA的RX接到USB芯片的TX,然后就出现了三个典型的坑。

第一个坑是TX和RX交叉连接问题。很多人只接一根线,或者接成直连,结果电脑发出去的数据FPGA收不到,FPGA发出去的数据电脑也收不到。记住串口交叉连接,一端的TX一定要接另一端的RX。

第二个坑是乱码。乱码的原因一般是波特率不匹配,或者分频计算有误。常见的错误是忘减一,比如50MHz除9600得5208,但计数器的比较值应该是5207,否则实际波特率偏低。还有一种是系统时钟频率没看准,有的开发板默认用12MHz而不是50MHz,导致分频数完全不对。出现乱码时先用逻辑分析仪抓波形,或者把收到的字节用十六进制显示,看是不是固定偏移,比如收到的全是0xFF或0x00,那大概率是时钟或分频问题。

第三个坑是USB转串口芯片的驱动和映射问题。Windows上插上USB线,设备管理器里应该多出一个COM口,如果驱动没装好,串口助手就找不到设备。把设备管理器截图发给我之前,先自己确认COM口号,以及串口助手的波特率、数据位、停止位、校验位是否和FPGA配置一致。这类问题里,80%都是参数设置不一致造成的。

4.3 加一个发送使能的交互方式

回环测试验证了收发链路,但如果想体验“按一下按键发送一个字节”,可以加一个按键消抖模块,按键按下时产生一个单周期脉冲,送给tx_start。按键消抖模块的写法跟UART起始位检测有点像,也是等一段时间再采样确认,建议自己写一遍。我自己的调试流程是先用回环确认链路,然后用按键主动发送固定字节,用LED显示接收到的字节的低四位。这样每按一次按键,LED就会变一次状态,直观又方便。

5. 常见问题速查与调试心得

5.1 问题速查表

现象可能原因排查方法
发送端仿真正常,上板后电脑收不到数据TX/RX接反、USB芯片供电异常、引脚约束错误用示波器或逻辑分析仪抓TX引脚,看是否有波形翻转
电脑发数据,FPGA收不到RX引脚约束错误、接收状态机卡死先用回环把FPGA的TX短接到RX,排除接收逻辑问题
收到乱码但波形看起来正常波特率分频参数错误、收发双方波特率不一致核对分频参数计算公式,再用串口助手切换波特率测试
能收到首字节,后续全错接收状态机没有正确回到IDLE、rx_done占用时间太长检查停止位后的状态切换,确认rx_done只拉高一个周期
偶尔收到错误字节时钟抖动、信号质量差、中位采样不够稳考虑加两级同步器、过采样或把信号线缩短

5.2 用一个范围决定“多少波特率误差可以接受”

不少同学会纠结波特率误差。UART接收的容错能力取决于采样窗口,理论上一个bit的采样误差不超过半个bit就不会出错。考虑到起始位需要精确检测,实际工程建议波特率误差控制在2%以内。举例来说,50MHz时钟配9600波特率,实际波特率误差是0.32%,完全安全;但如果系统时钟是33MHz,除9600得到3437.5,取整后误差约0.44%,也能用;如果系统时钟是8MHz,除9600得到833.33,取整后误差约0.64%,依然能工作,但误差已经明显偏大,发送长数据帧时要留意。最稳妥的方法还是选择能够整除的时钟频率,或者用小数分频器(NCO方式)来产生精确的波特率。

5.3 从UART到更复杂的通信外设,思路是通用的

写完UART之后,你会发现SPI、I2C这些带时钟线的同步协议其实更简单,因为总线自带时钟,不需要自己去猜采样点。而RS485只是在UART的电平基础上增加了差分驱动和方向控制,数据链路层跟UART一模一样。我的建议是,先把UART吃透,然后趁热打铁去写SPI Slave,再挑战I2C,最后回头看UART,你会对整个串行通信有更立体的理解。

根据我个人的实际调试经验,每调一个新板子,我都会先用回环测试验证硬件链路,再用固定数据跑一遍,最后再上完整逻辑。这样一步步排查,能省下大量抓狂的时间。学习写代码时,多一份耐心把每个模块的时序画清楚,比多啃十篇教程更有用。的希望这篇内容能帮你把UART这个老朋友彻底“焊”在脑子里,后续再做FPGA项目时,调试串口这个基本功会一直为你兜底。

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

数字化营销落地实战:从数据资产到私域转化的完整打法

简介&#xff1a;这份《数字化营销分享》是一套一百零七页的高阶演示文稿&#xff0c;面向企业营销、品牌运营及数字化转型从业者&#xff0c;旨在帮助大家应对VUCA、RUPT、BANI叠加的复杂变化环境&#xff0c;找到更稳健的营销打法。内容按数字化营销方法论、数字化营销系统、…

作者头像 李华
网站建设 2026/9/7 1:12:01

七天入门PowerBI:从Excel思维到数据建模与可视化

简介&#xff1a;这是一份面向零基础读者的《七天入门PowerBI》电子书&#xff0c;由微信公众号精选文章按模块整理而成&#xff0c;旨在帮助刚接触Power BI的新人以最短路径完成上手&#xff0c;避免一开始就陷入函数公式与计算理论的细节。全书按7天规划学习节奏&#xff1a;…

作者头像 李华
网站建设 2026/9/7 1:11:45

从硬编码配方到AI工艺自适配:工业煎药控制系统的三代技术演进与实战复盘

做医药工控这几年,接触了大大小小二十多个煎药中心项目,发现一个很普遍的现象:很多煎药中心花大价钱上了自动化设备,可工艺控制还停留在“老师傅调参数、PLC写死程序”的阶段。换个饮片批次,煎出来的浸膏率能差出10%;改个特殊煎法,还要厂商上门改PLC程序。 工业煎药的控…

作者头像 李华
网站建设 2026/9/7 1:11:40

医药工控新赛道:工业煎药系统如何构建处方-煎煮-成品全链路可信溯源体系

随着2025年国家药监局《中药生产监督管理专门规定》落地,以及2026版《医院中药饮片管理规范》首次将中药代煎纳入正式管理框架,中药煎药的质量管控已经从“人工经验”转向“数字化合规”阶段。 很多煎药中心还停留在“买几台智能煎药机+套管理软件”的阶段,实际运行下来问题…

作者头像 李华
网站建设 2026/9/7 1:09:51

从建模到验证:ViCarrealTime实时仿真在车辆HIL测试中的工程实践

简介&#xff1a;这是一份围绕 ViCarRealTime 软件建模与模型验证的 PPT 培训资料&#xff0c;面向车辆动力学仿真工程师、ADAMS/Car 用户及实时仿真方向学习者&#xff0c;重点解决从多体模型导入到整车验证的流程问题。内容覆盖 ADAMS/Car 模型导入、悬挂特性文件创建、弹性运…

作者头像 李华
网站建设 2026/9/7 1:07:34

SHX字库制作与SHP编辑:用ShxEditPro打造CAD自定义符号字库

简介&#xff1a;面向AutoCAD字库定制需求的 ShxEditPro 使用说明书&#xff0c;适合需频繁处理shx/shp矢量字库的CAD工程师、广告制作与模具标识设计人员&#xff0c;也适合希望在激光打标、IC贴标等场景快速生成特殊字符的技术人员。文档系统讲解14种一笔画编程指令的调用规则…

作者头像 李华