前两天带一个做数字逻辑课设的学弟调运动码表,他一开始就扑进 Logisim 里拖门电路,拖到第二次数码管不亮,整张电路图已经成了一张蜘蛛网。我把他的设计文件关掉,先让他打开 Excel:把状态转换表和输出函数表填清楚,再回去改电路,十分钟就定位到问题。运动码表这个题目看着是“数数”,本质是一个有限状态机加一整套组合逻辑输出,你在 Logisim 里看到的每一个门,都应该能从一张真值表里找到出处。这篇文章就讲我怎么用 Excel 把运动码表的“状态转换”和“输出函数”这两张表做扎实,再反哺到 Logisim 电路里。正在做课设、或者想把组合逻辑设计流程真正走一遍的人,可以直接照着做。
1. 运动码表不是简单的计数器:状态机先于电路
很多同学拿到“运动码表”这个题目,第一反应是找计数器芯片、接数码管、拼时钟,先把显示跑起来。这个思路在只做“从 0 数到 59.99 秒”的时候勉强能用,一旦要求加入启动、暂停、继续、清零这些交互,电路立刻失控。原因很简单:运动码表是有记忆的数字系统,它的行为不只看当前输入,还取决于之前处于什么工作状态,这种系统不能靠纯组合逻辑堆出来,必须用状态机来设计。
1.1 为什么直接拖门电路总会翻车
组合逻辑的特点是“输入一变,输出立刻跟着变”,没有记忆能力。运动码表则需要记住“我现在在计时、在暂停、还是已经清零待机”,否则同一个按键按下时,电路不知道该做什么。
举个例子:启动/暂停共用一个按键,在停止状态按下是“开始计时”,在运行状态按下是“暂停”,在暂停状态按下又是“继续”。同一个输入信号,三个完全不同行为,区别就来自当前状态。这种“当前状态 + 输入 -> 次态”的映射关系,就是有限状态机的核心。
用 Logisim 直接拖门电路做状态机不是不行,而是顺序逻辑天然需要反馈:状态寄存器的输出要接回组合逻辑的输入,组合逻辑的次态输出又要接到寄存器的输入。没有一张表先定清楚每个状态在每个输入下的走向,连线的时候纯靠脑子去记,十几条线一交叉必然出错。更麻烦的是,仿真报错时你根本分不清是状态转移错了、输出译码错了,还是线接错了。
正确顺序是:先在 Excel 里把状态转换表和输出函数表变成一张张完整的真值表,然后用 Logisim 的组合分析工具去生成门电路。真值表错了,电路一定错;真值表对了,电路基本不会跑偏。
1.2 运动码表最小状态集:三个状态、两个按键
课程设计里的运动码表,功能上通常可以收敛为三个状态:
- STOP(停止待机):计时值清零,等待启动。
- RUN(运行):计时值持续累加,数码管实时显示。
- PAUSE(暂停):停在当前计时值,等待继续或清零。
操作键可以合并成两个:
- START_PAUSE 键:启动/暂停/继续,一个键循环切换。
- RESET 键:清零,无论当前在哪个状态,按下都回到 STOP。
状态跳转关系是:STOP 下按 START_PAUSE 进入 RUN;RUN 下按 START_PAUSE 进入 PAUSE;PAUSE 下按 START_PAUSE 回到 RUN;任意状态按 RESET 都回到 STOP。用状态机的语言说,这是三个状态、两个输入、若干个输出信号的设计。为什么不再加一个 STOP 专用键?因为课设题目往往限定按键数量,而且“同一个键在不同状态做不同事”恰好能考察状态机设计能力,所以两个键是更典型的题目设定。
1.3 Excel 到底在帮我们做什么
状态转换表和输出函数表,本质都是真值表。Excel 的天然优势是:横向列好输入输出,纵向一行一行列出所有组合,中间可以用公式自动计算次态,还能用条件格式把特殊行标红。无论是 4 个输入的 16 行状态转换表,还是 4 个 BCD 输入到 7 段输出的 10 行译码真值表,用 Excel 都比纸上手写更清晰,也比 Logisim 里直接改真值表更容易批量处理。
后面几步我会把这三张表全部建出来:第一张是状态转换表,第二张是 BCD 计数器的进位输出真值表,第三张是七段数码管的译码真值表。三张表齐了,运动码表的逻辑就等于完成了一大半。
2. 状态转换表:两位触发器、三个有效状态、16 行真值表
状态转换表要回答的是:给定当前状态和输入,次态是什么,输出是什么。在设计运动码表时,我习惯用两位 D 触发器来存放状态,因为三个状态需要两位二进制编码,多出来的一个编码可以拿来兜底。
2.1 状态编码:保留 11 这个陷阱状态
状态编码方案如下:
| 状态 | 编码 Q1Q0 | 含义 |
|---|---|---|
| STOP | 00 | 停止待机,计数清零 |
| RUN | 01 | 运行计时 |
| PAUSE | 10 | 暂停保持 |
| 未定义 | 11 | 强制回到 STOP |
很多初学者只看到三个状态,就自然以为两位编码里 11 用不上,直接在真值表里留空。这是一个隐患。Logisim 仿真时 D 触发器上电的初始值并不总能保证是 00,一旦落到 11,而组合逻辑里 11 对应的次态又是空,电路就可能停在 11 状态“假死”,数码管不跳,怎么按都没反应。把 11 显式地映射回 00,是设计状态机时最省事的兜底策略。
输入我定义为:
- A = START_PAUSE,1 表示按下。
- B = RESET,1 表示按下。
由于 RESET 的优先级最高,只要 B=1,无论当前什么状态,次态都回到 STOP。
2.2 在 Excel 里把次态写成布尔公式
打开一个空工作表,列头按这样排:Q1、Q0、A、B、D1、D0、EN、CLR。其中 Q1Q0 是现态,AB 是输入,D1D0 是次态,EN 是计数使能,CLR 是计数器清零。
在 Excel 里我通常会先写公式,让电脑把次态算出来,而不是手填。以第二行数据为例,假设 A2 是 Q1,B2 是 Q0,C2 是 A,D2 是 B,那么:
D1(次态高位)可以这样写:
=IF(D2=1, 0, IF(AND(A2=0, B2=1, C2=1), 1, 0))含义是:RESET 按下,次态高位是 0;否则只有当现态是 RUN(01)且按下启动/暂停键时,次态才变成 PAUSE(10),高位为 1。
D0(次态低位)的公式稍长一点:
=IF(D2=1, 0, IF(C2=1, IF(OR(AND(A2=0, B2=0), AND(A2=1, B2=0)), 1, 0), IF(AND(A2=0, B2=1), 1, 0)))这个公式做的事情是:RESET 优先回 00;若没有 RESET,按下启动/暂停键时,STOP 或 PAUSE 都变成 RUN(低位 1),而 RUN 变成 PAUSE(低位 0);若没有按键,则只有 RUN 保持 RUN,低位为 1。
用 Excel 公式算次态,不只是省事,它还能和你之后用 Logisim 组合分析自动生成的表达式交叉验证。两边结果一致,说明状态转换表没抄错。
2.3 完整状态转换表:16 行一次看明白
四位输入(Q1Q0AB)一共 16 种组合,最终表如下:
| Q1 | Q0 | A | B | D1 | D0 | EN | CLR | 行为说明 |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | STOP 保持 |
| 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | STOP 启动 |
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | STOP 清零 |
| 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | RESET 优先 |
| 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | RUN 继续计时 |
| 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | RUN 暂停 |
| 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | RUN 清零 |
| 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | RESET 优先 |
| 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | PAUSE 保持 |
| 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | PAUSE 继续 |
| 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | PAUSE 清零 |
| 1 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | RESET 优先 |
| 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 未定义回 STOP |
| 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 未定义回 STOP |
| 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 未定义回 STOP |
| 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 未定义回 STOP |
看着这张表,输出函数其实已经浮出水面了。EN 只在现态为 RUN 时为 1,写成布尔式就是:
EN = Q1' · Q0CLR 在现态为 STOP 或未定义状态时为 1,即 Q1 和 Q0 相等的时候为 1:
CLR = Q1 · Q0 + Q1' · Q0' = NOT(Q1 XOR Q0)这两个输出只跟现态有关,不跟输入直接挂钩,属于 Moore 型输出,好处是稳定,不会因为按键瞬时变化产生毛刺。
2.4 用 Excel 快速生成全部行的技巧
16 行如果手填,容易漏。我的做法是先用 Excel 的自动填充生成 0 到 15 的十进制数,再用 BITAND、BITRSHIFT 之类函数拆出二进制位,或者直接手动拉四个输入列的组合。更土但更稳的办法是:输入列 A、B 两列设数据验证下拉,只有 0 和 1,然后一行行按表填。填完后用条件格式把 B=1 的行标红,一眼就能看出 RESET 优先逻辑是否覆盖完整。
这样一张表,既是设计文档,也是后面 Logisim 组合分析的输入依据,千万别关掉。
3. 输出函数表:从显示需求倒推到每一段点亮条件
状态转换表解决的是“下一步去哪”,输出函数表解决的是“当前这个状态应该把哪些信号拉高”。运动码表的输出分成两层:一层是控制计数器工作的信号,另一层是把计数值变成数码管上可见笔画的译码输出。
3.1 运动码表要显示什么信号
典型的课设运动码表显示四位数字,我按“秒十位、秒个位、百分秒十位、百分秒个位”来排,也就是显示 00.00 到 99.99 秒,或者按 00.00 到 59.99 秒循环。每一位都是 BCD 码的 0-9 计数器,级联成十进制计数器链。计时时钟进百分秒个位,百分秒个位每数满 9 次产生一个进位给百分秒十位,百分秒十位每数满 9 次产生进位给秒个位,秒个位每数满 9 次产生进位给秒十位。
这里的“进位信号”就是一种输出函数,它由当前计数器的 BCD 值和使能信号共同决定。以一位 BCD 计数器为例:输入是 EN(计数使能)和当前计数,输出是次态和进位 C。当 EN=1 且当前值是 9 时,下一拍回到 0,同时 C=1;其他情况 C=0。
3.2 用 Excel 做单个 BCD 计数器的状态表
一位 0-9 计数器在 Excel 里可以这样建:一列写当前值(十进制),一列写 EN,一列写次态十进制,一列写进位 C,再把十进制转成四位二进制位。
次态逻辑用公式表示:
=IF(EN=0, 当前值, IF(当前值=9, 0, 当前值+1))进位逻辑:
=IF(AND(EN=1, 当前值=9), 1, 0)十进制转二进制可以用DEC2BIN(当前值, 4),再用MID函数把四位拆到单独的 W、X、Y、Z 列里,作为后面译码表的输入。如果你用的 Excel 没有 DEC2BIN 函数,手动列出 0 到 9 的二进制码也不费事,毕竟只有 10 行。
如果一个设计需要 60 秒上限,只要把秒十位独立做成 0-5 循环的计数器即可,0-5 计数器在 Excel 里不过是把“等于 9 回 0”改成“等于 5 回 0”,其他逻辑完全一样。这就是建表的好处:需求一变,改一个条件,整张表重算,电路再跟着改。
3.3 七段数码管译码表:BCD 输入到段码输出
这是整张 Excel 表里最像“输出函数”的部分。共阴极七段数码管,高电平点亮,输入是四位 BCD 码 WXYZ,输出是 a、b、c、d、e、f、g 七段。
标准 0-9 的段码表如下:
| 数字 | WXYZ | a | b | c | d | e | f | g |
|---|---|---|---|---|---|---|---|---|
| 0 | 0000 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
| 1 | 0001 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
| 2 | 0010 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
| 3 | 0011 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
| 4 | 0100 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
| 5 | 0101 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
| 6 | 0110 | 1 | 0 | 1 | 1 | 1 | 1 | 1 |
| 7 | 0111 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| 8 | 1000 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 9 | 1001 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
输入组合 1010 到 1111 在运动码表里永远不会出现,这些就是无关项。在卡诺图里可以把它们当作 1 或 0 来合并,从而得到更简单的门电路。以 g 段为例,它在 2、3、4、5、6、8、9 时点亮,如果在卡诺图里把无关项合理并入,表达式会比直接列七项短不少。
四个数码管可以共用同一套译码函数,因为在同一时刻每位数码管显示的数字不同,但译码逻辑本身是同一个“0-9 到七段”的映射。课设里最简单的接法是四位各接一片译码电路,不做什么扫描显示,Excel 表也只需要一份七段译码真值表,复制四份电路实例即可。
3.4 从真值表到最简布尔式的判断
拿到真值表之后,手动画卡诺图是一种能力,但真值表行数一多,人工化简容易漏项。Logisim 自带的组合分析工具可以直接从真值表生成化简后的门电路,这一步我在第 4 节详细说。
这里想提醒的是:在 Excel 表格里千万不要只记录“最终化简结果”,原始真值表的每一行都要留着。因为 Logisim 自动生成的电路不一定是最优解,你后期如果想手动优化某一盏数码管段的逻辑,回头对照原始 10 行数据比对照化简式更可靠。
4. 从 Excel 到 Logisim:把真值表变成能跑的电路
Excel 表做得再漂亮,最终还是要落回电路。Logisim 的 Combinational Analysis(组合分析)工具,恰好是连接真值表和门电路之间的桥梁。运动码表的总体思路是:状态机组合逻辑用组合分析生成,状态寄存器和计数器用手工接线,译码部分再次用组合分析生成。
4.1 把状态转换表喂给组合分析
打开 Logisim,新建一个电路,操作路径是 Project -> Analyze Circuit,或者直接按 Ctrl+B 打开组合分析窗口。
在 Inputs 标签页里添加四个输入:Q1、Q0、A、B。在 Outputs 标签页里添加四个输出:D1、D0、EN、CLR。然后切到 Table 标签页,把 Excel 状态转换表里 16 行的内容逐行抄进去。
需要注意,Logisim 的组合分析表格默认要求每个输出格填 0 或 1。如果你用的版本支持把输出格设为 x(无关项),你可以把无法输出的组合标成 x;如果不支持,就暂时填 0,先让电路跑起来,后面再手动优化。对于运动码表来说,状态转换表的 16 行全部是确定值,不会有 x;七段译码表才会有无关项,但也只有 6 行。
填完后点击 Build Circuit,Logisim 会自动把最简门电路放到新电路里。这一步生成的就是状态机的次态组合逻辑和输出逻辑,不需要你手动从布尔代数开始推。
4.2 状态寄存器与计数器接线:时钟必须统一
组合分析生成的电路只有组合逻辑,没有记忆功能。要让状态机“记住”当前状态,需要把 D1D0 接到两位 D 触发器的 D 输入端,D 触发器的 Q 输出再反馈回组合逻辑的 Q1Q0 输入端。切换状态依赖时钟边沿,所以两个 D 触发器要接同一个时钟源。
计数器部分的接线最容易出现误区:有人会把 EN 信号直接接到计数器的 Clock 端,用“有没有脉冲”来控制计不计时。这个方案在 Logisim 仿真可能看起来正常,实际是错误设计。因为 EN 是电平信号,和时钟信号做与门会产生窄脉冲、毛刺,后续转到真实芯片或更高要求的仿真时会被打得很惨。
正确做法是:计数器内部每一位触发器都接统一系统时钟,EN 作为“下一拍是否改变计数值”的控制端。如果是 D 触发器搭计数器,就在 D 输入端用选择逻辑:EN=1 时 D 取“加一后的值”,EN=0 时 D 取当前值,相当于保持。
运动码表的四位计数器可以用同一个系统时钟驱动,状态寄存器的时钟也接这里。这样做可以保证状态切换、计数累加、清零发生在同一时钟边沿,行为完全同步。
4.3 按键边沿检测:不能按一下跳两个状态
状态转换表里输入 A=1 代表“启动/暂停键按下”,但物理按钮按下去之后,电平会保持一段时间,几十毫秒甚至更久,而系统时钟可能在几百 kHz 甚至更高,结果就是一个高电平期间,状态机会被触发很多次:RUN 会跳到 PAUSE,又立刻跳回 RUN,再跳到 PAUSE,数码管表现就是乱闪。
标准的处理是加边沿检测电路,把“按钮按下”这个持续电平转换成“一个时钟周期宽的脉冲”。做法是用两个 D 触发器对按钮信号打两拍,取第二拍和第一拍的逻辑关系:上升沿时,当前值为 1 且上一拍为 0,用一个 AND 门检测,输出一个脉冲。这个脉冲再接状态机的输入 A。
用一句话概括:按键信号不是直接用,而是先同步、再检边、最后才交给状态机。这一点在做课设时非常关键,少了它,状态表再对也没有用。
4.4 整体接线检查单
电路接完后,按这个清单过一遍:
- 状态寄存器:两位 D 触发器,D 接 D1D0,Q 反馈回组合输入,Clock 接系统时钟。
- 组合逻辑:输入 Q1Q0AB,输出 D1D0、EN、CLR,由组合分析生成。
- 计数器链:四位 BCD 计数器级联,百分秒个位 -> 百分秒十位 -> 秒个位 -> 秒十位。
- 计数器控制:EN 控制加一/保持,CLR 控制清零,全部接状态机输出。
- 按键:START_PAUSE 和 RESET 都经过边沿检测得到单脉冲信号。
- 显示:每位 BCD 输出接七段译码器输入,译码器输出接七段数码管对应段。
接线顺序建议从状态机开始,先保证状态能在 STOP、RUN、PAUSE 之间正确切换,再接计数器和显示。一层一层来,不要一步到位把所有线都拉完,否则出错时根本无从下手。
5. 实测最容易翻车的四个点,我在 Excel 表里提前标过
跑通电路只是第一步,运动码表课设的真正难点在调试。以下四个问题,是我带过的设计里出现频率最高的,每个都能从 Excel 表里找到根源。
5.1 长按按钮导致状态反复横跳
症状:按一下启动键,状态在 RUN 和 PAUSE 之间来回切换,像 PPT 自动播放一样停不下来。原因不是状态表写错,而是没有做边沿检测,输入 A 的高电平期间每个时钟周期都会导通一次。排查时可以打开 Logisim 的单步时钟,手动 tick 几下观察状态。如果每 tick 一次状态就翻一次,说明按钮信号没有被转换成单脉冲。
解决方法是前面说的边沿检测。在 Logisim 里可以简单用寄存器芯片或者 D 触发器打一拍,再和原始信号做“当前为 1、上一拍为 0”的组合判断。注意:检查边沿检测电路本身能不能工作,要手动制造一个上升沿,而不是用手一直按住按钮慢慢看。
5.2 清零信号异步且太快,计数器没清干净
如果 CLR 信号是直接从状态机组合逻辑输出接到计数器异步复位端,问题在于:按下 RESET 后状态从 RUN 变到 STOP,组合逻辑输出 CLR 变成 1,计数器清零;但下一次时钟边沿到来时状态仍然保持 STOP,CLR 依然是 1,看起来没问题。问题出在从“按下 RESET”到“下一拍状态真正变成 STOP”之间,有一个时钟周期的延迟,而这个延迟里计数器还在 RUN 状态的 EN=1 控制下继续累加了一拍。
更稳妥的做法是采用同步清零:让 CLR 作为计数器组合逻辑的一部分,在 D 触发器搭的计数器里,把 CLR 和 EN 一起放在次态计算的优先级最前面。CLR=1 时,不管当前计数值是多少,下一拍统一回到 0。这样清零动作和时钟边沿对齐,不会产生半个时钟周期的乱码。
5.3 未定义状态 11 让电路“假死”
症状:电路一上电或者随便按了几下之后,数码管显示乱码,怎么按按键都没反应。多半是状态寄存器进入了 11 编码,而组合逻辑里 11 的次态没有定义。前面我在状态转换表里专门给 11 留了四行,全部映射到 00,就是要防止这种情况。
如果你已经在 Excel 里写了包含 11 行的完整 16 行表,就能提前发现问题。如果在 Logisim 里改真值表时把 11 行空着或者填成了保持 11,就等于制造了一个死锁状态。上电初始状态不确定时,D 触发器完全可能落到 11。
5.4 用 Excel 逐行对照仿真结果
调试状态机最笨也最有效的方法,是一行一行地用 Logisim 手调输入,观察输出,和 Excel 表核对。
具体操作:把 Logisim 时钟停掉,手动把输入 Q1Q0AB 设置成一个值,比如 0、1、1、0(RUN + 按下启动),观察输出 D1D0 是否等于 1、0、0、0,也就是 PAUSE。如果组合分析生成的电路输出和 Excel 表不一致,说明转录真值表时漏行、填错位,或者生成的电路被手动改过。不要怕这个方法慢,运动码表的状态转换表只有 16 行,逐行走一遍也就十几分钟,定位到的都是致命问题。
七段译码部分也可以用同样办法验证:给 BCD 输入设一个十进制数,比如 5,看七段输出是不是 1011011。如果哪一段不对,直接用查找表反推是哪一行抄错,比盯着电路图看半天快得多。
6. 一点实操经验
我自己的习惯是把设计文件命名为“码表设计表.xlsx”,三个 sheet 分别放状态转换表、计数器真值表、七段译码表。状态转换表里用条件格式把 RESET 优先的行标成浅红色,把未定义状态 11 的行标成浅蓝色;七段译码表里用色块区别亮段和灭段。答辩的时候不用费劲画状态图,直接把 Excel 表投影出来,从状态跳转讲到进位输出,再到每段数码管点亮条件,逻辑非常完整。
这个方法也不只适用于运动码表。交通灯控制器、洗衣机状态机、电梯楼层控制器,凡是“状态机 + 组合逻辑输出”的课设,都可以先用 Excel 把真值表坐实,再交给 Logisim 生成电路。你在表上多花的半小时,通常会帮你省下调试时的三小时。