尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Keil uVision4 51单片机:建工程生成HEX与软件仿真教程

Keil uVision4 51单片机:建工程生成HEX与软件仿真教程 手上拿到一块最小系统板USB 转串口模块也插好了Keil uVision4 装在电脑上结果第一步就卡住——写好的 C 代码怎么变成能烧进芯片的那个 .hex 文件这个问题我见过太多刚接触 51 单片机的人卡在这里有人折腾一晚上最后发现只是没勾选“Create HEX File”。这篇就把我这些年带新人、自己踩坑总结出来的 Keil uVision4 使用流程完整写一遍从建工程、配选项、编译到软件仿真、下载排错把每一步背后的理由都讲清楚。它适合谁刚上手 51 单片机的学生、转行做嵌入式的朋友、以及多年没碰 51 想快速捡起来的老手。看完整篇你至少能做到三件事独立建一个能编译出 HEX 的工程、不接硬件也能用软件仿真把逻辑跑通、遇到常见报错知道去哪儿找原因。至于 51 单片机本身的寄存器、定时器、串口这些今天先放一边工具用不顺后面学什么都是事倍功半。1. 为什么51单片机入门绕不开 Keil uVision41.1 一套工具链到底在替你干什么写代码只是整个流程里最轻松的一环。真正把一行LED 0;变成芯片引脚上的电平变化中间要经过四道工序编译器把 C 语言翻译成 51 汇编汇编器把汇编翻译成机器码链接器把你自己写的代码和启动代码、库函数拼成一个完整的映像最后打包成 Intel HEX 格式的文本文件烧录软件才认。Keil uVision4 的价值就是把这四道工序塞进一个窗口里改完代码按一下 F7输出窗口直接告诉你成没成。拿生活中的事打个比方这套工具链就像厨房。C 语言是你写的菜谱编译器是切菜师傅汇编器是掌勺的链接器负责把主菜、配菜、调料摆到一个盘子里HEX 文件就是端上桌的那道菜。你可以不用集成环境自己找编译器、自己写 Makefile、自己调链接脚本但对手上只有一块开发板、目标是先让灯亮起来的人来说这不是必要的复杂度。我个人的判断是入门阶段把精力花在搞清楚寄存器和时序上比花在搭工具链上划算得多。还有一点容易被忽略uVision4 内置的 C51 编译器对 8051 内核做了大量针对性优化比如sbit、data、xdata、code这些存储类型关键字是标准 C 里没有的别的通用编译器根本不认识。你写sbit LED P1^0;能直接映射到位寻址空间一行顶汇编里好几行。这种“贴着硬件写代码”的便利是 Keil 在 8 位机领域站了几十年的根本原因。1.2 uVision4 和 uVision5、其它方案怎么选现在的现实是网上大部分 51 单片机教学资料、课程设计模板、开源代码都是基于 uVision4 截图的。你装 uVision5界面菜单位置变了更麻烦的是它默认不带 C51 编译器需要额外单独安装 C51 支持包装完还不一定认老工程。对新手来说第一周就耗在环境差异上非常打击信心。我的建议很直接纯粹学 51 单片机就用 uVision4配套资料多遇到问题一搜就有答案。如果你同时要碰 STM32 之类的 ARM 芯片那可以装 uVision5 并把 C51 支持包一起装上两个内核共用一个 IDE工程之间切换也方便。另外还有一条路是开源工具链 SDCC完全免费但语法细节、库函数、烧录流程都和 Keil 不一样现有教程基本对不上除非你有明确的“必须用开源方案”的理由否则入门阶段不推荐。至于版本号C51 编译器建议用 9.60 左右的版本稳定且对主流芯片支持完整。安装包到手先看来源是否可靠工程文件从别人那儿拷来的注意里面可能夹带旧路径配置打开报错“找不到文件”十有八九是这个原因。2. 装之前先搞明白目录结构和文件类型2.1 安装路径和授权这件事安装路径我强烈建议保持默认的C:\Keil至少保证全英文、无空格、无中文。这不是迷信Keil 的编译器和链接器在处理带中文或空格的路径时历史上出过不少“找不到文件”“无法生成输出”的怪问题排查起来极其浪费时间。我见过一个同学把工程放在桌面、桌面又设在用户中文名下编译死活不通过换到D:\work\led立马就好。装完之后要在C:\Keil\C51\BIN目录下确认有没有C51.exe、A51.exe、LX51.exe这几个可执行文件它们分别是编译器、汇编器和链接器是整套工具的心脏。再往上有INC目录放头文件reg51.h、reg52.h、intrins.h都在这里LIB目录放库文件STARTUP.A51这份启动代码通常也在附近。搞清楚这几个目录以后遇到“找不到头文件”的报错你就知道该去哪儿翻。关于授权Keil 的 C51 有评估版和正式授权两种状态。评估版可以直接用唯一的硬限制是编译出的代码不能超过 2KB。对学习阶段点的灯、跑的数码管、写的电子钟2KB 基本够用一旦你做课程设计要加液晶、加按键菜单、加串口协议很容易就撞上这个天花板编译时直接报FATAL ERROR L250: CODE SIZE LIMIT。到那一步就通过官方渠道获取授权别去折腾来路不明的工具浪费时间还容易中招。2.2 工程里那一堆后缀名哪些能动哪些不能碰新建完工程你会发现目录下冒出一堆文件很多人分不清哪个能删。我整理了一张表按照“能不能删”分类你可以直接对照后缀含义能否删除说明.uvproj工程文件记录芯片型号、文件列表、编译选项绝对不能删了整个工程就没了.uvopt用户界面配置记录断点、窗口布局可以删了会重建断点会丢.c/.h源文件和头文件绝对不能你的心血.A51汇编源文件如 STARTUP.A51谨慎删了可能缺启动代码.hex最终烧录文件可以重新编译即可生成.obj/.lst/.M51编译中间产物可以清理工程时会自动删.bak/.$$$备份文件可以备份盘满了就清这里有个经验如果工程要发给别人或者存档最干净的做法是在 Keil 里点Project Clean Targets把中间文件清掉再删掉.uvopt只留.uvproj、.c、.h和.A51。对方拿到后打开工程、重新编译一份干净的 HEX 就出来了不会有路径冲突和缓存干扰。3. 从零建一个能编译的工程3.1 新建工程与芯片型号的选择打开 uVision4菜单Project New uVision Project弹出保存对话框。这一步不要偷懒先想好目录结构我的习惯是每个项目一个文件夹里面再分src和doc工程文件放在项目根目录。路径用英文比如D:\mcu\led_test。保存之后立刻弹出器件选择窗口这是新手第一个坎STC89C52 这种国产芯片在 Keil 的器件库里根本搜不到因为它没有 STC 的官方支持。解决办法是选一个兼容型号Atmel 的AT89C52或AT89C51都可以二者的内核、指令集、寄存器布局完全一致编译出来的机器码 STC 芯片照样跑。选的时候展开Atmel AT89C52右边会显示 Flash 大小、RAM 大小确认一下再点 OK。选完芯片会弹窗问“是否把 STARTUP.A51 复制到工程目录”选“是”。这份启动代码干的事是上电后先清零内部 RAM、把堆栈指针 SP 指到合适的位置、初始化可重入栈然后才跳到你的main函数。你不加它程序有时候也能跑但变量初值、堆栈这些就不受控了出问题是随机的很难查。所以老老实实加进来。3.2 添加源文件与开启 HEX 输出芯片选完工程树里会出现Target 1下面挂着一个Source Group 1。这时候工程是空的得手动把.c文件加进去。右键Source Group 1 Add Files to Group选中你写好的main.c。很多人代码写完了、文件也放在工程目录里但忘了“Add Files”编译一下报0 Error却什么都没生成就是这个问题。接下来是那个让无数人翻车的勾选项。点工具栏的魔术棒图标或者菜单Project Options for Target切到Output页把Create HEX File勾上。不勾这个编译照样成功控制台还显示creating hex file...但你去目录里翻就是没有.hex。我建议顺手把输出目录改成独立的Objects文件夹源文件、中间文件、输出文件分开目录清爽也不会误删。同一页里还有Debug Information、Browse Information两个勾前者是调试时看变量用的后者是看符号交叉引用用的调试阶段都建议勾上占用空间可以忽略。3.3 目标选项里那几个必须改的参数魔术棒对话框我建议逐页过一遍。Target页最关键的是Xtal (MHz)这一栏填你的实际晶振频率。做软件仿真时这个值直接决定定时器、串口、延时函数的时序是否准确接了真实硬件时它不影响程序运行但影响调试器显示的时间。常用值有 11.0592 和 12 两种前者是为了串口波特率能被整除、误差小后者是整数兆算定时器方便。我一般点灯的项目填 11.0592要做串口通信的项目更得填它。Target页还有个Code Rom Size选项对应Small、Compact、Large三种模式。Small 模式下所有变量和代码都用data和code寻址效率最高但容量受限Large 模式对应 64KB 的外部寻址空间。入门项目直接选Large省心编译出来的代码略大一点但不会有空间不够的坑。C51页里的优化级别默认是 8 级。级数越高代码越紧凑但极端情况下会干扰调试时看变量的值。我的经验是调试阶段先降到 4 级左右等逻辑全通了、准备出正式版本时再调到 8 级压体积。Interrupt vectors at address这个值保持默认 0x0000 别改它是中断向量表的起始地址。3.4 第一次编译把输出窗口读懂按 F7 或者点Build按钮底部输出窗口会刷一串信息格式大概是这样先列出编译了哪几个文件然后是linking...最后是Program Size: dataxx.x xdataxx codexxx。这行Program Size是我每次都要看的code就是烧进 Flash 的字节数超过 2048 就说明撞上评估版的限制得想办法精简或者解决授权。输出窗口里的警告不要无视。warning C318: cant open file reg52.h说明头文件路径不对warning C206: missing function-prototype说明函数没声明就用了虽然能过但养成习惯把这些清干净。真正的目标是0 Error(s), 0 Warning(s)零警告的工程移植到别的芯片型号时出问题的概率低得多。要是编译报错双击错误行光标会直接跳到出问题那行代码这个功能比自己在几百行里找高效太多。4. 软件仿真不插硬件也能把程序跑通4.1 打开仿真模式和基本设置硬件还没焊好、或者手头只有一块板子但烧录还不通的时候软件仿真是救命的东西。设置位置在魔术棒的Debug页选左边的Use Simulator右边的Keil Monitor-51 Driver是配合硬件仿真器的没那个设备就别选。下面两个 DLL 和参数一般不用动对于 8051Dialog DLL通常是DLE51.DLLParameter是-pAT89C52选完芯片它会自动填。设好之后按CtrlF5进入调试界面会变样多出Registers窗口显示 ACC、B、PSW、SP、DPTR 这些寄存器的当前值左边还能看到Disassembly反汇编窗口。第一次看到这些可能有点懵但很快你就会发现它们有多好用——程序卡在死循环里看一眼 PC 指针和 SP大致就知道跑到哪儿了。退出调试用CtrlF5别直接关窗口否则断点配置有可能丢失。4.2 断点、单步、Watch 窗口和那几个“外挂级”的观察面板调试的基本操作在代码行左边的灰色区域点一下就是断点红色圆点表示生效F10是单步跳过不进入函数F11是单步进入进函数F5是运行到下一个断点。配合Watch窗口把想观察的变量名拖进去或者手动输入值会实时刷新比在代码里到处插打印方便一百倍。真正体现 Keil 对 51 支持深度的是Peripherals菜单。里面依次有I/O Ports、Timer、Interrupt、Serial几项进入软件仿真后点开I/O Ports Port 1会弹出一个面板实时显示 P1 口八个引脚的状态和 P1 寄存器的值。程序里写P1 0xFE面板上 P1.0 那一格立刻变亮或变暗。这个功能好在哪儿你点灯的程序不用真的接板子就能确认电平逻辑对不对、顺序对不对。同样是这个菜单Timer Timer 0面板能看到 TCON、TMOD、TL0、TH0 的数值还能直接看到计数方向。学定时器的时候我强烈建议一边看这个面板一边调参数比对着手册猜半天直观太多。Serial面板更夸张能虚拟出一个串口交互通道你发数据、单片机收数据全部在软件里跑完不用接任何线。4.3 一个完整例子延时闪烁 仿真观察光说概念太虚上一段能直接跑的代码。左边是经典的软件延时闪烁功能是 P1.0 上的 LED 每隔约 500ms 翻转一次#include reg52.h sbit LED P1^0; /* 定义 P1.0 接 LED */ void main(void) { unsigned int i; while (1) { LED 1; /* 输出高电平LED 灭灌电流接法 */ for (i 0; i 50000; i) /* 空循环延时 */ ; LED 0; /* 输出低电平LED 亮 */ for (i 0; i 50000; i) ; } }这段代码里i声明为unsigned int是有讲究的。如果用unsigned char最大只能到 255循环 50000 次会导致变量溢出、循环次数完全不对这是新手最常犯的错误之一。50000 这个数字怎么来的12MHz 晶振下51 的一个机器周期是 1 微秒12 个时钟周期一个简单的for空循环每轮大概消耗几个机器周期50000 轮大致在几十到几百毫秒量级具体多少得实测。想要精确延时就得上定时器。进仿真后首先点开Peripherals I/O Ports Port 1然后按F5全速运行你会看到 P1.0 那一格开始有节奏地变化。想看切换频率把i加进 Watch 窗口单步走几步看它每次自增的值。要是想数清楚一次延时到底多少毫秒可以在循环前后各设一个断点运行到断点时看右下角状态栏的sec计时一算就知道。这种“用数据说话”的方法比在网上抄一段不知道准不准的延时函数靠谱得多。如果换成定时器版本思路就变成让 Timer0 工作在模式 116 位定时每次溢出进中断中断里给一个计数变量加一加到 500 就翻转一次 IO#include reg52.h sbit LED P1^0; unsigned int cnt 0; void Timer0_ISR(void) interrupt 1 { TH0 (65536 - 1000) / 256; /* 12MHz 下 1ms 定时初值 */ TL0 (65536 - 1000) % 256; if (cnt 500) { cnt 0; LED !LED; } } void main(void) { TMOD 0xF0; /* 清 Timer0 控制位 */ TMOD | 0x01; /* Timer0 模式 116 位定时 */ TH0 (65536 - 1000) / 256; TL0 (65536 - 1000) % 256; EA 1; /* 开总中断 */ ET0 1; /* 开 Timer0 中断 */ TR0 1; /* 启动定时器 */ while (1) ; }初值的算法值得说清楚12MHz 晶振、12 分频下定时器每 1 微秒加一。要定时 1ms就需要 1000 个计数16 位定时器能装 65536 个所以初值是 65536 - 1000 64536。把这个数拆成高低字节写进 TH0 和 TL0。仿真的时候点开Timer 0面板你会看到 TL0、TH0 从初值开始往上加加到 65535 的下一次就溢出、进中断程序计数器跳到中断服务函数。这个画面比任何文字描述都有说服力。5. 常见报错与踩坑排查5.1 编译期的报错八成集中在这几个报错信息常见原因解决办法cant open file reg52.h头文件路径没配或安装不完整检查C:\Keil\C51\INC是否存在xxx: undefined identifier变量没定义、头文件没包含补上定义或#includesyntax error near xxx少了分号、括号不匹配看报错行号上一行multiple definition变量在头文件里定义了实体头文件里只放声明加externFATAL ERROR L250: CODE SIZE LIMIT评估版 2KB 限制精简代码或走正式授权0 Error 但没生成 hex没勾Create HEX File去 Output 页勾上error C231: redefinition同一变量重复定义检查是否被多个文件包含这里重点说“头文件里定义变量”这个坑因为它出现的频率极高。很多人的写法是在lcd.h里直接写unsigned char flag 0;然后在两个.c文件里都#include lcd.h链接时就报重复定义。正确做法是头文件里写extern unsigned char flag;在且仅在一个.c文件里写实体定义unsigned char flag 0;。这个规律记死了以后写库、拆多文件工程都不会再犯。5.2 下载和烧录环节的排查思路编译通过只是走了一半HEX 能不能烧进去是另一回事。用 STC 系列芯片的话烧录一般靠 STC-ISP 这类工具配合 USB 转串口模块。步骤是先选对芯片型号和串口号然后点“下载”接着给板子断电再上电——这个“冷启动”是关键芯片在复位瞬间检测串口上的下载握手信号热着的时候可能检测不到。很多人点了下载一直提示“正在检测单片机”八成就是没断电重启。串口不识别的话先看设备管理器里有没有多出一个 COM 口没有就是驱动问题有口但下载失败检查 P3.0、P3.1 是不是被其它外设占了这两个脚同时是串口收发脚有些板子上接了别的电路会干扰。波特率一般选 9600 以下更稳误码率低下载速度慢一点但成功率高。还有一个高频问题明明改了代码重新编译烧进去发现行为没变。先确认你烧的是最新生成的 HEX路径有没有搞错再确认 STC-ISP 里加载的文件路径就是你工程输出目录里的那个最后确认烧录工具没有勾“校验并保留原程序”之类的选项。这三个地方都排查完基本就能定位。5.3 编译通过但硬件不按预期动作这类问题最能锻炼人因为代码本身没毛病问题出在电路或配置上。我按排查优先级列一下思路。先看电源和晶振。用万用表量一下 VCC 是不是稳定的 5V再看晶振两边有没有起振有条件的话用示波器看一眼波形。晶振没起振程序根本不会跑表现就是“完全没反应”。接着看复位电路。51 是高电平复位的正常工作时 RST 脚应该是低电平。如果复位电路上拉电阻焊错位置芯片一直处于复位状态表现同样是“没反应”。这个点容易被忽略因为大家总怀疑代码。再看EA引脚。这个脚的接法直接决定芯片从哪里取指令接 VCC 表示执行内部程序存储器里的程序接 GND 表示从外部扩展的程序存储器取指令。对 AT89C52、STC89C52 这类内部有 Flash 的芯片EA必须接 VCC。我见过有人照着一块老图纸焊EA直接接地烧进去的 HEX 在主板上跑得好好的换到自己板子上死活不亮查了半天才找到是这个脚的问题。最后看 IO 的接法和极性。LED 有两种常见接法正极接 VCC、负极接 IO叫灌电流接法IO 输出低电平点亮反过来叫拉电流接法输出高电平点亮。代码里LED 0到底是亮还是灭完全取决于这个接法写代码前先确认硬件。另外 P0 口是开漏输出做普通 IO 输出高电平时必须外接上拉电阻P1、P2、P3 内部有弱上拉一般不用外接。这些细节在原理图上都有养成“先看原理图再写代码”的习惯能省下大量返工时间。6. 让工程更好维护的几个习惯6.1 做一个自己的模板工程每次都从新建工程开始配选项时间全浪费在重复劳动上。我的做法是维护一个模板工程里面已经把芯片型号、晶振频率、Create HEX File、输出目录、优化级别这些配好甚至预置好一个空的main.c和一个delay.c/h。新项目直接复制整个文件夹、改个名字就能开工。复制的时候注意两点一是把.uvopt和Objects目录删掉避免旧配置和旧编译产物带进来二是模板里的工程名和文件夹名保持一致免得时间长了分不清哪个是哪。用这个方法新工程从零到能编译两分钟以内就能搞定。6.2 引脚定义和常量集中管理项目一复杂引脚定义散落在各个.c文件里改一个引脚要全局搜索替换很容易漏。我的习惯是建一个config.h把所有sbit定义、常量、宏都放一起#ifndef __CONFIG_H__ #define __CONFIG_H__ #include reg52.h /* 引脚定义 */ sbit LED_RED P1^0; sbit LED_GREEN P1^1; sbit KEY_START P3^2; sbit BEEP P2^3; /* 常用常量 */ #define FOSC 11059200UL #define BAUD_RATE 9600 #endif这样一来硬件换了板子、引脚挪了位置只改这一个文件就行。sbit只能定义在全局不能放在函数里也不能被多个文件包含会重复定义所以这类引脚定义必须集中在唯一的头文件里别在多个.h里各写一份。6.3 代码写完先仿真、再上板我养成的顺序是写完一个功能模块先在软件仿真里跑一遍用 Watch 和外设面板确认逻辑正确、时序合理再烧到板子上验证硬件。这样做的好处是出错时能把“代码问题”和“硬件问题”分开定位。如果仿真里跑得好好的上板不亮那就往电路方向查如果仿真里就不对那跟硬件没关系安心改代码。这个习惯在调串口、调时序敏感的传感器时特别值钱。仿真里Serial面板可以把接收数据打出来你能确认发出的字节对不对而不是上板之后两眼一抹黑只能猜是芯片坏了还是线接错了。最后再分享一个小技巧调试时如果怀疑是延时不准别急着改代码先回到魔术棒的Target页确认Xtal填的频率和你手上晶振的实测频率一致。仿真器完全按这个值算时间填错了所有基于时间的推断都会偏。我第一次用软件仿真看定时器时就被这个坑过填了默认的 24MHz结果算出来的定时时间跟预期差一倍折腾了好久才发现是频率那一栏没改。这个细节看起来小但它会悄悄影响你对整个程序行为的判断早发现早省心。
返回列表