恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

FPGA实现可配置PWM:从计数器比较到寄存器设计与testbench验证

  • 首页
  • 资讯中心
  • /
  • FPGA实现可配置PWM:从计数器比较到寄存器设计与testbench验证

相关资讯

Windows虚拟内存配置指南:从原理到实操,避免OOM崩溃 2026/9/17 2:18:48
232元4年WPS超级会员值不值?拆解六项高频权益与避坑指南 2026/9/17 2:13:48
WPS与WPS 365的本质区别:从个人工具到企业办公平台 2026/9/17 2:13:48

最新资讯

Windows 10安装错误“无法判断”排查指南:从日志到分区表全解决
魔百盒改Linux服务器,从吃灰到SSH连通只要40分钟
Kubernetes SIG Scheduling 2021 年度技术回顾:调度重排队优化、抢占性能提升与调度框架组件配置演进
Home Assistant Insteon 集成:使用 `insteon.print_im_all_link_database` 操作打印调制解调器 All-Link 数据库
使用 EIM GUI 激活 ESP-IDF 开发环境:打开 IDF 终端完整指南
Python第五次作业全解析:函数、文件读写与爬虫入门实战

今日推荐

每日热评|13% 的 Agent 技能带严重漏洞,这个注册表想用“验证+签名”解决信任危机
即梦AI保姆级教程:从生图到数字人,一站式搞定AI视频创作
BERT+LLM混合架构:突破NER长尾实体抽取瓶颈的工程实践

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

FPGA实现可配置PWM:从计数器比较到寄存器设计与testbench验证

发布时间:2026/9/17 2:18:48
FPGA实现可配置PWM:从计数器比较到寄存器设计与testbench验证 简介面向FPGA初学者的占空比可配置PWM脉宽调制方波产生器设计基于Verilog语言实现并配套完整testbench可应用于数字电路课程设计、电机调速、通信信号生成等PWM调制场景。工程基于Vivado 2019.2搭建压缩包共65个文件涵盖v源码、wdf仿真波形、txt说明文档、bat辅助脚本、mp4操作步骤录屏以及Vivado工程文件等整体仅2.06MB便于快速下载其中wdf为仿真结果波形、bat为辅助脚本、mp4为操作演示视频。占空比定义为高电平时间与周期时间之比本设计通过参数配置灵活调整该比例结合仿真波形可直观理解PWM调制原理与FPGA时序验证流程。资源内附详细代码注释、操作视频和工程目录梳理并单独给出英文路径及路径长度不超过148个字符等使用注意事项可帮助规避常见编译错误。目前已有610人浏览学习适合FPGA初学者课堂实验后自主复现与调试。1. 把“占空比可配置”从参数改成接口才是FPGA方式的PWMPWM脉宽调制在FPGA里的实现几乎每个做过数字逻辑的人第一反应都是“计数器比大小”。这个思路本身没错但很多入门项目把它写死成了两个常数——频率固定、占空比固定测试时用testbench看一眼波形就交差。等真要把这路PWM接到电机驱动或LED调光上改一个占空比得重新综合更不要提在运行中做呼吸灯、软启动、故障关断这类动态行为。问题往往不是PWM难写而是“可配置”这三个字没在设计层面立住。这篇文章要解决的正是从“能出波形”到“可用的PWM外设”这一段路:核心振荡器怎么选、占空比寄存器怎么设计、死区插入放在哪一级、testbench要覆盖哪些边界。全都基于Verilog代码可以直接跑仿真也可以上板验证。适合已经能写简单计数器、想正经做一个小型PWM控制器IP的FPGA开发者也适合正在准备数字IC笔试、面试时被问到“PWM产生器如何设计状态与边界”的人。2. 频率与占空比分离PWM产生器的寄存器级设计2.1 为什么“计数器比大小”不能直接对应占空比可配置常见教科书写法是counter counter 1然后pwm_out (counter threshold)。这种方法有三个根深蒂固的问题。第一threshold如果被写成常量模块一综合就锁死了占空比仿真里能改板上改不了。第二threshold的位宽直接决定可调分辨率但很多人不假思索地和计数器同宽结果频率一高占空比只能按大步进跳。第三也是最容易被忽略的计数器溢出周期和threshold比较器天然耦合改变频率要动计数器上限占空比刻度跟着漂。把频率和占空比拆成两个独立寄存器是工程上最常见的做法。频率由计数器的溢出周期决定占空比由比较器的阈值决定两者各管各的。这样设计出来的PWM模块行为上更像一个寄存器组控制的硬件外设而不是一段写死的逻辑。放到SoC或软核系统里只要把这些寄存器挂到总线上CPU就能在运行中改写占空比才算真正配得上“可配置”三个字。// 核心参数系统时钟频率与目标PWM频率 parameter SYS_CLK_FREQ 50_000_000; // 50 MHz系统时钟 parameter PWM_FREQ 20_000; // 20 kHz PWM频率适合电机驱动 localparam COUNTER_W 16; // 计数器位宽足够容纳分频结果 reg [COUNTER_W-1:0] cnt; reg pwm_out_reg; // 周期计数上限系统时钟/PWM频率计算得到 2500 localparam PERIOD_CNT SYS_CLK_FREQ / PWM_FREQ; always (posedge clk or negedge rst_n) begin if (!rst_n) cnt d0; else if (cnt PERIOD_CNT - 1) cnt d0; else cnt cnt 1b1; end这段代码把计数器上限单独提取成PERIOD_CNT占空比比较器完全独立于周期逻辑。实际修改频率只需要改PWM_FREQ这一个参数COUNTER_W的计算要保证PERIOD_CNT不溢出50 MHz除以20 kHz等于250016位宽绰绰有余。2.2 占空比寄存器与比较器的位宽匹配细节比较器位宽的设计有一个常见陷阱有人直接把占空比寄存器设成和cnt同宽然后往里写“百分比×满量程”这本身没错但意味着占空比可调步进是1/PERIOD_CNT。对2500周期的场景步进是0.04%看着很细可如果PWM频率调到1 kHzPERIOD_CNT变成50000步进变成0.002%精度反而“过剩”了。更合理的做法是明确占空比寄存器的语义——到底是绝对周期数还是万分比。前者叫duty_cycle_cnt直白但换个频率就要重新算数后者叫duty_thousandth寄存器里存的是0到1000的整数比较时做一次乘法再比。两种都有人用我更推荐绝对周期数原因是在FPGA里乘法和比较器都会消耗逻辑资源能省则省而且testbench里直接写duty_cycle_cnt 1250一眼就知道是50%占空比。module pwm_gen #( parameter COUNTER_W 16 ) ( input wire clk, input wire rst_n, input wire [COUNTER_W-1:0] period_cnt, // 周期上限运行时也可调 input wire [COUNTER_W-1:0] duty_cycle_cnt, // 占空比阈值 output reg pwm_out ); reg [COUNTER_W-1:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) cnt d0; else if (cnt period_cnt - 1) cnt d0; else cnt cnt 1b1; end always (*) begin // 计数器小于阈值输出高电平经典边缘对齐模式 if (cnt duty_cycle_cnt) pwm_out 1b1; else pwm_out 1b0; end endmodule注意比较用的是而不是这会让“占空比0”时整个周期输出恒低而不会出现一周期只有一个时钟的高脉冲。细节决定边界行为后面做testbench时会专门覆盖duty_cycle_cnt0和duty_cycle_cntperiod_cnt这两个极端。2.3 运行时改写寄存器同步使能与毛刺抑制占空比寄存器如果直接在组合逻辑里参与比较CPU或状态机在运行中改写它时输出可能出现一周期宽度的异常脉冲。原因很直观cnt在递增过程中比较器输入的阈值突然跳变组合逻辑输出瞬间翻转。电机驱动器对这种毛刺非常敏感轻则电流抖动重则MOSFET直通。解决办法是在寄存器写入路径上加同步使能。具体做法是外部总线写入先打到duty_buf等一个同步信号确认后再一次性载入duty_cycle_cnt。更简单但同样有效的方案是让duty_cycle_cnt只在cnt0的时刻被更新因为此时一个PWM周期刚结束新的占空比从下个周期开始生效。// 在cnt0时锁存新的占空比避免周期中途跳变 always (posedge clk or negedge rst_n) begin if (!rst_n) begin duty_cycle_cnt d0; end else if (duty_update_en cnt d0) begin duty_cycle_cnt duty_new_value; end end这个“周期边界对齐更新”的机制在PWM控制电机转速时尤其重要。试想你在50%占空比运行中突然改成10%如果更新发生在周期中途输出会多出一个宽度异常的高电平脉冲等效于给电机一个错误的加速度指令。对齐到周期起点后行为变成可预测的——下一个完整周期开始执行新占空比。3. Verilog实现从单路PWM到带死区与故障保护的完整模块3.1 边沿对齐还是中心对齐模式选择影响谐波与纹波上一章的计数器从0加到period_cnt再归零输出高电平从周期起点开始这叫边沿对齐PWM。它的优点是实现简单、资源占用少缺点是所有通道如果共享同一个计数器输出上升沿天然对齐开关瞬间电流叠加电源纹波大。中心对齐PWM则把比较器放在递增和递减两个阶段分别判断输出高电平对称分布在周期中点在H桥电机驱动和LLC电源里更常见。中心对齐的实现要控制计数器方向递增到period_cnt后变成递减到0再变回递增。占空比比较逻辑也要分两段考虑递增段cnt duty输出高递减段cnt period_cnt - duty输出高。代码量翻倍但换来的是更低的电磁辐射和更平滑的电流纹波。reg up_down; // 1递增 0递减 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt d0; up_down 1b1; end else if (up_down) begin if (cnt period_cnt - 1) up_down 1b0; else cnt cnt 1b1; end else begin if (cnt d0) up_down 1b1; else cnt cnt - 1b1; end end // 递增阶段与递减阶段分别比较 always (*) begin if (up_down) pwm_out (cnt duty_cycle_cnt) ? 1b1 : 1b0; else pwm_out (cnt period_cnt - duty_cycle_cnt) ? 1b1 : 1b0; end选哪种模式要看出路接什么。通用电机驱动用边沿对齐足够但如果你在做一个LLC谐振变换器的数字控制中心对齐能让驱动信号的偶次谐波更低变压器利用率更高。做FPGA开发时建议把两种模式都写进模块用一个cfg_center寄存器选择上板后再对比电源纹波或者电机噪音决定取舍。3.2 互补PWM输出与死区插入的实现位置电机驱动和半桥电路需要互补的两路PWM一路控制上管一路控制下管。这两路信号绝不能同时为高——否则上下管直通电流瞬间烧毁MOSFET。所以必须在PWM模块内部插入死区时间让两路信号的边沿错开。死区插在哪一级有讲究如果放在PWM产生器内部死区时间随频率自动缩放逻辑统一如果放在后续的驱动芯片里则依赖硬件外围灵活性差但安全性更高。FPGA里做死区通常是在PWM输出后加一段延时链。// 死区插入以时钟周期为单位的固定延时 parameter DEADTIME_CNT 4; // 4个时钟周期50MHz下为80ns wire pwm_raw; reg [DEADTIME_CNT-1:0] delay_line; always (posedge clk or negedge rst_n) begin if (!rst_n) delay_line d0; else delay_line {delay_line[DEADTIME_CNT-2:0], pwm_raw}; end wire pwm_high_side pwm_raw; // 上管跟随原始PWM wire pwm_low_side delay_line[DEADTIME_CNT-1]; // 下管延迟DEADTIME_CNT拍死区时间和开关频率密切相关。高频率下死区占整个周期的比例变大有效占空比被压缩要在大占空比时做补偿。常见做法是上管占空比写入寄存器时把duty_cycle_cnt加上死区计数下管那边自然少输出同样宽度等效平均占空比回到设定值。3.3 故障保护引脚PWM故障关断的优先级设计带故障保护是PWM外设和玩具计数的本质区别。电机驱动板上的过流保护、母线欠压、温度过高等信号都会拉低一个fault_n引脚要求PWM立即关断输出而不是等CPU来查寄存器。在Verilog里实现这个逻辑优先级必须放到最顶层fault_n拉低的瞬间pwm_out无条件输出安全电平通常是低电平并且要有一个独立的锁存器记住故障状态防止外部信号抖动导致PWM反复开关。always (posedge clk or negedge rst_n) begin if (!rst_n) begin fault_latch 1b0; end else if (!fault_n) begin fault_latch 1b1; // 故障锁定等待软件清除 end else if (fault_clear) begin fault_latch 1b0; end end assign pwm_out fault_latch ? 1b0 : pwm_raw;故障锁存的意义在于安全一旦触发必须由软件明确确认故障已经排除并写入fault_clear才能恢复输出。很多开发者会把fault_n信号直接和PWM产生逻辑做组合结果噪声干扰导致PWM在“关断—恢复—关断”之间剧烈抖动反而加剧了系统的不可控。锁存器是必须的。3.4 顶层封装把PWM模块变成可复用的IP核单通道的PWM到多通道PWM不只是复制always块那么简单。共享计数器、独立占空比寄存器、多个比较器输出是工程上最常见也最经济的多通道架构——一个计数周期内可以同时产生四路不同占空比的PWM。下面是四路PWM的顶层封装思路。module pwm_top #( parameter CH_NUM 4, parameter COUNTER_W 16 ) ( input wire clk, input wire rst_n, input wire fault_n, input wire [COUNTER_W-1:0] period_cnt, input wire [CH_NUM*COUNTER_W-1:0] duty_regs, // 4路占空比打包总线 output wire [CH_NUM-1:0] pwm_out ); // 共享计数器所有通道复用同一个计数周期 reg [COUNTER_W-1:0] cnt; // 每路独立比较器输出互不干扰 genvar i; generate for (i 0; i CH_NUM; i i 1) begin : pwm_ch_gen assign pwm_out[i] (cnt duty_regs[i*COUNTER_W : COUNTER_W]) ? 1b1 : 1b0; end endgenerate endmodule4路PWM的占空比寄存器打包成了一个宽总线在实际接入系统总线时可以映射到4个独立的地址。Vivado里这个模块可以直接封装成IP核导出到Block Design里和MicroBlaze或Zynq的PS端连接剩下的工作就变成纯粹的寄存器读写。占空比是16位的4路就是64位这样的打包方式在AXI总线上也能利用DATA_WIDTH32自然分两次写入。4. testbench编写验证占空比可配置的三个关键角度4.1 基础波形验证从50%改到25%测量高脉宽testbench的第一使命是回答“PWM波形对不对”。对着仿真波形数周期不叫验证正确的做法是编写自检查代码统计高电平持续时钟数和期望值做比较误差超过一个时钟周期就报错。下面是一个可复用的自检验证框架。timescale 1ns / 1ps module tb_pwm_gen; reg clk; reg rst_n; reg [15:0] period_cnt; reg [15:0] duty_cycle_cnt; wire pwm_out; // 周期参数1MHz PWM50MHz系统时钟period_cnt50 localparam PERIOD_TICKS 50; pwm_gen #( .COUNTER_W(16) ) uut ( .clk(clk), .rst_n(rst_n), .period_cnt(period_cnt), .duty_cycle_cnt(duty_cycle_cnt), .pwm_out(pwm_out) ); initial clk 1b0; always #10 clk ~clk; // 50MHz时钟 // 测量高电平持续时间的变量 integer high_count; integer total_cycles; reg pwm_d1; always (posedge clk) begin pwm_d1 pwm_out; if (pwm_d1 !pwm_out) begin // 检测到下降沿比较高电平持续时间 // 期望值为 duty_cycle_cnt if (high_count duty_cycle_cnt - 1 || high_count duty_cycle_cnt 1) $display(ERROR: 高电平宽度 %0d, 期望 %0d, high_count, duty_cycle_cnt); else $display(PASS: 高电平宽度 %0d, high_count); high_count 0; end else if (!pwm_d1 pwm_out) begin // 上升沿开始计数 high_count 1; end else if (pwm_out) begin high_count high_count 1; end end initial begin rst_n 1b0; period_cnt PERIOD_TICKS; duty_cycle_cnt 16d25; // 初始50% #100; rst_n 1b1; #1000; // 改成25%占空比duty12 duty_cycle_cnt 16d12; #1000; // 改成0%边界 duty_cycle_cnt 16d0; #500; $finish; end endmodule这个testbench最核心的思路是边沿检测加计数检测到PWM上升沿时清零计数器开始数时钟检测到下降沿时比较计数值和期望的duty_cycle_cnt。pwm_d1打了一拍目的是用pwm_d1 !pwm_out来识别下降沿避免了在高电平中间误判的可能性。testbench里修改占空比用的是直接阻塞赋值仿真器在下一个always块才会采样到新值所以不会产生竞争。落在实际时序上等效于CPU写入寄存器的延迟一拍生效和上一章的同步机制保持一致。4.2 边界测试duty0和dutyperiod必须恒低和恒高duty_cycle_cnt0时如果输出仍然出现一个单周期脉冲说明比较器用的是而不是。duty_cycle_cntperiod_cnt时如果输出仍然有一小段低电平说明周期计数器提前归零导致比较器在最后一个节拍翻转。两种边界行为要单独验证duty0时预计整段仿真时间pwm_out保持低电平high_count永远为0任何一个上升沿出现都意味着设计错误。dutyperiod_cnt时预计pwm_out恒为高理论上high_count会无限递增能力够的testbench会设置一个超时上限来结束统计因为正常路径下永远不会出现下降沿。边界测试的价值在于暴露比较器实现细节。实际硬件中cnt到period_cnt-1时归零如果比较条件是cnt duty且dutyperiod_cnt那么仅有period_cnt-1拍为低剩余全部为高。这个行为从表达式上看满足“恒高”的预期但因为周期计数器本身漏掉了一个时钟占空比实际是(period_cnt-1)/period_cnt而不是100%。差异在普通应用里无感但在电池充电管理这类要求高精度占空比的场景这会直接导致输出达不到设定值。4.3 动态切换测试不同时刻改写占空比寄存器检查周期对齐前两种验证都是静态的——设置一个占空比跑一段时间再改另一个。真实系统里占空比是动态变化的比如呼吸灯、电机软启动、电源调压。验证这种场景的关键是占空比跳变的那个周期间高电平宽度要么按旧值完整输出要么按新值完整输出绝对不能出现一个被截断的高电平脉冲。我这个模块的testbench会在一个PWM周期的前半段改写duty_cycle_cnt然后统计当前周期高电平宽度。如果设计正确当前周期仍按旧值完整输出下一周期才按新值。如果设计了cnt0锁存机制这个断言是天然成立的如果没做仿真会立刻报出高电平宽度异常的ERROR信息。// 动态改写在cnt10时把占空比从25改成12 initial begin #1000; // 等一个周期结束后的10个时钟 wait (cnt d10); duty_cycle_cnt 16d12; end动态切换的仿真断言比静态更严格它要求设计者对“何时生效”有精确的预期而不是只满足“最终能出来25%的占空比”。这恰恰是FPGA设计和写C语言的最大区别——C程序的一次赋值立即对后续所有读操作生效而硬件逻辑需要一个明确的同步点。4.4 Modelsim与Vivado的仿真流程对比市面上的FPGA工具链中Vivado自带XSimModelsim则更通用。代码本身是纯Verilog没有IP核的情况下两个工具跑同一套testbench的结果应当完全一致。区别在于操作流程Vivado里在Sources面板右键testbench文件选“Simulation”XSim会自动编译并加载波形窗口Modelsim需要在命令行编译库或者用工程模式自动映射。如果你的PWM模块里使用了Vivado原语比如BUFG或MMCME2把sim_primitive库加进仿真库列表才能免报错。modelsim 如何仿真verilog文件这个操作层面的问题其本质是库映射。标准流程是在安装目录下找到vlib和vmap命令为Xilinx和Lattice分别建库然后在工程里指定该库。用命令行跑一次vsim -L xil_defaultlib work.tb_pwm_gen比每次点GUI添加库文件更高效也更适合脚本化的回归测试。5. 信号质量分析与占空比精度的边界条件5.1 理论输出占空比与真实输出的偏差来源PWM输出经过RC低通滤波器后平均电压等于占空比乘以电源电压这是pwm控制RC电路的理论基础。但滤波器只能平均掉信号平均不掉的是时间量化误差。如果占空比设为duty3、period10理想输出是30%高电平。实际上计数器从0递增到9比较器在cnt0,1,2时输出高一共3个时钟精确的30%没问题。可如果把周期改成period7、duty2理想值是28.57%实际输出是2/728.57%一个时钟都不能差了。真正引入误差的是死区时间。死区插入后上管和下管的实测占空比都变小了而负载上看到的是被死区“吃掉”掉的拓扑。驱动感性负载时电流续流路径会把这部分被吃掉的时间补回来表现是输出平均电压和理论值偏差不大但驱动纯阻性负载时死区损失完全体现为电压下降。这个时候就要做占空比补偿向比较器加一个死区时间的偏移量。5.2 寄存器位宽对占空比步进的实际限制以50MHz系统时钟为例位宽每增加1位最大计数翻倍占空比步进精度也随之提升。但当period_cnt不是2的幂时寄存器位宽并不能完全决定步进。举例来说50 MHz系统时钟输出20 kHz PWMperiod_cnt250016位寄存器可以精细表达0.04%的步进如果输出100 kHz PWMperiod_cnt500步进变成0.2%。一味的加宽寄存器并不生效因为基数period_cnt不够大。所以工程上更常见的方式是高频率用大分频系数低频率用小分频再配合调频换挡策略让占空比步进跨档保持一致。fpga信号发生器ego1项目里经常出现的一个调优方向是高频PWM下LED的亮度闪烁。人眼对50 Hz以下的闪烁敏感所以PWM频率要有意识地选在1 kHz以上同时占空比步进至少256级。按这个要求反推寄存器位宽:如果period_cnt2500256级对应步进约9.77计数器需要输出10位以上的变化才能看到肉眼可辨的亮度差异16位设计通常能满足。5.3 提高有效占空比分辨率抖动与微步进策略某些应用需要在固定频率下实现超过1/period_cnt的级数比如pwm驱动led rgb要求平滑呼吸效果pwm控制电机要求低速时转速变化细腻。单周期内分辨率已到极限但可以在多个周期之间用“时间平均”微调——这一周期输出dutyd下一周期输出dutyd1高频时负载网络的平均行为等效于d0.5。这就是抖动技术也是低成本数字化PWM提升有效分辨率的标准做法。Verilog里实现需要两个计数状态一个主占空比计数一个抖动方向计数器。以二阶Sigma-Delta为例每256个PWM周期做一次累加余项累加到阈值就输出一个额外的高脉冲。reg [7:0] sigma_accum; // 余数累加器 reg [15:0] effective_duty; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sigma_accum d0; effective_duty duty_cycle_cnt; end else begin // 把余数累加到当前占空比中产生微步进 sigma_accum sigma_accum duty_cycle_cnt[7:0]; // 取低8位作为余数 if (sigma_accum duty_cycle_cnt[7:0]) effective_duty duty_cycle_cnt 1b1; else effective_duty duty_cycle_cnt; end end这个逻辑的直觉理解是低8位余数不断累加累加到一定量就借出一个高脉冲。宏观上256个周期内的平均占空比不再受1/period_cnt的限制能被分解到更细的级别代价是PWM频谱上多出小幅的噪声抬升。电机应用通常不在意音频电路要评估一下是否引入可听噪声。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号