简介:这是一份面向单片机课程设计与初学者的汇编语言版可控流水灯课程设计报告,完整覆盖8个LED灯5种流水灯显示方式的实现方案。文档以80C51单片机为控制核心,从课题背景、总体设计、模块划分、硬件连接、汇编代码编写到系统调试与运行结果分析逐项展开,并详细介绍了按键切换方式、数码管显示模式编号以及两键调速的实现逻辑,适合需要完成类似课设或学习单片机编程的读者直接参考。资源包共1个doc文件,整体大小约226KB,内容集中、便于查阅,目前已服务700余人学习下载。报告包含完整的目录结构、流程框图与模块说明,关键部分还配有硬件接口接线思路和汇编代码设计说明,可直接用于课程设计撰写、答辩准备或作为项目改写的参考底稿。
1. 可控流水灯:为什么说跑通流水灯容易,跑通“可控”才见汇编功底
流水灯大概是单片机课程设计里出镜率最高的题目,但它也是最容易“看起来会了、一验收就露馅”的题目。能让 8 个 LED 从左到右依次点亮,只能算跑通;真正拉开差距的是标题里“可控”两个字——按键调速、方向反转、暂停继续。很多人用延时函数硬憋流水效果,结果按下按键毫无反应、流水速度全看按键心情,调起来像在碰玄学。问题通常不是汇编指令不熟,而是把“流水”和“控制”揉在了同一个阻塞循环里,谁都抢不到资源。
这篇笔记给出一套能直接落地、也能写进课程设计报告的汇编方案:定时器中断负责节奏,主循环负责按键扫描,查表法负责花样,状态变量负责方向与启停。思路按 8051 内核展开,你手上是常见的 51 系开发板就能移植。适合正在做课设、需要交程序和报告的同学,也适合工作后想用汇编重新梳理一遍中断与端口时序的开发者。
2. 选型与原理:把“可控”拆成三个寄存器级行为,汇编课设才做得明白
做可控流水灯之前,先把目标拆清楚。这里的“可控”不是一句话,而是三个可以独立实现的硬件行为:速度快慢、流水方向、启动暂停。每一个行为在汇编里都有对应的寄存器或内存变量,拆得越细,后面写代码越不会乱。
2.1 “可控”到底控什么:速度、方向与启停的三个控制维度
速度控制的本质是换灯间隔时间。常见做法有两种:主循环里调用延时函数,或者用定时器中断产生固定节拍。延时函数不是不行,但它有个隐蔽问题——主循环里每条指令的执行时间都在消耗节拍,按键扫描、花样刷新都会让实际间隔漂移。你在仿真里把延时调准了,换到真实开发板上,因为晶振和指令路径不同,速度立刻变味。更稳的做法是让 T0 定时器以固定周期(比如 10ms)进入中断,中断里只把一个计数器加一,主循环检测到这个累加值达到档位阈值后,才做一次换灯。这样流水速度只取决于“定时器初值 × 累加次数”,和主循环里写了多少代码无关。
方向控制在查表方案里就是下标往加还是往减。用一个内存变量 IDX 表示当前应点亮的 LED 序号,正向时每拍 IDX 加一,加到 7 后回到 0;反向时 IDX 减一,减到 0 后跳到 7。如果你用循环移位 RL A / RR A 做流水,方向切换就要判断移位的方向,逻辑上绕一些,而且只能做单一流水,没法做花样。查表法把“当前序号对应哪个 LED 状态”存在 ROM 表里,换方向只改下标操作,后面想加花样也只是换一张表的问题。
启动暂停最容易写错。很多人一暂停就把 TR0 清零,再启动又把 TR0 置一,表面上没问题,但定时器停止期间 TICK 累加不更新,按键扫描里如果用到了定时节拍,会连带失灵。我的习惯是:暂停只屏蔽换灯动作,不关定时器。用一个 RUN_FLAG 位变量,主循环检测到该位为 1 才刷新流水,为 0 就保持当前 LED 状态,按键仍能正常扫描。这样暂停只是“停下来”,不是“整个系统睡着”。
2.2 汇编方案与 C 方案的本质差别:指令周期、寄存器与黑匣子
如果课程设计允许任选语言,很多人会纠结用 C 还是汇编。C 的可读性和维护性确实好,代码量也小,但在“可控流水灯”这个题目里,C 有一个短板:编译器在启动阶段帮你做了一堆隐含动作,中断入口、寄存器保存、堆栈初始化对初学者来说是个黑匣子。课上如果重点考核你“知不知道中断响应时硬件做了什么”,用 C 反汇编去讲也能讲,但对还没建立时序概念的人,直接手写汇编更能看清每条指令的代价。
汇编的好处在于看得见。一条 MOV P1, #0FEH 就是让 P1.0 输出低电平,两条 DJNZ 构成的延时循环吃了多少指令周期,手算都能算出来。课程设计报告里写“本设计采用定时器 T0 方式 1,机器周期 1μs,定时 10ms 需要装载初值 0xD8F0”,这句话在答辩时很有分量,因为它表明你是真的算过,而不是抄了一段代码。C 也能算,但编译器在循环里插入的额外指令会让初值计算变得没那么直观。
也不是说汇编就一定优于 C。如果题目对花样复杂度要求很高,比如要做呼吸灯、按键长按连发、多组 LED 联动,用 C 写状态机会轻松很多;如果题目明确要求汇编,那就老老实实用汇编,别绕道。选型建议只有一句话:优先满足课程要求,其次考虑你想通过这个课设练什么能力。
| 对比项 | 汇编方案 | C 方案 |
|---|---|---|
| 时序控制 | 指令周期可手算,直观 | 依赖编译器优化,需看反汇编 |
| 程序量 | 代码较长,需仔细管理寄存器 | 代码量小,逻辑清晰 |
| 调试难度 | 必须会看寄存器与内存窗口 | 断点调试更友好 |
| 课设契合度 | 适合强调硬件原理与中断流程的题目 | 适合强调功能复杂度的题目 |
2.3 定时器初值怎么算:12MHz 与 11.0592MHz 两组现成结果
定时器初值是这门课设里最不应该抄、却最多人抄的参数。8051 内核的标准 12T 模式下,机器周期是晶振周期的 12 倍。以 12MHz 晶振为例,机器周期正好 1μs,定时 10ms 需要计数 10000 个脉冲。T0 在方式 1 下是 16 位计数器,从初值加到 65536 溢出,所以初值 = 65536 - 10000 = 55536,换成十六进制就是 0xD8F0。
很多开发板用的是 11.0592MHz 晶振,它的机器周期约 1.085μs,同样的 10ms 定时只需要约 9216 个计数脉冲,初值约 0xDC00。差别不是一点点,所以“抄了初值跑起来速度不对”基本都栽在这里。下面这组初值可以直接用,但建议你自己套公式验算一遍,答辩时能讲清楚每一行的来历。
| 晶振频率 | 机器周期 | 定时 5ms 初值 | 定时 10ms 初值 | 定时 20ms 初值 |
|---|---|---|---|---|
| 12MHz | 1μs | 0xEC78 | 0xD8F0 | 0xB1E0 |
| 11.0592MHz | 约 1.085μs | 约 0xEE00 | 约 0xDC00 | 约 0xB800 |
还有一个容易被忽略的变量:CPU 内核的时钟模式。部分增强型芯片支持 6T 或 1T 模式,同一颗晶振在不同模式下机器周期完全不同。上电默认 12T 只是最常见的配置,不一定适用于你的开发板。拿到板子第一件事是查芯片手册里关于时钟模式的描述,如果真的有 6T 模式且被配置成了 6T,那么 12MHz 晶振下机器周期只剩 0.5μs,同样的 10ms 初值要改成 0xB1E0。这算不清,后面所有速度档位都会按比例跑飞。
提示:别死记硬背“0xD8F0 就是 10ms”,记住公式才能应对换晶振、换开发板的场景。
3. 汇编架构设计:把“流水”拆成查表、移位与状态机,三步理清逻辑
原理理清后,下一步不是急着写代码,而是先选流水算法、定主循环结构、规划中断入口。这三件事决定了后续代码是越写越顺,还是写到一半推翻重来。
3.1 查表法 vs 循环移位:两种流水算法的资源账
流水灯最简单的实现是循环移位。读当前 P1 的值,左移一位,再写回 P1。逻辑上非常直接:
MOV A, P1 ; 读回当前 LED 状态 RL A ; 左移一位 MOV P1, A ; 写回端口这段代码在单灯流水场景下没问题,但一旦做了“可控”,短板就暴露了:方向切换要把 RL 换成 RR,暂停要额外保存当前值,花样扩展基本靠堆代码。还有一个细节,MOV A, P1 读的是引脚,而 P1 作为输出口时引脚电平与锁存器通常一致,但外部负载较重时会有偏差,严谨的读-修改-写流程要注意区分端口锁存器和引脚状态。
查表法则把 LED 的状态预先存成一张表,每次按序号取。核心指令是 MOVC A, @A+DPTR,意思是从程序存储器里按偏移量取字节。表定义在程序段里,不占 RAM:
PATTERN: DB 0FEH, 0FDH, 0FBH, 0F7H DB 0EFH, 0DFH, 0BFH, 07FH这 8 个值分别对应只有 P1.0 到 P1.7 输出低电平,其余为高。如果你板子上 LED 是共阳接法,输出低电平点亮;共阴接法则需要取反。表的长度固定为 8,查表时只需要维护一个 0 到 7 的 IDX 变量。方向控制就变成“IDX 加一还是减一”,和表本身无关。两段代码放一起对比,查表法多占十几个字节的 ROM,但换来了方向控制简单和花样扩展空间,课设里我选查表法。
3.2 主循环 + 定时标记:让按键扫描不阻塞流水
新手最容易犯的错是把整段流水逻辑写在一个死循环里,按键扫描也放在里面,中间再夹一个几十毫秒的延时。按下按键时,程序正卡在延时里,等到循环转回来键已经弹起来了。这就是“按键没反应,松手之后才动一下”的典型症状。
正确结构是把主循环变成一个非阻塞轮询:定时器中断负责产生节拍,主循环只做两件事,扫键盘、查节拍。伪代码如下:
MAIN_LOOP: ACALL KEY_SCAN ; 扫描按键,不等待 MOV A, TICK_CNT ; 当前累计的节拍数 CJNE A, SPEED_TH, MAIN_LOOP ; 没到阈值,继续扫键 MOV TICK_CNT, #00H ; 到阈值,清零重计 ACALL UPDATE_LED ; 刷新一次 LED 状态 SJMP MAIN_LOOPSPEED_TH 是速度档位。T0 每 10ms 中断一次,TICK_CNT 就加一;如果 SPEED_TH 等于 2,LED 每 20ms 流动一次;等于 5 就是每 50ms 一次。改速度只改 SPEED_TH,定时器初值完全不用动。KEY_SCAN 里即便要做 10ms 软件消抖,也只是在按键状态变化时阻塞,平时一次扫描只执行几条指令,对流水节拍的影响可以忽略。
这套结构里,状态变量就那几个,规划清楚就能写明白:
| 变量 | 作用 | 取值 |
|---|---|---|
| IDX | 当前 LED 序号 | 0 到 7 |
| FWD_FLAG | 方向标志 | 1 正向,0 反向 |
| RUN_FLAG | 启停标志 | 1 运行,0 暂停 |
| TICK_CNT | 节拍累加器 | 中断中自增,主循环清零 |
| SPEED_TH | 速度阈值 | 1 到 10 之间可调 |
3.3 最小可跑框架:初始化、主循环与中断向量
先把程序的骨架搭出来,再往里面填逻辑。汇编程序的入口不是 main,而是复位向量 0000H。8051 内核上电后会从 0000H 开始取指令,所以在 0000H 放一条跳转指令,跳到真正的初始化位置;T0 的终端入口是 000BH,中断响应后会自动跳到那里:
ORG 0000H LJMP INIT ORG 000BH LJMP T0_ISR写过 C 的人第一次看到 ORG 往往会愣一下,ORG 是汇编伪指令,告诉汇编器下一行代码放到哪个地址。0000H 和 000BH 是硬件规定的入口地址,不能随便改。INIT 里做寄存器和定时器初始化,T0_ISR 里做节拍累加,主循环就是上一节里那个非阻塞轮询。这个骨架在任何 IDE 里都通用:新建汇编工程、选芯片型号、把代码贴进去、编译下载,最少几十行就能看到灯按节拍流动。至于芯片型号,以你开发板上实际那颗为准,代码只依赖 8051 内核通用寄存器,不需要特殊外设。
4. 手写汇编落地:从栈指针初始化到按键防抖,一份能跑的最小实现
架构定了,代码就是逐个模块填上去。这一章给出一份最小可跑的汇编实现,每段代码后面解释为什么这么写,参数怎么调。
4.1 初始化段该怎么写:SP、TMOD、TH/TL、IE
初始化段是所有问题的起点。SP 初始值很多人忘了设,默认值 07H 会导致堆栈和工作寄存器组重叠,压栈一多,变量被覆盖,程序跑飞查起来非常痛苦。一般放在 60H 附近,避开直接寻址区和位寻址区。
INIT: MOV SP, #60H ; 栈顶设在 60H,避开 00H-2FH 的寄存器与位寻址区 MOV TMOD, #01H ; T0 方式 1,16 位定时器 MOV TH0, #0D8H ; 12MHz 下定时 10ms 的初值高 8 位 MOV TL0, #0F0H ; 初值低 8 位,合起来是 0xD8F0 SETB ET0 ; 允许 T0 中断 SETB EA ; 总中断使能 SETB TR0 ; 启动 T0 MOV IDX, #00H ; 从第 0 个 LED 开始 MOV FWD_FLAG, #01H ; 默认正向 MOV RUN_FLAG, #01H ; 默认运行TMOD 的 01H 表示 T0 工作在方式 1,也就是 16 位定时器,这是最常用的模式。方式 0 是 13 位计数,方式 2 是 8 位自动重装,各有用途,但方式 1 对课程设计最直观,初值计算和后面讲到的节拍累加都简单。TH0 和 TL0 合起来构成 16 位初值,先写高字节还是先写低字节在 8051 上一般无所谓,但养成“先高后低”的习惯,遇到某些增强型芯片的 16 位寄存器时能少踩坑。SETB 是位置位指令,作用是把某个位变量写成 1。ET0 在中断允许寄存器里,EA 是总开关,两个都要置 1,缺一个中断都不响应。
4.2 流水主循环代码块:查表、方向判断与换灯更新
换灯逻辑是核心。这里不直接操作端口,而是根据 IDX 查表,查表结果经过极性处理后写 P1。方向和启停逻辑在同一段里处理:
UPDATE_LED: MOV A, RUN_FLAG JZ LED_HOLD ; 暂停时保持当前状态,不换灯 MOV DPTR, #PATTERN ; 指向流水表首地址 MOV A, IDX MOVC A, @A+DPTR ; 取 IDX 对应的 LED 状态 CPL A ; 共阳接法取反,低电平点亮 MOV P1, A ; 输出到 P1 MOV A, FWD_FLAG JZ GO_BACKWARD ; 方向标志为 0 则反向 MOV A, IDX INC A CJNE A, #08H, SAVE_IDX ; 没到 8 就保存,到了就回 0 MOV IDX, #00H SJMP LED_DONE GO_BACKWARD: MOV A, IDX JZ SET_TAIL ; 已经是 0,反向则跳到 7 DEC A SJMP SAVE_IDX SET_TAIL: MOV IDX, #07H SJMP LED_DONE SAVE_IDX: MOV IDX, A LED_DONE: LED_HOLD: RET查表用 DPTR 作为基址寄存器,IDX 作为偏移量。MOVC A, @A+DPTR 是汇编里查表的固定写法,取出来的是程序存储器里的字节。CPL A 是取反指令,因为流水表里存的是低电平有效状态,直接写 P1 会让 LED 反着亮。如果你的板子是共阴接法,删掉这条 CPL 即可。
方向逻辑容易写错的地方是边界。正向到 7 要回 0,反向到 0 要跳 7,这两条分支必须单独判断。我在调试时亲眼见过有人反向只写了 DEC A,结果 IDX 从 0 变成 255,查表直接越界,LED 乱成一团。循环边界在汇编里没有编译器帮你检查,只能靠代码逻辑保证。
4.3 按键扫描与防抖:不引入消抖芯片的软件写法
机械按键按下和释放的瞬间会产生几毫秒的抖动,直接读引脚会在一次按压中读到多次跳变。软件消抖的思路是:第一次检测到按下,先延时约 10ms,再读一次,如果还是按下状态,才认为是一次有效按键。
KEY_SCAN: JB KEY_PIN, KEY_UP ; KEY_PIN 为 P3.2,高电平表示未按下 ACALL DELAY_10MS ; 第一次检测到按下,延时跳过抖动 JB KEY_PIN, KEY_UP ; 延时后再读,已释放则忽略 MOV A, KEY_STATE ; 检查是否已处理过这次按下 JNZ KEY_WAIT_REL MOV KEY_STATE, #01H ; 标记已处理,防止重复触发 ACALL HANDLE_KEY ; 执行切换速度/方向/启停 KEY_WAIT_REL: JNB KEY_PIN, KEY_WAIT_REL ; 等待按键释放 MOV KEY_STATE, #00H KEY_UP: RETKEY_PIN 是 P3.2 的位定义,汇编里用类似 KEY_PIN EQU P3.2 的伪指令指明。JB 和 JNB 是位条件跳转,JB 为 1 跳转,JNB 为 0 跳转。KEY_STATE 是按键处理状态,目的是区分“按下瞬间”和“长按保持”,否则一次按压会被主循环扫描到很多次。HANDLE_KEY 是按键动作函数,每按一次在“调速、反向、暂停/继续”之间切换,具体实现看你的功能定义。
这块有两个常踩的坑。一是 KEY_STATE 这个变量不能省,省了之后一次按键会连续切换好几个档位。二是等待释放的循环不要用 RET 提前退出,否则下次进入 KEY_SCAN 时 KEY_STATE 还是 1,会以为又按了一次。DELAY_10MS 用 DJNZ 嵌套实现,12MHz 下大约是 10ms,误差在可接受范围。
4.4 中断服务子程序:只改标志,不在中断里做流水更新
中断服务子程序越短越好,这是嵌入式开发里最值得养成的习惯。T0 中断只做两件事:重装初值、节拍累加。
T0_ISR: MOV TH0, #0D8H ; 重装 10ms 初值高字节 MOV TL0, #0F0H ; 重装 10ms 初值低字节 INC TICK_CNT ; 节拍计数加一 RETI ; 中断返回用 MOV 重装初值必然有误差:执行 MOV 和 INC 需要几个指令周期,这些周期会被计入下一个定时周期。10ms 里差 2μs 误差约 0.02%,肉眼完全看不出来。但如果定时周期只有 100μs,这个误差就不能忽略了。所以本方案特意选了 10ms 的固定节拍,速度差异靠主循环累加次数拉开,避免在中断里频繁修改初值带来的额外误差。
RETI 和 RET 的区别要清楚。RETI 会额外清除中断优先级状态,让同级中断能再次响应;写成 RET 在简单程序里可能碰巧能跑,但遇到嵌套中断会出诡异问题。写汇编这件事,指令用错就是错了,不存在“碰巧能跑”就行的说法。
5. 常见问题与排查:五个让流水灯翻车的隐性细节
程序跑不起来,或者跑起来行为不对,80% 的问题集中在五个地方。每一条都是我见过或者踩过的真实案例,按“现象→原因→解决”列清楚。
5.1 LED“三三两两”乱跳:查表偏移量的隐蔽错误
现象是 LED 不按顺序从第一盏开始亮,而是从中间某个位置开始,甚至跳跃式点亮,毫无规律。查表法里最常见的原因是 IDX 初值不是 0,或者查表前 DPTR 被其他操作改掉了。汇编里 DPTR 是唯一的 16 位指针,如果你的扫码模块里不小心用了 DPTR 做别的用途,回到 UPDATE_LED 后查表地址就全乱了。
解决方法是每次查表前重新给 DPTR 赋值,IDX 初值在初始化段明确写 #00H。如果还不正常,在仿真环境里加断点,运行到 MOVC A, @A+DPTR 之前,查看 DPTR 的值是不是 #PATTERN 的地址,A 是不是 0 到 7。这种问题靠眼睛看代码不容易发现,但用调试器的内存窗口一眼就能定位。我还见过一个人把 PATTERN 表定义在代码段之外,汇编器直接报错,这种情况编译期就能发现。
5.2 按键松手才生效:忘了“先写 1 再读引脚”
现象是按下按键没反应,松手瞬间功能才切换一下,或者需要用力按、多按几次才有响应。原因几乎全是同一个:8051 的 P3 口是准双向口,内部有上拉结构,但输入引脚真正要读外部电平之前,必须先把端口锁存器写成 1。如果你之前对 P3.2 写过 0,读引脚时会一直读到 0,按键电平变化被掩盖了。
解决方法是读之前对按键引脚置 1。最稳妥的做法是初始化时把整个 P3 写成 FFH:MOV P3, #0FFH。这样所有 P3 引脚都被上拉,外接按键接地后按下读 0,松开读 1。如果你的程序里有用 P3 输出控制其他电路,那就只置位按键对应引脚,比如SETB P3.2。这个坑几乎每届课设都会有人踩,原因是查原理图时没注意准双向口的读引脚规则。
5.3 速度档位全按倍率出错:初值没有按晶振重算
现象是程序在仿真里速度正常,下载到开发板上整体变快或变慢,每个档位偏差比例一致。原因就是 2.3 节说的,初值按 12MHz 算的,但板子上是 11.0592MHz,或者 CPU 被配置成了 6T 模式。仿真器通常默认 12T,真实芯片未必。
解决办法是回到公式算一遍。12MHz 下 10ms 初值 0xD8F0,11.0592MHz 下约 0xDC00,差距大约少计数 784 次,反映到流水速度上就是明显变快。改完初值后,用示波器或逻辑分析仪量一下某盏 LED 的脉冲宽度,和理论值对比,误差在 1% 以内就是正常的。记住,晶振频率是你板子上的实物参数,不是 IDE 里的预设参数。
5.4 程序下载后不跑,但电压正常:复位电路与下载线电平
现象是在线仿真一切正常,拔掉仿真器单独上电,LED 不亮,量电源电压又正常。这类问题多半不在程序里,而在硬件环境。8051 内核单片机的复位引脚需要稳定的高电平持续若干个机器周期,如果复位电路的上电延时不够,芯片可能停在复位状态。常见做法是复位引脚接一个 10μF 电容到地,再并联一个 10kΩ 电阻到电源,这个组合给上电复位提供足够的 RC 延时。不同板子参数略有差异,但这两个值是通用的起点。
另一个容易忽略的是下载端口的电平。脱机运行和下载时共用某些引脚,如果下载完成后引脚状态没释放干净,程序可能被锁在编程模式。解决方法是拔掉下载线再上电测试。我见过一个案例,开发者把下载线一直插着,每次上电都卡死在引导区,拔掉下载线立刻正常。这不是程序 bug,是使用习惯问题,但排查时往往会绕很大一圈。
5.5 用手碰晶振引脚灯就乱闪:干扰、去耦与引脚悬空
现象是不碰电路板一切正常,手指靠近晶振或按键区域,LED 开始乱跳,节奏明显不对。原因有两类:一类是按键引脚悬空时受到人体感应干扰,被误判为按下;另一类是晶振电路附近的干扰直接影响时钟,导致定时器计数不稳定。
解决方法是给按键引脚加弱上拉,硬件上可以在按键并联一个 10nF 电容滤高频;如果板子没留位置,软件上把扫描频率降低一些,减少误触发窗口。晶振附近不要走长导线,晶振电容的地线要靠近芯片引脚。电源引脚附近加 0.1μF 去耦电容是板上常做的事,如果板子没有,可以飞线加一个。这类干扰问题在仿真里永远复现不了,只能靠硬件排查经验。
6. 进阶与扩展:多组流水表切换,在示波器上核对定时器周期再交报告
基础功能跑通之后,有余力可以做两件让报告更有说服力的事:扩展花样表和离线验证时序。
花样扩展不需要改主循环框架,只需要多定义两张流水表,比如跑马交替表:0AAH, 055H交替,或者来回弹跳表。UPDATE_LED 里根据一个花样变量选择不同的 DPTR 基址,其余逻辑完全复用。注意查表偏移量和表长度要匹配,8 个元素的表 IDX 范围 0 到 7,2 个元素的表就要对 IDX 取模。简单做法是每种花样用自己独立的 IDX 范围,切换花样时把 IDX 清零。
验证时序最直接的工具是示波器或者逻辑分析仪。把探头接到 P1.0,在速度阈值 SPEED_TH 设为 1 时,理论上 P1.0 的低电平脉宽等于定时器周期 10ms。为什么不是 80ms?因为 8 盏灯轮流转,每一盏都是每 80ms 轮到一次,但轮到的那一拍只持续 10ms,所以示波器上看到的是周期 80ms、低电平 10ms 的脉冲。如果你量出来低电平明显大于 10ms,查初值;如果周期不是 80ms,查 IDX 是否按时清零。这一步验证做完,报告里的时序数据就是实测值而不是估算值,答辩时很有底气。
写报告时,把状态变量表、初值计算过程、P1.0 的实测波形截图放进去,比大段贴代码更能说明问题。答辩老师最关心的通常不是“灯会不会亮”,而是“这个速度档位怎么算出来的、暂停时定时器还在不在跑”。这两句话能答清楚,课设就稳了。
我当年做这个题目时也栽过初值计算的跟头,仿真器里用的是默认 12MHz,板子上实际是 11.0592MHz,答辩现场被导师追问“你的 10ms 是怎么算的”,当时答不上来,回去重算才发现两个初值差了将近一百行代码的认知。从那以后,凡是涉及时间的汇编程序,我都先拿示波器量一遍再写报告。这个习惯救过我很多次,希望帮到你。
本文还有配套的精品资源,点击获取