拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Verilog手写I²C主机驱动PCF8563实现实时时钟系统

简介本资源是一套基于FPGA实现的高精度实时时钟RTC系统完整工程面向数字电路、嵌入式硬件及FPGA初学者与课程设计者解决传统单片机RTC方案扩展性弱、时序调试难的问题。工程采用NXP PCF8563专用RTC芯片通过I²C总线与FPGA通信配合Verilog编写的驱动模块、BCD数码管译码显示控制及按键消抖逻辑实现年月日时分秒的本地化实时显示与初始时间配置功能适配Quartus 18.0开发环境可直接编译下载至主流Cyclone IV/V开发板验证。压缩包共124个文件含核心源码.v、综合/布局布线数据库.hdb/.cdb/.ddb、编译日志.qmsg/.rpt、约束文件.qsf、烧录镜像.sof/.jic及说明文档.txt/.readme整体3.76MB结构规范、模块解耦清晰。目前已有382人学习下载读者可直接复用PCF8563驱动、I²C执行控制状态机、七段数码管动态扫描等关键模块快速构建自主RTC应用系统。1. 用 Verilog 在 Quartus 18.0 里驱动 PCF8563 实现带数码管显示的独立 RTC 系统不是“调个 IP 核”就能跑通的事你手上有块 Cyclone IV 或 V 的开发板想加一个掉电不丢时间的实时时钟又不想依赖单片机协处理器——这时候 PCF8563 是极务实的选择I²C 接口简单、内置晶振补偿、功耗低至 200nA且支持秒/分/时/日/月/年/星期全字段读写。但问题来了Verilog 不是 C没有rtc.read_time()这种函数Quartus 18.0 虽然兼容老器件但其自带 I²C IP 核默认不支持 PCF8563 的寄存器映射与起始地址0x51更不处理其特有的“世纪位”和“闰年自动修正”逻辑而数码管动态扫描若直接用计数器硬怼极易因时序竞争导致闪烁或错码。本工程不是“把例程改个地址就烧录”它完整覆盖了从 I²C 主机状态机设计、PCF8563 寄存器解析协议、BCD-十进制转换、7 段译码查表、共阴/共阳极性适配到 Quartus 18.0 下 pin assignment 约束与 timing closure 的闭环路径。适合 FPGA 初学者走通全流程也值得数字 IC 工程师复用其 I²C 时序鲁棒性设计——尤其当你的板级 I²C 总线存在 10cm 以上走线或多个从设备时。2. 用 Verilog 手写 I²C 主机控制器精准匹配 PCF8563 的 100kHz 时序与寄存器访问协议PCF8563 的 I²C 通信看似标准实则暗藏三处关键非标点第一其内部寄存器地址空间为 0x00–0x0F 共 16 字节但读写操作必须使用“当前地址指针自动递增”模式即写入起始地址后连续读取无需重复发送地址第二其秒寄存器0x02第 7 位为“STOP”标志位清零才启动计时而多数通用 I²C IP 核默认忽略该位第三年寄存器0x08仅存 8 位 BCD 值00–99需外部逻辑判断是否跨世纪并补全 20/21 前缀。因此直接套用 Quartus 自带的i2c_masterIP 会读出全 0 或乱码。我们采用纯 RTL 方式实现主机控制器核心是四级状态机IDLE → START → ADDR → DATA每步严格对齐 PCF8563 数据手册中 tSU:STA4.7μs、tHD:STA4.0μs、tLOW4.7μs、tHIGH4.0μs等参数。2.1 四相状态机与 SCL/SCL 同步采样逻辑I²C 时钟由系统时钟分频生成。假设开发板晶振为 50MHz目标 SCL 频率 100kHz则分频系数为 250。但注意PCF8563 要求 SCL 低电平时间 ≥4.7μs对应 23.5 个 50MHz 周期高电平时间 ≥4.0μs20 个周期故实际分频应设为CNT_MAX 24低20高 44再加 1 作计数偏移// i2c_controller.v localparam CLK_DIV 24; // SCL low time: (CLK_DIV1)*20ns 500ns * 24 12μs 4.7μs localparam CLK_DIV_HIGH 20; // SCL high time: (CLK_DIV_HIGH1)*20ns 420ns * 20 8.4μs 4.0μs always (posedge clk or negedge rst_n) begin if (!rst_n) begin scl_cnt 0; scl_state 0; end else if (scl_en) begin if (scl_state 0) begin // SCL low if (scl_cnt CLK_DIV) scl_cnt scl_cnt 1; else begin scl_cnt 0; scl_state 1; end end else begin // SCL high if (scl_cnt CLK_DIV_HIGH) scl_cnt scl_cnt 1; else begin scl_cnt 0; scl_state 0; end end end end提示此处scl_en由主状态机在ADDR和DATA阶段置高确保 SCL 仅在数据传输时振荡。若直接用assign scl ~scl_state会导致空闲时 SCL 毛刺PCF8563 可能误判为 START 条件。2.2 地址帧与数据帧的字节级握手控制PCF8563 的写操作流程为START → SLAW(0xA2) → ACK → REG_ADDR(0x02) → ACK → DATA_BYTE → ACK → STOP。其中 SLAW 固定为 8b101000100x511 | 0而寄存器地址必须为单字节如写秒寄存器填 0x02。关键在于 ACK 检测I²C 规范要求主设备释放 SDA 后在 SCL 第 9 个时钟上升沿采样 SDA若为低则从机应答。我们的实现中ack_detect信号在SCL上升沿锁存sda_in并在下一个SCL下降沿触发状态跳转// 在 SCL 上升沿采样 SDA always (posedge scl or negedge rst_n) begin if (!rst_n) ack_sample 1b1; else if (scl_rise sda_en) ack_sample sda_in; // sda_en 在第9个SCL周期使能 end // 在 SCL 下降沿判断 ACK 并推进状态 always (negedge scl or negedge rst_n) begin if (!rst_n) state IDLE; else case(state) IDLE: if(start_cond) state START; START: if(ack_ok) state ADDR; // ack_ok (ack_sample 1b0) ADDR: if(ack_ok) state DATA; DATA: if(stop_cond) state IDLE; endcase end2.2.1 PCF8563 特有的 STOP 位清除与时间启动逻辑很多初学者烧录后发现数码管时间不动根源就在秒寄存器0x02的 bit7。该位为 1 时芯片停止计时必须显式写 0 才启动。因此初始化序列不能只写时间值必须包含一次“写 0x02 → 0x00”的操作// rtc_init_seq.v —— 必须在首次配置前执行 always (posedge clk) begin if (init_done 0) begin case(init_step) 0: begin i2c_addr 8h51; i2c_reg 8h02; i2c_data 8h00; i2c_wr 1; init_step 1; end // 清 STOP 1: begin i2c_addr 8h51; i2c_reg 8h03; i2c_data 8h30; i2c_wr 1; init_step 2; end // 设分 48 2: begin i2c_addr 8h51; i2c_reg 8h04; i2c_data 8h12; i2c_wr 1; init_step 3; end // 设时 18 3: begin i2c_addr 8h51; i2c_reg 8h05; i2c_data 8h05; i2c_wr 1; init_step 4; end // 设日 5 4: begin i2c_addr 8h51; i2c_reg 8h06; i2c_data 8h03; i2c_wr 1; init_step 5; end // 设月 3 5: begin i2c_addr 8h51; i2c_reg 8h08; i2c_data 8h24; i2c_wr 1; init_step 6; end // 设年 24 (2024) 6: init_done 1; // 启动完成 endcase end end注意i2c_wr为高时触发写操作i2c_addr是 7 位从机地址0x51i2c_reg是目标寄存器地址i2c_data是待写入值。此序列必须在系统上电后一次性执行不可重复——否则会覆盖当前时间。3. 数码管动态扫描与 BCD 解析把 PCF8563 的 BCD 时间码转成稳定无闪烁的 6 位显示PCF8563 所有时间寄存器均以 BCD 格式存储如 0x12 表示十进制 18而数码管段码驱动需要纯二进制数值查表。若直接用seg7[3:0] bcd_val[3:0]会因高位 BCD如 0x24 的 2 和 4未分离导致显示错乱。更严峻的是动态扫描6 位数码管需 6 路位选信号DIG0–DIG5每路点亮时间需 ≥1ms 才肉眼不觉闪烁但总刷新周期又不能超过 20ms50Hz否则有残影。这就要求精确的 6 分频与同步更新机制。3.1 BCD 拆分与七段译码查表PCF8563 的小时寄存器0x04为 2 位 BCD范围 0x00–0x23。需先分离十位与个位// bcd_decode.v wire [3:0] hour_bcd rtc_hour_reg; // from i2c read, e.g., 0x18 wire [3:0] hour_ten hour_bcd[7:4]; // high nibble wire [3:0] hour_one hour_bcd[3:0]; // low nibble // 查表生成段码共阴极a-g 对应 seg[6:0] always (*) begin case(hour_one) 4h0: seg_code 7b1111110; // 0 4h1: seg_code 7b0110000; // 1 4h2: seg_code 7b1101101; // 2 4h3: seg_code 7b1111001; // 3 4h4: seg_code 7b0110011; // 4 4h5: seg_code 7b1011011; // 5 4h6: seg_code 7b1011111; // 6 4h7: seg_code 7b1110000; // 7 4h8: seg_code 7b1111111; // 8 4h9: seg_code 7b1111011; // 9 default: seg_code 7b0000000; endcase end提示hour_ten同理处理但注意 PCF8563 小时为 24 小时制hour_ten最大为 20x23故只需支持 0–2 三个值。若误将hour_ten当作 0–9 处理会导致 20:xx 显示为 00:xx。3.2 6 位动态扫描时序与消隐控制6 位数码管需 6 路位选DIG0–DIG5对应显示小时十位、小时个位、冒号、分钟十位、分钟个位、秒十位秒个位常省略以节省资源。扫描频率设为 1kHz周期 1ms由 50MHz 时钟分频得// scan_ctrl.v reg [15:0] scan_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) scan_cnt 0; else scan_cnt scan_cnt 1; end wire scan_clk (scan_cnt 16d49999); // 50MHz / 50000 1kHz reg [2:0] digit_sel; always (posedge clk) begin if (scan_clk) digit_sel digit_sel 1; end // 位选信号低电平有效共阴极 assign dig0 (digit_sel 3b000) ? 1b0 : 1b1; assign dig1 (digit_sel 3b001) ? 1b0 : 1b1; assign dig2 (digit_sel 3b010) ? 1b0 : 1b1; assign dig3 (digit_sel 3b011) ? 1b0 : 1b1; assign dig4 (digit_sel 3b100) ? 1b0 : 1b1; assign dig5 (digit_sel 3b101) ? 1b0 : 1b1; // 段码输出根据 digit_sel 选择对应 BCD 值查表 always (*) begin case(digit_sel) 3b000: seg_out seg_code_hour_ten; 3b001: seg_out seg_code_hour_one; 3b010: seg_out 7b0100000; // colon: only g lit for dot 3b011: seg_out seg_code_min_ten; 3b100: seg_out seg_code_min_one; 3b101: seg_out seg_code_sec_ten; default: seg_out 7b0000000; endcase end3.2.1 冒号闪烁与消隐技巧为增强可读性常让冒号以 1Hz 频率闪烁。可在scan_clk基础上再分频reg [24:0] blink_cnt; always (posedge clk) begin if (scan_clk) blink_cnt blink_cnt 1; end wire blink_on (blink_cnt[24]) ? 1b1 : 1b0; // 0.5Hz toggle // 修改冒号段码仅当 blink_on 为高时点亮 assign seg_colon blink_on ? 7b0100000 : 7b0000000;注意若直接用assign seg_colon {6{blink_on}}会同时点亮所有段必须指定仅g段对应小数点为 1其余为 0。4. Quartus 18.0 工程配置与引脚约束确保 I²C 和数码管在 Cyclone IV 上可靠工作Quartus 18.0 对 Cyclone IV E 的支持已进入维护期其 Pin Planner 默认不启用开漏Open-Drain模式而 I²C 的 SDA/SCL 必须为开漏输出上拉电阻。若忽略此点直接 assign 引脚会导致总线无法释放PCF8563 永远收不到 START 信号。此外数码管位选信号若未设置电流驱动能力可能因灌电流不足导致亮度不均。4.1 I²C 引脚的开漏与上拉约束在 Quartus 18.0 中右键点击 SDA/SCL 引脚 → “Pin Properties” → “Current Strength” 设为 “8mA”再勾选 “Open-drain output”。随后在.qsf文件中强制添加# i2c_pins.qsf set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 8mA -to sda_pin set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 8mA -to scl_pin set_instance_assignment -name OPEN_DRAIN_OUTPUT ON -to sda_pin set_instance_assignment -name OPEN_DRAIN_OUTPUT ON -to scl_pin set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to sda_pin set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to scl_pin提示WEAK_PULL_UP_RESISTOR ON仅启用 FPGA 内部弱上拉约 50kΩ实际电路仍需外接 4.7kΩ 上拉电阻至 3.3V。若仅靠内部上拉SCL 上升时间会超限1000nsPCF8563 无法识别。4.2 数码管引脚的驱动能力与时序例外共阴极数码管每位需灌入 20–30mA 电流FPGA IO 通常仅支持 16mA 单端驱动。因此必须启用“Fast slew rate”并设置为“High current”# seg_pins.qsf set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_a set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_b set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_c set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_d set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_e set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_f set_instance_assignment -name CURRENT_STRENGTH_ONE_DRIVE_CURRENT 16mA -to seg_g set_instance_assignment -name SLEW_RATE FAST -to seg_a set_instance_assignment -name SLEW_RATE FAST -to seg_b # ... 其余段同理4.2.1 关键时序例外扫描时钟与 I²C 时钟域交叉处理I²C 控制器运行在 50MHz 主时钟域而数码管扫描时钟为 1kHz二者异步。当 I²C 读取新时间值后需安全传递至扫描模块。若直接assign hour_disp rtc_hour_reg可能因采样时刻落在 BCD 更新中间态如 0x19→0x20 过程中高位先变导致显示为 0x29。解决方案是加入两级同步器 有效指示// sync_i2c_to_scan.v reg [7:0] hour_sync1, hour_sync2; reg hour_valid_sync1, hour_valid_sync2; always (posedge scan_clk or negedge rst_n) begin if (!rst_n) begin hour_sync1 0; hour_sync2 0; hour_valid_sync1 0; hour_valid_sync2 0; end else begin hour_sync1 rtc_hour_reg; hour_sync2 hour_sync1; hour_valid_sync1 rtc_update_ack; // I²C 完成读取时拉高1 cycle hour_valid_sync2 hour_valid_sync1; end end // 仅当 hour_valid_sync2 为高时才更新显示寄存器 always (posedge scan_clk) begin if (hour_valid_sync2) hour_disp hour_sync2; end注意rtc_update_ack信号由 I²C 控制器在成功读取 6 字节时间数据0x02–0x07后产生单周期脉冲不可用i2c_done该信号在每次读写后都有效。5. 验证与调试用 SignalTap II 抓取 I²C 波形与时间寄存器快照定位 90% 的常见故障烧录后数码管不亮时间停在 00:00冒号狂闪别急着改代码——Quartus 18.0 内置的 SignalTap II Logic Analyzer 是最高效的现场调试工具。它能直接捕获 FPGA 内部信号无需外接示波器且支持触发条件设置。针对 PCF8563 RTC 系统我们固定抓取 4 组关键信号scl,sda,i2c_state,rtc_hour_reg并设置触发条件为i2c_state READ_START。5.1 SignalTap II 配置要点与触发策略在 Quartus 18.0 中点击 Tools → SignalTap II Logic Analyzer → 新建 .stp 文件。添加信号时注意scl/sda必须设为“Input pin”否则无法捕获真实电平i2c_state使用枚举类型定义如typedef enum logic [2:0] {IDLE0, START1, ADDR2, DATA3}SignalTap 可自动解码为文字rtc_hour_reg添加为 8-bit bus格式设为 “Unsigned Decimal”。触发条件设置为Trigger Condition: (i2c_state 2b10) AND (scl 1) AND (sda 0)即在ADDR状态下SCL 为高且 SDA 为低表示地址字节正在传输此时捕获后续 128 点波形足够覆盖一次完整读操作。5.2 三类典型波形故障与修复对照表故障现象SignalTap 抓取波形特征根本原因修复动作数码管全灭scl恒高sda恒高i2c_state停在IDLESCL 引脚未正确 assign 或OPEN_DRAIN_OUTPUT未启用导致总线无法拉低检查.qsf中OPEN_DRAIN_OUTPUT ON是否生效用万用表测开发板 SCL 引脚对地电压应为 3.3V上拉而非 0V短路时间始终为 00:00scl正常振荡sda在ADDR阶段无下降沿i2c_state卡在STARTPCF8563 未上电或 I²C 地址错误误用 0xA0 而非 0xA2用逻辑分析仪确认sda在 START 后是否被 PCF8563 拉低检查i2c_addr是否为 8h51SLAW 0x511|0 0xA2冒号每秒闪两次blink_cnt[24]在 1kHz 扫描时钟下翻转过快blink_cnt计数时钟误接clk50MHz而非scan_clk1kHz修改blink_cnt的 always 块敏感列表确保仅在scan_clk上升沿计数5.2.1 快速验证 PCF8563 寄存器内容的“内存快照法”SignalTap 支持对 RAM 块进行快照。若工程中将读取的时间值暂存于 block RAM如time_ram[5:0]存储秒到年可直接在 SignalTap 中添加该 RAM 实例设置“Data Depth”为 6“Data Width”为 8触发后查看各地址值。例如正常情况下time_ram[0]秒应为 0xXXtime_ram[1]分为 0xYY。若全为 0则证明 I²C 读操作未返回有效数据问题必在物理层或地址帧。提示若 SignalTap 无法识别time_ram需在 RTL 中显式声明为(* ramstyle auto *) reg [7:0] time_ram [5:0]并确保综合时未被优化掉加(* keep *)属性。最后一步也是最容易被忽略的在 Quartus 18.0 的 “TimeQuest Timing Analyzer” 中检查scan_clk的 clock uncertainty。由于该时钟由 50MHz 分频生成若未添加create_generated_clock约束时序分析会将其视为异步时钟导致 setup/hold 违例被忽略。务必在.sdc文件中加入create_generated_clock -name scan_clk -source [get_ports clk] -divide_by 50000 [get_ports scan_clk]本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门