Note 01 of 02: 笔记 01 / 02:Latching Switches and Counter Control自锁按键与计数控制

Studying latching contacts, input bias, debouncing, state changes, and elapsed-time counting to define predictable pause and reset behaviour.从自锁触点和输入上下拉,研究消抖、状态变化与计数时序,理清暂停和复位怎样按预期响应。

Pause and reset each take one switch, yet once they reach the program they need quite different handling. Pause has to keep the counter sitting in a state; reset can be defined as clearing the number at the moment a particular action happens. Those two six-pin latching switches were mapped back then with a borrowed multimeter, one pin pair at a time. What follows picks up from there: how the switch's mechanical position, the voltage on the pin and a single program action actually correspond.

暂停和复位各用一只开关,接进程序以后,处理方式却差得挺远。暂停要让计数器一直待在某个状态,复位则可以约定在某个动作发生的那一下把数字清零。当年这两个六脚自锁按键,是拿借来的万用表一对一对量通的。接着往下要弄清楚的是,开关的机械位置、引脚上的电平、程序执行的一次动作,三者到底怎么对应。

Switch Contacts开关触点

The course uses six-pin latching pushbuttons. Latching describes the mechanical action: press once and it stays in one position, press again and it switches back. How the six pins connect depends on the contacts inside. Lifting a finger does not necessarily return the contacts to where they started, and pause shows this clearly, because the hand has let go and the counter stays paused.

课程用的是六脚自锁按键。自锁说的是机械动作,按一下停在一个位置,再按一下切回来;六只脚怎么连通,要看里面的触点结构。手指松开了,触点不一定回到原位。放到暂停功能上就很直观,手已经松了,计数器照样停着。

Reading the contacts starts with taking the switch out of the circuit, numbering its six pins from a fixed viewing direction, and recording continuity in each of the two stable positions. If one pin connects to its left-hand neighbour in one position and to its right-hand neighbour after switching, that is a changeover contact group; the second group gets its own check. Recording the viewing direction with the numbers is what lets the drawing go back onto the physical part correctly. Drawing those connections first and only then deciding which wire goes to the input and which to ground saves a lot of guessing while wiring.

要读懂触点,先把开关从电路里拆出来,从固定的方向给六只脚编号,再用通断档分别记下两个稳定位置的导通关系。某只脚在一个位置和左边的脚通,切过去以后改成和右边的通,这就是一组转换触点;另一组也单独确认。编号和观察方向要一起记,画出来的图才能原样接回实物。先把这张图画清楚,再决定哪根接输入、哪根接地,比边接边猜省事得多。

Course slide showing six-pin latching switches, pull-up and pull-down circuits, and an input test sketch / 课程中的六脚自锁按键、上下拉电路与输入测试程序
Course slide showing six-pin latching switches, pull-up and pull-down circuits, and an input test sketch课程中的六脚自锁按键、上下拉电路与输入测试程序

The Default Input Level默认输入电平

Once the contacts are known, the open state still needs a definite level. The group's report records a 10 kΩ pull-up from the Arduino pin to VCC, with the switch's other side grounded, so a press reads low; the course slide draws the same pull-up arrangement. With the contact open, the resistor pulls the input high, and with it closed the input is grounded. Using a different contact pair can flip how mechanical position maps to voltage, so HIGH and LOW in the program only mean something once read against the actual wiring.

触点弄清楚了,还得给断开的状态安排一个确定的电平。小组报告里记的是 10 kΩ 电阻从 Arduino 引脚上拉到 VCC,开关另一端接地,按下读低电平,课件里画的也是这种上拉接法。触点断开时,电阻把输入拉高;闭合时输入接地。换一组触点用,机械位置和高低电平的对应关系就可能反过来,程序里的 HIGH 和 LOW 代表什么,要对着实际连线才说得清。

That resistor has a second job that is easy to overlook. With the contact closed, it is the only thing between supply and ground, and it limits the current; 5 V across 10 kΩ comes to about 0.5 mA. Replace it with a wire and closing the switch would short the supply straight to ground. Setting the level while open and limiting current while closed, seen together, make up the whole job of a pull-up.

这只电阻还有一个容易被忽略的活儿。触点闭合时,电源到地之间隔着的就是它,电流被它限住了,按 5 V、10 kΩ 算大约 0.5 mA。要是把电阻换成一根导线,开关一闭合,电源就直接短到地。断开时定电平,闭合时限电流,两种状态放一起看,上拉电阻的作用才算完整。

The course test sketch sets its inputs to INPUT and keeps printing the readings. Arduino's `pinMode()` reference keeps INPUT and INPUT_PULLUP clearly apart; only the latter turns on the internal pull-up. Wiring along with this sketch means actually fitting the external pull-up or pull-down from the slide, because setting a pin as an input does not conjure a default level. A floating input is one plausible explanation for the readings that jumped about back then.

课程测试代码把输入设成 INPUT,然后一直打印读数。Arduino 的 `pinMode()` 说明把 INPUT 和 INPUT_PULLUP 分得很清楚,后者才会打开内部上拉。照着这段程序接线,课件里画的外部上拉或下拉就得老老实实接上,光把引脚设成输入,默认电平不会凭空出现。当年那阵读数乱跳,悬空的输入就是一种说得通的可能。

Switch Debouncing and State-Change Detection按键消抖与状态变化检测

Debouncing comes next. At the moment of switching, mechanical contacts can touch and part several times, so one operation leaves several transitions on the input, and one press registering twice is exactly what that tends to look like. Arduino's Debounce example restarts its timer every time the raw reading changes and accepts a new button state only once the reading has held steady for an interval. The raw reading changes first and the accepted state changes later, and the two are worth following separately.

接下来才轮到消抖。机械触点在切换的一瞬间可能反复碰上、分开好几次,一次操作就在输入上留下好几次跳变,按一次响应两次,正是这种跳变常见的样子。Arduino 的 Debounce 示例每看到原始读数变一次,就重新记时间,等它连续稳住一段时间,才认定是新的按键状态。原始读数先变,程序认定的状态后变,这两个值得分开跟。

Take 20 ms as the settling time for a worked example. The reading drops low at 0 ms, goes high at 4 ms, drops low again at 7 ms and then stays put. Timing restarts at that last change, so low is accepted at about 27 ms. The 20 ms figure just makes the sequence easy to follow; a real value depends on the switch and the response needed, and the longer the wait, the later the action is confirmed.

拿 20 ms 当稳定时间推一遍。读数在 0 ms 变低,4 ms 变高,7 ms 再变低,之后不动了。计时要从最后一次变化的 7 ms 算起,到 27 ms 左右才认低电平。20 ms 只是为了看清过程,实际多长要看开关和响应要求,等得越久,动作被确认得也越晚。

After debouncing, the loop still reads the same stable state over and over. Counting or toggling every time it sees low would let one latched switch fire a whole string of actions. Turning one operation into one action means comparing the old and new stable states and firing only on the chosen transition, which is what Arduino's state-change example does. Debouncing confirms the state and change detection picks out the moment of the action, and that is how the two jobs come apart.

消完抖,主循环还是会一遍遍读到同一个稳定状态。要是每读到一次低电平就计一次数或切一次状态,一只锁住的按键照样能触发一大串。想让一次操作只对应一次动作,还得比较新旧两个稳定状态,只在选定的变化方向上触发,Arduino 的状态变化示例做的就是这件事。消抖负责确认状态,变化检测负责挑出动作发生的时刻,两件事就这样分开了。

For this counter, I would write down the rules for both controls first. Pause follows the debounced switch state: latched in the pause position, no counting; switched back, counting resumes. Reset picks one definite transition, such as entering the active position, and clears the number once, without clearing again for as long as it stays there. At startup the program should read the initial state too, so a switch already pressed at power-on is not mistaken for a fresh press. Other interactions can follow other rules, as long as the code and the way the controls are actually used agree.

放到这只计数器上,我会先把两个功能的规矩写清楚。暂停跟着消抖后的开关状态走,锁在暂停位置就一直不加数,切回来接着计。复位挑一个确定的变化,比如进入有效位置的那一下清零一次,停在那个位置不再反复清零。程序启动时也要先读一次初始状态,免得把开机前就按着的开关当成刚按下去的一次操作。别的交互也可以定别的规矩,只要代码和实际用法对得上就行。

Loop Timing and Button Response主循环计时与按键响应

The course's single-digit example counts with delay(1000), and my own code back then leaned on the same kind of blocking delay. That adds a timing layer to everything above. While the main loop sits out its second, the button polling after it has to wait too. A brief press and release can fall entirely between two reads; a latching switch that stays in its new position will usually still be seen on the next pass, just half a beat late. Arduino's `delay()` reference also separates pausing the main program from mechanisms such as interrupts that keep running. For these buttons, what matters is how often the loop comes back to read them.

课程的单个位示例用 delay(1000) 计时,我当年的程序也是这种阻塞式 delay() 的写法,前面这些问题于是又多了一层时间上的考虑。主循环在那儿干等一秒,后面的按键轮询只能跟着等。一次很短的按下又松开,可能整个落在两次读取之间;自锁开关停在新位置,下一轮通常还能读到,只是慢了半拍。Arduino 的 `delay()` 说明也区分了主程序被暂停和中断这类还能继续工作的机制。对这两个按键来说,要紧的是主循环多久回来读一次。

One second can serve as the condition between two increments while the loop keeps watching the switches. Each pass reads millis(), handles inputs and state changes, and updates the number only once a full second has passed since the last count. Blink Without Delay uses exactly this elapsed-time check. Display updates and other work need to stay short as well, so the loop returns often enough to give debouncing and change detection frequent samples.

一秒可以当成两次加数之间的时间条件,等的时候让主循环继续看开关。每一轮读一次 millis(),处理输入和状态变化,再看离上次计数满没满一秒,满了才更新数字。Blink Without Delay 示例用的就是这种按经过时间判断的写法。显示更新和其他处理也要尽量短,循环回来得勤,前面的消抖和变化检测才有足够密的输入可用。

Resuming from pause raises a small question best settled before writing code. Suppose 700 ms of the current second has passed when pause is pressed. On resume, should the counter wait the remaining 300 ms or start a fresh second? Both are easy to write and they feel different in use. Laying this exercise out again, I would make resume start a full second and reset the timing reference on clear as well; keeping the 700 ms would mean storing that partial interval separately. Either way, an old timing reference can no longer trigger an extra count the instant the counter resumes.

暂停之后恢复,还有个小问题最好在写代码前想好。假设这一秒已经走了 700 ms,这时暂停,恢复以后是再等 300 ms,还是重新等满一秒?两种都写得出来,用起来感觉不一样。重新安排这个练习的话,我会约定恢复后重新计满一秒,清零时也一并重置计时起点;想保留那 700 ms,就得另外把这段时间存下来。这样一来,恢复的那一刻不会因为计时起点太旧而多加一次。

If reset arrives in the same pass as an increment falling due, I would let reset win and skip that increment. Otherwise the program writes 0, the increment condition is met immediately afterwards, and the display turns to 1, which from the outside looks as if reset did nothing. Input handling, state updates and count updates need their order worked out together, and when the display misbehaves, contact bounce and execution order each deserve a separate look.

同一轮里既遇到复位、计数又刚好到期,我会让复位优先,跳过这一轮的加数。不然刚写进 0,后面加数条件一满足,显示立刻变成 1,从外面看就像复位没管用。输入处理、状态更新、计数更新,谁先谁后要放在一起推。显示不对劲的时候,触点抖动和程序执行顺序,都值得分开查一查。

Pause holding the number, reset clearing it, and a response the moment the switch moves: that is all anyone using the counter expects. Getting there means arranging contact states, input decisions and the loop's ordering together. In the hand it is a single flick of a switch, and the program has to handle that one flick with real care.

暂停时停在原来的数,复位时归零,拨一下开关马上有反应,用这只计数器的人要的其实就是这些。要让它们按预期发生,触点状态、输入判断和主循环的先后顺序得一起安排。开关在手里只是拨一下,程序却要把这一下处理得明明白白。

10Back to the project回到项目Arduino Breadboard Two-Digit Seven-Segment CounterArduino 面包板两位数码管计数器