
简介一份基于SOPC的数字电子时钟课设完整工程资料以DE2-115开发板为验证平台使用Quartus II与NIOS II软件环境硬件语言采用Verilog面向电子信息类本科生FPGA课程设计与SOPC项目实践。系统实现了数码管、LCD、VGA大屏三种显示方式支持时钟、倒计时、闹钟功能计时精度达到1秒可在软件中直接运行。资料内含课设报告与完整可运行工程方便对照学习和验证。资源共4175个文件压缩包约50.29MB核心文件包括Verilog源文件.v、Quartus工程配置文件.qpf/.qsf、SOPC系统描述文件、NIOS II软件C/C代码、FPGA配置文件.sof、存储器初始化文件.mif以及课设报告.docx同时保留了编译中间文件便于还原完整工程结构。目前已有1793人学习/下载。借助该工程可系统学习SOPC系统集成、Verilog逻辑设计、NIOS II软核处理器编程以及数码管、LCD、VGA显示驱动的整体实现思路涵盖从系统搭建、软核配置到显示驱动调试的完整流程对完成同类课设和入门FPGA开发有直接参考价值。 看到“基于SOPC的数字电子时钟设计.rar”这个标题我第一反应就是又有人在为FPGA课设和电子设计竞赛挠头了。这个项目在高校的EDA/FPGA课程里算是常青树网上资源一大把但真正把SOPC方案讲透的并不多。SOPC全称System On Programmable Chip通俗讲就是把CPU、存储器、定时器、I/O接口这些原本摆在电路板上的东西一股脑塞进一颗FPGA芯片里然后用C语言写应用逻辑而不是拿Verilog/VHDL去堆状态机。本篇就以Nios II软核处理器加Qsys工具的方案把这个数字电子时钟从需求拆解、硬件搭建、软件实现到调试踩坑完整走一遍。1. 数字电子时钟的需求拆解与SOPC方案选型1.1 课设项目到底在考什么数字电子时钟听着简单实际拆开来看它至少包含了三个相互独立又需要协同的功能模块计时功能时、分、秒的实时递增满60进1、满24归零。显示功能至少6位数字管时/分/秒各两位需要解决动态扫描刷新问题。人机交互按键校时至少要有“模式切换”和“加一”两个按键。如果只用纯逻辑去实现这三件事都得写进RTL代码里。时间计数用分频器和计数器没问题但“校时”这种交互逻辑用状态机写起来会比较啰嗦尤其是“长按连续加”这类人性化功能纯状态机实现会相当痛苦。SOPC方案把“控制逻辑”交给Nios II软核处理器用C语言写状态机、写按键消抖、写扫描刷新开发效率完全不在一个量级。1.2 为什么选Nios II软核而不是纯Verilog我见过不少同学纠结要不要上软核觉得多此一举。这里直接说结论题目明确写了SOPC那就绕不开软核方案。从学习角度讲SOPC设计要掌握的技能更多包括Qsys组件配置、总线互联、外设地址映射、中断服务程序编写、软件驱动开发这些恰好是“嵌入式FPGA”两条线的交叉点。从实际工作量讲纯HDL方案的时间计数和消抖逻辑都不难难的是后续如果还要加闹钟、加秒表、加温度显示纯逻辑代码量会像滚雪球一样膨胀。而基于Nios II的软件架构下新增一个功能往往就是新增一个C函数的事。使用软核还能用上串口打印调试这对找bug的价值极大——用SignalTap去抠按键抖动问题比对着一行printf找问题慢多了。1.3 系统总体框架整个系统的数据流大概是这样的FPGA内部的Nios II软核通过Avalon总线挂接若干外设。一个Interval Timer定时器作为系统心跳产生固定周期中断驱动时间计数与显示刷新一组PIO输出端口控制数码管的段码和位选一组PIO输入端口接三个按键用于模式切换和数值调整。外部时钟源由开发板上的50MHz有源晶振提供经PLL倍频或直接作为系统时钟。时间信息保存在软件全局变量中由中断服务程序更新主循环负责响应按键并刷新显示。2. 硬件环境准备与Qsys组件配置要点2.1 开发板选型与工程建立SOPC实验普遍使用Altera/Intel系列的Cyclone IV芯片比如EP4CE6F17C8这种经典型号。很多学校的口袋实验板、DE2-115开发板都是这类芯片。初期设计无所谓具体板子型号因为Nios II软核本身就是“可裁剪”的板子上缺什么外设我就在软核里加什么IP硬件接口反而不像单片机选型那样锁死。工程建议直接使用Quartus II13.0以上版本就够用建一个空的顶层文件再打开Qsys老版本叫SOPC Builder搭建处理器子系统。顶层模块例化一个名为cpu_system的Qsys生成模块即可。这里有个新手经常踩的坑Quartus的顶层文件名必须与工程名一致否则综合会报错。2.2 Qsys组件清单与参数配置下面是我在Qsys里添加的组件及配置参数直接抄即可组件名实例名关键参数Nios II ProcessorcpuNios II/e经济型HW乘法器可选JTAG调试级别On-Chip Memoryram64KB类型RAM初始化文件可留空Interval Timertimer_0全功能模式计数器位宽32位初始周期500000PIO输出seg_out位宽8位连接数码管段码PIO输出sel_out位宽6位连接数码管位选PIO输入key_in位宽3位输入捕捉生成下降沿中断System IDsysid默认即可UART可选uart_debug波特率115200用于printf调试时钟方面直接使用50MHz外部时钟接入clock桥不加PLL也完全够用。Nios II/e软核在50MHz下跑这个任务毫无压力。定时器周期500000这个数字是有讲究的50MHz时钟下计满500000个周期正好是1秒。但更合理的做法是让定时器产生10ms中断用软件计数器累计100次得到1秒好处是显示扫描和计时共用同一个中断源逻辑更统一。我实际采用的是10ms方案定时器周期值设为500000是1ms方案若用10ms方案则设为5000000注意32位计数器完全承载得了。2.3 引脚分配与硬件连接Qsys配置完成后要在Quartus里做引脚分配。数码管的段码一般是8位含小数点接开发板的SEG0到SEG7。位选只有6位接数码管位选引脚。三个按键分别接入KEY0、KEY1、KEY2。引脚分配有个容易出问题的点很多开发板上的数码管位选经过了三极管或译码芯片驱动这意味着软件输出的逻辑电平与实际“选中哪一位”之间可能不是简单对应关系。比如位选为高电平时经反相驱动后实际点亮的是低电平的那一位。这种硬件极性差异直接看原理图最稳妥否则显示顺序会乱掉。段码也有共阴共阳之分共阴极数码管要输出高电平点亮段共阳极则要输出低电平点亮段。段码表需要在初始化阶段确认好否则数字显示会“缺胳膊少腿”。3. 软件架构与核心代码实现3.1 工程创建与BSP设置在Quartus里完成全编译后用Qsys导出的SOPC信息文件打开Nios II SBT for Eclipse。新建Software Application工程时选Blank ProjectBSP设置里指定好硬件配置文件即可。如果用UART调试记得在BSP的Linker Script里确认内存地址映射正确。工程里只需要一个main.c文件代码量控制在400行以内结构清晰。BSP里有一项启动模式设置需要注意如果最终要烧进FPGA配置芯片里独立运行需要在BSP Settings里将stdout/stderr指向uart启动loader选择epcs_flash_controller如果只是临时下载RAM运行保持默认就行。很多同学的板子断电后程序丢失就是因为用的是RAM运行模式代码没固化到EPCS里面。3.2 中断服务程序与时间计数逻辑时间计数是整个时钟的心脏我的常用做法是维护一个包含时、分、秒三个字段的结构体再加上一个毫秒级的累计变量全部定义成volatile全局变量因为中断和主循环都会访问它们。定时器中断每10ms触发一次。中断服务程序里做三件事毫秒计数值加10当计数值累加满1000毫秒时执行一次秒递增每次中断置一个display_update标志位通知主循环刷新数码管。伪代码如下static volatile unsigned int tick_10ms 0; static volatile unsigned char time_stamp 0; /* bit0秒位变化标志 */ void timer_0_isr() { IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_0_BASE, 0); tick_10ms 10; if (tick_10ms 1000) { tick_10ms 0; update_clock(); /* 秒1并处理分、时进位 */ time_stamp 1; } display_update 1; } void update_clock() { sec; if (sec 60) { sec 0; min; if (min 60) { min 0; hour; if (hour 24) hour 0; } } }这段逻辑的关键点是sec、min、hour三个变量在中断里被修改主循环读取显示时会可能读到“半更新”的状态。单字节读操作在Nios II上是原子的所以只要数据结构简单不用关中断也能安全访问。但如果把时间封装成结构体读取时就需要临时关闭中断再读否则可能出现秒加完分还没加的中间态。3.3 数码管动态扫描与段码表主循环里读取display_update标志位然后执行一次完整的扫描刷新。动态扫描的原理是利用人眼视觉暂留同一时刻只点亮一位数码管轮流点亮6位刷新频率大于50Hz就看不到闪烁。扫描时钟由主循环循环执行构成每个位保持约1到2ms。为了保证刷新率稳定我在扫描函数里也做了简单的软件延时。段码表可以预定义成数组const unsigned char SEG_CODE[10] { 0x3F, /* 0 */ 0x06, /* 1 */ 0x5B, /* 2 */ 0x4F, /* 3 */ 0x66, /* 4 */ 0x6D, /* 5 */ 0x7D, /* 6 */ 0x07, /* 7 */ 0x7F, /* 8 */ 0x6F /* 9 */ };扫描输出时把当前时间的每一位数字拆分出来映射成段码再选中对应位选。需要注意扫描函数如果放在主循环里做软件延时按键检测的实时性会受影响。我的做法是按键检测也放在扫描循环里每次扫描间隙顺便扫一遍按键状态这样自然就实现了分时复用不需要额外调度器。3.4 按键校时与消抖处理三个按键的规划KEY0为模式切换正常/秒校/分校/时校四个状态KEY1为加一KEY2为减一或复位。按键输入接的是PIO输入口读取时需消抖。软件消抖很简单检测到低电平后延时20ms再确认一次确认有效才执行操作。校时状态切换的关键是“不要在按键按下时反复切换”需要检测按键的下降沿而不是电平值。代码如下void key_scan() { unsigned char key_raw IORD_ALTERA_AVALON_PIO_DATA(KEY_IN_BASE); static unsigned char key_last 0xFF; unsigned char key_pressed key_raw ~key_last; key_last key_raw; if (key_pressed 0x01) { mode (mode 1) % 4; } if (key_pressed 0x02) { if (mode 1) { sec 0; } else if (mode 2) { min; if (min 60) min 0; } else if (mode 3) { hour; if (hour 24) hour 0; } } }这里用key_pressed key_raw ~key_last来捕捉下降沿既是按下瞬间有效避免了长按导致的连续触发。延时消抖则依赖主循环的扫描节奏实测下来效果稳定。如果要求更严格可以把按键PIO配置成中断模式在中断里做消抖但对本设计来说轮询足够。4. 常见问题与调试技巧实录4.1 数码管显示乱码、缺笔画、亮度不均的排查这类问题的概率最高。乱码基本是段码表极性写反了共阳极板子用了共阴极段码。缺笔画则是段码值不对比如数字6的0x7D和数字9的0x6F在市面上存在两种定义必须参考板子的原理图确认。亮度不均通常是因为扫描周期太长或太短单个位点亮时间最好控制在1毫秒左右小于0.5毫秒亮度会明显偏低大于3毫秒则能看到闪烁。还有个大坑是位选扫描顺序和PCB布线顺序不一致。明明写了低位先扫结果显示顺序是反的这不是代码问题是位选PIO的接线顺序可能与数码管物理位置不对应。用单个位轮流点亮的测试程序跑一遍把实际点亮顺序记录下来改数组下标匹配即可。4.2 计时不准、越走越快的纠正方法数字电子时钟的时间基准完全取决于定时器中断的精确度而定时器又依赖系统时钟频率。开发板上的50MHz晶振虽然有精度误差但短时间观察不出来真正让时钟变快的大多是定时器周期设置错误。比如系统时钟50MHz想要10ms中断定时器计数周期应该是50,000,000 × 0.01 500,000很多同学在这里少算了一个0导致中断频率变成100Hz而不是1000Hz时钟自然狂奔。另外中断服务程序里的处理时间也会影响实际周期。Nios II/e处理一条分支跳转指令大约需要几十纳秒相比10ms的中断周期可以忽略不计但如果中断里调用了printf或者复杂的除法运算开销就会变大。测量方法很简单让一个PIO引脚在中断里翻转用示波器或逻辑分析仪看翻转频率能直接算出来实际中断周期是否标准。4.3 下载程序后正常运行断电重启程序消失这个问题几乎每届学生都会遇到。程序正常下载能跑说明逻辑和软件都没问题掉电没程序是因为你把程序下载到了FPGA的RAM里RAM上电即失忆。要解决需要在Qsys里添加EPCS Serial Flash Controller编译后通过Nios II的Flash Programmer工具将软件代码烧写进EPCS芯片。硬件配置里还要把FPGA的配置模式设为Active Serial。这个步骤比较繁琐但做一次就能看到掉电保存的效果建议提前准备好。4.4 调试经验分享整套系统里最好用的调试工具其实是串口。早期搭建时不要急着写数码管扫描先把UART调通用printf往电脑上打印时间计数值确认进位逻辑正确后再写显示。串口打印的时间和数码管显示时间不一致时很容易判断出是显示模块的问题还是计时模块的问题。中断里不要加printf主循环里打印就好。SignalTap II逻辑分析仪也很有用。出现按键失灵这类诡异问题时拉出按键PIO的波形看看能直观地看到按键抖动波形从而判断是硬件电路问题还是软件消抖不足。实测发现很多开发板的按键接口已经加了RC滤波电路这种情况下10ms的软件消抖就足够没必要做20ms省下的时间让按键手感更跟手。4.5 常见问题速查表现象可能原因解决办法显示乱码段码极性不匹配确认共阴/共阳改段码表数字循环点亮但顺序错乱位选接线顺序与软件扫描顺序不一致单独位选测试程序修正扫描数组秒走时明显偏快定时器周期计算错误核对系统频率用示波器验证中断频率按按键无反应PIO位宽配置错误或消抖延时不足检查Qsys里的PIO位宽加大消抖延时下载后掉电程序丢失未烧写至EPCS添加EPCS控制器使用Flash Programmer烧写数码管亮度不均匀扫描周期不一致在扫描函数中加入固定延时确保每位停留时间一致5. 项目复盘与可扩展方向做完这个项目个人最深的感受是SOPC设计真正的难点不在“写代码”而在“搭系统”。从裸机逻辑设计思维切换到“硬件为软件服务”的思维需要一段适应期。刚开始我习惯性地想在硬件里把所有显示逻辑做掉后来才意识到有了Nios II软核大部分外设逻辑都可以软件化硬件设计只需提供足够数量的PIO口、定时器和内存即可。这也正是SOPC的核心思想——硬件平台化功能软件化。实际测试中Nios II/e内核主频50MHz跑这个应用绰绰有余CPU占用率目测不到20%。如果把定时器中断、按键扫描、数码管刷新全部做成独立模块后续扩展非常灵活。比如现在想加一个闹钟功能只需要在软件里增加一个时间比较逻辑让蜂鸣器PIO在匹配时翻转想加秒表功能就在定时器中断里维护一个独立的毫秒计数值想加温度显示加一个DS18B20的一线式驱动同样是软件层面的事。整个系统的扩展成本低得惊人。最后再分享一个小技巧如果你手头有一块带SD卡槽或SDRAM的开发板可以考虑把On-Chip Memory换成SDRAM控制器再把Nios II升级成Nios II/s标准型运行速度提升明显跑简单操作系统都不成问题。数字电子时钟只是SOPC的入门敲门砖套路熟悉之后用它做智能小车、图像采集、网络通信这类更复杂的项目才能真正体会到这套工具链的价值。本文还有配套的精品资源点击获取