简介:一套基于FPGA与Verilog的PM2.5及温湿度传感器系统设计资源包,面向硬件专业学生、FPGA入门开发者、环境监测项目爱好者以及电子竞赛备赛人员。方案围绕PM2.5光散射传感器与DHT系列温湿度传感器展开:PM2.5检测利用光散射原理,由FPGA控制ADC完成模拟量到数字值的转换;温湿度传感器通过串行接口解析出温度与湿度;LCD1602则需完成初始化、地址设置与字符写入,在FPGA时序驱动下实时刷新显示。整体适合智能家居、小型环境站等场景的课程设计或项目参考。压缩包共1976个文件,约16.49MB,以Quartus工程数据库(cdb/hdb)、Verilog/VHDL源文件、AHDL设计文件、引脚约束与编译报告及sof配置文件为主,目录结构与工程索引完整,便于定位和复用。其中包含了工程从设计输入、综合、布局布线到生成配置文件的中间产物,可辅助理解FPGA完整开发流程。已有645人学习,工程完整度较高。从中可提取完整的FPGA工程框架、传感器驱动模块、显示控制逻辑与综合配置流程,对理解顶层集成、信号时序和外设接口设计有直接帮助。
1. 把 PM2.5 和温湿度采集做成 FPGA 系统,到底值不值
一个很现实的需求:环境监测现场需要同时读 PM2.5 浓度、温度和湿度,数据要稳定、要响应快、要能挂到上位机或屏上。用单片机做当然可以,但当采集路数变多、要求多路并行采样或要做实时滤波时,FPGA 的优势才真正体现出来。FPGA + Verilog 做 PM2.5 温湿度传感器系统设计,核心工作是把两类传感器的时序协议用硬件逻辑实现——PM2.5 传感器通常走 UART 或模拟输出,温湿度传感器走单总线或 I2C,FPGA 要把这些异构接口统一成一组寄存器,再做滤波、校验、上报。这个方案适合已经有 FPGA 开发板、想往传感器采集方向深入的人,也适合做空气质量监测设备预研究的团队。它的难点不在逻辑复杂度,而在时序细节和跨时钟域处理上。
2. 传感器选型和硬件拓扑:为什么 PMS5003 + DHT22 是常见组合
2.1 三类 PM2.5 传感器接口对比:UART、模拟量、I2C
常见的 PM2.5 传感器按输出接口分三类。第一类是 PMS5003 这种激光散射式,内部自带 MCU 和风扇,直接通过 UART 以 9600 波特率输出 32 字节标准帧,包含 PM1.0/PM2.5/PM10 的浓度值,分标准颗粒物浓度和大气环境浓度两套数据,共 12 个字节的有效数据。第二类是 GP2Y1010AU0F 这种模拟输出型,叫 Sharp 粉尘传感器,需要 FPGA 配合外部 ADC 采样,输出引脚的电压和粉尘浓度呈线性关系,电路上要加一个 150Ω 限流电阻和 220μF 电解电容,属于比较老的方案。第三类是攀藤的 PMS1003 系列或 SDS011 这类传感器,本质还是 UART 数字输出,只是帧格式略有差异。
我在实际项目中选 PMS5003 的核心原因有三个:一是激光散射方案的数据可信度比红外方案高不少,对 0.3μm 以上颗粒有真实的计数统计;二是 UART 数字输出省掉了 ADC 前端模拟电路的标定工作;三是模块出厂前做过校准,FPGA 端只要解析帧数据,不需要再做线性修正。GP2Y1010 那套模拟方案不是不能做,但 ADC 基准、运放零漂、滤光片老化补偿这些坑堆在一起,不适合快速迭代。
选型时要确认帧格式细节,PMS5003 有两个版本固件,早期版本同时输出 CF=1 和大气环境数据,有些简化版只有一组。FPGA 解析时按帧头 0x42 0x4D 锁定同步,不要按固定字节偏移盲读。
2.2 温湿度传感器:DHT11 和 DHT22 的时序差异
温湿度传感器这侧比较常见的选择是 DHT11 和 DHT22(也叫 AM2302)。两者都走单总线协议,但时序参数差异很大。DHT11 的湿度分辨率只有 1%RH,温度分辨率 1°C,采样周期要求不低于 1 秒;DHT22 湿度分辨率做到 0.1%RH,温度 0.1°C,精度更高,但价格差不多是 DHT11 的三四倍。
单总线协议的物理层只有一根数据线,主机必须先发一个低电平启动信号,DHT11 要求低电平时间不小于 18ms,DHT22 也差不多,但释放后从机响应的时序窗口不同。DHT22 的低电平响应是 80μs,然后拉高 80μs 再开始发 40 位数据,每位数据以一个 50μs 低电平开始,后续高电平持续 26~28μs 表示逻辑 0,持续 70μs 表示逻辑 1。FPGA 端得用高频时钟去采样这根线,否则高低电平的宽度区分会出错。
选 DHT22 还是 DHT11,取决于系统对湿度数据的精度需求。做温室、仓库这类湿度变化平缓的场景,DHT11 够用;做气象站或实验室环境监控,DHT22 才有意义。FPGA 代码的兼容性方面,状态机把启动超时和数据位判决做成参数可配,同一套逻辑可以适配两种传感器,我一般建议直接把 DHT22 的参数写死,因为 DHT11 省不了多少成本。
2.3 FPGA 引脚规划与最小系统连接
传感器接口占用的 FPGA 引脚资源很少。PMS5003 的 UART 收发各占一个引脚,DHT22 单总线占一个引脚,加上复位和时钟,总共不到十个引脚,随便一块入门级开发板都够用。比较需要注意的是电气兼容性和上拉电阻。
DHT22 的数据线是开漏输出,外部必须接一个 4.7kΩ 到 10kΩ 的上拉电阻到 3.3V。如果开发板上没有预焊上拉,直接用杜邦线连接传感器和 FPGA 引脚,大概率读出来全是 0xFF 或者时序识别失败。PMS5003 的 UART 接口是 3.3V 电平,TTL 电平,不能接到 5V 管脚上,否则长期运行可能损坏 FPGA 的 IO bank。
系统级连接通常这样规划:
| 引脚用途 | FPGA 管脚类型 | 外部连接 | 注意点 |
|---|---|---|---|
| 系统时钟 | 专用时钟引脚 | 有源晶振 50MHz | 不要用普通 IO 做时钟源 |
| 复位 | 普通输入 + 上拉 | 按键或 RC 复位电路 | 低电平有效 |
| UART_RX | 普通输入 | PMS5003 TX | 3.3V 电平 |
| UART_TX | 普通输出 | PMS5003 RX(可选) | 传感器一般不要求回传 |
| DHT22_DATA | 双向 IO + 上拉 | DHT22 DATA | 4.7kΩ 上拉必不可少 |
我在做板子时习惯把 DHT22 的上拉电阻放在靠近 FPGA 引脚的位置,减少走线寄生电容对时序边沿的影响。PMS5003 的供电要单独看,模块内部有风扇,启动瞬间电流可能到 100mA 以上,如果和 FPGA 共用同一路 LDO,可能导致电压跌落引发逻辑错误,建议用独立稳压供电。
3. Verilog 实现传感器时序:UART 接收与 DHT22 单总线状态机
3.1 PMS5003 的 UART 帧接收:用状态机锁住字节流
PMS5003 以 9600 波特率、8 数据位、无校验、1 停止位连续发送数据帧,每帧 32 字节。FPGA 端需要实现的是一套 UART 接收逻辑,把串行位流还原成字节,再从字节流里找帧头、校验和、提取 PM2.5 浓度值。
UART 接收的标准做法是 16 倍过采样。50MHz 时钟下,9600 波特率的每个位周期约 10417 个时钟周期,过采样 16 倍意味着每个位采样 16 次。核心逻辑分成三块:检测起始位的下降沿、逐位采样、拼装字节。起始位检测要小心,串行线上的毛刺可能被误判成下降沿,通常在确认低电平持续了 8 个采样点后才认定起始位有效。
下面是一个完整的 UART 接收模块代码,带参数化分频:
module uart_rx #( parameter CLK_FREQ = 50_000_000, parameter BAUD_RATE = 9600 )( input wire clk, input wire rst_n, input wire rx, output reg data_valid, output reg [7:0] data_out ); localparam BIT_PERIOD = CLK_FREQ / BAUD_RATE; localparam HALF_BIT = BIT_PERIOD / 2; reg [15:0] clk_cnt; reg [3:0] bit_cnt; reg [7:0] shift_reg; reg rx_sync1, rx_sync2; reg rx_sampled; reg receiving; reg bit_sample; // 两级同步器消除亚稳态 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin rx_sync1 <= 1'b1; rx_sync2 <= 1'b1; end else begin rx_sync1 <= rx; rx_sync2 <= rx_sync1; end end // 主状态机 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin clk_cnt <= 16'd0; bit_cnt <= 4'd0; shift_reg <= 8'd0; receiving <= 1'b0; data_valid <= 1'b0; data_out <= 8'd0; end else begin data_valid <= 1'b0; if (!receiving) begin // 检测起始位:同步后的 rx 拉低 if ((!rx_sync2) && clk_cnt == 16'd0) begin clk_cnt <= 1'b1; receiving <= 1'b1; bit_cnt <= 4'd0; end end else begin if (clk_cnt < BIT_PERIOD) begin clk_cnt <= clk_cnt + 1'b1; end else begin clk_cnt <= 16'd0; // 在位的中间时刻采样 if (bit_cnt >= 4'd1 && bit_cnt <= 4'd8) shift_reg <= {rx_sync2, shift_reg[7:1]}; bit_cnt <= bit_cnt + 1'b1; // 停止位就绪 if (bit_cnt == 4'd9) begin receiving <= 1'b0; data_out <= shift_reg; data_valid <= 1'b1; end end end end end endmodule代码里最后取出的是 shift_reg 和 data_out 同时赋值,注意 UART 是低位先发,移位寄存器的拼接方向决定了字节序是否正确。如果发现收到的数据字节和上位机监视到的不一致,检查这一行的移位方式,实际项目中常见的翻车点就是把位序搞反了。
3.2 DHT22 单总线时序:状态机加超时保护
DHT22 读取比 UART 麻烦,因为数据线是双向的,FPGA 既要拉低总线发起启动信号,又要释放总线等从机响应,还要在数据阶段持续采样。Verilog 里把双向 IO 的控制做在一个状态机里,用 oe 信号切换 tristate 引脚的方向。
DHT22 读取完整流程分五个阶段:主机拉低 20ms 启动、释放总线等待从机低电平响应、等待从机拉高 80μs、读取 40 位数据、校验。每个阶段都要有超时退出机制,否则传感器没接好时状态机会死锁。
数据位读取的核心是测量高电平持续时间。每位数据以 50μs 低电平起始,随后的高电平宽度区分 0 和 1。用 50MHz 时钟测量高电平宽度,逻辑 0 的高电平约 26~28μs,对应 1300~1400 个时钟周期;逻辑 1 的高电平约 70μs,对应 3500 个时钟周期。判决阈值取 2000 个周期比较稳妥。
以下是 DHT22 读取控制器的关键部分:
module dht22_reader #( parameter CLK_FREQ = 50_000_000 )( input wire clk, input wire rst_n, inout wire dht_data, output reg read_done, output reg [15:0] humidity, output reg [15:0] temperature, output reg [15:0] crc_value ); localparam START_LOW_US = 20_000; // 20ms 启动低电平 localparam RESP_TIMEOUT = 100; // 100us 超时阈值 localparam BIT1_THRESHOLD = 2000; // 2000 个时钟周期约 40us reg [31:0] timer; reg [5:0] bit_index; reg [39:0] data_reg; reg [2:0] state; reg oe; reg rx_line; localparam IDLE = 3'd0; localparam START_LOW = 3'd1; localparam WAIT_RESP = 3'd2; localparam READ_BITS = 3'd3; localparam DONE = 3'd4; // 双向 IO 控制 assign dht_data = oe ? 1'b0 : 1'bz; assign rx_line = dht_data; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; oe <= 1'b0; read_done <= 1'b0; end else begin case (state) IDLE: begin // 发起读取,间隔保持 2s 以上 oe <= 1'b1; timer <= START_LOW_US; state <= START_LOW; end START_LOW: begin if (timer == 0) begin oe <= 1'b0; // 释放总线 timer <= RESP_TIMEOUT; state <= WAIT_RESP; end else begin timer <= timer - 1; end end WAIT_RESP: begin // 等待从机拉低,超时退出 if (rx_line == 1'b0) state <= READ_BITS; else if (timer == 0) state <= IDLE; else timer <= timer - 1; end READ_BITS: begin // 测量高电平宽度并判位 // 需要配合边沿检测逻辑 if (bit_index >= 6'd40) begin state <= DONE; read_done <= 1'b1; end end DONE: begin read_done <= 1'b0; state <= IDLE; end endcase end end endmodule这段代码展示的是状态框架,实际完整实现还要在 READ_BITS 里加边沿检测和宽度测量逻辑。注意一个关键点:整个读取过程中,除了启动阶段,FPGA 始终是输入模式,oe 信号要确保及时释放。如果不小心让 oe 一直为高,DHT22 永远不会有机会拉低总线响应,数据线直接冲突。
3.3 把两个传感器读到的数据统一成寄存器视图
从底层模块视角看,PMS5003 的 UART 接收和 DHT22 读取完成之后,产生的是异步的 valid 脉冲和数据总线。上层模块需要的是类似pm25_value[15:0]、temp_value[15:0]、humi_value[15:0]这样的寄存器。
我的做法是加一层数据锁存模块,把两个传感器的数据更新事件变成寄存器写入。PMS5003 每隔约 1 秒输出一帧数据,DHT22 的读取周期建议设置成 2 秒一次,这样两者数据更新频率差不多,方便显示端做统一刷新。
4. 数据解析与滑动窗口滤波:让 PM2.5 数据可信而不是好看
4.1 PMS5003 帧同步和校验和:先检查再做算术
PMS5003 的帧结构固定 32 字节,帧头两个字节 0x42 0x4D,第三四字节是帧长度,实际值通常是 0x00 0x1C(十进制 28),表示后面还有 28 个数据字节。校验和是帧头开始到校验和前一个字节的累加和低位字节。
FPGA 里做帧同步,最直接的办法是移位寄存器配合字节计数器。每个有效字节进来,先判断当前是否处于同步状态;如果没同步,就试探性匹配 0x42 0x4D 特征;一旦匹配成功,开始累计 32 字节,最后检查校验和。
// 帧同步模块片段 reg [7:0] frame_buf [0:31]; reg [5:0] byte_idx; reg [7:0] checksum; reg frame_sync; always @(posedge clk) begin if (uart_valid) begin frame_buf[byte_idx] <= uart_data; if (!frame_sync) begin // 尝试匹配帧头 if (byte_idx == 6'd0 && uart_data == 8'h42) begin byte_idx <= 6'd1; end else if (byte_idx == 6'd1 && uart_data == 8'h4D) begin byte_idx <= 6'd2; frame_sync <= 1'b1; end else begin byte_idx <= 6'd0; end end else begin if (byte_idx == 6'd31) begin // 校验并输出 frame_sync <= 1'b0; end byte_idx <= byte_idx + 1'b1; end end end这里有个比常见做法更稳妥的细节:帧同步头匹配时,PM2.5 传感器的字节流里可能凑巧在任意位置出现 0x42, 0x4D 组合,但只有当后续帧长字段等于 0x001C 时才真正进入同步态,否则继续滑窗搜索。如果只靠帧头两个字节就拉同步,误同步率会高很多。
校验和的 Verilog 实现只需在同步后的前 30 个字节累加求和,第 31 个字节和累加结果比较。如果校验失败,惯例是把这一帧丢弃,不更新寄存器。我在设计中会把校验失败计数暴露到一个状态寄存器里,调试时看这个数就知道是传感器坏了还是线路干扰。
4.2 滑动窗口滤波:均值窗口长度选 8 还是 16
PMS5003 原始输出在风扇转动周期内会有波动,这个波动不是测量误差,是气流扰动造成的真实变化。直接显示原始值会让数字跳来跳去,人眼看起来不舒服,上位机做的曲线也毛糙。滑动窗口平均是最简单且有效的处理手段。
滑动窗口滤波在 Verilog 里的实现方式:维护一个深度为 N 的 FIFO,新数据进来时写入队尾,同时把队首数据读出并从累加和中减去,累加和加上新数据。输出是累加和除以 N。这个结构在资源上只消耗 N 个字深的 RAM 或寄存器阵列和一个加法器。
module sliding_avg #( parameter WINDOW = 16, parameter DW = 16 )( input wire clk, input wire rst_n, input wire data_valid, input wire [DW-1:0] data_in, output reg [DW-1:0] avg_out ); reg [DW-1:0] buffer [0:WINDOW-1]; reg [DW+7:0] sum; reg [7:0] wr_ptr; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin wr_ptr <= 0; sum <= 0; avg_out <= 0; end else if (data_valid) begin // 减旧值 sum <= sum - buffer[wr_ptr]; // 更新窗口 buffer[wr_ptr] <= data_in; sum <= sum + data_in; // 输出均值 avg_out <= sum / WINDOW; wr_ptr <= wr_ptr + 1'b1; end end endmodule窗口长度选 8 还是 16 要根据响应速度的诉求来权衡。窗口越短,滤波后的数据越接近原始值,对突发污染事件的响应越快;窗口越长,曲线越平滑,但对浓度突变的响应会滞后。我的经验是,空气质量监测这种场景,16 个样本(约 16 秒)的窗口在平滑度和响应时间之间比较平衡。如果你做的是净化器控制器,需要快速感知颗粒物浓度变化触发风机调速,窗口取 4 会更合适。
4.3 输出接口设计:UART 上报和段码屏驱动的取舍
处理完的数据最终要输出。常见做法是 FPGA 定期把三个数据拼接成自定义帧,通过 UART 发送到上位机。帧格式可以设计成:帧头 0xAA 0x55 + 数据长度 + PM2.5 高字节/低字节 + 温度高字节/低字节 + 湿度高字节/低字节 + 校验和。波特率建议用 115200,因为数据量不大,9600 也够。
如果系统要接显示,用段码屏比用串口屏简单得多。FPGA 通过 GPIO 模拟段码屏的时序,显示 PM2.5、温度、湿度三行数据。也可以接 OLED,但 SPI 驱动的 OLED 需要额外的汉字字库存储,占用 Block RAM,对入门级 FPGA 来说有点紧张。
5. 避坑与排错:DHT22 读不出数、PM2.5 跳变、UART 乱码的现场排查
5.1 DHT22 一直返回 0xFFFF 或读取超时
现象:FPGA 端状态机始终卡在 WAIT_RESP 超时退出,或者读出的 40 位数据全是 1,校验和怎么都对不上。
原因:最常见的是数据线上拉电阻缺失。DHT22 数据线是开漏输出,没有上拉时主机释放总线后,线上电平悬空,从机拉低后无法恢复高电平,导致时序完全错乱。其次是启动低电平时间不够,DHT22 要求至少 18ms,状态机里定时器配置错误导致只有几毫秒低电平,传感器根本没被唤醒。
解决:先测量 DHT22 数据线的静态电平和波形。示波器挂在数据线上,正常时总线空闲电平为 3.3V;启动后能看到 20ms 低电平、随后两个 80μs 脉冲再跟着纷乱的窄脉冲。用万用表量上拉电阻是否为 4.7kΩ,再检查状态机里 START_LOW_US 参数,确保按传感器型号查规格书,不要想当然沿用 DHT11 的参数。
5.2 PM2.5 数据偶发跳变,滤波后仍有尖峰
现象:过滤波后曲线整体平滑,但每隔几秒出现一个异常尖峰,数值比正常高出一倍以上。
原因:UART 接收出现误帧。PMS5003 的 9600 波特率在长线传输时,信号边沿变缓,采样点可能落在位边沿附近。另外一个隐蔽原因是 FPGA 的复位释放没有同步,上电瞬间 UART 状态机跑到中间态,把异常位流当成数据打入帧缓冲。
解决:UART 接收模块里加双级同步器之外,还要在起始位检测时加宽度确认——低电平至少持续 8 个时钟周期才认定为有效起始位。另外检查电路板走线,PMS5003 的数据线和风扇电机线不要平行走太长的距离,电机开关瞬间的 EMI 会直接耦合进串行数据里。
5.3 温度读数比实际偏高 3~5°C
现象:湿度和 PM2.5 数据正常,但温度持续偏高,偏差稳定。
原因:DHT22 传感器靠近 FPGA 芯片或电源模块,PCB 上的热量传导导致传感器处环境温度高于真实气温。这是物理布局问题,不是程序问题。
解决:传感器用排线外置,跟板子拉开 10cm 以上的距离。如果结构上必须贴近主板,DHT22 和数据线之间加隔热垫片,或者换成带铜皮套管的延长线。
5.4 仿真正常但上板后状态机死锁
现象:Modelsim 仿真时 DHT22 读取流程完美,烧到板子上第一次读取成功,第二次就卡死。
原因:仿真环境里从机响应是理想时序,而实际上 DHT22 的读取周期要求两次读取间隔至少 2 秒。FPGA 里的读取状态机如果在 DONE 状态没有足够的等待时间,连续发起第二帧读取,传感器不响应,状态机又没处理超时,整个控制逻辑就锁死了。
解决:状态机从 DONE 回到 IDLE 时插入一个 2 秒的等待计数器,保证两次读取间隔满足传感器最低要求。同时 READ_BITS 阶段对每个位的高电平宽度测量加超时判断,超过 100μs 仍然没有边沿变化就判定线路异常,强制回到 IDLE。
5.5 UART 输出到上位机全是乱码
现象:FPGA 端发送的数据在上位机串口助手显示为乱码,但逻辑分析仪抓到 FPGA 引脚上的波形看起来正常。
原因:上位机配置的波特率和 FPGA 端不一致,或者 FPGA 端的波特率分频不是整数。50MHz 分频出 9600 波特率,参数为 50000000 / 9600 = 5208.33,取整 5208 导致的波特率误差约万分之六,这在 UART 规范允许范围内。但如果设计里时钟不是整数,比如用 33.333MHz 晶振分频 9600,误差超过 1%,通信就可能失败。
解决:确认上位机串口配置 8N1 无校验,波特率 9600 或 115200。检查 FPGA 代码里的分频参数计算,如果时钟频率不是标准值的整数倍,改用带小数分频的 DDS 方式,或者换一个方便分频的时钟频率,工程上最省事的是直接换 50MHz 或 100MHz 晶振。
6. 验证手段与进阶:用 ILA 观察单总线波形,把滤波结果存进 Block RAM
系统做完后验证优先级最高的两件事:时序正确性和数据可信度。DHT22 的位时序,逻辑分析仪观察最直观。没有逻辑分析仪时,用 FPGA 片内的 ILA(Integrated Logic Analyzer)核抓取 rx_line、state、bit_index 这几组信号,可以精确看到每一位数据的宽度。触发条件设为 WAIT_RESP 进入事件,就能捕获完整读取过程。
PM2.5 的数据验证可以这样:把 PMS5003 放到密闭盒子内,点一支蚊香作为颗粒物源,观察寄存器里 PM2.5 数值从初始值逐步爬升,再开窗通风观察下降过程。响应时间和数值变化趋势应该符合物理直觉。不要拿手指挡住传感器出风口做测试,那样会让风扇负载变化,得到的数据没有参考价值。
滤波效果的验证,我习惯把原始值和滤波值同时通过两个 UART 通道上送,或者写入 FPGA 里的 Block RAM,事后用上位机脚本读取画曲线。对比原始曲线的毛刺幅度和滤波曲线的平滑度,再量一下滤波后的阶跃响应延迟——窗口为 16 时阶跃响应大约 16 秒,符合预期。
进阶一点的做法是把采集数据存入双口 Block RAM,通过片上 MCU 软核或自定义 AXI 接口读取。这样 FPGA 就不只是传感器采集器,而是变成一个数据预处理节点,后续挂 Wi-Fi 模块或以太网协议栈时,数据通路已经现成。时序方面,如果开发板提供的是 100MHz 时钟,建议在顶层做一次 PLL 分频,给 UART 模块一个 50MHz 时钟域,避免分频系数太大导致计数位宽浪费。我自己的习惯是所有外部接口模块用独立的时钟使能信号来驱动,而不是到处做时钟分频,这样时序约束干净,也不会碰时钟域交叉的坑。这套系统从选型到调通,我大约花了两天时间,其中一半耗在 DHT22 的时序调试上。如果你打算复刻,先写 DHT22 模块,单独验证通过后再接 PMS5003,别把两个传感器的调试混在一起做。希望帮到你。
本文还有配套的精品资源,点击获取