☰
计数器技术全解析:从51单片机、FPGA到PLC的工程实践与项目复盘
2026/10/1 3:45:17 网站建设 项目流程

计数器这名字听着不起眼,但干过嵌入式、工控或者数字电路这行的人都知道,它几乎是所有逻辑控制的地基。学生时代搞51单片机跑秒表,工作以后做产线统计、车位检测,绕来绕去都离不开“计数”这件事。很多刚入行的朋友觉得计数器不就是数个数嘛,可等真正动手,从555搭出来的硬件计数电路,到FPGA里一行Verilog写出的模值计数器,再到西门子S7-1200和三菱GX Works2里面的计数器指令,每个场景的坑都不太一样。这篇我就把自己在硬件计数、单片机定时器、FPGA计数器、PLC计数器这几个方向上的实操经验串起来聊一聊,最后还会复盘易拉罐计数和停车场车位计数这两个典型项目,把选型思路、关键代码和排查技巧一次性说清楚。不管你是刚摸开发板的新手,还是在产线上调试的老手,都应该能从里面找到点可以直接用的东西。

1. 计数器到底是什么:三个层面理解它

1.1 从硬件电路的角度看

计数器的本质是一组能保存状态的触发器(Flip-Flop),每来一个有效沿,状态就按照预定规律变化一次。我当时第一次在数字电路实验课上搭4位二进制计数器,用四个D触发器和门电路折腾了一下午,焊完才意识到:计数器不是一个“会加一”的黑盒子,而是一个有记忆功能的时序逻辑电路。它必须有时钟输入、复位输入,还要决定是加法、减法还是可逆计数。这个基础认知很重要,因为后面不管是看Verilog代码里的cnt <= cnt + 1,还是看PLC里的CTU指令,底子都是同一套东西。

1.2 从单片机/软件的角度看

到了单片机层面,计数器更多时候被包装成“定时器/计数器”外设。同一个硬件模块,内部其实就是一个不断累加的寄存器,只不过给它提供计数脉冲的时钟源有两种:一种是内部的机器周期或分频时钟,这时它叫定时器;另一种是外部引脚输入的脉冲,这时它才真正叫计数器。刚学51单片机的时候,很多人都搞混定时和计数,其实只要记住一句话:定时器数的是内部时钟的周期,计数器数的是外部事件的发生次数,硬件结构几乎一样,区别只在时钟来源。

1.3 几个绕不开的关键参数

不管用什么平台实现计数器,有几个参数永远要心里有数:

  • 计数模值:计数到多少回绕或者停止。比如模10计数器数到9就归零,模65536的16位计数器数到65535回零。
  • 初值:从哪个数开始数。51单片机定时器常用“重装初值”的方式,计算出要定时的时间,再反推初始值。
  • 计数方向:加法、减法还是可逆。PLC里会有CTU、CTD、CTUD之分。
  • 使能和复位:能不能暂停计数,以及什么时候清零。这条在工程上特别关键,尤其是产线统计,复位时机没处理好,统计数据就是一笔糊涂账。

可以这么类比:计数器就像操场跑圈用的计圈器,每到一圈按一下按键,寄存器相当于表盘上显示的数字。定时器则是计圈器接到了固定节拍器上,每响一声加一。搞懂了本质,换到哪个平台都只是变个写法而已。

为了更直观,把常见几类计数器方案放在一起对比:

实现方案典型器件/平台计数速度主要优势主要局限
硬件计数器555产生脉冲 + CD4017取决于输入脉冲频率纯硬件响应快,无需编程逻辑固定,改一次模值要改电路
单片机计数器51的T0/T1等一般不超过系统时钟的几分之一配置灵活,能配合显示和通信软件处理中断有一定延迟
FPGA计数器Verilog RTL可以做到很高,受时钟约束并行处理、时序精确可控开发门槛较高,成本相对大
PLC计数器S7-1200、三菱FX系列等普通计数取决于扫描周期工业环境稳定,梯形图直观高速场景需用高速计数模块

2. 硬件计数器实战:555定时器加CD4017

2.1 555到底是计数器还是振荡器

先说清楚一个容易被误解的点:NE555本身不是计数器芯片,它是个定时/振荡芯片。网上经常说“555计数器”,其实完整方案是“555产生计数脉冲,后面再接一个计数芯片”。555在这里负责提供稳定的脉冲源,常见用法是构成多谐振荡器,输出一个方波。要让这个方波的频率准确,就得会算它的充放电时间常数。

2.2 一个经典的脉冲计数电路

我当时做过一个纯硬件的产品计数演示,结构很简单:

  1. NE555接成多谐振荡器,输出连续方波作为计数时钟。
  2. CD4017作为十进制计数器,输出端Q0到Q9依次出现高电平,表示“数到几”。
  3. 用一排LED接在CD4017的Q输出端,直观看到计数状态变化。
  4. 想复位清0,把CD4017的MR(复位)引脚接一下高电平就行。

这个电路适合给纯硬件入门的同学练手,因为每个芯片功能单一,出问题也好排查。一般排查顺序是:先拿示波器看555的OUT脚有没有方波,再看CD4017的时钟引脚有没有信号,最后看Q输出逻辑对不对。很多时候不是芯片坏了,而是面包板接触不良或者复位引脚悬空乱跳。

2.3 关键参数计算和选型

NE555多谐振荡器的频率计算公式是:

f = 1.44 / ((R1 + 2 * R2) * C)

这里的R1是连接VCC和放电脚的上电阻,R2是充放电共用的电阻,C是定时电容。我实际想要一个大约1Hz的慢速脉冲做演示,选了R1=1kΩ,R2=680kΩ,C=1μF代入公式:

f = 1.44 / ((1k + 2 * 680k) * 1μF) ≈ 1.44 / 1.361 ≈ 1.06Hz

实测输出周期约0.94秒,和计算基本吻合。需要注意,公式里的电阻单位要和电容单位换算正确,千万不能把兆欧和微法直接相乘后不换算,我第一次算就是因为单位搞混,算出个完全离谱的值。实际搭建时,定时电容尽量用CBB电容或钽电容,普通电解电容漏电大,频率会漂,实测能差百分之十几。

2.4 纯硬件计数电路的工程局限

这类纯硬件计数方案虽然直观,但在真实产线上很少单独用。原因有二:一是计数结果要显示的话,CD4017只能配合LED或数码管,数据不好远传;二是没有存储和判断能力,比如“数到100就停机”这种逻辑,需要再加比较器和触发器,电路会变得很复杂。所以工程上更常见的做法是,用传感器产生的脉冲直接送给单片机或PLC的计数器引脚,由程序或梯形图完成后续判断和输出。

3. 51单片机定时计数器讲解:从寄存器到实际配置

3.1 定时器和计数器在硬件上其实是一家

51单片机经典的定时/计数器T0和T1,内部都是16位加1计数器,核心寄存器是TH0/TL0或TH1/TL1。它们的工作模式由TMOD寄存器决定,启动控制在TCON寄存器里。当TMOD里的C/T位为0时,计数器数内部机器周期,这就是定时模式;当C/T位为1时,数外部引脚T0或T1输入的脉冲,这就是计数模式。GATE位的用途也值得一提,它可以控制计数只在外部INT引脚为高时进行,用来测量某个外部信号的持续时间很实用。

3.2 定时器初值的计算过程

以最常用的模式1(16位定时器)为例,假设晶振12MHz,机器周期就是1μs,我要定时50ms,那要计数的机器周期个数是50000。16位计数器的最大计数值是65536,所以初值就是65536 - 50000 = 15536,换算成十六进制是0x3CB0。程序里就写成:

TH0 = 0x3C; TL0 = 0xB0;

这里容易犯的毛病是只给TH0和TL0赋初值,却忘了在中断里重装初值,结果第一次定时准,后面全都变成65.536ms了。所以我习惯在中断服务函数开头直接重新给TH0和TL0赋值,保证每次溢出后都从同一个起点开始数。

3.3 一个可复用的51定时器代码示例

下面是一段我实际调试过的定时器0初始化代码,定时50ms,中断里翻转LED,可以当模板直接用:

#include <reg51.h> sbit led = P1^0; void Timer0_Init(void) { TMOD &= 0xF0; // 清除T0的模式位 TMOD |= 0x01; // 设置T0为模式1,16位定时 TH0 = 0x3C; // 50ms定时初值高8位 TL0 = 0xB0; // 50ms定时初值低8位 ET0 = 1; // 允许T0中断 EA = 1; // 打开总中断 TR0 = 1; // 启动定时器0 } void Timer0_ISR(void) interrupt 1 { TH0 = 0x3C; // 中断里重新装初值 TL0 = 0xB0; led = !led; // 翻转LED,周期为100ms } void main(void) { Timer0_Init(); while (1); }

这段代码的关键在于TMOD的赋值方式。我用了先清后或的方式,避免影响T1的配置,这种写法在同一个程序里同时使用T0和T1时特别有用,不容易互相覆盖。

3.4 用T0做外部脉冲计数

把C/T位改成1,T0就变成计数器了。外部脉冲从P3.4(T0引脚)输入,每来一个下降沿,计数器加1。使用前有个细节要注意:外部脉冲的宽度和频率必须满足单片机的采样要求,通常是至少保持若干个机器周期,频率太高会漏计数。此时TH0和TL0的值就是实际的脉冲个数,读取的时候需要先读TH0再读TL0,否则可能读到“半个”变化过程中的乱值。

晶振频率偏差是定时不准的常见根源。如果用12MHz晶振,实际频率可能是11.996MHz,一天积累下来误差挺可观。工程上需要精准定时时我会用外部有源晶振或者GPS授时模块对齐,但做普通计数统计,这个误差完全在可接受范围。

4. Verilog计数器的设计思路与RTL实现

4.1 FPGA里的计数器为什么无处不在

在FPGA设计里,计数器不只是“数数”这么简单。几乎所有的时序逻辑都需要计数器参与:分频产生各种时钟、状态机跳转计时、串口波特率生成、数码管扫描刷新,本质上都是计数器的应用。所以verilog计数器是FPGA入门必须吃透的内容,它考察的是你对时钟沿、复位方式、位宽溢出这几个基本概念的掌握程度。

4.2 一个带异步复位的模10计数器

下面这段是典型的同步时序逻辑写法,模10计数器,带异步复位和使能:

module counter_10 ( input wire clk, input wire rst_n, input wire en, output reg [3:0] cnt ); always @(posedge clk or negedge rst_n) begin if (!rst_n) cnt <= 4'd0; else if (en) begin if (cnt == 4'd9) cnt <= 4'd0; else cnt <= cnt + 1'b1; end end endmodule

这里面有几个新手特别容易踩的点。第一,复位用rst_n这种低电平有效信号时,条件判断里必须写!rst_n而不是rst_n,写反了复位失效,上板跑起来就是未知状态。第二,比较条件用的是cnt == 4'd9,不要图省事写成cnt == 10,因为cnt位宽只有4位,10根本表示不了,综合器会告警。第三,非阻塞赋值<=在时序逻辑里是标配,不能在同一个always块里混用阻塞赋值=,否则仿真和实际行为会不一致。

4.3 从计数器到分频器的演进

计数器最常见的应用之一就是分频。想得到时钟的偶数分频,直接对计数器溢出位取反就行。比如系统时钟50MHz,我需要1MHz的时钟,那就是50分频,计数值从0加到24翻转一次,再从25加到49翻转回来。代码如下:

module clk_div_50 ( input wire clk, input wire rst_n, output reg clk_out ); reg [5:0] cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt <= 6'd0; clk_out <= 1'b0; end else begin if (cnt == 6'd49) begin cnt <= 6'd0; clk_out <= ~clk_out; end else begin cnt <= cnt + 1'b1; end end end endmodule

这个代码我建议初学者自己手动跑一遍仿真,重点看clk_out的翻转周期是不是正好是clk的50倍。实际调试中还可以用逻辑分析仪抓引脚波形,光靠仿真有时候看不出竞争冒险问题,上板实测更靠谱。

4.4 上板调试的独家体会

做FPGA计数器项目时,最容易出现“仿真对了,上板不对”的情况。原因大多是异步信号没有处理:外部按键直接作为复位或使能信号,在FPGA内部会产生亚稳态。解决办法很标准,就是把外部信号经过两级D触发器打拍同步后再用,也就是常说的“双触发器同步器”,能极大降低亚稳态概率。另外,计数器的位宽一定要算准,预估最大计数值是多少,就留出足够的二进制位,不然计到一半溢出了,逻辑直接乱掉。

5. PLC计数器深度解析:西门子S7-1200与三菱GX Works2

5.1 西门子S7-1200计数器说明

S7-1200的计数器指令在TIA Portal里以功能块的形式提供,常见的三种是CTU(加计数)、CTD(减计数)和CTUD(加/减计数)。与老款S7-200的计数器不同,S7-1200的IEC计数器需要分配一个背景数据块,用来保存计数器的当前值和内部状态。刚开始从200转1200的人经常问,为什么我拖一个CTU出来会提示我选DB块,其实就是这个原因。

以CTU为例,关键参数有:

  • CU:计数脉冲输入端,每个上升沿计数值加一。
  • R:复位端,为1时CV清0。
  • PV:预设值,达到这个值后Q输出为1。
  • CV:当前计数值。
  • Q:计数输出,CV >= PV时导通。

这种“当前值+预设值+复位端”的结构,是PLC计数器最通用的框架,了解一个品牌后,换到其他品牌只是名字和调用方式略微变化。

5.2 S7-1200计数器在程序里怎么用

在TIA Portal里新建一个CTU指令后,我一般会这样配置:在FC或FB里调用CTU,背景DB命名为“Counter_Product”,CU接一个传感器信号“Sensor_Pulse”,R接复位按钮“Reset_Btn”,PV设为100,Q接一个输出线圈“Full_Lamp”。当传感器检测到100个脉冲后,Full_Lamp置1,表示达到设定数量。

如果你习惯用SCL,也可以直接调用IEC计数器块:

"Counter_Product"(CU := "Sensor_Pulse", R := "Reset_Btn", PV := 100, Q => "Full_Lamp", CV => "Current_Value");

需要注意,普通计数器指令受PLC扫描周期限制,输入信号的频率不能太高。如果现场传感器脉冲很密集,比如高速产线每秒几十上百个脉冲,必须使用S7-1200的高速计数器(HSC)功能,通过组织块中断和工艺对象来处理,普通CTU会漏计很多。

5.3 三菱PLC计数器C的用法与GX Works2配置

三菱FX系列PLC的计数器用C编号,分16位增计数和32位增/减计数两大类,还有一类高速计数器专门接高速输入端子。GX Works2里配置计数器的思路和西门子略有不同:三菱PLC的计数器编号自带“保持型/非保持型”属性,比如C0到C199是非保持型,断电归零;C200到C234是保持型,断电后能记住当前值。做产线表单统计时一定要选保持型,否则突然停电,当前计数值全丢。

在GX Works2里写计数器梯形图,典型的两行逻辑:

  • 第一行:X0作为计数脉冲输入,驱动线圈C0。
  • 第二行:C0的常开触点驱动Y0输出,表示计数到达。

比如X0是一段检测传感器,C0的设定值在指令里写成K100,当X0接通次数达到100次时,C0触点动作,Y0得电。执行这段逻辑时,三菱PLC是按扫描周期刷新普通计数器的,所以同样不能用于超高速输入。要做高频计数,必须用C235等高速计数器端子,配合程序里的SPD指令或DHSCS指令读取。

这里要提一个坑:三菱的普通计数器指令在目标值到达后,即使脉冲继续来,计数值保持不再增加。如果现场想达到目标后继续累计,需要额外用别的计数器或改用比较指令。这个行为特点和西门子CTU的“到目标值后继续增加”不太一样,跨品牌做项目时要格外留意。

5.4 用PLC计数器实现产线产品统计

我做过一个最简单也最实用的产线统计逻辑:传送带上有光电传感器,每过一个产品就输出一个脉冲,PLC计数,达到设定数量后声光报警提示换箱。整个逻辑的核心就是:传感器信号接到PLC输入点,计数器指令累加,当前值送到触摸屏显示,达到预设值触发输出。这个方案稳定可靠,触摸屏还可以随时修改预设数量,比纯硬件计数器灵活太多。做这类项目时,比较花时间的往往是传感器安装位置和抗干扰处理,比如光电传感器的灵敏度调整、屏蔽线接地,这些物理层面的细节对计数准确度影响极大。

6. 两个热门应用场景完整拆解

6.1 易拉罐计数器项目方案

易拉罐计数的需求很典型:生产线上易拉罐快速通过,统计数量,达到设定装盒数量时通知工人或驱动挡板动作。我之前做过一个以PLC为核心的方案,传感器选的是对射式光纤传感器,因为易拉罐是金属反光面,用漫反射光电容易误判,用对射光纤检测稳定很多。传感器输出接到PLC高速计数输入端子,PLC内部做计数、比较和驱动输出。

如果不用PLC,用单片机也能实现类似功能,但要注意信号抖动问题。易拉罐经过时,传感器输出不是理想的方波,会有多次抖动,直接计数会多计。解决办法有两种:硬件上用RC低通滤波或施密特触发器整形;软件上做延时消抖,比如检测到下降沿后延时10ms再采样,确保同一个产品只计一次数。STM32等单片机实现起来也不难,外部中断加定时器消抖,计数器记录脉冲个数,最终显示在OLED屏幕上。

这里的关键参数是传感器响应时间。产线速度很高时,两个易拉罐之间的时间间隔极短,传感器响应太慢会丢包。选型时要算好:假设产线每秒过5个易拉罐,两个罐体间隔最小200ms,那传感器的响应时间至少要在20ms以内,实际留2到3倍余量,用响应时间小于10ms的传感器比较稳妥。计数频率越高,对控制器接口的要求也越高,普通单片机外部中断在低频时没问题,几十千赫以上就要考虑硬件计数器或专用计数芯片了。

6.2 基于32的简易停车场车位计数器

停车场车位计数器是热搜词里比较真实的需求。做一个基于STM32的简易停车场系统,最常见的检测方案是地感线圈和红外对射。地感线圈准确率高,但要切割地面施工,成本大;红外对射安装简单,适合做演示和简易改造。我做过一个基于红外的方案,每个车位入口装一对对射式红外传感器,车辆经过时遮挡光线,传感器输出电平变化,STM32检测到下降沿/上升沿就更新车库剩余车位数,剩余车位显示在数码管或LCD上。

用STM32做的核心逻辑可以概括为:

// 伪代码:外部中断检测车辆进入/离开 void EXTI_IRQHandler(void) { if (进入方向传感器触发) { if (剩余车位 > 0) 剩余车位--; } if (离开方向传感器触发) { if (剩余车位 < 总车位) 剩余车位++; } }

实际做到能稳定运行,比这个伪代码要复杂得多。首先是传感器信号的消抖,红外对射在车辆遮挡瞬间会产生多次沿跳变,不消抖就会一次过往计好几辆车。我当时的做法是在外部中断里记录时间戳,小于200ms的重复触发直接忽略。其次是断电保持问题,停车场不可能一直有人盯着,断电后剩余车位数必须保存在STM32内部Flash或外部EEPROM里,重新上电能恢复。第三,传感器安装高度和对齐角度很有讲究,装歪了或者阳光直射传感器,会产生大量误报,现场调试时我吃了不少亏。

6.3 多方向计数和特殊情况处理

现实场景比想象中复杂。比如易拉罐输送带可能有两路汇流,停车场可能有进有出,这些都是“双方向计数”问题。单一计数器只能加不能减,所以需要两个计数器:来的方向加一,走的方向减一。如果用PLC实现,可以用CTUD加/减计数器,一个指令就搞定;用单片机做,就是两个外部中断分别处理加减逻辑。更麻烦的是“同一辆车倒车反复遮挡”的情况,光靠两个传感器还不够,需要逻辑判断触发顺序,这就上升到状态机设计了。停车场车位计数做到后期,我建议至少在入口和出口各放两个传感器,通过传感器被遮挡的顺序判断真实进出方向,能过滤掉大部分误判。

7. 常见问题与排查技巧实录

计数器项目出问题,归纳起来其实就几类:信号源、逻辑配置、干扰和掉电保存。下面这张表是我在实际调试中遇到的高频问题,基本覆盖了从硬件到软件的排查方向。

现象可能原因快速排查方法
计数值乱跳,没东西经过自己加传感器灵敏度太高、线路干扰检查屏蔽层是否接地,调整传感器灵敏度;用示波器看输入波形是否干净
实际经过100个,只计了80多个脉冲频率超过计数器上限换高速计数器模式或专用计数芯片;检查扫描周期是否过长
PLC计数器到目标值后不再累计该品牌计数器到达预设值后的行为查阅手册,改用加/减计数器CTUD或比较指令完成后续统计
51单片机定时器定时时间偏长/偏短初值没在中断里重装、晶振不准确认TH/TL是否在中断里重载;用频率计校准晶振
555电路没有输出脉冲定时电容漏电、供电不足用示波器量NE555的OUT脚;检查VCC是否稳定在5V;更换CBB电容
Verilog仿真正常,上板计数异常外部信号未同步、位宽溢出外部信号加两级D触发器打拍;检查计数器位宽是否足够
停车场车位断电后数据清零没有做掉电保存用Flash或EEPROM定期保存计数值,上电时读取恢复
易拉罐计数重复计传感器输出抖动增加软件消抖或硬件RC滤波、施密特触发器

排查计数类故障,我摸索出一个固定套路:先看原始信号,再看控制器的接收,最后看逻辑处理和数据存储。比如计数值不对,我第一件事永远是拿示波器或逻辑分析仪看传感器输出端的波形,看脉冲个数是否和实际一致。很多时候问题根本不在程序,而在传感器安装位置、信号线干扰或者电源纹波,这些物理层面的东西没搞定,软件怎么写都白搭。

最后分享一点实际做项目的体会

计数器项目看上去小,真做好其实挺考验人的细节功底的。我个人的感受是,做这类项目前一定先想清楚三个问题:现场的计数频率是多少,需不需要断电保存,信号源会不会有抖动。这三个问题确定了,方案基本就成型了。另一个实用习惯是,无论用单片机还是PLC,都先把计数功能单独调通,再接显示、报警这些外围功能,分步联调会省很多排查时间。做产线或者停车场的项目时,数据准确性是第一位的,计错了再好看的界面也没有用。以后如果再让我给新人推荐练手项目,我一定还是首推计数器,因为从纯硬件电路到Verilog到PLC,它一条线串起了电子工程师需要的大部分基本功。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询