资讯详情

资讯详情

基于Nios II软核的FPGA电子钟设计与软硬协同实现

简介面向FPGA与Nios II学习者及课程设计开发者这份电子钟工程以Verilog与SOPC协同实现为核心可用于理解软硬件划分、定时器计时和外设驱动方法。设计采用Qsys生成的1ms定时器计时8位数码管显示时间3个按键完成正常显示、调时、调分、调秒及闹钟小时/分钟设置并加入调整位闪烁、按键对应LED指示以及蜂鸣器闹钟和流水灯提示功能链路较完整。压缩包共602个文件、约22.66MB包含Verilog源码、C/H程序、Nios II工程配置、Quartus与SOPC相关工程文件、MIF初始化数据及编译输出等便于对照硬件驱动与软件逻辑。已有2296人学习下载适合作为FPGA数字系统课程设计、SOPC入门实验和答辩展示的参考工程。1. 从一道为什么还要塞软核的题开始拿到基于 Nios II 的电子钟这道题多数人第一反应是走时逻辑用一百多行 Verilog 状态机就写完了为什么还要在 FPGA 里塞一个软核这个反问恰好是设计的分水岭。只做走时纯硬件确实省资源但按键长按短按、闹钟比对、整点报时响法、12/24 小时切换每改一条需求纯 Verilog 就要重新综合、布局布线、下载一轮十几分钟。把这些挪进 Nios II 的 C 代码改完只重编软件几秒就能验证。这套方案处理的就是这条软硬边界FPGA 承担分频、扫描、消抖和并口时序软核承担状态与交互从系统搭建、Verilog 模块、中断走时、校时状态机一路讲到验收测试。适合正在做 FPGA 课程设计或毕设、板子已经在手但软硬怎么切还没定的人。2. 总体架构设计软硬划分、SOPC 系统与 1Hz 时基电子钟这个题目看着小架构上却有一个绕不开的问题时间和显示到底谁说了算。定错了后面要么通信开销大得离谱要么改动一处就得全盘重来。这一章先把职责切清楚再把系统搭起来。2.1 哪些逻辑交给硬件哪些留给软核我一般用一条判据来切需要确定时序、不能被其他任务打断的放硬件需要改参数、加分支、调交互的放软件。按这条判据落到电子钟上是这样的功能归属理由50MHz 到 1kHz 扫描时钟分频硬件纯计数时序确定软件做反而占中断六位数码管动态扫描与消隐硬件刷新必须连续稳定软件刷新会被主循环延迟拖出闪烁按键同步与 20ms 消抖硬件亚稳态处理必须在时钟域内完成蜂鸣器方波产生硬件2kHz 方波靠软件翻转会抖时分秒计数与进位软件逻辑简单但规则会变12/24 小时、闰秒处理校时状态机软件分支多、要配合按键长短按改起来最频繁闹钟比对与响铃策略软件响几声、响多久、按哪颗键停全是策略显示数据打包软件格式跟着显示模式走有两个典型的错误分法值得说。第一种是把数码管扫描放进 1ms 定时器中断里做六位数码管就意味着中断里要循环六次写段码和位选中断执行时间被拉长到几十微秒主循环稍微一忙就开始丢拍屏幕出现肉眼可见的暗区。第二种是把校时逻辑写成 Verilog 状态机看起来纯硬件很酷但老师一句再加个长按 3 秒恢复默认就得改代码重新综合一次答辩前根本折腾不起。还有一点容易忽略软硬之间的接口要窄。软件不该直接去操作段选和位选引脚硬件对外只暴露一个 24 位的 BCD 数据寄存器和一两个控制寄存器。接口越窄后来换显示屏比如改成 LCD1602 或 OLED时软件侧只需要换一个刷新函数走时和校时逻辑一行都不用动。2.2 SOPC 系统组建与地址映射系统在 SOPC Builder 或 Platform Designer 里搭全部挂在同一个 50MHz 时钟域上不做跨时钟这一条能省掉后面一大半的调试时间。核心选 Nios II/e经济型。它没有指令缓存、没有桶形移位器、乘法器可选但电子钟对算力的要求几乎为零用 /e 能比 /s 省下几千个逻辑单元对中低容量器件很关键。片上存储器给 16KB程序加数据足够堆可以关掉栈给 1KB 就行。完整组件与地址分配如下组件类型基地址大小用途cpuNios II/e——主控复位向量指向 onchip_memonchip_memOn-Chip Memory0x0000_000016KB程序段 数据段timer_0Interval Timer0x0000_400032B1ms 周期中断IRQ 0key_pioPIO (Input)0x0000_408016B3 路消抖后按键脉冲buzzer_pioPIO (Output)0x0000_40A016B蜂鸣器使能seg_ctrl_0自定义 Avalon-MM Slave0x0000_410032B六位数码管自定义外设 seg_ctrl_0 的寄存器定义建议就两个偏移 0x00DATA只写低 24 位从高到低依次是时十位、时个位、分十位、分个位、秒十位、秒个位每 4 位一个 BCD 码偏移 0x04CTRLbit0 为显示使能bit1 为小数点或分隔符位置选择bit[7:4] 为亮度占空比。这个寄存器划分的好处是软件一次 32 位写就能刷新整屏不用逐位操作中断里也不会耽误时间。提示中断号分配完记得在软件工程里核对system.h中的TIMER_0_IRQ宏SOPC 重新生成后中断号可能变这个坑很常见。2.3 时基与精度预算50MHz 怎么变成一秒时基有两种做法选哪个直接决定走时精度。第一种是硬件直接分频出 1Hz喂给一个 PIO 或者直接驱动秒计数。优点是准缺点是只能在整秒粒度上操作想做 1/100 秒秒表或者调试时观察毫秒都不行。第二种是定时器产生 1ms 周期中断软件累加到 1000 进位成秒。这是我在这个方案里用的方式原因是它给软件留了余地闹钟比对、整点报时判断、按键长按计时都能复用同一个时基。定时器配置很直接50MHz 时钟下周期寄存器写 49999即计数 50000 次1 分频自动重载中断使能// 定时器初始化1ms 周期中断自动重载 #include altera_avalon_timer_regs.h #include altera_avalon_timer.h #include sys/alt_irq.h void timer_init(void) { // 关定时器 - 写周期 - 清标志 - 开中断 启动 IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_0_BASE, ALTERA_AVALON_TIMER_CONTROL_STOP_MSK); IOWR_ALTERA_AVALON_TIMER_PERIODL(TIMER_0_BASE, 49999 0xFFFF); IOWR_ALTERA_AVALON_TIMER_PERIODH(TIMER_0_BASE, (49999 16) 0xFFFF); IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_0_BASE, 0); // 写 0 清 TO 标志 alt_ic_isr_register(TIMER_0_IRQ_INTERRUPT_CONTROLLER_ID, TIMER_0_IRQ, timer_isr, NULL, 0); IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_0_BASE, ALTERA_AVALON_TIMER_CONTROL_ITO_MSK | ALTERA_AVALON_TIMER_CONTROL_CONT_MSK | ALTERA_AVALON_TIMER_CONTROL_START_MSK); }参数说明49999是计数值减一因为计数器从 0 开始写到 49999 就是 50000 个时钟周期如果是 100MHz 系统时钟这个值要改成 99999。CONT_MSK打开连续运行不开的话中断只触发一次。精度这块必须算清楚否则答辩时被问你的钟一天误差多少会答不上来。误差来源就三条晶振本身板上常用的普通有源晶振精度在 ±20ppm 到 ±50ppm 之间。50ppm 换算成一天是 50×10⁻⁶×86400 ≈ 4.3 秒。这是物理上限代码写得再好也突破不了。中断响应延迟只要定时器是自动重载的中断晚到只会让本次处理晚一点下一个周期仍然是严格的 1ms延迟不累积。真正会累积的是丢中断——ISR 执行太久下一个中断到来时标志还没清那一次就永久丢了。所以 ISR 里只做计数绝不刷屏。软件进位逻辑if (ms 1000)和ms 0这个写法本身没有误差但如果写成每次中断 ms 1然后判断 ms % 1000对 1000 取模的开销在 /e 核上不小能省则省。如果对精度有更高要求通常的做法是加一颗独立 RTC 芯片走 I²C软核掉电后时间不丢FPGA 这边只负责读取和显示。题目不强制的话不值得为此增加外设。3. 硬件逻辑实现扫描、消抖与蜂鸣器驱动三个模块都不复杂但每个都有几个不改就出问题的细节。这一章给的是可以直接搬进工程的写法。3.1 六位数码管动态扫描与消隐六位数码管共用一组段选线靠位选轮流点亮人眼靠视觉暂留看成同时亮。扫描频率太低会闪太高则亮度不足、驱动管来不及开关。6 位 × 每 1ms 换一位 单屏刷新率约 166Hz这个值在实测里既不闪亮度也够是我常用的取值。// 六位数码管动态扫描1kHz 换位节拍换位瞬间消隐一拍 module seg_scan ( input wire clk, // 50MHz input wire rst_n, input wire [23:0] bcd_data, // 6 位 BCD[23:20] 为最高位 input wire en, // 显示使能 output reg [7:0] seg, // 段选 a~g,dp共阴低有效取反 output reg [5:0] sel // 位选低有效 ); reg [15:0] div_cnt; reg tick_1k; reg [2:0] scan_idx; reg blank_phase; // 消隐相位标志 // 50MHz / 50000 1kHz always (posedge clk or negedge rst_n) begin if (!rst_n) begin div_cnt 16d0; tick_1k 1b0; end else if (div_cnt 16d49_999) begin div_cnt 16d0; tick_1k 1b1; end else begin div_cnt div_cnt 1b1; tick_1k 1b0; end end // 位选推进 换位时先关段选防止上一位残影串到下一位 always (posedge clk or negedge rst_n) begin if (!rst_n) begin scan_idx 3d0; sel 6b111_111; seg 8h00; blank_phase 1b0; end else if (tick_1k) begin scan_idx (scan_idx 3d5) ? 3d0 : scan_idx 1b1; sel ~(6b000_001 ((scan_idx 3d5) ? 3d0 : scan_idx 1b1)); seg 8h00; // 消隐 blank_phase 1b1; end else if (blank_phase) begin seg ~seg_code(bcd_data[{scan_idx, 2b00} : 4]); blank_phase 1b0; end end // 共阴段码表 0~9 function [7:0] seg_code; input [3:0] d; case (d) 4d0: seg_code 8h3F; 4d1: seg_code 8h06; 4d2: seg_code 8h5B; 4d3: seg_code 8h4F; 4d4: seg_code 8h66; 4d5: seg_code 8h6D; 4d6: seg_code 8h7D; 4d7: seg_code 8h07; 4d8: seg_code 8h7F; 4d9: seg_code 8h6F; default: seg_code 8h00; endcase endfunction endmodule逻辑说明div_cnt负责降频tick_1k是单周期脉冲只在计数到顶时为高。位选推进和消隐放在同一个时序块里tick_1k到来时先更新位选、同时把seg清 0下一拍才把新数据送上段选这样换位瞬间段选是全灭的不会出现上一位的数字在新一位上留一道残影。{scan_idx, 2b00}等价于scan_idx × 4就是取 24 位数据中对应的那 4 位 BCD。参数怎么改扫描频率改49_999这个比较值想更亮可以把下标改小但别低于 4ms反射式数码管要留出响应时间驱动共阳数码管时把~seg_code的取反符去掉同时把sel改成高有效如果板上加了 74HC595 串行驱动那这里要改成移位输出段选和位选的时序要按芯片手册重新排。工程里还有个常被忽略的电气问题段选每段限流电阻一般 220Ω~470Ω位选如果直接由 FPGA 引脚驱动六位全亮的瞬间电流可能达到几十毫安超过单脚驱动能力。稳妥做法是位选加三极管或者专用驱动芯片这一点在画板子时就得定不是代码能补救的。3.2 按键消抖状态机与边沿检测机械按键按下和松开时触点会弹跳抖动时间典型 5~20ms。如果 FPGA 直接采样一次按下会变成一串脉冲软件侧就会按一下加了好几个数。处理分三层异步输入先过两级触发器做同步消除亚稳态再用计数器确认电平稳定时间最后输出单周期脉冲让软件只认一次。// 20ms 消抖50MHz 下计数 1_000_000 约等于 20ms module key_debounce ( input wire clk, input wire rst_n, input wire key_in, // 原始输入按下为低 output reg key_pulse // 单周期脉冲按下瞬间有效 ); localparam CNT_MAX 20d999_999; reg [19:0] cnt; reg sync0, sync1, key_stable; // 两级同步防亚稳态 always (posedge clk or negedge rst_n) begin if (!rst_n) begin sync0 1b1; sync1 1b1; end else begin sync0 key_in; sync1 sync0; end end // 电平稳定保持 20ms 才认变化瞬间输出脉冲 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 20d0; key_stable 1b1; key_pulse 1b0; end else if (sync1 ! key_stable) begin if (cnt CNT_MAX) begin cnt 20d0; key_stable sync1; key_pulse ~sync1; // 稳定到低电平 按下 end else begin cnt cnt 1b1; key_pulse 1b0; end end else begin cnt 20d0; key_pulse 1b0; end end endmoduleCNT_MAX决定了消抖窗口20ms 是经验值按键质量差可以加到 30ms但不要超过 50ms否则快速连按会跟不上。key_pulse只有一拍有效所以软件侧用 PIO 读边沿寄存器读一次就清掉不用再做任何去抖。三颗按键MODE / UP / DOWN各例化一份输出接到 key_pio 的 bit0~bit2。如果板子按键数量紧张也可以用 AD 键盘一个 ADC 通道分压识别多键那是另一套写法。3.3 蜂鸣器与整点报时的驱动方式先分清有源和无源。有源蜂鸣器内置振荡电路给高电平就响控制最简单无源蜂鸣器本质是个电磁或压电片必须外部给方波频率决定音调。开发板上配无源的居多因为它能弹不同音调做整点报时的叮咚两声更好听。无源蜂鸣器需要 2kHz 左右的方波用硬件分频最简单50MHz / 25000 2kHz占空比 50% 就是数到 12500 翻转一次。// 无源蜂鸣器驱动产生 2kHz 方波en 控制是否发声 module buzzer_drv ( input wire clk, // 50MHz input wire rst_n, input wire en, // 1 响0 静音 output wire buz_out ); reg [14:0] cnt; reg wave; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 15d0; wave 1b0; end else if (!en) begin cnt 15d0; wave 1b0; end else if (cnt 15d12_499) begin cnt 15d0; wave ~wave; end else cnt cnt 1b1; end assign buz_out wave; endmodule方波在硬件产生软件只负责拉高或拉低 buzzer_pio 的那一位。整点报时策略全放软件秒和分都归零时拉高 en 响 200ms静音 200ms再响一次用 1ms 时基计数控制节奏即可硬件一行都不用改。闹钟响铃同理只是持续到用户按键停止。注意蜂鸣器启动电流不小直接接 FPGA 引脚容易把电源拉出毛刺导致软核复位。板上一般有三极管驱动如果没有务必自己加。4. Nios II 软件实现中断走时、校时状态机与寄存器读写软件这一层其实只有三件事让时间准、让用户能改时间、让改完的时间显示出来。分开写清楚代码量不大。4.1 定时器中断与走时进位中断服务程序必须短。我的做法是 ISR 里只做毫秒累加和秒进位最多再置一个秒变化标志给主循环显示刷新一律放到主循环里。#include system.h #include altera_avalon_timer_regs.h typedef struct { unsigned char h, m, s; } rtc_t; static volatile rtc_t g_now {12, 0, 0}; // 当前时间 static volatile unsigned int g_ms 0; // 毫秒累加 static volatile unsigned char g_sec_flag 0; // 秒变化标志主循环消费 static void timer_isr(void *ctx) { // 清 TO 标志位必须写 0否则中断会反复进 IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_0_BASE, 0); if (g_ms 1000) { g_ms 0; g_sec_flag 1; if (g_now.s 60) { g_now.s 0; if (g_now.m 60) { g_now.m 0; if (g_now.h 24) g_now.h 0; } } } }g_now必须加volatile否则编译器可能把它优化进寄存器主循环永远读到旧值。另一个隐性问题是读一致性主循环读g_now.h/m/s时如果正好被中断打断在秒进位到分进位的中间读到的时分秒可能是错位组合。稳妥写法是先关中断做一次快照再开static rtc_t rtc_snapshot(void) { rtc_t t; alt_irq_context ctx alt_irq_disable_all(); // 关中断 t g_now; alt_irq_enable_all(ctx); // 恢复 return t; }在 /e 核上关中断的时间只有几微秒完全不影响 1ms 时基。这个习惯我从做第一个时钟项目起就保留着比事后查为什么显示偶尔跳一下省事太多。4.2 校时与闹钟的人机交互状态机按键只有三颗MODE、UP、DOWN。交互逻辑用状态机写最清楚状态表如下当前状态MODE 短按UPDOWNRUN进入 SET_HOUR秒清零无动作无动作SET_HOUR进入 SET_MIN时 1模 24时 −1SET_MIN进入 SET_ALARM_H分 1模 60分 −1SET_ALARM_H进入 SET_ALARM_M闹钟时 1闹钟时 −1SET_ALARM_M返回 RUN写入生效闹钟分 1闹钟分 −1代码骨架typedef enum { ST_RUN 0, ST_SET_HOUR, ST_SET_MIN, ST_SET_ALARM_H, ST_SET_ALARM_M } ui_state_t; static ui_state_t g_state ST_RUN; static rtc_t g_alarm {7, 30, 0}; // 闹钟默认 07:30 static unsigned char g_alarm_en 1; // 每帧调用一次key 为 -1 / 0(MODE) / 1(UP) / 2(DOWN) void ui_step(int key) { rtc_t t rtc_snapshot(); switch (g_state) { case ST_RUN: if (key 0) { t.s 0; rtc_write(t); g_state ST_SET_HOUR; } break; case ST_SET_HOUR: if (key 0) g_state ST_SET_MIN; else if (key 1) { t.h (t.h 1) % 24; rtc_write(t); } else if (key 2) { t.h (t.h 23) % 24; rtc_write(t); } break; case ST_SET_MIN: if (key 0) g_state ST_SET_ALARM_H; else if (key 1) { t.m (t.m 1) % 60; rtc_write(t); } else if (key 2) { t.m (t.m 59) % 60; rtc_write(t); } break; // SET_ALARM_H / SET_ALARM_M 同理改为操作 g_alarm default: if (key 0) g_state ST_RUN; break; } }几个约定值得写进注释进入设置时秒清零这是电子钟的通用做法用户改完时间就是整分开始1和23配合取模实现加减双向且不出现负数比写分支判断干净设置过程中走时不停只是显示的是被修改的副本。闹钟触发判断放在秒变化时做if (g_sec_flag g_alarm_en g_now.s 0 g_now.h g_alarm.h g_now.m g_alarm.m) { buzzer_on(); g_alarm_ringing 1; }用秒变化做触发条件保证一次闹钟只触发一次不会因为主循环跑得快重复响。4.3 Avalon 自定义外设的读写封装自定义外设暴露给软件的只有两个寄存器用IOWR/IORD直接读写封装成函数后主循环看起来就很干净。#include io.h #define SEG_DATA_REG 0 // 偏移 024 位 BCD #define SEG_CTRL_REG 1 // 偏移 1控制位 // 把 rtc 结构体打包成 6 位 BCD 写进显示控制器 static void seg_flush(rtc_t t, unsigned char dot_pos) { unsigned int packed ((unsigned int)(t.h / 10) 20) | ((unsigned int)(t.h % 10) 16) | ((unsigned int)(t.m / 10) 12) | ((unsigned int)(t.m % 10) 8) | ((unsigned int)(t.s / 10) 4) | ((unsigned int)(t.s % 10)); IOWR(SEG_CTRL_0_BASE, SEG_DATA_REG, packed); IOWR(SEG_CTRL_0_BASE, SEG_CTRL_REG, (1u 0) | // 显示使能 ((unsigned int)dot_pos 1) | // 分隔符位置 (0x8u 4)); // 亮度占空比 50% }IOWR(base, offset, data)里的 offset 是 32 位字数不是字节地址这一点新手常错——写IOWR(SEG_CTRL_0_BASE, 1, x)访问的是基地址 4 字节处。seg_flush每次全量刷新整屏六个数字一次性写下去硬件那边扫描模块自己循环取数软件不需要关心中间过程。主循环大致长这样读按键脉冲 → 交给ui_step处理 → 若处于设置状态则闪烁对应位 → 否则按秒变化标志调seg_flush。整个主循环没有延时函数全靠 1ms 中断驱动CPU 占用率很低。提示Nios II 工程编译后如果提示system.h里找不到SEG_CTRL_0_BASE是 SOPC 修改后没有重新生成 BSP右键 BSP 工程执行一次 generate 即可。5. 避坑与排查五个最容易翻车的点这一章的五条基本都是我在别人板子上真实见过的故障。按现象—原因—解决梳理照着查能省掉大半天。5.1 数码管出现重影和亮度不均现象某一位数字比别的亮或者相邻两位的数字有淡淡叠影静止画面看不出来一动就明显。原因换位时没有消隐上一位的段码还在引脚上新位选一打开就把旧数字点到新位置上了。另一种可能是位选驱动能力不足六位扫描时电流被分摊亮的那一位恰好是扫描周期占得多的。解决按 3.1 的做法在位选切换的同一拍把段选清零下一拍再送数据。如果消隐后仍有轻微叠影检查段选线上有没有对地电容过大导致下降沿拖尾必要时降低扫描频率到 200Hz。亮度不均则要确认六路的限流电阻一致、位选三极管都工作在饱和区。5.2 下载 sof 后断电就变砖现象用下载器把 sof 烧进去板子上电一切正常拔掉下载器重新上电屏幕全灭什么反应都没有。原因sof 是 SRAM 对象文件配置信息存在 FPGA 内部的易失性配置存储器里掉电就没了。板上那颗配置芯片EPCS 系列是空的。解决把 sof 转成 jic 文件通过下载器的 Active Serial 模式烧进配置芯片。转换时注意选对配置器件型号和容量烧写完成后把启动模式开关拨到 AS 位置。这一步答辩前一定要做一遍现场演示拔掉下载器更可信。5.3 程序跑飞或卡在 alt_main 不动现象软件下载后没有输出JTAG UART 里一片空白暂停调试发现程序停在某个异常地址。原因排前三的是——片上存储器太小代码段放不下被截断栈溢出局部大数组把栈挤穿中断里调用了不可重入的库函数比如printf浮点格式化把栈用光。解决先看编译输出的 map 文件确认 text 段和 bss 段加起来没超过 onchip_mem 容量栈从默认的 512 字节调到 1KB 以上同时在链接脚本里给堆留 0 或者干脆关掉mallocISR 里禁止任何格式化输出调试信息通过一个变量带出来在主循环打印。这三条查完九成的跑飞都能定位。5.4 走时每天快慢几秒现象跑一晚上第二天早上发现快了五六秒或者一小时就慢一秒。原因如果误差量级在一天几秒大概率是晶振精度决定的属正常范围如果一小时就差一秒约 280ppm那多半是定时器周期值算错了——比如系统时钟实际是 50MHz 但 PLL 配置成了 48MHz或者周期寄存器写成了 49999 却忘了减一。解决先用示波器量 PLL 输出的实际频率确认是 50MHz 整数再把周期值按f_clk / 1000 - 1重新算一遍。确认硬件无误后剩下的误差要靠校准在软件里加一个每日补偿秒数参数或者在硬件上换更高精度的晶振。答辩时主动说清楚误差的来源和量级比含糊带过更有说服力。5.5 按键一次动作触发好几回现象按一下 UP 键时间加了 3 分钟松手时又跳一次。原因软件侧还在按电平判断而不是认边沿或者消抖模块的脉冲没被消费掉PIO 的边沿寄存器没清也可能是按键没有上拉悬空时乱跳。解决硬件侧确认消抖模块输出的是单周期脉冲且软件读一次边沿寄存器就自动清除PIO 的 edgecapture 模式写 1 清零如果消抖做在软件里就在主循环里做 20ms 计数确认且只在上一次是松开、这一次是按下时才触发。松手误触发通常是按键没接上拉电阻检查板上是否有 10k 上拉没有就在代码里打开 FPGA 内部弱上拉。6. 验收与答辩一张测试表和三条讲解主线验收这件事最忌讳现场凭感觉说你看它走得多准。带一张测试表进场逐项打勾比讲十分钟原理都有用。测试项测试方法合格判据基本走时与标准时间同步后连续运行 24 小时日误差 ≤ 5 秒普通晶振量级显示完整性观察 00:00:00 到 23:59:59 全程无缺笔、无重影、无闪烁校时功能分别修改时、分各加减 5 次每次响应一次模值回绕正确校时边界23:59 按 UP 加 1 分正确进位为 00:00闹钟触发设为当前时间后 1 分钟误差 ≤ 1 秒响铃可按键停止整点报时观察 12:00:00、13:00:00准点响且只响一次掉电重启断电 10 秒后上电显示恢复无花屏、无卡死长时间稳定性连续通电 8 小时无死机、无显示漂移答辩讲三件事就够了别贪多。第一件是软硬划分的理由。老师最常问的就是为什么这部分不做进 FPGA。把 2.1 那张表搬出来重点讲数码管扫描如果放软件里中断执行时间会拉长到几十微秒主循环一忙就丢拍这个因果链比背概念管用。第二件是地址映射和中断流程。拿出 SOPC 的地址分配表和system.h里的宏对照说明 CPU 是怎么通过 Avalon 总线一次 32 位写把六个数字送进自定义外设的。如果能把 SignalTap 抓到的位选扫描波形和总线写时序放在一页 PPT 上说服力会强很多。第三件是误差分析。主动说出晶振精度决定的 4.3 秒/天上限再说清楚哪些是自己代码能控制的、哪些是物理限制。这一步是拉开档次的地方——大部分人会做但很少有人能说清自己的钟为什么是这个精度。演示顺序也有讲究先通电看走时再演示校时然后设闹钟等它响最后拔掉下载器重启一次。拔线这个动作一定要做前面 5.2 那条坑我踩过一次现场被追问拔了还能不能跑的时候那种尴尬至今记得。从那以后我每次做带软核的板子都强制把生成 jic、烧配置芯片、断电重启这一步放进验收流程里不管多赶时间都不跳过。这套工程从系统搭建、Verilog 模块到 C 代码的结构都留得比较完整需要的可以拿去对着自己的板子改引脚和地址改完直接就能跑起来。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →