Actel FPGA上从零实现UART串口通信(Verilog)

Actel FPGA上从零实现UART串口通信(Verilog)
简介面向初学者的Actel SmartFusion FPGA串口UART例程资料包围绕混合信号SoC上的UART通信实现系统梳理了从Verilog/VHDL逻辑设计、时钟管理到Cortex-M3固件开发的完整流程适合正在学习SmartFusion或FPGA串口应用的工程师与学生。压缩包共842个文件大小约4.72MB其中VHDL源码vhd对应核心收发逻辑C/C固件c/h负责寄存器配置与中断处理prw/psm/dbs等工程文件保存项目配置与仿真数据Tcl/XML脚本则用于自动化流程或IP配置整体结构便于按模块查阅。已有227人浏览学习。通过分析和修改这些工程文件读者能掌握UART起始位、数据位、校验位与停止位的帧格式处理理解波特率发生器、移位寄存器、状态机等核心逻辑还能借助CMIP时钟管理以及Cortex-M3端的中断收发机制熟悉从仿真验证到JTAG下载调试的完整链路有效避开发送乱码、波特率不匹配等常见问题为后续在工业控制等场景中扩展FPGA通信设计打下基础。1. 为什么在Actel上写串口例程这么磨人先说个背景Actel这家公司早年做反熔丝和Flash工艺的FPGA后来被Microsemi收购再后来整个Microsemi又被Microchip收入囊中。所以你手头如果有一块印着Actel或者Microsemi标识的核心板不要觉得自己拿了什么古董这套东西在军工、航天、医疗、工业控制里依然活得很好主打一个低功耗、非易失、上电即跑。跟Xilinx和Intel原Altera那套SRAM工艺不同Actel的FPGA配置存在芯片内部Flash里掉电不丢不需要外部配置芯片也没有启动加载过程这在很多可靠性要求高的场景里是实打实的优势。但问题也出在这套生态太小众。你搜“Actel FPGA 串口 例程”翻来覆去就那么几篇博客还大多是十年前的帖子截图模糊、代码不全、工具链版本老到装不上。相比之下Xilinx的UART例程一抓一大把随便找个开发板配套资料就能抄作业。所以很多从Xilinx转过来的人第一反应是“这玩意儿怎么连个像样的例程都没有”第二反应是“我自己写个UART总行了吧”结果又卡在Libero IDE的工程配置上。这篇文章就围绕一个目标展开在Actel/Microsemi系的FPGA上从零搭一个可用的串口收发例程。硬件平台以IGLOO2和SmartFusion系列为参考开发环境用Libero SoC代码风格是纯Verilog不依赖厂商自带的CoreUART IP这样你能看清每一拍逻辑在干什么。适合这几类人看刚拿到Actel开发板不知道从哪下手的入门者、从其他FPGA平台迁移过来的老手、以及需要在项目里快速做一版串口联调但又不想被IP核配置界面绕晕的工程师。先说结论Actel的FPGA做串口通信本质上跟其他FPGA没有区别不就是把UART协议用状态机实现出来嘛。真正磨人的地方在于工具链的熟悉成本、时钟来源的选择以及那套跟主流FPGA差别很大的工程管理方式。这篇文章会把发送、接收、顶层集成、引脚约束、板上实测的完整链路走一遍中间穿插我实际调试中遇到过的坑。2. 环境准备Libero版本选择与工程创建里的坑2.1 版本选型与许可证Actel/Microsemi的FPGA开发环境叫Libero SoC早期版本叫Libero IDE。现在Microchip官网能下到的最新版本是Libero SoC v12.6再往前的12.5、12.4也还有人用。如果你手里的开发板比较老比如SmartFusion A2F200建议优先用12.5或12.6老版本对系统兼容性差在Win10/11上经常各种报错。许可证是个容易被忽略的点。Libero SoC有免费版叫Libero SoC Platinum但你需要先去Microchip官网注册账号申请License文件然后把License路径配置到软件里。这个申请过程审核时间不算长但是要填一堆公司信息个人学习就填个人邮箱一般都能过。没License的情况下软件能打开但综合和布局布线会受限跑到一半就卡住。2.2 新建工程时最容易搞错的器件选择打开Libero后新建工程流程跟Vivado/Quartus差不太多需要填工程名、选器件型号。这里面有个很容易翻车的点器件系列选错后面Pin Constraint和综合脚本全要重来。Actel/Microsemi的FPGA主要有这几个系列系列工艺典型型号应用场景ProASIC3FlashM1A3P1000老项目、军工遗留IGLOOFlashM1AGL250超低功耗手持设备IGLOO2FlashM2GL010/M2GL025目前最常用的通用系列SmartFusionFlash硬核ARMA2F200/A2F500需要内嵌Cortex-M3的场合PolarFireFlashMPF300高端、高速接口场景如果你只是想跑串口例程强烈建议用IGLOO2系列M2GL010或者M2GL025都行。这个系列资源够用、价格适中、资料相对多一些。千万别一上来选PolarFire那玩意儿的License和工程配置复杂度高一个档次串口还没跑通就先被DDR和SerDes折腾疯了。新建工程时还有一个选项问你是否要“Create SmartDesign”新手建议直接选“Create HDL”以纯Verilog文件方式建工程不要被SmartDesign这种图形化连线工具带跑偏。图形化工具适合搭复杂系统但你要理解UART原理就必须看着代码学SmartDesign会把这些细节全部封装掉。2.3 时钟来源内部RC还是外部晶振这一步是很多串口例程跑飞的根本原因。Actel的FPGA内部有一个RC振荡器IGLOO2上这个振荡器默认频率大约是50MHz但它只是一个粗略的频率源工艺偏差和温度漂移能到±1%甚至更大。串口通信对波特率精度有要求标准UART要求收发双方的时钟误差在一定范围内典型值是不超过±2%如果通信双方都用内部RC两边误差叠加长帧数据就会开始出错。所以我在这个例程里用的是外部晶振开发板上通常焊了一个50MHz的有源晶振接到FPGA的专用时钟引脚上。如果你的板子没有外部晶振或者你只是做功能验证、短帧传输、距离又很近临时用内部RC也能跑起来但不要指望它做长时间稳定通信。实际项目中如果只能靠内部RC建议在Libero里配置RC振荡器校准或者用接收端的过采样做容错后面接收章节会细说。2.4 创建工程后的默认代码结构新建HDL工程后Libero会生成一个工程目录里面已经有一个顶层文件模板。你只需要在Design Hierarchy里右键Add HDL Source新建一个Verilog文件然后开始写代码。注意Libero的流程是写RTL - 约束 - 综合 - 布局布线 - 生成比特流每一步都有独立按钮不像Vivado一键跑到底。第一次用的人很容易在综合之后找不到布线入口其实只要在Design Flow窗口里按顺序点每个步骤右侧的Run按钮就好。3. 串口发送分频、状态机与波形时序3.1 UART协议回顾与分频计算先把UART协议扒开看一眼。串口的物理层就是一根TX线、一根RX线加上共地。空闲时TX线为高电平要发数据时先拉低一个波特率周期作为起始位然后按低位先行的顺序发送8个数据位最后拉高至少一个周期作为停止位。不需要时钟线收发双方各自用自己的时钟按约定的波特率采样。这就引出一个核心问题频率源是50MHz波特率是115200怎么从50MHz得到115200Hz的节拍答案就是分频。计算方式是系统时钟周期 1 / 50MHz 20ns波特率周期 1 / 115200 ≈ 8680.6ns需要计数 8680.6 / 20 ≈ 434.03取整为434所以设计一个计数器数到433后清零并产生一个时钟周期的高脉冲这个脉冲就是115200波特率下的一个位周期。实际产生的波特率为 50MHz / 434 ≈ 115207Hz误差约0.006%完全在容差范围内。如果时钟是12MHz算出来是104.17取104后波特率就是115384Hz误差约0.16%也还能接受但再低就危险了。算分频系数这件事我建议不要手工算完就写死在代码里而是用参数化的方式parameter CLK_FREQ 50_000_000; parameter BAUD_RATE 115_200; localparam BAUD_DIV CLK_FREQ / BAUD_RATE; // 434这样以后换时钟、换波特率只需要改参数不用动逻辑结构。而且仿真时如果你想快速看到结果把CLK_FREQ对应的分频值缩小就能在仿真波形里几微秒内看到完整帧不用真的等115200波特率下那870微秒的一帧。3.2 发送状态机的完整实现发送模块的核心是一个状态机状态就四个空闲、起始位、数据位、停止位。一个使能信号拉高后状态机开始工作。我习惯用计数器来控制每一位持续时间数据位期间用bit_index标记当前发第几位。直接上代码module uart_tx #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115_200 )( input wire clk, input wire rst_n, input wire [7:0] tx_data, input wire tx_en, output reg tx, output reg tx_busy ); localparam BAUD_DIV CLK_FREQ / BAUD_RATE; localparam IDLE 2d0; localparam START_BIT 2d1; localparam DATA_BIT 2d2; localparam STOP_BIT 2d3; reg [1:0] state; reg [15:0] clk_cnt; reg [2:0] bit_idx; reg [7:0] data_buf; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; clk_cnt 16d0; bit_idx 3d0; data_buf 8d0; tx 1b1; tx_busy 1b0; end else begin case (state) IDLE: begin tx 1b1; tx_busy 1b0; if (tx_en) begin tx_busy 1b1; data_buf tx_data; clk_cnt 16d0; state START_BIT; end end START_BIT: begin tx 1b0; if (clk_cnt BAUD_DIV - 1) begin clk_cnt 16d0; bit_idx 3d0; state DATA_BIT; end else begin clk_cnt clk_cnt 1b1; end end DATA_BIT: begin tx data_buf[bit_idx]; if (clk_cnt BAUD_DIV - 1) begin clk_cnt 16d0; if (bit_idx 3d7) begin state STOP_BIT; end else begin bit_idx bit_idx 1b1; end end else begin clk_cnt clk_cnt 1b1; end end STOP_BIT: begin tx 1b1; if (clk_cnt BAUD_DIV - 1) begin clk_cnt 16d0; state IDLE; tx_busy 1b0; end else begin clk_cnt clk_cnt 1b1; end end endcase end end endmodule这段代码里有几个细节值得说。数据位在tx_en拉高的那个时钟沿同步锁存到data_buf里避免外部数据变化影响发送过程。bit_idx在START_BIT结束时清零进入DATA_BIT后每个位周期递增发到bit_idx等于7也就是第8个数据位后下一个位周期切换到STOP_BIT。停止位期间tx拉高计数器走完一个完整波特率周期后回到IDLE。tx_busy信号在收到使能后立刻拉高直到停止位发完才拉低外部逻辑可以靠这个信号判断当前是否允许发送新数据。3.3 发送模块的仿真验证写完模块别急着上板先用仿真把时序跑通。Libero自带仿真工具但它那个启动速度和波形界面用起来确实不如ModelSim顺手。可以在工程里配置ModelSim作为第三方仿真器或者像我一样在Libero里直接跑自带的仿真然后看波形。仿真时要注意激励的写法tx_en信号必须等tx_busy为低的时候才能拉高否则会丢数据。我见过不少人第一次仿真时连续两个周期就给tx_en结果第二个数据根本没进data_buf。仿真波形上要重点观察几件事tx线在空闲状态下保持高电平不能有毛刺起始位低电平持续时间是否接近一个波特率周期数据位波形是否能看出低位先行的顺序比如发送0x5501010101时波形应该是高低交替的方波停止位后是否有足够的恢复时间这几点确认没问题发送路径基本就稳了。4. 串口接收过采样、起始位判断与数据可靠性4.1 为什么接收比发送难得多发送的逻辑比较简单本质是“用计数器把数据按时间顺序移位输出”。接收就麻烦了你不知道对方什么时候开始发数据你只有一个RX线上一秒还是高电平突然拉低了这有可能是起始位也有可能是噪声毛刺。你要在正确的时间点上采样每一位采样早了或者晚了都有可能导致数据错位。业内通用的做法是过采样。典型方案是16倍波特率采样把每个波特率周期分成16份在每个位周期的中间时刻采样一次这样可以最大程度避开数据边沿附近的跳变区域。16倍过采样需要接收时钟频率达到 16 × 波特率比如115200波特率下采样时钟需要 1.8432MHz而这通常由系统时钟分频得到所以需要一个更精细的分频器。也可以选择8倍过采样逻辑简单一些但抗干扰能力差一些。我第一次做的时候用了16倍逻辑量并没有增加多少所以直接以16倍为标准写法。4.2 起始位检测与毛刺过滤起始位检测是接收端最关键的环节。RX线空闲时为高检测到下降沿就认为对方发来了起始位。但这个下降沿有可能是干扰毛刺所以不能一看到下降沿就开始接收而是要在下降沿出现后的第8个采样点也就是半个位周期后再采样一次如果RX线仍然是低电平才确认这是真正的起始位如果已经变回高电平说明是毛刺丢弃这次接收。这个“二次确认”机制是串口接收可靠性的一大保障。实际工程里如果环境电磁干扰严重还可以把确认采样点从第8个移到第6个左右牺牲一点抗干扰能力换取更快的响应但一般场景下第8个点是合适的。下面是接收模块核心逻辑module uart_rx #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115_200 )( input wire clk, input wire rst_n, input wire rx, output reg [7:0] rx_data, output reg rx_valid ); localparam BAUD_DIV CLK_FREQ / BAUD_RATE; localparam OVS_DIV BAUD_DIV / 16; // 16倍过采样的分频值 localparam IDLE 2d0; localparam START_CHK 2d1; localparam DATA_BIT 2d2; reg [1:0] state; reg [15:0] clk_cnt; reg [3:0] sample_cnt; reg [2:0] bit_idx; reg [7:0] shift_reg; reg rx_d, rx_dd; // 同步打拍抑制亚稳态 always (posedge clk or negedge rst_n) begin if (!rst_n) begin rx_d 1b1; rx_dd 1b1; end else begin rx_d rx; rx_dd rx_d; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; clk_cnt 16d0; sample_cnt 4d0; bit_idx 3d0; shift_reg 8d0; rx_data 8d0; rx_valid 1b0; end else begin rx_valid 1b0; // 默认拉低仅在采到完整字节时拉高一个周期 case (state) IDLE: begin clk_cnt 16d0; sample_cnt 4d0; bit_idx 3d0; // 检测起始位下降沿用打拍后的rx_dd作为同步信号 if (!rx_dd rx_d) begin // 实际下降沿检测应写成 rx_d 0 rx_dd 1 state START_CHK; end end START_CHK: begin if (clk_cnt OVS_DIV - 1) begin clk_cnt 16d0; if (sample_cnt 4d7) begin // 半个位周期位置 if (!rx_dd) begin // 仍是低电平确认是起始位 state DATA_BIT; sample_cnt 4d0; end else begin // 变回高电平是毛刺 state IDLE; end end else begin sample_cnt sample_cnt 1b1; end end else begin clk_cnt clk_cnt 1b1; end end DATA_BIT: begin if (clk_cnt OVS_DIV - 1) begin clk_cnt 16d0; if (sample_cnt 4d7) begin // 每个位周期的中点采样 shift_reg {rx_dd, shift_reg[7:1]}; sample_cnt 4d0; if (bit_idx 3d7) begin state IDLE; rx_data {rx_dd, shift_reg[7:1]}; rx_valid 1b1; end else begin bit_idx bit_idx 1b1; end end else begin sample_cnt sample_cnt 1b1; end end else begin clk_cnt clk_cnt 1b1; end end endcase end end endmodule4.3 为什么必须先用两级寄存器把RX打拍这段代码里我刻意写了rx_d和rx_dd两级同步寄存器这是FPGA跨时钟域处理的基本功。RX线上来的信号跟FPGA系统时钟没有任何相位关系如果直接用这个信号驱动状态机一旦它刚好在时钟上升沿附近变化触发器就可能进入亚稳态导致状态下一次判断错误。这种错误在高速通信中会被放大串口低速还好但养成打拍的习惯能帮你避免后面做SPI、I2C、以太网时遇到的同类问题。注意起始位检测用的是rx_dd和rx_d的组合判断实际上有一个细节我这里简化了当rx_dd为高、rx_d为低时说明同步后的信号刚检测到下降沿。如果你直接用原始rx信号判断毛刺干扰更严重。把rx接到rx_d再接到rx_dd相当于做了一次同步和一次延迟牺牲两个时钟周期的响应时间换来的是稳定性非常值得。4.4 接收错误处理与超时保护实际使用中接收模块还有一个经常被忽视的问题如果对方发送了一半突然断线比如起始位之后RX线一直保持低电平或者高电平模块会卡在DATA_BIT状态出不来。所以我后来在项目里给接收模块加了一个超时保护逻辑如果从起始位开始超过11个波特率周期一个起始位8个数据位2个停止位的时间还没有收完强制回到IDLE状态等待下一次完整帧。类似的在IDLE状态下如果RX线长时间为低也可以加一个超时清零逻辑不过在串口这种低速场景下只要起始位二次确认做得好卡死的情况很少见。这一点如果你用逻辑分析仪抓过UART波形就会有体感工程上很多看似“硬件不稳定”的问题本质上是状态机没有做异常状态恢复。5. 顶层集成、引脚约束与板上实测5.1 顶层模块设计要点发送和接收两个模块单独写好后需要一个顶层把二者例化起来。我常用的做法是做一个环路自测结构顶层有一个拨码开关拨到0是自发自收测试拨到1是外部透传模式。自发自收就是把TX引脚和RX引脚在FPGA内部直接相连这样不需要接任何外部线就能验证收发逻辑。透传模式则把外部RX收到的数据立即从TX发出去方便用串口助手观察数据是否回环正常。顶层代码示意module uart_loopback_top ( input wire clk_50m, input wire rst_n, input wire rx, output wire tx, input wire switch_sel, output wire [1:0] led_status ); wire [7:0] rx_data_w; wire rx_valid_w; wire tx_busy_w; uart_rx #( .CLK_FREQ(50_000_000), .BAUD_RATE(115_200) ) u_rx ( .clk(clk_50m), .rst_n(rst_n), .rx(rx), .rx_data(rx_data_w), .rx_valid(rx_valid_w) ); uart_tx #( .CLK_FREQ(50_000_000), .BAUD_RATE(115_200) ) u_tx ( .clk(clk_50m), .rst_n(rst_n), .tx_data(rx_data_w), .tx_en(rx_valid_w), .tx(tx), .tx_busy(tx_busy_w) ); // 内部环回时直接把tx模块的输出接到LED指示 // switch_sel为1时tx通过IOBUF输出到外部引脚为0时内部直接连回rx // 具体引脚约束在PDC文件里处理 endmodule这里有一个要特别注意的地方rx_valid只拉高一个时钟周期它直接接到tx_en上这时候tx_busy可能还没拉低如果连续收到数据第二帧可能丢失。所以真实的顶层里需要加一个小FIFO或者至少加一个握手逻辑rx_valid到来后如果tx_busy为高就把数据锁存到一个寄存器里等tx_busy拉低后再把寄存器的数据发出去。我这个串口例程里数据量不大直接用了一个一字节深的寄存器做缓冲。5.2 引脚约束与IO标准Libero里管引脚约束的不是XDC也不是UCF是PDC文件。在工程里新建一个Physical Constraints文件把顶层端口映射到具体引脚上。这里不同板子的引脚定义差别很大一定以你手里开发板的原理图为准。比如我用的板子上外部晶振接在FPGA的P22脚UART_TX是P15UART_RX是P16LED是P12、P13。PDC文件内容大致长这样set_io clk_50m -pin_name P22 -IO_TYPE LVCMOS33 set_io rst_n -pin_name P18 -IO_TYPE LVCMOS33 set_io rx -pin_name P16 -IO_TYPE LVCMOS33 set_io tx -pin_name P15 -IO_TYPE LVCMOS33 set_io switch_sel -pin_name P20 -IO_TYPE LVCMOS33 set_io led_status[0] -pin_name P12 -IO_TYPE LVCMOS33 set_io led_status[1] -pin_name P13 -IO_TYPE LVCMOS33IO标准这里默认用LVCMOS33如果板子是1.8V或者2.5V电平要改成对应的电压档否则上板后电平不匹配串口助手那边会收到乱码或者完全收不到数据。还有一点如果引脚名在原理图里是A9这种纯字母数字格式但在PDC里要求写十进制格式比如A9对应的是某个脚号这个翻译过程要看Libero的封装引脚映射表别想当然直接写。写完后在Design Flow窗口里依次Run Synthesis、Run Place and Route、Run IO Designer这个可以帮你图形化确认引脚分配最后Run PROGRAM生成比特流用下载器烧进Flash。注意Actel的Flash架构意味着你编程进去后断电重上电依然保留不需要每次上电都烧一边这点比SRAM工艺的FPGA方便很多。5.3 实测步骤与常见现象烧录完成后上电实测。我习惯按下面这个顺序走准备一块USB转串口模块比如CH340或者FT232的接到电脑。这里直接用CH340就够驱动装好后在设备管理器里能看到对应的COM口号。把USB转串口模块的TX接到FPGA开发板的RX引脚RX接到开发板的TX引脚GND接GND。注意串口是交叉接法同名端相连是收不到数据的。打开串口调试助手波特率选115200数据位8停止位1校验位None也就是常说的8N1。发送一个单字节0x55观察接收区是否立即回显0x55。发送一串数据比如”Hello Actel FPGA”看回显是否完整无乱码。把波特率切换到9600再试一轮确认分频参数没写死。如果回显乱码优先排查三个地方波特率是不是匹配、USB转串口模块的TX/RX是不是接反了、板子的外部时钟频率是不是真如你参数里填的那样。尤其是第三个问题我遇到过有人板子上焊的是12MHz晶振代码里却按50MHz算分频发送方和接收方波特率差了四倍多收到的全是乱码。这种问题在串口助手里表现非常典型发送0x55收到的是0xC5、0xE5这种看起来有点规律又不对的数据。6. 我在实际调试中踩过的几个关键坑6.1 内部RC振荡器的精度陷阱前面提过内部RC振荡器的精度问题这里详细说一个我踩过的坑。有一块IGLOO2的板子外部晶振脚被复用成普通IO了我图省事用内部RC做时钟源。单发一个字节没事发送超过4个字节后后面几个字节偶尔出现错误。用示波器抓TX波形才发现实际波特率比标称值低了将近1%而接收端用的USB转串口芯片是标称石英晶振的115200双方累积误差超过容限长帧就出错了。解决办法有两个一是改成外部晶振二是降低通信速率把波特率从115200降到9600误差比例不变但绝对时间变长之后采样点偏差占比变小所以低速跑长帧反而更稳定。如果你的电路板已经定型没法改时钟源又必须高速跑那只能考虑在接收端做动态波特率校准这个复杂度就上来了一般项目和产品都不太建议。6.2 复位信号的处理差异Actel的FPGA跟Xilinx有个很大的不同它的寄存器有专用的上电复位全局网络内部寄存器在上电时可以默认初始化为指定值。这意味着你的复位信号不一定要接到全局复位网络上Actel允许你通过设置寄存器的初始化值来省掉一部分复位逻辑。但这也带来一个坑如果你某些寄存器没有显式给初始值上电后状态可能是随机的状态机可能从奇怪的中间状态开始运行。我的建议很简单写代码时每个always块的复位分支里把所有寄存器都赋一次初值不要偷懒。虽然这会多消耗一点逻辑资源但对于调试期来说一个可预测的上电行为比省那几十个寄存器值钱多了。而且Libero综合时对这种有初始化值的寄存器支持很好不会像某些SRAM FPGA那样完全忽略初始值。6.3 引脚约束文件的编码格式PDC文件如果是从Windows记事本里创建的默认可能是ANSI编码而Libero对UTF-8 with BOM和纯ASCII的处理有区别。我遇到过一次很诡异的问题PDC文件内容看着完全正确但运行到Place and Route时报错说找不到引脚最后发现是文件里包含了中文字符注释又被保存成了GBK格式Libero解析乱了。建议所有约束文件用纯英文注释编码保存为UTF-8无BOM格式从根上避免这类问题。类似的工程路径不要有中文和空格Actel工具链对路径的容忍度比Vivado还差路径里有中文综合阶段可能直接罢工。6.4 串口助手交互中的时序心理最后说个经验层面的东西。做串口联调时很多人的操作习惯是“在串口助手里疯狂点发送一次点几十个包”然后去波形里找问题。但如果你想验证FPGA这边收发逻辑是否正常我建议一次只发一个字节等回显确认无误后再逐渐增大发送间隔和数据量。因为串口助手的发送间隔如果比FPGA处理一帧的时间还短数据会在缓冲寄存器里排队看起来像FPGA丢数实际上是外部发得太快。这个现象在数据回环测试里特别容易误导人先排除外部原因再回头查FPGA内部逻辑能省下大量排查时间。换一个角度说串口这个接口虽然简单但它是FPGA开发里最常用的“人机对话”通道。把发送模块和接收模块吃透之后后面做ADC数据采集、传感器数据读取、甚至以太网调试都可以用串口作为观察窗口。这个例程看似平淡但它是你在Actel平台上积累的第一块基石值得慢慢调、细细看。本文还有配套的精品资源点击获取

最新新闻

日新闻

周新闻

月新闻