简介:这份实验指导书面向高校电子信息、自动化等专业学生及嵌入式初学者,系统梳理嵌入式系统课程实验的完整流程与规范要求,帮助读者在缺乏教师逐条讲解时也能独立完成实验并规范撰写报告。压缩包内共1个doc文档,约965KB,内容按实验逐项编排,涵盖实验目的、设备、原理与环境、内容、步骤及报告格式等模块,并给出预习、实验过程与实验后总结三阶段的具体要求。指导书以Keil C51使用(汇编语言)为起点,涉及工程建立、源文件加入、编译连接、软件模拟与硬件仿真调试,以及十六进制与十进制转换等顺序结构编程训练,还包含方波输出等典型实验任务,可帮助读者掌握单片机实验箱操作、汇编程序编写与调试排错思路。目前已有338人学习下载,适合作为课程配套实验与自学练习的参考材料。
1. 六个实验拆开看:这份 8051 实验指导书到底能跑通什么
手上这份《嵌入式系统实验指导书(6 个实验)》不是那种只给目录不给细节的课程大纲,它把每个实验的目的、设备、原理、内容、步骤、报告格式全写死了,连 Keil C51 里点哪个菜单、存储器窗口敲d:30H还是i:30H都标了出来。整套东西围绕一块 8051 实验箱展开,六个实验从汇编入门一路推到 A/D 转换加数码管显示,覆盖了 Keil C51 工程建立、十六进制转十进制、8 段 LED 动态扫描、4×4 矩阵键盘、ADC0809 采样这几块嵌入式入门最常被考到的硬骨头。适合谁?正在带单片机实验课、需要一份能直接照着搭环境的助教,或者自学 8051 想找一条从点灯到键盘加显示的完整链路的人。它最大的价值不是理论多深,而是把“实验箱型号、8255 地址、字形码表、键值表”这些容易卡住新手的硬参数全摊开了,照着敲就能出结果。
2. 把 Keil C51 工程跑起来:从建工程到 P1.0 出方波
2.1 工程参数为什么必须锁死 12MHz 和 89C51
第一个实验看着简单,就是 P1.0 输出 1Hz 方波,但它把后面五个实验的地基全打了。建工程时选 Atmel 的 89C51,晶振频率填 12MHz,Output 页勾上生成 HEX,这三步一个都不能错。晶振频率不是随便填的,它直接决定 Keil 软件仿真时的时间基准,也决定你后面写软件延时循环时每条指令折算多少微秒。12MHz 下 8051 的机器周期是 1μs,标准 12T 模式,一条 DJNZ 是 2 个机器周期即 2μs,实验里那段MOV R2,#250 / DJNZ R2,$内层循环就是 250×2=500μs,三层嵌套凑出 0.5 秒,这个账必须自己会算,否则换个晶振程序时序全乱。
工程建好后新建源文件,存成test.asm,注意扩展名是 asm 不是 a51,虽然 Keil 都认,但指导书统一用 asm,后面 Add Files to Group 时按这个找。源文件里第一行必须是ORG 0000H,这是复位入口,六个实验全一样,漏了这条链接能过但跑起来行为不可预期,属于典型的“编译绿灯、上板翻车”。
2.2 方波程序逐行拆解与编译产物
ORG 0000H AGAIN: CPL P1.0 ; 取反 P1.0,方波高低电平各半周期 MOV R0,#10 ; 外层计数 10 次 LOOP1: MOV R1,#100 ; 中层 100 次 LOOP2: MOV R2,#250 ; 内层 250 次 DJNZ R2,$ ; 内层自减到 0,约 500us DJNZ R1,LOOP2 ; 中层循环,约 50ms DJNZ R0,LOOP1 ; 外层循环,约 0.5s SJMP AGAIN ; 回到取反,形成 1Hz 方波 END这段的核心是三层延时嵌套。DJNZ R2,$里的$表示当前指令地址,等价于原地跳转,R2 从 250 减到 0 共 250 次,每次 2μs,得 500μs;R1 循环 100 次得 50ms;R0 循环 10 次得 0.5s。CPL 取反一次耗时 1μs 可忽略,所以高低电平各 0.5s,整周期 1Hz。参数想改频率就动 R0/R1/R2 三个初值,但注意 8051 的 R 寄存器只有 8 位,最大 255,想要更长延时只能再加一层或者换 16 位计数。
编译走 Project→Build target,产物里 OBJ 是目标文件、LST 是带地址的列表文件、HEX 是烧录或仿真用的十六进制文件。LST 文件值得养成看的习惯,它把每条指令的机器码和地址列出来,程序跑飞时对着 LST 查 PC 落在哪条指令上,比盲猜快得多。编译报错会打在 Output 窗口,双击错误行能跳到源码对应位置,改完存盘重新 Build 即可。
2.3 软件仿真和硬件仿真的切换点
Debug→Start/Stop Debug Session 进的是软件仿真,不接实验箱也能跑,适合验证纯逻辑。但第一个实验的方波要观察 P1.0 电平变化,软件仿真里得开 Peripherals→I/O Ports→Port 1 窗口看位状态,或者用 Logic Analyzer 加 P1.0 信号。真正上板要切到硬件仿真:Project→Options for Target→Debug 页选实验箱对应的驱动,串口线接好、通电、拨位开关 K10 打到 B 端,再把 P1.0 接 L00、P1.1 接 L01,这样端口电平才能驱动 LED 看到闪烁。
这里有个反复被强调的细节:进硬件连接前先按一次单片机复位键 SS10,退出时也按一次。原因是实验箱的仿真串口和单片机复位状态有耦合,不复位直接连,偶尔会出现端口初始态不对、程序像没跑一样。血泪经验是,凡是“下载成功但灯不亮”,先按 SS10 再连一遍,八成能好。
3. 十六进制转十进制与 8 段 LED 动态扫描:两个实验的衔接
3.1 除以权值法的寄存器分配
第二个实验把片内 RAM 30H 里的两位十六进制数转成三位十进制,按高位到低位放进 31H~33H,存成非压缩 BCD。原理是除以权值:先除 100 得百位商,余数再除 10 得十位商,最后的余数就是个位。8051 没有除法指令直接给余数,得用 DIV AB,A 放被除数、B 放除数,执行后 A 是商、B 是余数,正好一次拿到两个值。
MOV A,30H ; 取待转换的十六进制数 MOV B,#100 ; 除权值 100 DIV AB ; A=百位商, B=余数 MOV 31H,A ; 存百位 MOV A,B ; 余数转去继续除 MOV B,#10 ; 除权值 10 DIV AB ; A=十位商, B=个位余数 MOV 32H,A ; 存十位 MOV 33H,B ; 存个位 SJMP $ ; 原地停住,方便观察这段要在单步模式下跑,View→Memory Window 调出存储器窗口,地址栏敲d:30H看片内 RAM 低 128 字节,敲i:30H看间接寻址区,敲x:xxxxH看片外 RAM。改 30H 的值用右键 Modify Memory at,连续多个单元用逗号隔开。断点用 Debug→Insert/Remove Breakpoint,Step 是纯单步会进子程序,Step Over 把子程序调用当一条指令过,调显示子程序时用 Step Over 更省事。
3.2 8255 三个口的分工与字形码表
第三个实验是重头,6 位 8 段 LED 动态显示。硬件上 8255 的 A 口做位驱动、B 口做段驱动,A/B/C/控制口地址分别是片外 RAM 的 4000H/4001H/4002H/4003H。段码低电平点亮,位码也是低电平选中,这个极性搞反了就是全亮或全灭。字形码表按 0~F、0.~F.、-、灭的顺序排,比如 0 是 C0H、1 是 F9H、灭是 FFH,这张表必须原样抄进程序,自己推容易在带小数点的字形上翻车。
动态扫描的本质是“骗眼睛”:每次只点亮一位,延时 0.5ms 后关掉,再换下一位,六位轮一遍 3ms,刷新率 333Hz,远高于人眼 50Hz 的闪烁阈值,看起来就是六位同时亮。显示子程序入口约定待显示数放 20H~25H,对应左起第 1~第 6 位,出口是每位亮 0.5ms 后返回,占用 R0、R1、R2、A、PSW、DPTR。
DISP: MOV R0,#20H ; 显示数缓冲区首址 MOV R2,#0FEH ; 位码初值,左起第一位低电平 MOV DPTR,#SEGTAB; 字形码表首址 DLOOP: MOV A,@R0 ; 取当前位待显示数 MOVC A,@A+DPTR ; 查字形码 MOV DPTR,#4001H ; B 口段驱动 MOVX @DPTR,A ; 送段码 MOV A,R2 ; 取位码 MOV DPTR,#4000H ; A 口位驱动 MOVX @DPTR,A ; 送位码,选中当前位 LCALL DELAY05 ; 延时 0.5ms MOV A,#0FFH ; 关全部位,防拖影 MOVX @DPTR,A MOV A,R2 RL A ; 位码左移,换下一位 MOV R2,A INC R0 ; 缓冲区指针加一 CJNE R0,#26H,DLOOP ; 六位没扫完继续 RET关键在MOVX而不是MOV,8255 挂在片外总线,必须用外部数据传送指令。位码初值 FEH 是 11111110,最低位为 0 选中第一位,每轮 RL 左移让 0 往高位走。延时 0.5ms 用 R1 做计数器,注意这个 R1 不能和主程序里的循环计数冲突,指导书专门提醒“使用的计数单元与显示子程序中用到的不能冲突”,这是新手最常见的隐形 bug:显示正常但主程序节奏全乱,查半天发现寄存器被覆盖。
3.3 主程序初始化与流水显示
子程序自己跑不起来,得配主程序。主程序先对 8255 初始化,控制字写 4003H,A 口 B 口方式 0 输出、C 口任意,控制字 80H 就够(C 口不参与显示时)。然后设堆栈 SP 到 6FH,往 20H~25H 填 00H~0FH 调显示应看到 0~F,填 10H~1FH 看到 0.~F.,填 20H、21H 看到“-、灭”。流水显示那段是每约 1 秒把 20H~25H 整体左移一位、25H 加一后与 0FH 相与,形成 0~F 循环左移的动画,调用显示子程序 330 次凑 1 秒,用双重循环实现且计数单元避开显示子程序占用的 R0/R1/R2。
上板前记得把 P0.0~P0.7 与 JD0~JD7 用排线连起来,开关 KC 打到上方,这两步漏了显示器就是全灭,很多人以为是程序错,其实是线没接。
4. 矩阵键盘扫描与 ADC0809 采样:中断式查询和键值映射
4.1 4×4 键盘的行列扫描与消抖
第四个实验的键盘接在 8255 的 PC0~PC3(4 行)和 PC4~PC7(4 列),所以 8255 控制字要改成 C 口高 4 位输出、低 4 位输入,即 10000001B 也就是 81H。扫描逻辑是逐列输出低电平、其余列高,读行输入,某行变低说明该列该行交叉点的键被按下。键值表按图 4-1 排,第一行 00H~03H、第二行 04H~07H 依此类推。
消抖是必须的:读到行状态有低电平后延时 20ms 再读一次,两次一致才认,否则机械抖动会让一次按键被识别成多次。松手检测是反复读行状态直到低 4 位全为 1,注意高 4 位是无用且不定的,必须用ANL A,#0FH滤掉,不然永远等不到“全 1”。
KEY: MOV R2,#0FEH ; 列驱动码,第一列低 KSCAN: MOV DPTR,#4002H ; C 口 MOV A,R2 MOVX @DPTR,A ; 输出列驱动码 MOVX A,@DPTR ; 读回行状态 ANL A,#0F0H ; 只看高 4 位行输入 CJNE A,#0F0H,KFOUND ; 有行变低则找到按键 MOV A,R2 RL A ; 换下一列 MOV R2,A CJNE A,#0EFH,KSCAN ; 四列没扫完继续(0EFH 为第四列) MOV A,#0FFH ; 无键返回 FFH RET KFOUND: LCALL DELAY20 ; 延时 20ms 消抖 MOVX A,@DPTR ANL A,#0F0H CJNE A,#0F0H,KCONFIRM SJMP KEY ; 二次不一致,重扫 KCONFIRM: ; 此处合并列驱动码与行状态,调查键值子程序查键值子程序 KEY20 的入口约定是 A 高 4 位为列驱动码、低 4 位为行状态,出口 A 为键值。它先数高 4 位里 0 在第几位算列号,再数低 4 位里 0 在第几位算行号,列号乘 4 加行号得索引,查 KEYTAB 表出键值。KEYTAB 的内容是0FH,0BH,07H,03H / 0EH,0AH,06H,02H / 0DH,09H,05H,01H / 0CH,08H,04H,00H,这张表和硬件接线强绑定,换实验箱就得重排。
主程序把键盘扫描和显示子程序串起来:初始化 8255 后循环调 DISP 和 KEY,KEY 返回非 FFH 就把 20H~24H 依次挪到 21H~25H、新键值放 25H,效果是每按一键从右端移入一个数字。这里 DISP 用的是实验 3 的子程序,所以两个实验的代码要合到一个 asm 文件里,寄存器占用得统一规划,否则又是覆盖问题。
4.2 ADC0809 的启动、查询与显示拼接
第五个实验把电位器 W1 分压出的 0~+5V 送 ADC0809 的 IN4,启动转换是往 8004H 写任意数,取结果是读 8004H,EOC 经反相器接到 P1.0,所以查询逻辑是等 P1.0 变低表示转换结束(反相后极性翻转,这点极易搞错,直接查 EOC 原极性会死等)。
MOV DPTR,#8004H MOVX @DPTR,A ; 写任意数启动 A/D WAIT: JB P1.0,WAIT ; EOC 反相后接 P1.0,变低才结束 MOVX A,@DPTR ; 读转换结果,8 位数字量 MOV 30H,A ; 转存,复用实验 2 的转换程序 LCALL HEX2DEC ; 调十六进制转十进制,结果在 31H~33H ; 再把 31H~33H 搬到 23H~25H,调 DISP 显示在第四~六位显示位置是第四到第六位,对应 20H~25H 里的 23H、24H、25H,前三位可以填灭(FFH 对应字形码)或留空。这样整个链路就通了:模拟电压→ADC0809→8 位数字量→除以权值转 BCD→动态扫描显示。调试时先用软件仿真确认 HEX2DEC 结果对,再上板看显示,分两段排查比一锅端快。
5. 避坑与排查:六个实验里最容易翻车的五处
5.1 编译通过但上板无反应
现象是 Build 零错误、下载也提示成功,LED 或数码管毫无动静。原因多半是没按 SS10 复位就进硬件连接,或者拨位开关 K10 没打到 B 端、P0 与 JD 的排线没插。解决顺序固定为:断电→按 SS10→检查 K10 和排线→上电→重新进 Debug。这套动作我后来做成 checklist,每次上板前过一遍,能省掉大半“程序是不是写错了”的自我怀疑。
5.2 数码管显示拖影或某位常亮
现象是数字边缘发虚、或者某一位始终亮着不灭。原因是动态扫描里关显示那步漏了,或者位码左移后没及时把上一位关掉。解决是在每位延时结束后先往 A 口写 FFH 关全部位,再移下一位;另外检查 0.5ms 延时是否被主程序拉长,延时过长会让刷新率掉到 50Hz 以下,肉眼就能看到闪烁。
5.3 键盘按一下出好几个数
现象是一次按键在显示器上移入多个相同数字。原因是消抖没做或 20ms 延时太短,也可能是松手检测没滤高 4 位导致提前退出。解决是把二次读行状态比对补上,松手循环里加ANL A,#0F0H只判高 4 位,确认全 1 才返回。另外检查 20ms 延时用的寄存器有没有和显示子程序撞车。
5.4 十六进制转十进制结果错位
现象是 30H 放 64H(十进制 100)却显示成 010 或 001。原因是 DIV AB 后没及时把余数从 B 挪到 A 再除第二次,或者存 31H~33H 的顺序反了。解决是严格按“商存高位、余数转下轮”的顺序写,单步跑一遍看 31H/32H/33H 三个单元的值,和手算对一遍再继续。
5.5 ADC 查询死等不返回
现象是程序卡在JB P1.0,WAIT出不来。原因是 EOC 经反相器后极性变了,等错了电平;或者启动转换的写地址不是 8004H。解决是先确认硬件上 EOC 到 P1.0 中间有没有反相器,有就等低电平,没有就等低到高再判;启动地址对着原理图核一遍,写错地址转换根本没启动,EOC 永远不变。
6. 把六个实验串成一条验证链:从单步到整机联调
六个实验单独跑通只是及格线,真正有价值的是把它们串成一条链做整机验证。我的习惯是从实验 5 倒着往回验:先确认 ADC 采样值随电位器线性变化,再把 HEX2DEC 的输出接到 DISP 上看第四到第六位数字跟着变,然后加键盘,按不同键切换显示内容,最后回到实验 1 的方波确认基础时序没被后续代码破坏。这条链上任何一环断,问题范围立刻缩小到相邻两个模块之间,比六个实验各调各的效率高得多。
具体做法是建一个总工程,把 DISP、KEY、HEX2DEC、ADC 采样四个子程序放同一个 asm 文件,主程序用状态机轮询:无键按下时持续采样显示,有键按下时把键值作为显示偏移或模式切换。寄存器分配提前画一张表,R0~R2 归显示、R3~R5 归键盘、R6~R7 归转换,谁都不越界,这样就不会再出现“显示正常但键盘失灵”的覆盖问题。延时子程序统一成一个入口、参数走 R7,避免每个模块各写一套延时导致时序对不上。
| 验证环节 | 观察点 | 通过标准 |
|---|---|---|
| ADC 采样 | 31H~33H 随 W1 变化 | 0V 对应 000,5V 对应 255 附近 |
| 进制转换 | 30H 与 31H~33H 对应 | 手算 BCD 一致 |
| 动态显示 | 六位数码管亮度均匀 | 无闪烁、无拖影 |
| 键盘扫描 | 每次按键移入一个数 | 无重复、无漏键 |
| 整机联调 | 按键切换显示模式 | 采样与显示同步更新 |
还有一条容易被忽略的:每次改完程序,必须先退出硬件连接、重新 Build、再重新连接才生效。指导书在实验 2 和实验 3 的重要提示里都写了这句,但新手往往改完直接点运行,看到结果没变就以为代码错了,其实是旧 HEX 还在跑。从那以后我每次改完代码都强制走一遍“退出连接→Build→复位→重连”的固定动作,再没在这上面浪费过时间。希望这套拆法帮到你,少走几个我已经踩过的坑。
本文还有配套的精品资源,点击获取