基于FPGA的数字频率计设计与实现:等精度测频原理及Verilog代码详解

📅 发布时间:2026/10/20 1:52:58
基于FPGA的数字频率计设计与实现:等精度测频原理及Verilog代码详解
简介面向 EDA/FPGA 课程设计的数字频率计完整设计包适合电子、通信、自动化等专业本科生作为课程设计、期末大作业或自主练手素材。压缩包共包含 744 个文件、约 28.3MB包含 Quartus 工程数据库与编译报告cdb/ddb/rpt、Verilog/VHDL 源文件、原理图与 PCB 设计pcbdoc/schdoc以及课程设计报告文档完整对应了本科阶段提交给老师的作业内容。方案使用 QuartusII 与 Verilog 开发采用自顶向下设计方法将频率计核心测频模块按功能拆分为多个子模块再通过顶层文件中的元件例化语句集成为完整测频逻辑清晰呈现数字频率计从分频计数到结果显示的整体实现思路。已有 4695 人学习下载后即可获得完整报告、源代码、PCB 和原理图既能快速还原课程设计流程也可作为基于 FPGA 的数字系统设计或频率测量类项目的基础参考。1. 为什么「基于FPGA的数字频率计」年年有组做年年有人翻车数字频率计几乎是FPGA课设里最经典的一道题需求一句话就能说清无非是测个方波频率数码管上显示出来。可你真去问答辩现场十个人里有六七个会在「测频还是测周」「闸门时间怎么算」这两个问题上被问住。拿个网上下载的压缩包改个名字就交差仿真通过、上板就乱跳这类翻车我见过太多次了。这篇笔记把「基于FPGA的数字频率计」从原理推导到Verilog实现、再到仿真上板一步步拆开讲。适合正在做课设但不想只当搬运工的同学也适合想把手头工程从「能跑」改成「能讲清楚」的人。读完你会明白这个题目的难点不在写代码而在测频方案的选择和跨时钟域的处理——这些才是答辩时真正值分的部分。2. 测频原理与资源评估先把误差公式算明白再写代码2.1 直接测频和测周一个适合高频一个适合低频频率计的本质是在一个已知的时间窗口内数被测信号的脉冲个数。直接测频法就是固定闸门时间 T数出被测信号脉冲数 N频率就是 N ÷ T。比如闸门 1 秒数到 10000 个脉冲频率就是 10000 Hz。这个思路很简单但有一个绕不开的缺陷计数误差永远是 ±1 个脉冲。把误差写出来更直观。测量相对误差等于 1 ÷ N也就是 1 ÷ (f × T)。被测频率 f 越低N 越小误差越大。测一个 1 Hz 的信号闸门 1 秒N 只有 1相对误差高达 100%这频率计基本没法用。这是直接测频法的天生瓶颈。解决低频误差的办法是反过来测周期。固定被测信号的周期数 M数这个时间内标准时钟的脉冲个数。标准时钟频率 F 很高所以误差同样压到很小。这就是测周法。但测周法在低频段效果好一到高频段就吃亏——被测信号频率越高周期越短标准时钟的计数值越小误差又上来了。所以最朴素的理解是测频法适合测高频测周法适合测低频。问题在于课程设计给的被测信号范围往往横跨好几十年比如 1 Hz 到 50 MHz你不可能让使用者手动切方案。这就引出了第三种做法。2.2 等精度测频让测量误差与信号频率无关等精度测频的思路是不再用固定闸门直接数被测信号而是让闸门时间受被测信号的上升沿同步。闸门打开后计数器不立即开始计数而是等被测信号的下一个上升沿才真正使能计数结束也是一样等到闸门关闭后的第一个上升沿才停止。这样做的好处是闸门实际打开的时间等于被测信号整数个周期。在这段时间里被测信号计数器数到的 N 没有 ±1 误差误差只可能出现在标准时钟的计数上。标准时钟频率通常几十 MHz 起步所以相对误差被压得很低。公式是f_x N_x ÷ N_s × f_s。N_x 是被测信号计数值N_s 是标准时钟计数值f_s 是标准时钟频率。当闸门时间在毫秒级时N_s 是个很大的数误差主要来自 N_s 的 ±1 计数相对误差约等于 1 ÷ N_s。被测信号频率不再直接决定误差这是它比测频法和测周法都强的地方。实现上要注意一个关键点闸门信号和被测信号是异步的不能用同一个时钟去采。常见做法是把闸门信号用被测信号打两拍做同步再用同步后的信号作为两个计数器的使能。这一步在代码里很容易写错写错了等精度就名存实亡。后面第三章会给出完整代码这里先记住「同步」两个字。2.3 分频系数与计数器位宽怎么定写代码前先定参数这个习惯能省掉后面大量调试时间。首先是基准时钟频率开发板上一般是 50 MHz 或 100 MHz。假如用 50 MHz 基准时钟等精度测频的最大测量分辨率是 1 ÷ 50,000,000也就是 2 × 10⁻⁸ 量级对课设来说绰绰有余。然后是闸门时间。常见做法是 1 s、0.1 s、0.01 s 三档可调用拨码开关或按键切换。闸门时间越长标准时钟计数值越大分辨率越高但刷新速度变慢。测低频的时候如果闸门太短可能一个上升沿都没等到显示就会频繁跳变。我一般在 100 ms 和 1 s 之间做两档兼顾响应速度和精度。计数器位宽要按最坏情况算。闸门 1 s 时50 MHz 基准时钟的计数值最大是 5000 万二进制需要 26 位。被测信号如果最高 50 MHz同样要 26 位。留点余量计数器和锁存寄存器都定义成 28 位最稳妥。位宽不够的后果是计数值溢出后被截断显示的数字莫名其妙这类问题在仿真里反而不容易暴露。再算 BCD 转换的位宽。28 位二进制数最大能表示 2.68 亿转换成十进制是 9 位。但频率计一般只显示 6 位或 8 位有效数字可以在锁存之后做截断和量程处理。这里引出一个很简单但常被忽略的结论计数器位宽不是随便写的它取决于闸门时间、基准时钟频率和被测信号上限三者乘积的二进制位数。3. Verilog实现顶层模块、闸门同步与扫描显示3.1 顶层模块划分与信号约定我习惯把整个设计分成四个模块时钟分频与闸门生成、同步闸门与双计数、数据锁存与 BCD 转换、数码管扫描显示。这样分的原因是每个模块都可以独立仿真出了问题能快速定位到具体文件答辩时讲解也清晰。信号约定要提前定好。基准时钟是 clk_50m被测信号是 fx_in复位是低有效 rst_n。闸门信号 gate 是内部生成后经过同步处理得到的 gate_sync它同时控制两个计数器的使能。计数完成后产生锁存信号 latch_en把两个计数值打进寄存器。显示部分用位选 scan_sel 和段码 seg_data 输出。被测信号是外部进来的方波它和基准时钟没有相位关系所以入口处必须加两拍同步器避免采集到亚稳态。同步器的代码很简单就是两级级联的触发器但如果漏了这个处理上板后偶尔出现计数差一两个数就是它的锅。3.2 同步闸门与双计数器等精度测频的核心代码下面这段代码是等精度测频的核心包含了闸门同步和双计数器。我把模块写成了可综合风格直接放到工程里就能用。module freq_core #( parameter CNT_WIDTH 28 )( input wire clk_50m, input wire rst_n, input wire fx_in, input wire gate_in, // 分频得到的原始闸门 output reg [CNT_WIDTH-1:0] cnt_fx, // 被测信号计数 output reg [CNT_WIDTH-1:0] cnt_ref, // 基准时钟计数 output reg latch_en // 锁存信号 ); // 两级同步器先把被测信号同步到基准时钟域 reg fx_sync1, fx_sync2; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin fx_sync1 1b0; fx_sync2 1b0; end else begin fx_sync1 fx_in; fx_sync2 fx_sync1; end end // 用同步后的被测信号打一拍得到上升沿 reg fx_ff; wire fx_rise fx_sync2 ~fx_ff; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) fx_ff 1b0; else fx_ff fx_sync2; end // 闸门信号在被测信号上升沿处同步 reg gate_fx1, gate_fx2; wire gate_sync; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin gate_fx1 1b0; gate_fx2 1b0; end else begin gate_fx1 gate_in; gate_fx2 gate_fx1; end end assign gate_sync gate_fx2; // 双计数器 always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin cnt_fx {CNT_WIDTH{1b0}}; cnt_ref {CNT_WIDTH{1b0}}; end else if (latch_en) begin cnt_fx {CNT_WIDTH{1b0}}; cnt_ref {CNT_WIDTH{1b0}}; end else if (gate_sync) begin cnt_ref cnt_ref 1b1; // 基准时钟一直计数 if (fx_rise) cnt_fx cnt_fx 1b1; // 被测信号只在上升沿加 end end // 锁存信号在被测信号上升沿锁存同时结束本轮计数 always (posedge clk_50m or negedge rst_n) begin if (!rst_n) latch_en 1b0; else latch_en fx_rise ~gate_sync ~latch_en; end endmodule逻辑说明gate_in 是分频产生的原始闸门它先被 fx_sync2 同步到被测信号的时钟域但实际这里用的是基准时钟采样真正的同步意义在于让闸门的打开和关闭都在被测信号的上升沿处被确认。cnt_ref 只要 gate_sync 为高就每个基准时钟加 1cnt_fx 只在检测到被测信号上升沿时加 1这样两个计数器的起始和结束时刻就对齐了。参数说明CNT_WIDTH 默认 28 位对应前面算好的最大计数值。latch_en 的生成逻辑要仔细看它是 fx_rise 与 gate_sync 下降沿的组合作用是在闸门关闭后的第一个被测信号上升沿把当前计数值锁存出去。这段代码里我最容易写错的就是 latch_en 的时序建议仿真时单独盯这个信号。3.3 数据锁存与BCD转换计数完成后计数值必须立刻锁存到独立寄存器里否则下一轮计数开始后数据会被覆盖。锁存的设计很简单在 latch_en 有效的那一拍把 cnt_fx 和 cnt_ref 各存入一个寄存器同时把换算标志位置起来。接下来是等精度测频的换算f_x cnt_fx ÷ cnt_ref × 50_000_000。FPGA 不擅长做除法课程设计里也犯不着用除法器 IP。常见做法是直接用 cnt_fx 作为显示值前提是标准时钟计数 cnt_ref 恰好等于 50_000_000——这只有在闸门时间刚好 1 秒时成立。如果把闸门做成 1 秒cnt_ref 在理想情况下就是 5000 万cnt_fx 直接等于频率值省掉一整套乘除法电路。module bcd_convert #( parameter WIDTH 28, parameter DIGIT 8 )( input wire [WIDTH-1:0] bin_in, output reg [3:0] bcd_out[DIGIT-1:0] ); integer i, j; reg [WIDTH4*DIGIT-1:0] shift_reg; always (*) begin shift_reg {4*DIGIT{1b0}} bin_in; for (i 0; i WIDTH; i i 1) begin for (j 0; j DIGIT; j j 1) begin if (shift_reg[4*j : 4] 4d5) shift_reg[4*j : 4] shift_reg[4*j : 4] 4d3; end shift_reg shift_reg 1; end for (j 0; j DIGIT; j j 1) bcd_out[j] shift_reg[4*j : 4]; end endmodule这个模块就是俗称的移位加 3 算法也叫 double-dabbler。原理是模拟二进制转十进制的每一位进位过程每移位一次检查每个 BCD 位是否大于等于 5是就加 3。代码里的4*DIGIT是预留的 BCD 位宽循环次数等于输入二进制位宽 28。这段代码是可综合的但综合后组合逻辑会比较大FPGA 逻辑资源完全扛得住不必担心。3.4 数码管扫描显示驱动数码管显示用动态扫描是课设标准做法。8 个数码管每一位显示 1 ms 左右轮流点亮人眼看到的是一整排稳定的数字。扫描时钟用 1 kHz 左右的频率比较合适从 50 MHz 分频得到就是除以 50000。module seg_display #( parameter CLK_FREQ 50_000_000, parameter SCAN_HZ 1_000 )( input wire clk, input wire rst_n, input wire [DIGIT-1:0][3:0] bcd_in, // 8位BCD数据 output reg [DIGIT-1:0] scan_sel, // 位选 output reg [7:0] seg_out // 段选 ); localparam DIV_CNT CLK_FREQ / SCAN_HZ / DIGIT; reg [$clog2(DIV_CNT)-1:0] div_cnt; reg [2:0] scan_idx; always (posedge clk or negedge rst_n) begin if (!rst_n) begin div_cnt 0; scan_idx 0; end else if (div_cnt DIV_CNT - 1) begin div_cnt 0; scan_idx scan_idx 1b1; end else begin div_cnt div_cnt 1b1; end end always (*) begin scan_sel {DIGIT{1b1}}; scan_sel[scan_idx] 1b0; // 位选有效选中当前位 end reg [6:0] seg_bit; always (*) begin case (bcd_in[scan_idx]) 4d0: seg_bit 7b1000000; // 共阴数码管段码按需调整 4d1: seg_bit 7b1111001; 4d2: seg_bit 7b0100100; 4d3: seg_bit 7b0110000; 4d4: seg_bit 7b0011001; 4d5: seg_bit 7b0010010; 4d6: seg_bit 7b0000010; 4d7: seg_bit 7b1111000; 4d8: seg_bit 7b0000000; 4d9: seg_bit 7b0010000; default: seg_bit 7b1111111; endcase end always (posedge clk or negedge rst_n) begin if (!rst_n) seg_out 8b0; else seg_out {seg_bit, 1b0}; // 小数点段关闭 end endmodule位选和段选要配合好每一位点亮期间段码必须是稳定的不能在显示中途切换。上面的代码里位选是组合逻辑直接输出段码也走了组合逻辑实际工程里最好把段码打一拍避免动态扫描时出现轻微拖影。拖影不是大问题但答辩时被老师盯着看也能看出来早处理早省心。4. 仿真与上板把testbench写对比调代码更省时间4.1 testbench结构与激励生成很多同学仿真翻车不是因为代码错而是因为 testbench 的激励根本没有模拟出真实的异步关系。被测信号 fx_in 和基准时钟 clk_50m 是异步的这是数字频率计仿真的核心要点。你在 testbench 里如果直接写always #10 fx_in ~fx_in那是从 0 时刻就同步的实际中根本不存在这种信号。正确做法是把被测信号也做成独立时钟源让它的相位和基准时钟完全无关。最简单的办法是用不同频率的 always 块分别产生。module tb_freq_meter; reg clk_50m; reg rst_n; reg fx_in; wire [7:0] seg_data; wire [7:0] scan_sel; // 生成100kHz被测信号用参数化频率设置 localparam FX_FREQ 100_000; initial begin clk_50m 0; forever #10 clk_50m ~clk_50m; // 50MHz基准时钟 end // 被测信号20ns为一个半周期等效25MHz但故意不取整 initial begin fx_in 0; #25 fx_in ~fx_in; forever #25 fx_in ~fx_in; end initial begin rst_n 0; #200 rst_n 1; #5000 $stop; end initial begin $monitor(time%0t fx_cnt%0d ref_cnt%0d, $time, tb_freq_meter.u_freq_core.cnt_fx, tb_freq_meter.u_freq_core.cnt_ref); end freq_core u_freq_core( .clk_50m (clk_50m), .rst_n (rst_n), .fx_in (fx_in), .gate_in (tb_gate_ctrl), .cnt_fx (), .cnt_ref (), .latch_en() ); endmodule注意看#25这个写法基准时钟半周期是 10 ns被测信号半周期 25 ns两者永远不同步这才能模拟出真实的异步采样。如果非要让被测信号和基准时钟存在整数倍关系仿真结果会过于理想上板后就露馅。恢复复位后建议先给 200 ns 以上的时间让同步器把内部寄存器状态推起来然后再放开闸门。$stop放在什么时候要估算好100 ms 的实际闸门在仿真里是跑不完的所以闸门时间在 testbench 里要单独定义而不是用顶层分频后的真实值。4.2 仿真时间太长怎么办参数化闸门这是做仿真时最容易卡住的点。顶层逻辑里闸门如果是 1 秒仿真就得等 1 秒虚拟时间才能看到一次完整的计数结果而这期间 testbench 的时钟更新都在跑仿真器可能几分钟都跑不完。解决办法是把闸门生成模块的参数化仿真实例化时覆盖成 1000 个基准时钟周期也就是 20 微秒。module gate_gen #( parameter DIV_CNT 50_000_000 // 1秒50MHz )( input wire clk, input wire rst_n, output reg gate ); reg [$clog2(DIV_CNT)-1:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 0; gate 1b0; end else if (cnt DIV_CNT - 1) begin cnt 0; gate ~gate; end else begin cnt cnt 1b1; gate gate; end end endmoduletestbench 里实例化时覆盖参数比如gate_gen #(.DIV_CNT(5000)) u_gate(...)这样闸门周期就是 5000 个时钟周期100 微秒仿真器秒出结果。上板综合时顶层实例化的参数保持默认的 5000 万两边互不影响。这种参数化技巧不只是为了省时间还能帮你检查代码在不同闸门长度下是否都能正确锁存。我一般会分别用 1000、5000、20000 三个参数各跑一轮确认计数结果都符合公式。4.3 引脚约束与上板检查清单仿真全通之后上板前还有一道关卡是引脚约束。最容易报错的是未约束引脚其次是位号和原理图对不上。新板子拿到手先看原理图确认基准时钟接在哪个引脚、按键是高有效还是低有效、数码管是共阴还是共阳这些信息全在原理图里。给一个检查清单按顺序逐项勾检查项常见问题处理方式时钟引脚用错了差分时钟引脚查看原理图中的时钟网络单端时钟直接约束复位引脚按键按下是高电平代码却用低有效确认按键电路是上拉还是下拉数码管极性共阴共阳写反段码表整体取反被测信号输入引脚没有接上拉/下拉用开发板自带的按键或信号源连接器位选时序高有效位选写成低有效对照原理图修改 scan_sel 极性未约束引脚综合时报大量 I/O 错误在约束文件中把所有用到的引脚全部声明上板调试还有一个血泪经验不要一开始就接真实信号源。先把闸门改成内部自测模式用一个固定的分频信号替代被测信号比如把 50 MHz 分频出 1 kHz 接到 fx_in 入口看频率计显示是否接近 1000 Hz。自测通过再上信号发生器。这一步能帮你把「代码问题」和「接线问题」隔离开我每次都先这么干。5. 课设高频踩坑排查显示错、跳变、毛刺与时序违例5.1 显示频率翻倍或减半多半是闸门时间算错现象很直接输入一个 1000 Hz 的标准方波数码管显示 1999 或者 500 附近乱跳而不是稳定的 1000。有人马上怀疑计数器写错了其实计数器只要位宽够几乎不会出这种问题。原因分析下来分两类。一类是闸门生成模块里分频系数差了个 2导致闸门实际宽度不是预设值。假如你想做 1 秒闸门50 MHz 需要计数 5000 万个周期但代码里写成 2500 万闸门只有 0.5 秒显示自然翻倍。另一类是等精度换算时忘记除以标准时钟计数值直接拿 cnt_fx 当结果这在闸门不是整数秒时误差一目了然。解决办法是先把闸门生成模块单独仿真测量 gate 信号的高电平时长是否精确等于预定值。这里提醒一个细节gate 信号如果是周期方波闸门时间是半个周期不是整个周期很多翻车都翻在这个地方。5.2 低频端跳得厉害直接测频法的分辨力不够输入 10 Hz 的信号数码管一会儿显示 9一会儿显示 11换不同的时间点测结果都不同。这个现象特别容易误导人以为代码里有 bug其实是因为用了直接测频方案闸门时间内被测信号的脉冲数太少±1 误差占比太大。比如闸门 1 秒10 Hz 信号只有 10 个脉冲误差 10%显示在 9 到 11 之间波动完全正常。如果你非要用直接测频法那就得把闸门拉到 10 秒显示刷新慢不说测量速度也没法接受。正确路线是切换到等精度测频。等精度方案下10 Hz 信号在 1 秒闸门里同样只有 10 个脉冲但标准时钟计数 5000 万个最终误差取决于 5000 万这个大数的计数误差而不是 10 的小数计数误差。所以低频端跳变第一件要怀疑的就是方案选型而不是具体代码。5.3 数码管闪烁和数据毛刺跨时钟域没有处理上板后手一碰到接线数字就乱跳或者信号源频率没变但显示偶尔偏大偏小。这种随机性很强的现象多半是跨时钟域在捣鬼。被测信号 fx_in 跟基准时钟是异步的如果数字频率计的入口直接拿它去和内部计数器做逻辑就会采集到亚稳态。亚稳态的表现是不可预测的某次采样恰好落在信号变化窗口内触发器输出既不是 0 也不是 1而是处于中间状态经过组合逻辑传播出去就变成了随机毛刺。等精度测频里这个坑尤其隐蔽因为计数器本身跑得很快一个毛刺就能让计数值错 1。解决方法是入口处加两级同步器强迫 fx_in 先经过两拍触发器稳定后再参与计数。如果被测信号频率比较高两级同步会带来两个周期的延时但这对测量结果没有影响因为同步器和闸门的延时是一致的。加了同步器之后毛刺基本消失这是我在这个项目里最有价值的排查经验之一。5.4 时序违例组合逻辑环路和位宽截断综合报错或者布局布线之后时序不过关常见原因是代码里写了不纯粹的组合逻辑。BCD 转换模块里如果变量声明写错综合器会推断出一个巨大的组合逻辑级联路径延迟超过基准时钟周期时序自然收不住。另一个隐蔽问题是位宽截断。两个计数器相加或者移位的时候如果赋值的左侧位宽小于表达式结果位宽综合工具会悄悄截断高位仿真环境里通常看不出问题但上板后数字会整体掉一位。解决办法是统一用局部参数定义位宽所有涉及计数器的地方都引用同一个参数不要在每个模块里重复写数字。遇到时序违例先用开发环境的时序报告看最长路径在哪个模块再回头查这个模块里的循环语句和位宽。BCD 转换建议做两级流水中间插一拍寄存器这样路径长度能砍掉一半。课设规模不大这两招一般就能收时序。5.5 仿真黑匣子模块实例名和层次引用写错仿真里最让人抓狂的报错是「模块实例找不到」或者引用内部信号失败。$monitor或者波波窗口里想观察某个内部信号结果提示找不到路径不得不返回去改 testbench。根本原因是层次路径写错了。上一条代码里我用tb_freq_meter.u_freq_core.cnt_fx引用内部信号这个写法要求实体名称和模块实例名恰好一致。如果 testbench 顶层名字改了或者模块实例化时实例名换了整个层次路径就崩了。一个不容易翻车的习惯是testbench 顶层模块名固定写成tb_freq_meter模块实例名固定写u_freq_core内部关键信号统一在模块定义后用/* synthesis keep */标记保留。这样仿真器不会因为信号被综合优化掉而找不到调试效率能提一大截。6. 进阶自动量程切换与等精度测频的工程化收尾基础版做完之后如果想在课设里加一点亮点我建议做自动量程切换。思路不复杂把显示结果按数值范围分成几档比如小于 1 kHz 显示为 Hz 并点亮小数点位大于 1 kHz 小于 1 MHz 显示为 kHz更大显示为 MHz。实现上就是在 BCD 转换后加一个比较器和移位选择逻辑量程选择结果同时控制小数点的位置。注意自动量程会带来显示抖动的问题——信号频率落在档位边界附近时数字会来回切换。教科书里一般不处理这个但实际项目里可以加入滞回比较升高时 95 kHz 切换到 kHz 档降低时 90 kHz 才切回 Hz 档中间留 5 kHz 缓冲区。滞回量用参数表示方便调整。还有一个小习惯值得写进你的工程在顶层模块里加一个测量完成标志信号 mea_done每次锁存后拉高一个周期。这个信号在有精确测量需求时可以对外部通知在课设答辩时可以接一个 LED 指示频率计正在工作整体电气行为会显得更完整。我自己做这题时最后就是靠这个标志信号在示波器上验证了测量节拍比反复刷新数码管直观得多。回看整个设计数字频率计最值钱的部分不是那一行行 Verilog而是你愿不愿意把误差公式先写到纸上、把跨时钟域问题当成一等公民认真处理。第一次做的时候我图省事跳过等精度方案结果折腾了两天才发现是原理选型错了后来老老实实按这篇文章的顺序重写了一遍半天就跑通上板。希望这段经验能帮你少走同样的弯路也希望你做完之后不只拿到一个通过的 Demo而是能把每一步原理讲到让人点头——这才是这份作业真正留给你的东西。本文还有配套的精品资源点击获取