简介:这份资源是北京工业大学8051单片机实验报告的PDF文档,适合正在学习单片机原理与汇编语言的本科生,以及需要参考实验报告撰写规范的读者。报告以C8051F020芯片为核心,完整记录了并行数据传送、中断控制、定时器/计数器操作等实验过程,重点涵盖LED循环点亮、数码管0-9显示、延时子程序设计等基础编程训练。内容对汇编指令(MOV、CPL、RR、RL、CALL、DJNZ)的功能和用法做了细致说明,并给出修改源程序前后的对比思路,便于理解单片机I/O口控制与循环逻辑。资源共1个文件,为PDF格式,包体大小673KB,已有84人学习。通过该报告可以快速掌握8051系列单片机从初始化设置到程序调试的完整流程,同时也能借鉴其实验报告目录结构、粗体突出修改部分等规范写法,适合作为课程设计或实验报告的参考模板。
1. 8051单片机实验报告拆开以后,到底是一张什么图
一份北工大8051单片机实验报告.pdf放在桌面上的时候,多数人第一反应是打开找代码、找答案。但真正正确的打开方式是把它当作一份“课程实验地图”:LED跑马灯、按键输入、数码管显示、定时器计数、外部中断、串口通信,这些实验名称看起来各自独立,内里却是一条递进链。前半程在解决“怎么控制一个引脚”,后半程在解决“怎么让单片机自己处理时序而不占CPU”,等到串口实验做完,整颗 8051 的常用外设就被你完整摸过一遍了。
这篇文章按照我平时带课程设计时的思路来写:先解释每个实验为什么要这样做、寄存器和引脚关系在哪,再给出可以直接抄进 Keil 的代码,最后补充参数计算和实验报告里数据该怎么呈现。顺序我会调整为“环境搭建→GPIO→定时中断与串口→调试和数据整理”,每一步的坑都会单独点出来。你不需要一本正经从头读到尾,直接跳到当前正在做的那个实验也没有问题。
2. 准备工作不能跳过的三层:引脚、时钟和下载链路
2.1 引脚图与最小系统:P 口怎么分工决定了代码怎么写
8051 的引脚图在网络上一搜能出来几百张,但实验里高频出现的其实只有一组关系:P0、P1、P2、P3 四个 8 位 IO 口,外加 P3 口的第二功能。P0 口在外部扩展存储器和总线访问时要接上拉电阻,单纯做 LED 控制和按键读取时建议直接串联限流电阻使用,避免电流过大烧引脚;P1 口是纯通用 IO,实验板上跑马灯最常用这一组;P2 口在没有外部扩展时就是普通 IO,但在使用片外 RAM 或访问大容量 Flash 时会变成高 8 位地址线,这条要在工程配置里提前选对。
P3 口是最容易出问题的一组。它的第二功能覆盖了串口收发(P3.0 RXD、P3.1 TXD)、外部中断 0/1(P3.2 INT0、P3.3 INT1)、定时器 0/1 外部计数输入(P3.4 T0、P3.5 T1),当你想要同时使用多个外设时,P3 口的引脚会互相挤占。一个典型的错误是:先拿 P3.2 接了按键,后面做外部中断实验时发现中断信号根本拉不进去,因为有按键电路在强制拉低电平。所以设计实验板原理图时,P3 口全部要预留跳线帽或 0 欧电阻断开的可能。
一个标准的最小系统只需要四样东西:5V 供电、晶振加两个 20~30pF 负载电容、10μF 电容加 10k 电阻组成的复位电路、EA 引脚接 VCC(从片内程序存储器取指)。晶振频率的选择要结合后面的串口实验一起定,如果计划用 9600bps 以上波特率,11.0592MHz 晶振是唯一不用特殊误差补偿的常见选择;只做定时和 LED 的话 12MHz 更省事,因为定时器计数周期刚好是 1μs,计算初值不用除小数点。
| 引脚组 | 实验用途 | 第二功能/注意事项 |
|---|---|---|
| P0 | LED、数码管段选 | 总线模式下需要上拉电阻 |
| P1 | 跑马灯、按键读取 | 纯 IO,无第二功能 |
| P2 | 数码管位选、外扩地址高 8 位 | 有外部扩展时不要当普通 IO 用 |
| P3 | 串口、外部中断、定时器输入 | 第二功能优先级高,要复用必须处理冲突 |
2.2 Keil C51 与 IAR 6.3 的工程配置差异
北工大类 8051 课程的工程案例以 Keil 系列为主,近两年也还有同学用 IAR 6.3 for 8051 搭环境。两者在编译上最大的差异是:Keil C51 对 51 的 data/idata/xdata 内存模型封装得更直接,IAR 的代码密度和优化级别控制更细。对于课程实验,我更推荐 Keil C51 做日常编译、IAR 只用来做代码体积对比,理由很简单:课程资料里大部分现成代码是 Keil 风格,后续调试和答疑链条更短。
工程建立过程中,有三处配置必须手动改。第一是 Device 型号,如果选成 AT89C52 而板子上用的是 STC89C52RC,虽然寄存器一致,但 STC-ISP 下载时可能提示芯片 ID 不匹配;第二是 Target 选项卡里的晶振频率,填 11.0592MHz 不填 12MHz,否则延时函数的实际偏差会被放大;第三是 Output 选项卡勾选 Create HEX File,这个不勾,烧录器根本拿不到可写文件。另外,如果选用 STC 系列单片机,还需要在 STC-ISP 软件里设置硬件选项,最常见的是选择“12T 模式”还是“1T 模式”,这直接决定定时器计数速度和串口波特率误差,后面第 4 章会具体算。
注意:STC 芯片的下载时序本质是冷启动进 ISP 引导区。下载时点完“下载/编程”按钮后,必须给目标板断电再重新上电,否则会一直卡在“正在检测目标单片机”。这个动作是新手在环境搭建阶段卡住最多的地方。
2.3 一个最短可运行的蜂鸣器与 LED 联控模板
在开始复杂实验前,我习惯先做一个最简单的“上电 LED 闪三下”程序,用来验证下载链路和时钟是否正常。以下代码适用于 12MHz 晶振、任意 8051 兼容芯片,编译环境为 Keil C51。
#include <reg52.h> sbit LED = P1^0; sbit BEEP = P1^1; void delay_ms(unsigned int n) { unsigned int i, j; for (i = 0; i < n; i++) for (j = 0; j < 125; j++) ; } void main(void) { unsigned char k; for (k = 0; k < 3; k++) { LED = 0; BEEP = 0; delay_ms(200); LED = 1; BEEP = 1; delay_ms(200); } while (1) ; }逻辑说明:reg52.h头文件里定义了 AT89C52 的 SFR 和位寻址地址,sbit LED = P1^0相当于给 P1.0 引脚起了别名,后面直接写LED = 0就能让引脚拉低。delay_ms里两层循环是纯软件延时,内层循环j < 125在当前编译默认优化等级下约等于 1ms,这个值在不同 Keil 版本里会有 8% 以内的偏差,但用来点灯判断下载链路是否正常足够。
参数说明:如果程序烧进去 LED 不闪,先量晶振引脚电压和复位脚电平,再看下载器有没有真正识别到芯片,最后考虑 PC 端 USB 转串口驱动是否异常。不要上来就怀疑代码,这个模板和任何外设冲突都没有关系。
3. GPIO 实验的进阶路径:跑马灯、按键消抖和数码管扫描
3.1 跑马灯实验:从一个引脚到八个引脚的时序控制
跑马灯是所有 8051 实验里第一个能产生“程序在跑”感觉的题目,也是学校实验报告里几乎不会缺席的一项。常见的实验要求是:8 个 LED 依次点亮并循环,周期为 1 秒,要求用左移指令实现而不能逐位赋值。这个要求的考察重点不是左移本身,而是 8051 在循环移位时如何处理最高位。
#include <reg52.h> void delay_ms(unsigned int n) { unsigned int i, j; for (i = 0; i < n; i++) for (j = 0; j < 125; j++) ; } void main(void) { unsigned char dat = 0xFE; while (1) { P1 = dat; delay_ms(500); dat = (dat << 1) | 0x01; if (dat == 0xFF) dat = 0xFE; } }逻辑说明:0xFE的二进制是1111 1110,对应 P1 口只有最低位接地,其余引脚输出高点平,所以只有第 1 个 LED 亮。每次循环把dat左移一位并和0x01做或运算,作用是让新移进来的最低位补 1,保证除了当前位之外的其他 LED 全灭。当移位到dat变成0xFF时说明已经把 8 个 LED 全部走完一轮,重新赋初值0xFE。
参数说明:延时 500ms 乘以 8 个 LED 正好 4 秒一个完整来回,这个时间在实验报告里写“循环周期 4 秒”,不是每个 LED 的亮灯周期。如果你想做成“单灯回扫”效果,可以在if分支里把方向标志位翻转,用两个方向的移位序列交替执行,这一点可以在报告的拓展部分补充,但主体代码保持左移循环即可。
3.2 按键消抖的正确做法:状态判断比单纯延时更可靠
按键实验是 8051 课程里第一道真正需要“考虑机械特性”的题目。按键按下和松开的瞬间,机械触点会产生 5ms 到 20ms 不等的抖动,如果不做消抖,一次按压可能被单片机识别成多次触发。常见的消抖方案有两种:一种是软件延时 20ms 后重新读取并确认,另一种是采样多次取稳定状态。课程实验里标准做法是前者,因为代码直观、运行逻辑容易解释,但这其中最关键的细节是读取时机。
#include <reg52.h> sbit KEY = P3^2; sbit LED = P1^0; void delay_ms(unsigned int n); void main(void) { unsigned char key_state = 0; unsigned char key_last = 0; while (1) { key_state = KEY; if (key_state == 0 && key_last == 1) { delay_ms(20); if (KEY == 0) { LED = !LED; } } key_last = key_state; } }逻辑说明:key_last保存上一次读取的按键电平,只有检测到“电平从高到低的下降沿”时才进入消抖确认流程。按下后 20ms 再读一次,如果读到的还是低电平,说明按键确实按下了而不是抖动。松开瞬间检测到上升沿,key_last恢复为 1,下一次按下才能再次触发。这段代码的精妙之处在于“边沿触发”而不是“电平触发”,它保证按住按键不放时 LED 只翻转一次,而不会像直接写if (KEY == 0)那样在按住低速连闪。
参数说明:20ms 是按键消抖的通用下限,如果用的按键手感较硬或有焊锡残留,可以加到 30ms 到 50ms,但注意不要超过按键的物理按下时间,否则按正常速度操作时会丢失按键。实验报告里建议记录一组对比数据:无消抖时按下一次识别出几次脉冲、加 20ms 消抖后识别出几次,这个数据要写进“实验结果与分析”部分。
3.3 数码管动态扫描:段选与位选到底怎么分
数码管实验是 GPIO 实验阶段最难的一项,它要求同时使用两组 IO:一组驱动数码管的 7 段笔画和公共端(段选),另一组驱动多个数码管的公共端或位选端。动态扫描的核心思想是人眼视觉暂留:以 1ms 到 2ms 的间隔轮流点亮每个数码管,让它们在 100Hz 以上的刷新频率下看起来像同时显示。
#include <reg52.h> unsigned char code table[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07}; sbit LSA = P2^2; sbit LSB = P2^3; sbit LSC = P2^4; void delay_ms(unsigned int n); void main(void) { unsigned char i; while (1) { for (i = 0; i < 8; i++) { switch (i) { case 0: LSC = 0; LSB = 0; LSA = 0; break; case 1: LSC = 0; LSB = 0; LSA = 1; break; case 2: LSC = 0; LSB = 1; LSA = 0; break; case 3: LSC = 0; LSB = 1; LSA = 1; break; case 4: LSC = 1; LSB = 0; LSA = 0; break; case 5: LSC = 1; LSB = 0; LSA = 1; break; case 6: LSC = 1; LSB = 1; LSA = 0; break; case 7: LSC = 1; LSB = 1; LSA = 1; break; } P0 = table[i]; delay_ms(1); P0 = 0x00; } } }逻辑说明:table[]是共阴数码管 0~7 段码的十六进制字模表,0x3F对应数码管显示“0”。LSA/LSB/LSC三根线控制 74HC138 译码器的输入,用来选择当前点亮哪个位,3 根线可以扩展控制 8 个数码管。P0 = table[i]把当前位的段码送到 IO,延时 1ms 让数码管稳定显示,然后P0 = 0x00消隐,防止屏幕上出现位与位之间的串影。
提示:不少学生学习此实验时觉得“只亮了一个数码管”,这是因为刷新频率不够高。延时 1ms 换 8 个管子正好 8ms 一轮,刷新率 125Hz,眼睛已经看不到闪烁。但如果你用 10ms 延时,刷新率降到 12.5Hz,肉眼就会明显感到闪烁,这个现象写在报告里作为“动态扫描刷新率对显示效果的影响”是加分项。
4. 定时器、中断与串口实验:把时间交给硬件去数
4.1 定时器计数初值的计算:11.0592MHz 和 12MHz 差在哪里
多数 8051 课程实验在定时器部分的题目是:“用定时器 0 实现 LED 灯精确延时 50ms 翻转”。这里的“精确”意味着不能再用delay_ms的软件循环,而要用定时器硬件计数,计数溢出时触发一次中断或在查询标志位后翻转 LED。定时器 0 的计数周期由晶振频率和分频模式共同决定。
以 STC89C52 为例,默认工作在 12T 模式下,即 12 个时钟周期计一个数。如果晶振是 12MHz,那么定时器每计一个数耗时 1μs,要定时 50ms 就需要计数 50000 次,所以定时器的初值应为 65536 减去 50000,即 15536,换算成十六进制是0x3CB0。如果晶振换成 11.0592MHz,每计数一次是 12 除以 11.0592MHz,约等于 1.085μs,50ms 需要 46080 次计数,初值为 19456,即0x4C00。
| 晶振频率 | 12T 模式计数周期 | 定时 50ms 需要的计数值 | 初值 |
|---|---|---|---|
| 12MHz | 1μs | 50000 | 0x3CB0 |
| 11.0592MHz | 1.085μs | 46080 | 0x4C00 |
| 6MHz | 2μs | 25000 | 0x9E58 |
#include <reg52.h> sbit LED = P1^0; void timer0_init(void) { TMOD = 0x01; TH0 = 0x4C; TL0 = 0x00; ET0 = 1; EA = 1; TR0 = 1; } void main(void) { timer0_init(); while (1) ; } void timer0_isr(void) interrupt 1 { TH0 = 0x4C; TL0 = 0x00; LED = !LED; }逻辑说明:TMOD = 0x01把定时器 0 设为 16 位定时模式,TH0/TL0构成一个 16 位计数器,计数溢出后硬件自动把 TR0 的定时中断标志位 TF0 置 1,并向 CPU 发出中断请求。中断服务函数timer0_isr开头必须重新装载初值,因为 16 位定时器溢出后就从 0 开始继续计数,不重装初值的话下一次定时时间会变成 65536 个计数周期,灯的翻转节奏会突然拉长一倍。
参数说明:ET0 = 1使能定时器 0 中断,EA = 1打开总中断开关,TR0 = 1让定时器启动。三个位少一个都不会工作,而且顺序不要调反,虽然实际写在一起也能用,但规范顺序是先关全局中断再配置局部中断,最后启动定时器,这样能避免配置一半时产生意外中断。
4.2 外部中断的触发方式选择与中断优先级
外部中断实验通常要求用 P3.2 引脚上的按键触发中断,控制 LED 状态切换。这里比普通按键实验多出一个关键点:中断触发方式有下降沿触发和低电平触发两种,由 TCON 寄存器中的 IT0/IT1 位决定。下降沿触发适合按键这种短促信号,低电平触发则适合外部低电平持续有效的系统信号。
#include <reg52.h> sbit LED = P1^0; void main(void) { IT0 = 1; EX0 = 1; EA = 1; while (1) ; } void int0_isr(void) interrupt 0 { LED = !LED; }逻辑说明:IT0 = 1把外部中断 0 设为下降沿触发,EX0 = 1允许外部中断 0 请求,EA = 1开总中断。中断服务函数里简单翻转 LED 即可,不需要再做按键消抖,因为在下降沿触发模式下,硬件只会在引脚电平从高变低的瞬间产生一次请求,抖动可能导致多次触发,但概率远低于电平触发方式,减轻了软件负担。
参数说明:8051 只有两级中断优先级,由 IP 寄存器控制。如果外部中断 0 和定时器 0 同时发生请求,系统按自然优先级响应,定时器 0 的优先级高于外部中断 0。如果你希望外部中断不管什么时候都能立即响应,需要写PX0 = 1把它设为高优先级。这个设置在实际工程中的意义是:让紧急外部事件能抢占耗时较长的定时任务。
4.3 串口通信的波特率配置:为什么必须用定时器 1 方式 2
串口实验是整份实验报告里最有含金量的部分,也是最容易出现“硬件正常代码也对但收发乱码”的部分。8051 的串口工作在模式 1 时,波特率由定时器 1 的溢出率决定,计算公式为波特率等于定时器 1 溢出率除以 32 再乘以 2 的 SMOD 次方。如果一个系统晶振选择了 11.0592MHz,那么把定时器 1 配置为方式 2(8 位自动重装),TH1 取 0xFD,SMOD 取 0,就能得到标准 9600bps。
#include <reg52.h> unsigned char buf; void uart_init(void) { TMOD = 0x20; TH1 = 0xFD; TL1 = 0xFD; TR1 = 1; SM0 = 0; SM1 = 1; REN = 1; EA = 1; ES = 1; } void main(void) { uart_init(); while (1) ; } void uart_isr(void) interrupt 4 { if (RI) { RI = 0; buf = SBUF; SBUF = buf; while (!TI) ; TI = 0; } }逻辑说明:TMOD = 0x20把定时器 1 配置为方式 2,也就是 8 位自动重装模式,这个模式下 TH1 每次溢出后自动把初值装回 TL1,不需要在中断里手动重装,波特率稳定性远高于方式 1。TH1 = 0xFD的初值来自波特率计算:256 减去 11.0592MHz 除以 12 再除以 32 再除以 9600,结果是 253,也就是 0xFD。SM0 = 0; SM1 = 1把串口设为模式 1,即 10 位异步收发,8 位数据位加 1 位起始位和 1 位停止位。REN = 1打开接收允许开关,ES = 1打开串口中断。
逻辑说明补充:中断服务函数里先判断RI接收中断标志位,置 1 说明收到一个字节,读SBUF就是读出数据,然后SBUF = buf把收到的数据原路发回,实现回显功能。while (!TI)等待发送完成是因为 SBUF 写入后要经过一个完整的移位过程才能发完,TI 置 1 表示发送结束,这时才能写下一个字节。TI = 0是清标志,因为中断标志如果不手动清零,会反复进入中断。
参数说明:如果用 12MHz 晶振跑 9600 波特率,TH1 初值取 0xFD 时实际波特率为 7812.5bps,和 9600 相差约 18%,收发两端必然乱码。所以串口实验对晶振频率的讲究比定时器实验更严格,报告里可以加一行“晶振频率与波特率误差关系”的测试数据,这是拿高分的关键点。
5. 实验数据验证与报告整理的三个实用手段
5.1 把实验现象变成可信的波形和图
实验报告如果只写“LED 正常闪烁”,审阅人既无法判断延时是否准确,也无法判断你真正跑过设备。最常见的做法是用示波器或逻辑分析仪抓波形。LED 翻转时在引脚上量到的是一个方波,方波的周期就是你定时的两倍,如果代码写 50ms 翻转一次,示波器上看到的应该是 100ms 周期。串口实验则抓 TXD 引脚上的波形,波特率 9600 时每位宽度约 104μs,起始位是低电平,可以数出一帧电平变化的次数来判断数据是否正确。
逻辑分析仪是比示波器更适合课程实验的设备,它有多通道并行采集能力,可以同时抓 P3.0 和 P3.1 的收发波形,并用软件自动解析位流为十六进制数。报告中直接给出解析后的收发数据帧对比表,比贴代码可靠得多。
5.2 用数据表代替大段文字描述实验效果
单片机课程实验报告最忌讳重复实验原理,原理书里全都有,老师想看的是你的实验数据和异常分析。整理数据表时,建议按“实验项目、测试条件、理论值、实测值、误差、说明”六列排布。
| 实验项目 | 测试条件 | 理论值 | 实测值 | 误差 | 说明 |
|---|---|---|---|---|---|
| 软件延时 | 12MHz, 100ms | 100ms | 108ms | 8% | 与 Keil 优化等级有关 |
| 定时器 0 | 12MHz, 50ms 初值 0x3CB0 | 50ms | 50.02ms | 0.04% | 中断重装造成微小误差 |
| 串口发送 | 11.0592MHz, 9600 | 9600bps | 9600bps | 0% | 晶振频率精确时无累计误差 |
| 按键消抖 | 20ms 确认 | 识别 1 次 | 识别 1 次 | 0% | 抖动被滤除 |
最后提醒一个操作性技巧:实验报告里的代码块不要直接截 Keil 的蓝色背景截图,Word 里用等宽字体粘贴纯文本代码,然后把关键代码段单独加高亮,这样做搜索时可以搜到、打印时也清晰。每个实验配一至两张实物接线照片和一张波形截图,这份报告就会显得“可审计”“可复现”,也方便后续复盘时快速定位是硬件问题还是时序问题。
本文还有配套的精品资源,点击获取