# 8051_c51_单片机从汇编到C_从Boot到应用实践教程 **Repository Path**: langcai1943/8051-from-boot-to-application ## Basic Information - **Project Name**: 8051_c51_单片机从汇编到C_从Boot到应用实践教程 - **Description**: 这是一个教学仓库。不使用具体硬件,直接使用Keil做模拟器,使用虚拟串口做输入输出,直接在电脑上编译运行,保证不被硬件问题卡住,从Keil官网下载Keil软件后再下载此工程,打开工程运行程序后能直接看到结果。重点介绍8051的寄存器、指令集、Keil伪指令、汇编Boot、汇编编程套路、C语言编程套路、软件框架、通信框架。 - **Primary Language**: Assembly - **License**: Not specified - **Default Branch**: develop - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 4 - **Created**: 2022-09-29 - **Last Updated**: 2025-08-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 8051(c51)单片机从汇编到C语言,从Boot到应用开源系列教程 |作者|将狼才鲸| |---|---| |修改日期|2024-07-23 | * 本仓库相关网址: [CSDN文章地址](https://blog.csdn.net/qq582880551/article/details/127594062) [Gitee工程和源码地址](https://gitee.com/langcai1943/8051-from-boot-to-application) * 相关仓库: 嵌入式整体介绍,里面也描述了部分8051的内容: [才鲸嵌入式 / 嵌入式知识图谱WiKi](https://gitee.com/langcai1943/embedded-knowledge-wiki) C语言框架讲解,让你对C语言要学哪些东西有一个完整的了解: [embedded_programming_skills/ 0_doc / 02-C语言框架讲解.md ](https://gitee.com/langcai1943/embedded_programming_skills/blob/develop/0_doc/02-C%E8%AF%AD%E8%A8%80%E6%A1%86%E6%9E%B6%E8%AE%B2%E8%A7%A3.md) --- ## 一、仓库介绍 |工程名|作用| |---|---| |001_点亮一个LED灯|从IO口输出高低电平,并使用Keil示波器查看输出,分别用汇编和C语言工程实现| |002_延时与函数|使用汇编进行精准的us和ms延时,用C语言进行大致的延时,介绍汇编函数传入的参数和返回值位置,C语言如何调用汇编函数,通过Keil示波器查看延时的准确度| |……|……| |001-at89c51-simulator-1|Keil 新建Hello world工程| |002-at89c51-simulator-2|使用汇编展示8051代码是从哪些地址开始运行的| |002-at89c51-simulator-3|使用汇编让定时器持续产生中断| |002-at89c51-simulator-4|使用汇编从IO输出波形,并用Keil模拟器查看输出(有图)| |002-at89c51-simulator-5|使用汇编从串口输出字符串,并通过Keil模拟器查看输出| |002-at89c51-simulator-6|使用汇编从Keil模拟器IO获取数据并进行响应(有图)| |……|……| |01_Hello_world|直接从Keil调试窗口中输出Hello world| |02_Keil_boot_annotation|对Keil自带的汇编boot源码进行注释| |03_Assemble_register|展示8051真正的程序入口, 和添加自定义寄存器头文件| |04_Macro_func_and_irq|展示汇编宏定义函数和中断处理| |05_Assemble_hello_world|用汇编从Keil调试窗口中输出Hello world| |06_uart0_loopback|Keil调试输出窗口串口回环| * 这是一个8051教学仓库。不使用具体的硬件,直接使用Keil做模拟器,使用虚拟串口做输入输出,直接在电脑上编译运行,保证不被硬件问题卡住,从Keil官网下载Keil软件后再下载此仓库,打开仓库里的工程运行程序后能直接看到结果。重点介绍8051的寄存器、指令集、Keil伪指令、汇编Boot、汇编编程套路、C语言编程套路、软件框架、通信框架。 * 仓库中有多个Keil工程和源码,每个仓库都是独立的,能够直接打开、编译和运行。 * 本仓库面向的人员有: 1. 在学校学过《C51单片机原理(汇编)》、《C语言程序设计》等类似课程,但是并没有使用一款8051的芯片进行过商用的项目开发的。 2. 使用一款8051芯片做过C语言的商用项目开发,但是对8051从芯片上电开始到main()函数执行前的boot流程不了解的。 3. 没有进行过8051纯汇编编程的。 4. 没有使用汇编从头开始写过8051的boot程序的。 5. 主业是32位CPU的C语言开发,工作中临时接触到了8051芯片的编程,或者只是想复习一下8051相关的知识,特别是汇编逻辑的。 6. 对Keil C51 C语言编程与一般32位 C语言的区别不清楚,不熟悉8051的数据类型、显式指明数据存放位置、8051大端与ARM小端的区别、8051 RAM和ROM最大容量限制、8051的串口等常用驱动寄存器与ARM差异的人员。 --- * 为什么直接使用Keil中的模拟器来运行程序,而不选用一款8051的芯片? 1. 编程习惯: 在学习C语言过程中,或者单纯验证C语言纯逻辑功能时,可以直接使用GCC+Makefile、Qt或者VS(Microsoft Visual Studio)编译运行;一条printf就能验证编译环境的正确性。 而如果使用带8051芯片的硬件,则搭建环境的过程中会遇到各种各样的问题,没有人指导的话经常会遇到瓶颈;购买开发板也需要额外的开支。 如果是进行32位CPU的嵌入式开发,也有开源的QEMU模拟器可供使用。 2. 工作习惯: 在芯片原厂,当一款芯片正在开发中,还不能在FPGA上跑的时候,嵌入式软件工程师的工作也不会等着,往往是直接先用模拟器搭建软件工程,同步编写和测试程序。 3. 节省时间,方便随时随地调试程序: 只要不涉及到具体的硬件驱动编写,使用软件模拟器已经能模拟各种通讯驱动的逻辑和业务逻辑,只需要一台电脑,你就可以在家里、在外地都编写和调试代码。 4. 普适性: 嵌入式芯片各种各样,五花八门,没有谁能对所有的芯片都熟悉,而芯片的最大公约数:内核,在模拟器中已经能完美运行。如果是32位CPU,QEMU中已经能模拟USB、网络、串口、SPI、I2C、显示屏等各种外设;而Keil C51中也能模拟串口、IO,这对于学习软件逻辑已经够用了。 5. 简单: 打开Keil工程,直接运行就能在软件上看到串口输出的结果。 --- * 为什么不选用Keil + Proteus的方式来模拟8051硬件? 1. 的确是有一些单片机开发人员软件硬件都熟悉,能接受这种方式,但是大部分嵌入式软件开发人员,是没有硬件相关软件的使用经验的,Proteus大大增加了学习成本,而且以后的工作中也用不到。 * 网上能搜到大量8051的教学文章,为什么还要编写这个仓库? 1. 在网上能搜到的大量8051教程,都很多是学校里的那一套逻辑,分章节描述各种寄存器、汇编指令,没有形成一个完整的工程,不能开箱即用。 2. 网上有很多8051的文章都是重复的,在查找时浪费时间。 3. 我自己其实也是在学习8051的过程中,学到哪写到哪加深记忆,也方便以后时间久了以后遇到同样的问题能回来查阅。 ## 二、环境准备及知识储备 * 开始下一步前,你需要下载并安装Keil软件,并且了解Keil的基本使用。如果你只是看一下源码中的一些写法,并不需要实际运行程序看结果,则忽略此条。Keil软件安装的基本流程如下,其它安装教程的网址会在后面列出: 1. 从官网下载Keil C51程序,这是一个IDE,集成了编辑器、编译器、链接器、模拟器。 2. 下载地址 https://www.keil.com/demo/eval/c51.htm 需要注册并填写个人信息,评估版只支持编译2K容量的代码,但本仓库前面一部分的工程在2K范围内,可以直接运行和调试。 3. 已经用过Keil的可以自行下载Keil破解版并进行破解。 4. 安装过程中,安装路径不要有空格,不要有中文目录。 5. 安装完成后打开“Keil uVision5”软件。 * *Keil安装与介绍参考链接* 1. 一是在Keil官网注册账号并下载Keil C51安装,但是安装好的软件只支持编译2K以内的程序。 https://www.keil.com/demo/eval/c51.htm 2. 二是安装破解的Keil C51。 [51单片机——如何安装Keil5(保姆级教程)](https://blog.csdn.net/m0_61744194/article/details/123785522) [【嵌入式学习】单片机入门——1.Keil安装(51版本) ](https://www.bilibili.com/read/cv14720113/) --- ## 四、工程与源码介绍 ### 1、点亮一个LED灯 * 本源码包含C语言和汇编工程,能直接在电脑中通过Keil模拟器运行,并在Keil示波器窗口看到 IO 输出的矩形波。 * [源码及工程链接](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/04_8051汇编和C语言教程/Src/001_点亮一个LED灯) * 汇编效果:![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/04_8051汇编和C语言教程/Src/001_点亮一个LED灯/ASM/模拟器示波器输出效果图.PNG) * C语言效果:![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/04_8051汇编和C语言教程/Src/001_点亮一个LED灯/C/模拟器示波器输出效果图.PNG) * 参考网址: * [2课:单片机引脚介绍](http://www.51hei.com/mcuteach/250.html) 该文章后半部分有C语言原始工程下载链接 * [4课:第一个单片机小程序](http://www.51hei.com/mcuteach/248.html) 该文章前半部分有汇编原始工程下载链接 ### 2、延时与函数 * ms、us级别的延时最好使用定时器,ns级别的延时就可以关闭所有中断后使用汇编,不需要精准的延时则可以使用C语言的for循环; * 使用C语言进行延时时,延时的时间不好算,一般通过实际测试得到,并且延时的时间长短容易受到代码优化的影响; * 在关闭中断的情况下,汇编函数可以做到精准控制延时,精度和指令周期一致,前提是你要知道当前主频和每条指令的执行时间(指令周期); * 延时可以用在IO输出的时序控制和通信端口模拟,例如用IO口模拟I2C、SPI、SDIO等协议; * [源码及工程链接](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/04_8051汇编和C语言教程/Src/002_延时与函数) * 该汇编工程里演示了汇编函数的编写、参数的调用和精准的延时; * 该C语言工程里演示了一般的延时,在C语言中如何调用汇编函数; * 汇编效果:![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/04_8051汇编和C语言教程/Src/002_延时与函数/ASM/汇编实现精准延时.PNG) * C语言效果:![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/04_8051汇编和C语言教程/Src/002_延时与函数/C/C语言使用循环实现延时.PNG) * C语言中调用汇编精准延时效果:![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/04_8051汇编和C语言教程/Src/002_延时与函数/C+ASM/C语言中调用汇编函数实现精准延时.PNG) * 参考网址: * [4课:第一个单片机小程序](http://www.51hei.com/mcuteach/248.html) 该文章前半部分有汇编原始工程下载链接 * [C51中汇编的使用及参数传递与数据返回](https://blog.csdn.net/lincomail/article/details/78910683) * [试把如下c函数改写成汇编语言函数,用51汇编完整写一个函数](https://blog.csdn.net/weixin_26782843/article/details/117149103) * [当AT89C51单片机外接晶振为6MHz时,其震荡周期、状态时钟周期、机器周期、指令周期的值各是多少?](https://zhidao.baidu.com/question/598121669.html) * [Keil官方8051 Instruction Set Manual指令集在线查看](https://www.keil.com/support/man/docs/is51/is51_instructions.asp) * [MicroChip Atmel官方指令集文档8051 Microcontroller Instruction Set下载](https://ww1.microchip.com/downloads/en/DeviceDoc/doc0509.pdf) * [MicroChip Atmel官方芯片手册Atmell 8051 Microcontrollers Hardware Manual下载](https://www.microchip.com/content/dam/mchp/documents/OTH/ProductDocuments/UserGuides/doc4316.pdf) * [Keil官方MCS-51 INSTRUCTION SET指令集文档下载](https://www.keil.com/dd/docs/datashts/intel/ism51.pdf) * Keil C51 C语言中调用汇编函数时,最多使用三个参数,默认第一个参数从R7开始放,第二个参数从R5开始放,第三个参数从R3开始放,如果是2字节的int,那么遵循8051的大端模式,高字节放在R2、R4、R6,低字节放在R3、R5、R7;如果是指针参数,无论是第一第二第三个参数,都放在R1~R3,存储类型是R3,指针值是0xR2R1(注意此时是小端模式存储);超过三个的参数请用外部RAM来实现; * 汇编函数将返回值返回给C语言时,返回值需要放在R7开始的位置,char就放在R7,int是0xR6R7,long和float是0xR4R5R6R7,指针是类型在R3,指针值0xR2R1; * 汇编里的函数如果要给C语言用,那么函数名(标号)前要叫下划线,例如 ```_LOOP: NOP; RET;```,C语言调用时去掉下划线,如:LOOP(); * 其实加下划线是代表有参数调用,但是无参数的汇编函数你也这么加并没有问题; * 想找原文的,在Keil安装目录的C:\Keil_v5\C51\Hlp\c51.chm,在里面搜索Parameter Passing或者Passing in Registers,原文摘抄如下: **Passing in Registers** C functions may pass parameters in registers and fixed memory locations. A maximum of 3 parameters may be passed in registers. All other parameters are passed using fixed memory locations. The following tables define which registers are used for passing parameters. | Arg Number | char, 1-byte ptr | int, 2-byte ptr | long, float | generic ptr | | ---------- | ---------------- | ----------------------------- | ----------- | --------------------------------------------- | | **1** | R7 | R6 & R7 (MSB in R6,LSB in R7) | R4—R7 | R1—R3 (Mem type in R3, MSB in R2, LSB in R1) | | **2** | R5 | R4 & R5 (MSB in R4,LSB in R5) | R4—R7 | R1—R3 (Mem type in R3, MSB in R2, LSB in R1) | | **3** | R3 | R2 & R3 (MSB in R2,LSB in R3) | | R1—R3 (Mem type in R3, MSB in R2, LSB in R1) | The following examples clarify how registers are selected for parameter passing. | Declaration | Description | | ---------------------------------- | ------------------------------------------------------------ | | **func1 ( int a)** | The first and only argument, **a**, is passed in registers R6 and R7. | | **func2 ( int b, int c, int \*d)** | The first argument, **b**, is passed in registers R6 and R7. The second argument, **c**, is passed in registers R4 and R5. The third argument, **d**, is passed in registers R1, R2, and R3. | | **func3 ( long e, long f)** | The first argument, **e**, is passed in registers R4, R5, R6, and R7. The second argument, **f**, cannot be located in registers since those available for a second parameter with a type of long are already used by the first argument. This parameter is passed using fixed memory locations. | | **func4 ( float g, char h)** | The first argument, **g**, passed in registers R4, R5, R6, and R7. The second parameter, **h**, cannot be passed in registers and is passed in fixed memory locations. | Copyright © Keil, An ARM Company. All rights reserved. ### 001-at89c51-simulator-1 * Keil 新建Hello world工程 ### 002-at89c51-simulator-2 * 使用汇编展示8051代码是从哪些地址开始运行的 ### 002-at89c51-simulator-3 * 使用汇编让定时器持续产生中断 ### 002-at89c51-simulator-4 * 使用汇编从IO输出波形,并用Keil模拟器查看输出 ![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/03_AT89C51系列/004-at89c51-simulator-4/Keil_C51模拟器IO口输出实际效果.JPG) ### 002-at89c51-simulator-5 * 使用汇编从串口输出字符串,并通过Keil模拟器查看输出 ### 002-at89c51-simulator-6 * 使用汇编从Keil模拟器IO获取数据并进行响应 ![img](https://gitee.com/langcai1943/8051-from-boot-to-application/raw/develop/03_AT89C51系列/006-at89c51-simulator-6/8051获取到Keil模拟器IO输入信号.JPG) ### 1)Hello world输出 * 本工程主要演示使用Keil创建一个R8051XC2的默认工程,使用Keil自带的Boot汇编文件,然后新建一个main.c文件,写一个printf函数 + 简单的串口0驱动,并能从软件窗口上看到printf的结果。 * 创建工程的步骤详见章节“二、环境准备及知识储备”中“Keil创建工程”小节中的内容,你可以省掉这步,直接打开现有的工程。 * 工程路径:[本文档同级目录/01_proj_and_src/01_Hello_world/](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/01_proj_and_src/01_Hello_world) * 你可以直接双击打开 ./01_proj_and_src/01_Hello_world/01_Hello_world.uvproj工程 * 然后编译:点击软件上面状态栏第三排左侧两个向下小箭头的图标。看到.\Objects\01_Hello_world" - 0 Error(s)表示编译通过。 * 然后开始运行:点击软件上面状态栏第二排右侧黑色放大镜+红色“d”的小图标;如果你的软件是官网下载的未破解的评估版,会出现一个弹窗,关掉那个弹窗不管它;程序会停在main()函数的第一行,先不要继续运行。 * 输出的结果会在Keil Debug状态下的UART #1窗口中;这个窗口Keil不会主动为你打开,你需要点击在软件上面第三排图标中的小串口带一个黑色串口的图标旁边的小三角形,选中里面的UART #1,然后软件右下角就会出现UART #1窗口。 * 继续运行:点击软件左上角一个向下箭头的图标,UART #1窗口中出现了Hello world!的输出。 * 额外的知识: * Keil输出窗口选项中,除了有UART #1、UART #2和UART #3外,还有一个Debug (Printf) Viewer窗口,这个窗口C51是用不了的,这是Keil MDK为ARM等芯片准备的,例如调试STM32时,不需要写串口驱动,而且直接写串口驱动还有点麻烦,而直接对fputc()函数进行重定向之后,能直接在这个窗口看到printf()的输出;这和在PC上直接写C语言程序有点类似,就更方便了。 * *参考网址 * [keil C51 重定向printf到串口](https://blog.csdn.net/Wekic/article/details/77418443) ### 2)Keil自带的汇编boot源码解析注释 * 本工程主要演示使用Keil创建一个R8051XC2的默认工程,使用Keil自带的Boot汇编文件,然后对这个STARTUP.A51进行注释。 * 工程路径:[本文档同级目录/01_proj_and_src/02_Keil_boot_annotation/](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/01_proj_and_src/02_Keil_boot_annotation) * 你可以直接双击打开 ./01_proj_and_src/02_Keil_boot_annotation.uvproj/02_Keil_boot_annotation.uvproj工程 ### 3)展示8051真正的程序入口, 和添加自定义寄存器头文件 * 本工程主要演示使用Keil创建一个空工程,不使用Keil自带的boot汇编文件,也不使用Keil自带的寄存器头文件,而是自己添加一切文件,展示8051真正的程序入口,添加自定义寄存器头文件,顺便对8051的每个寄存器进行注释。 * 工程文件:[本文档同级目录/03_Assemble_register/03_Assemble_register.uvproj](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/01_proj_and_src/03_Assemble_register) ### 4)展示汇编宏定义函数和中断处理 * 工程文件:[本文档同级目录/04_Macro_func_and_irq/04_Macro_func_and_irq.uvproj](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/01_proj_and_src/04_Macro_func_and_irq) ### 5)用汇编从Keil调试窗口中输出Hello world * 工程文件:[本文档同级目录/05_Assemble_hello_world/05_Assemble_hello_world.uvproj](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/01_proj_and_src/05_Assemble_hello_world) 双击打开并直接运行,能在Keil UART #1串口中看到hello world输出(这个窗口可能需要你手动在软件中打开) ### 6)Keil调试输出窗口串口回环 * 本工程主要演示使用Keil创建一个默认工程,然后添加串口0的回环收发用例,在Keil的调试串口进行串口收发,并且可以使用虚拟串口,通过SSOM32与Keil通信来进行串口收发。 * 工程文件:[本文档同级目录/06_uart0_loopback/06_uart0_loopback.uvproj](https://gitee.com/langcai1943/8051-from-boot-to-application/tree/develop/01_proj_and_src/06_uart0_loopback) * 将Keil的UART #1调试输出串口与SSCOM32等串口软件绑定的话,需要用到VSPD软件。 * *参考网址:* * [Keil实例仿真AT89C51串口UART收发数据(附程序)](https://blog.csdn.net/tj_nonstoper/article/details/124271543) * [keil MDK 中使用虚拟串口调试串口](https://blog.csdn.net/NICHUN12345/article/details/124423615) * [虚拟串口 VSPD 的使用](https://blog.csdn.net/qq_17351161/article/details/89607458)