1. 项目概述
这个基于FPGA的数字时钟项目,是我最近在橙月FPGA开发板上实现的一个实用小作品。它通过IIC接口驱动0.96寸OLED显示屏,显示完整的星期、小时、分钟和秒钟信息。板载的四个按键分别用于调整天数、小时和分钟(包括增减),当接近整点时,LED灯会闪烁提醒,非常实用。
我选择这个项目是因为它涵盖了FPGA开发的多个核心技能点:时钟分频、状态机设计、IIC总线协议实现、按键消抖处理以及OLED显示控制。整个系统采用模块化设计,每个功能都有对应的Verilog模块实现,结构清晰,便于理解和扩展。
2. 硬件平台搭建
2.1 开发板选型与配置
我使用的是橙月FPGA开发板,主控芯片为Intel Cyclone IV E系列的EP4CE6E22C8N。这款FPGA具有以下特点:
- 6272个逻辑单元(LE)
- 270Kbits嵌入式存储器
- 2个通用PLL
- 最大用户I/O数量:91个
这个配置对于数字时钟项目来说绰绰有余,实际上只使用了不到10%的逻辑资源。开发板还配备了丰富的周边接口:
- 摄像头接口
- VGA接口
- SDRAM芯片
- 4个独立按键
- 多个LED指示灯
- OLED显示屏接口
提示:选择开发板时,确保其I/O电压与OLED模块兼容。本项目中OLED支持3.3V和5V供电,而橙月开发板的I/O电压为3.3V,完美匹配。
2.2 OLED显示屏规格
项目使用的OLED模块关键参数:
- 尺寸:0.96英寸
- 接口类型:IIC(4线制)
- 分辨率:128×64
- 驱动芯片:SSD1306
- 供电电压:3.3V/5V
- 可视角度:>160°
这种OLED屏功耗极低,显示清晰,且不需要背光,非常适合嵌入式应用。IIC接口只需要两根信号线(SCL和SDA)就能实现通信,大大节省了FPGA的I/O资源。
3. 系统架构设计
3.1 整体模块划分
整个数字时钟系统由9个主要功能模块组成,各模块之间的关系如下图所示:
code复制[时钟源50MHz] → [1s计数模块] → [计时模块] ← [按键消抖模块]
↓
[IIC主控] ← [显示控制模块] ← [字符数据模块]
↓
[OLED显示屏]
3.2 关键模块功能详解
3.2.1 IIC总线驱动模块(I2C_Master.v)
这是整个系统的通信基础,实现了标准的IIC协议。主要功能包括:
- 起始条件(START)生成
- 停止条件(STOP)生成
- 数据字节发送/接收
- 应答(ACK)检测
- 时钟拉伸支持
在实际编写时,我采用了三段式状态机设计:
- 空闲状态:等待传输请求
- 数据传输状态:按位发送/接收数据
- 结束状态:生成停止条件或准备下一次传输
注意:IIC时钟频率设置为400KHz(快速模式),需要在SCL高低电平间插入适当延时,确保信号稳定。
3.2.2 初始化模块(Oled_Init.v)
OLED屏幕在使用前必须进行正确的初始化。这个模块按照SSD1306数据手册的要求,依次发送以下配置命令:
- 显示关闭命令(0xAE)
- 设置时钟分频和振荡频率(0xD5)
- 设置多路复用比例(0xA8)
- 设置显示偏移(0xD3)
- 设置显示起始行(0x40)
- 设置充电泵(0x8D)
- 设置内存地址模式(0x20)
- 设置列地址(0x21)
- 设置页地址(0x22)
- COM扫描方向设置(0xC8)
- 对比度设置(0x81)
- 预充电周期设置(0xD9)
- VCOMH反压设置(0xDB)
- 整个显示开启(0xA4)
- 非反相显示(0xA6)
- 显示开启命令(0xAF)
3.2.3 计时模块(clock_timer.v)
这是数字时钟的核心逻辑模块,主要功能包括:
- 秒、分、时、星期的计数和进位
- 按键校时功能处理
- 整点报时触发
计时逻辑采用层次化计数器结构:
verilog复制always @(posedge clk or posedge rst) begin
if(rst) begin
second <= 0;
minute <= 0;
hour <= 0;
day <= 0;
end
else if(one_sec_pulse) begin
if(second == 59) begin
second <= 0;
if(minute == 59) begin
minute <= 0;
if(hour == 23) begin
hour <= 0;
day <= day + 1;
if(day == 7) day <= 0;
end
else hour <= hour + 1;
end
else minute <= minute + 1;
end
else second <= second + 1;
end
end
4. 关键实现细节
4.1 时钟分频设计
开发板提供的系统时钟为50MHz,而我们需要1Hz的秒信号。clock_divide.v模块实现了这个功能:
verilog复制module clock_divide(
input clk, // 50MHz时钟输入
input rst, // 复位信号
output reg one_sec_pulse // 1秒脉冲输出
);
reg [25:0] counter; // 26位计数器,可计数到67,108,863
always @(posedge clk or posedge rst) begin
if(rst) begin
counter <= 0;
one_sec_pulse <= 0;
end
else begin
if(counter == 49_999_999) begin // 50,000,000个周期=1秒
counter <= 0;
one_sec_pulse <= 1;
end
else begin
counter <= counter + 1;
one_sec_pulse <= 0;
end
end
end
endmodule
4.2 按键消抖处理
机械按键在按下和释放时会产生抖动,通常持续10-20ms。key_filter.v模块采用状态机+计时器的方式实现消抖:
verilog复制module key_filter(
input clk, // 50MHz时钟
input key_in, // 原始按键输入
output reg key_out // 消抖后输出
);
reg [19:0] cnt; // 20位计数器,1ms计时
reg key_reg; // 按键状态寄存器
reg state; // 状态机状态
always @(posedge clk) begin
case(state)
0: begin // 等待按键按下
if(key_in != key_reg) begin
state <= 1;
cnt <= 0;
end
end
1: begin // 消抖计时
if(cnt == 49_999) begin // 1ms计时
cnt <= 0;
if(key_in != key_reg) begin
key_reg <= key_in;
key_out <= ~key_out;
end
state <= 0;
end
else cnt <= cnt + 1;
end
endcase
end
endmodule
4.3 OLED字符显示原理
OLED显示基于预先取模的字库数据。我们使用PCtoLCD2002等取模软件生成字符点阵数据,存储在DClock_font_data.v模块中。
显示过程分为三步:
- 显示控制模块(Oled_DClock_control.v)确定要显示的字符及其位置
- 字符数据模块提供对应的点阵数据
- IIC主控将这些数据写入OLED的GRAM
例如,显示数字"0"(16×16点阵)的Verilog代码片段:
verilog复制case(char_index)
0: char_data = {8'h00,8'hE0,8'h10,8'h08,8'h08,8'h10,8'hE0,8'h00,
8'h00,8'h0F,8'h10,8'h20,8'h20,8'h10,8'h0F,8'h00}; //"0"
// 其他字符定义...
endcase
5. 系统调试与优化
5.1 常见问题排查
在实际调试过程中,我遇到了几个典型问题:
-
OLED不显示或显示乱码
- 检查IIC总线是否正常:用逻辑分析仪抓取SCL和SDA信号
- 确认初始化序列是否正确发送
- 检查电源电压是否稳定
-
时间走时不准
- 验证1秒定时是否准确:用示波器测量one_sec_pulse信号
- 检查计数器进位逻辑是否正确
- 考虑使用更精确的时钟源(如外部晶振)
-
按键响应不灵敏
- 调整消抖时间参数(通常10-20ms)
- 检查按键硬件连接是否可靠
- 确保按键中断优先级设置合理
5.2 性能优化技巧
通过实践,我总结出几点优化经验:
-
资源优化
- 共享计数器:多个模块可以共用同一个分频计数器
- 状态机编码:使用独热码(one-hot)提高时序性能
- 合理使用流水线:平衡速度和资源消耗
-
功耗优化
- 不使用的模块时钟门控
- 降低IIC总线频率(在不影响显示效果的前提下)
- 优化OLED刷新策略,减少不必要的数据传输
-
代码可维护性
- 参数化设计:使用
parameter定义常量 - 模块化设计:功能独立,接口清晰
- 充分注释:特别是状态机和时序逻辑部分
- 参数化设计:使用
6. 功能扩展思路
这个基础数字时钟还有很大的扩展空间:
-
增加闹钟功能
- 添加闹钟时间设置
- 实现多种提醒方式(蜂鸣器、LED闪烁等)
-
温度显示
- 集成DS18B20温度传感器
- 在OLED上同时显示时间和环境温度
-
蓝牙/WiFi连接
- 添加无线模块实现时间自动校准
- 支持手机APP远程控制
-
多界面切换
- 设计多个显示界面(时间、日期、温度等)
- 通过按键切换不同显示模式
-
低功耗设计
- 实现自动亮度调节
- 加入休眠/唤醒功能
在实际项目中,我尝试添加了温度显示功能,需要额外开发DS18B20的驱动模块,并在显示控制模块中增加温度显示区域的处理逻辑。这个扩展使系统更加实用,同时也验证了原始架构的良好可扩展性。