0
点赞
收藏
分享

微信扫一扫

C51 boot程序编写,C51资源介绍

C51 boot程序编写,C51资源介绍

作者将狼才鲸
创建日期2022-04-09

Gitee文档源码地址:embedded-knowledge-wiki/ documents / 2.3.1.1_c51编程.md
CSDN文章阅读地址:
B站视频讲解(待完成):才鲸嵌入式
这篇文章是一系列文章中的一部分,文章根入口:才鲸嵌入式 / 嵌入式知识图谱WiKi

一、概述

  • C51是8位CPU。顾名思义,它的某个总线是8位,或者一些总线是8位的。实际上它的数据总线是8位的,每条CPU指令只能处理一个8位的数据,而它的外部地址总线是16位的,可以执行最大64KB的程序,也可以简单的理解为编译出来的可执行程序不能超过64KB(可以类比理解为在电脑上你只能下载安装64K以内的软件),这是C51的限制。

为什么51单片机的地址总线是16位的,但是它却是8位机?

  • 不想使用盗版Keil的话,可以尝试使用SDCC开源编译器,只是没有IDE,还需要自己编写Makefile进行编译。
    C51 开源编译器SDCC学习笔记-安装
    开源SDCC编译器(一)–基本介绍
    还在用Keil做51单片机开发吗?快来试试开源的SDCC吧
    SDCC下载地址
    SDCC使用说明

  • 如果你只是自己学习,也可以控制将程序编译在2k的范围内(c51总共也才只支持64k的代码容量),去keil官方下载软件试用。
    Keil C51官方下载地址(评估版只能编译2k的代码)

  • 启动流程:
    8051 MCU学习之分析单片机的启动过程

  • C51资源:
    51单片机知识重点汇总一,干货分享,学习单片机必懂知识
    51单片机CPU的基本构成及作用
    mcs-51单片机CPU的内部结构及工作原理
    51单片机CPU结构各部件的原理详细分析
    第一章C51系列单片机的硬件结构
    51单片机的内部硬件结构(CPU工作原理,储存器结构,51,52和89C51,89S51型号对比)

  • 典型芯片:AT89C51

  • C51寄存器:
    C51 特殊功能寄存器
    51单片机寄存器一览表
    【实用】51单片机寄存器功能一览表
    寄存器一般使用格式

二、寄存器和地址

  • 地址分为ROM地址和RAM地址,最大16位64K。

  • RAM:

地址大小描述备注
0x00~0x2F32工作寄存器地址4组R0~R7寄存器
0x20~0x2F16位寻址为了让寄存器可直接位寻址,用SBIT设置
0x30~0x7F48内部RAMIRAM,可设置为堆栈
0x80~0xFF128内部RAM和特殊寄存器共用IRAM、SFR
0x0100~0xFFFF65280B外部RAMRAM
  • ROM:
地址大小描述备注
0x0000~0x0FFF4KB内部ROMIROM
0x1000~0xFFFF60KB外部ROMROM
  • 寄存器介绍:
    21个特殊功能寄存器(52系列是26个)不连续地分布在128个字节的SFR存储空间中,地址空间为80H-FFH,在这片SFR空间中,包含有128个位地址空间,地址也是80H-FFH,但只有83个有效位地址,可对11个特殊功能寄存器的某些位作位寻址操作(这里介绍一个技巧:其地址能被8整除的都可以位寻址)。
    SFR被称为特殊功能寄存器,芯片厂商自定义外设的寄存器地址也都在这组地址里面。
C51单片机的寄存器
符号地址功能介绍
R0~R70x00~0x2F工作寄存器地址
位寻址0x20~0x2F为了让寄存器可直接位寻址
堆栈或内部RAM0x30~0x7F
P080HP0口锁存器
SP81H堆栈指针
DPL82H数据地址指针(低8位)
DPH83H数据地址指针(高8位)
PCON87H电源控制寄存器
TCON88HT0、T1定时器/计数器控制寄存器
TMOD89HT0、T1定时器/计数器方式控制寄存器
TL08AH定时器/计数器0(低8位)
TL18BH定时器/计数器0(高8位)
TH08CH定时器/计数器1(低8位)
TH18DH定时器/计数器1(高8位)
P190HP1口锁存器
SCON98H串行口控制寄存器
SBUF99H串行口锁存器
P2A0HP2口锁存器
IEA8H中断允许控制寄存器
P3B0HP3口锁存器
IPB8H中断优先级控制寄存器
PSWD0H程序状态字
ACCE0H累加器
BF0HB寄存器

51单片机寄存器功能一览表
C51/C52 特殊功能寄存器表

三、指令集

  • 指令:
    8位总共256个,每条指令的耗时占一个到多个指令周期,也就是CPU主频每跳动一次所消耗的时间
指令代码指令长度指令周期助记符操作对象描述举例
0011NOP延时1个指令周期的时间NOP ; 延时
0122AJMPaddr11短跳转,绝对转移,地址范围0xXX±0x000~07FF,转移范围为当前指令地址高5位相同的2K范围AJMP ADDR1
0232LJMPaddr16全域跳转,可以跳转到64K范围所有绝对地址,后面常用自定义的标号跳转LJMP ADDR2
0311RRA累加器循环右移,实际上也是除以2
0411INCA累加器自增1可用作循环处理
0521INCdirect将给出地址当中的数据自增1(自增完后放回原处)同上
0611INC@R0将R0中的数据当作地址,将这个地址中的数据自增1同上
0711INC@R1同上同上
0811INCR0将R0中的数据自增1同上
0911INCR1同上同上
0A11INCR2同上同上
0B11INCR3同上同上
0C11INCR4同上同上
0D11INCR5同上同上
0E11INCR6同上同上
0F11INCR7同上同上
1032JBCbit, offset如果某个可位寻址的位地址值为1,则清零后跳转到offset这个地址继续执行,否则顺序执行下一条指令JBC BIT C
1122ACALLaddr11短函数调用,函数中最后一条要是RET。绝对跳转,地址范围0xXX±0x000~07FF,跳转范围为当前指令地址高5位相同的2K范围,有参数的话子函数中还需要压栈和弹栈。调用完成后接着顺序执行本指令的下一条ACALL FUNC1
1232LCALLaddr16全域函数调用,函数中最后一条要是RET。地址范围0x0000~FFFF,有参数的话子函数中还需要压栈和弹栈。调用完成后接着顺序执行本指令的下一条LCALL FUNC2
1311RRCA带进位累加器循环右移,也就是除以2
1411DECA累加器自减1可用作循环处理
1521DECdirect将给出地址当中的数据自减1(自增完后放回原处)同上
1611DEC@R0将R0中的数据当作地址,将这个地址中的数据自减1同上
1711DEC@R1同上同上
1811DECR0将R0中的数据自增1同上
1911DECR1同上同上
1A11DECR2同上同上
1B11DECR3同上同上
1C11DECR4同上同上
1D11DECR5同上同上
1E11DECR6同上同上
1F11DECR7同上同上
2032JBbit, offset如果某个可位寻址的位地址值为1,则跳转到offset这个地址继续执行,否则顺序执行下一条指令,该bit不清零实现if else switch
2122AJMPaddr11重复1
2212RET从子函数中返回
2311RLA累加器循环左移,也相当于乘以2
2421ADDA, #immed累加器和一个立即数(常数)相加,结果放回到AADD A, #080H
2521ADDA, direct累加器和内存地址里的值相加,结果放回到A
2611ADDA, @R0累加器和R0里面存的内存地址里面指向的值相加,结果放回到A
2711ADDA, @R1同上
2811ADDA, R0累加器和R0里面的值相加,结果放回到AADD A, R0
2911ADDA, R1同上
2A11ADDA, R2同上
2B11ADDA, R3同上
2C11ADDA, R4同上
2D11ADDA, R5同上
2E11ADDA, R6同上
2F11ADDA, R7同上
3032JNBbit, offset如果直接寻址位为0则转移实现if else switch
3122ACALLaddr11重复2
3212RETI中断程序返回
3311RLCA带进位累加器循环左移,也就是乘以2
3421ADDCA, #immed带进位求和
3521ADDCA, direct
3611ADDCA, @R0
3711ADDCA, @R1
3811ADDCA, R0
3911ADDCA, R1
3A11ADDCA, R2
3B11ADDCA, R3
3C11ADDCA, R4
3D11ADDCA, R5
3E11ADDCA, R6
3F11ADDCA, R7
4022JCoffset如果进位位为1 则跳转
4122AJMPaddr11重复3
4221ORLdirect, A后面的与前面的相或,结果放到前面,*direct &= A
4331ORLdirect, #immed
4421ORLA, #immed
4521ORLA, direct
4611ORLA, @R0
4711ORLA, @R1
4812ORLA, R0
4912ORLA, R1
4A12ORLA, R2
4B12ORLA, R3
4C12ORLA, R4
4D12ORLA, R5
4E12ORLA, R6
4F12ORLA, R7
5022JNCoffset如果进位位为0 则转移
5122ACALLaddr11重复4
5221ANLdirect, A累加器“与”到直接地址
5332ANLdirect, #immed
5421ANLA, #immed
5521ANLA, direct
5611ANLA, @R0
5711ANLA, @R1
5811ANLA, R0
5911ANLA, R1
5A11ANLA, R2
5B11ANLA, R3
5C11ANLA, R4
5D11ANLA, R5
5E11ANLA, R6
5F11ANLA, R7
6022JZoffset累加器为0 则转移
6122AJMPaddr11重复5
6221XRLdirect, A累加器“异或”到直接地址
6331XRLdirect, #immed
6421XRLA, #immed
6521XRLA, direct
6611XRLA, @R0
6711XRLA, @R1
6812XRLA, R0
6912XRLA, R1
6A12XRLA, R2
6B12XRLA, R3
6C12XRLA, R4
6D12XRLA, R5
6E12XRLA, R6
6F12XRLA, R7
7022JNZoffset累加器为1 则转移
7122ACALLaddr11重复6
7222ORLC, bit直接寻址位“或”到进位位
7312JMP@A+DPTR相对DPTR 的无条件间接转移
7421MOVA, #immed
7531MOVdirect, #immed
7621MOV@R0, #immed
7721MOV@R1, #immed
7822MOVR0, #immed
7922MOVR1, #immed
7A22MOVR2, #immed
7B22MOVR3, #immed
7C22MOVR4, #immed
7D22MOVR5, #immed
7E22MOVR6, #immed
7F22MOVR7, #immed
8022SJMPoffset无条件相对转移
8122AJMPaddr11重复7
8222ANLC, bit直接寻址位“与”到进位位
8312MOVCA, @A+PC代码字节传送到累加器
8414DIVAB累加器除以B 寄存器
8532MOVdirect, direct
8622MOVdirect, @R0
8722MOVdirect, @R1
8821MOVdirect, R0
8921MOVdirect, R1
8A21MOVdirect, R2
8B21MOVdirect, R3
8C21MOVdirect, R4
8D21MOVdirect, R5
8E21MOVdirect, R6
8F21MOVdirect, R7
9031MOVDPTR, #immed16 位常数加载到数据指针
9122ACALLaddr11重复8
9222MOVbit, C进位位位传送到直接寻址
9312MOVCA, @A+DPTR代码字节传送到累加器
9421SUBBA, #immed累加器减去立即数(带借位)
9521SUBBA, direct
9611SUBBA, @R0
9711SUBBA, @R1
9811SUBBA, R0
9911SUBBA, R1
9A11SUBBA, R2
9B11SUBBA, R3
9C11SUBBA, R4
9D11SUBBA, R5
9E11SUBBA, R6
9F11SUBBA, R7
A022ORLC, /bit直接寻址位的反码“或”到进位位
A122AJMPaddr11重复9
A221MOVC, bit直接寻址位传送到进位位
A312INCDPTR数据指针加1
A414MULAB累加器和B 寄存器相乘
A5reserved
A621MOV@R0, direct
A721MOV@R1, direct
A822MOVR0, direct
A922MOVR1, direct
AA22MOVR2, direct
AB22MOVR3, direct
AC22MOVR4, direct
AD22MOVR5, direct
AE22MOVR6, direct
AF22MOVR7, direct
B022ANLC, /bit直接寻址位的反码“与”到进位位
B122ACALLaddr11重复10
B221CPLbit取反直接寻址位
B311CPLC取反进位位
B432CJNEA, #immed, offset比较直接地址和累加器,不相等转移
B532CJNEA, direct, offset
B632CJNE@R0, #immed, offset
B732CJNE@R1, #immed, offset
B832CJNER0, #immed, offset
B932CJNER1, #immed, offset
BA32CJNER2, #immed, offset
BB32CJNER3, #immed, offset
BC32CJNER4, #immed, offset
BD32CJNER5, #immed, offset
BE32CJNER6, #immed, offset
BF32CJNER7, #immed, offset
C022PUSHdirect直接地址压入堆栈
C122AJMPaddr11重复11
C221CLRbit清直接寻址位
C311CLRC清进位位
C411SWAPA累加器高、低4 位交换
C521XCHA, direct直接地址和累加器交换
C611XCHA, @R0
C711XCHA, @R1
C811XCHA, R0
C911XCHA, R1
CA11XCHA, R2
CB11XCHA, R3
CC11XCHA, R4
CD11XCHA, R5
CE11XCHA, R6
CF11XCHA, R7
D022POPdirect直接地址弹出堆栈
D122ACALLaddr11重复11
D221SETBbit置位直接寻址位
D311SETBC置位进位位
D411DAA累加器十进制调整
D532DJNZdirect, offset直接地址减1,不为0 则转移
D611XCHDA, @R0间接RAM 和累加器交换低4 位字节
D711XCHDA, @R1
D822DJNZR0, offset寄存器减1,不为0 则转移
D922DJNZR1, offset
DA22DJNZR2, offset
DB22DJNZR3, offset
DC22DJNZR4, offset
DD22DJNZR5, offset
DE22DJNZR6, offset
DF22DJNZR7, offset
E012MOVXA, @DPTR外部RAM(16 地址)传送到累加器
E122AJMPaddr11重复12
E212MOVXA, @R0外部RAM(8 地址)传送到累加器
E312MOVXA, @R1
E412CLRA累加器清零
E521MOVA, direct
E611MOVA, @R0
E711MOVA, @R1
E811MOVA, R0
E911MOVA, R1
EA11MOVA, R2
EB11MOVA, R3
EC11MOVA, R4
ED11MOVA, R5
EE11MOVA, R6
EF11MOVA, R7
F012MOVX@DPTR, A累加器传送到外部RAM(16 地址)
F122ACALLaddr11重复13
F212MOVX@R0, A累加器传送到外部RAM(8 地址)
F312MOVX@R1, A
F411CPLA累加器求反
F521MOVdirect, A累加器传送到直接地址
F612MOV@R0, A直接地址传送到间接RAM
F712MOV@R1, A
F811MOVR0, A
F911MOVR1, A
FA11MOVR2, A
FB11MOVR3, A
FC11MOVR4, A
FD11MOVR5, A
FE11MOVR6, A
FF11MOVR7, A
伪指令ORG汇编起始伪指令
伪指令END结束伪指令
伪指令DB字节数据定义伪指令
伪指令DW字数据定义伪指令
伪指令DS空间定义伪指令
伪指令EQU赋值伪指令
伪指令BIT位地址符号定义伪指令
伪指令DATA片内RAM直接字节地址定义伪指令
伪指令XDATA片外RAM直接字节地址定义伪指令

还有一些编译器自定义的符号,如Keil的伪指令:
$NOMOD51
$NOPRINT
NAME ; 给当前模块命令,同时也是一段代码的入口
SEGMENT ;类似于typedef
PUBLIC
?C_START ; main函数入口
IF
ELSE
ENDIF
标号:
CODE
IDATA
EXTRN
RSEG ; 段选择指令
CSEG
AT
$INCLUDE(USER.ASM)

C51 各个存储区说明
keil C51中各个地址的区别
51单片机片内RAM的128B(00H~FFH) 分为哪几部分各部分地址范围及功能?
51单片机特殊功能寄存器中的字节寻址和位寻址
C51 特殊功能寄存器SFR的名称和地址
C51最全111条汇编指令合集,以及使用时的注意事项,超详细
51单片机指令表
正确区分LJMP、AJMP、SJMP、JMP单片机跳转指令
51单片机的汇编指令中AJMP 和SJMP都是两个字节,都是两个机器周期,它们有什么区别呢?
谁能帮我解释一下 INC A ; INC direct INC Rn INC @Ri INC DPTR
MCS51单片机的伪指令有哪些?
keil_C51伪指令

四、boot编写

  • 如何写纯汇编程序
; $NOMOD51 ; 使A51不使用8051所有预定义的符号,使用自定义符号
; 不同的芯片厂商可以将SFR寄存器进行全新的定义

	;==== SFR寄存器定义====
	P0		DATA	80H  ; P0 IO口
	SP		DATA	81H  ; 堆栈指针
	DPL		DATA	82H  ; 数据指针低字节
	DPH		DATA	83H  ; 数据指针高字节
	PCON	DATA	87H  ; 电源控制
	TCON	DATA	88H  ; 定时器控制
		TF1	BIT	TCON.7
		TR1	BIT	TCON.6
		TF0	BIT	TCON.5
		TR0	BIT	TCON.4
		IE1	BIT	TCON.3
		IT1	BIT	TCON.2
		IE0	BIT	TCON.1
		IT0	BIT	TCON.0
	TMOD	DATA	89H  ; 定时器方式
	TL0		DATA	8AH  ; 定时器0低字节
	TH0		DATA	8CH  ; 定时器0高字节
	TL1		DATA	8BH  ; 定时器1低字节
	TH1		DATA	8DH  ; 定时器1高字节
	P1		DATA	90H  ; P1 IO口
	SCON0	DATA	98H	 ; UART0
		TI0	BIT	SCON0.1
		RI0	BIT	SCON0.0
	SBUF0	DATA	99H  ; 串口0数据
	SCON1	DATA	9BH	 ; UART1 ; 芯片厂商自行添加的
	SBUF1	DATA	9CH  ; 串口1数据
	P2		DATA	0A0H ; P2 IO口 ; 对于最高位大于等于10(ABCDEF)的数前面必须带0
	IEN0	DATA	0A8H ; 中断使能
		EA	BIT	IEN0.7	
		WDT BIT	IEN0.6	 ; 芯片厂商自行添加
		EX1	BIT	IEN0.2
		EX0	BIT	IEN0.0	
	P3		DATA	0B0H ; P3 IO口
	T2CON	DATA	0C8H ; 定时器2控制
	TL2		DATA	0CCH ; 定时器2低字节
	TH2		DATA	0CDH ; 定时器2高字节
	PSW		DATA	0D0H ; 程序状态寄存器
		CY	BIT	PSW.7
		AC	BIT	PSW.6
		F0	BIT	PSW.5
		RS1	BIT	PSW.4
		RS0	BIT	PSW.3
		OV	BIT	PSW.2
		F1	BIT	PSW.1
		P	BIT	PSW.0
	ACC		DATA	0E0H ; 累加器
	B		DATA	0F0H ; 寄存器B
	EXADR	DATA	0FEH ; SFR扩展接口 ; 支持更多的寄存器
	EXDATA	DATA	0FFH

	; 赋值
	APP_MODE    EQU  0F8H ; 类似于宏定义

	; 中断入口,程序入口(程序从0地址开始执行)
    ORG     0000H
    LJMP    RESET

    ORG		000BH ; 中断入口的地址都是固定的
    LJMP	T0INT

    ORG		001BH
    LJMP	T1INT

    ORG		002BH
    LJMP	T2INT

	ORG		0100H ; 程序起始地址
	
	$INCLUDE(USER.ASM)
	
RESET:
	; 你的汇编代码,初始化各个模块,执行函数,响应中断,执行程序
	
END
  • 如何写汇编boot程序,并引导到main()函数执行
	;;;;
	; 其它未写出的准备操作:
	; 用DATA申明所有的SFR寄存器名字,P0(80H) ~ B(0F0H)
	; 自定义的宏定义,如DEBUG_LEVEL EQU 01H,用于配置软件的不同功能

	CSEG	AT	  0000H	  ; 板子复位后执行的第一条指令
	LJMP	STARTUP		  ; 执行初始化函数

	CSEG	AT	  0003H	  ; 外部中断0
	LJMP	interupt_0	  ; 依次注册好所有中断处理函数
	;;;; 省略其它中断处理函数

	; SEGMENT申明本模块在CODE代码段,CODE代码段起始地址是0x100,这也是程序默认运行的起始地址,前面的地址是一些固定的中断处理的函数地址
	STARTUP_FUNC  SEGMENT  CODE AT 0100H  ; 等同于ORG	0100H
	RSEG  STARTUP_FUNC ; 定义函数再定义段
	
	PUBLIC STARTUP ; 申明函数,并向别的.asm暴露出函数接口
	
STARTUP: ; 标号,同时也是函数名,和C语言中标号类似,C语言的标号可以goto跳转
	NOP ; 延时一个时钟周期
	CLR EAL ; SBIT(EAL, IE, 7) ; 关闭中断7
	CLR RS0 ; RS0 BIT PSW.3 ; 
	CLR RS1 ; RS0 BIT PSW.3 ; 和上条命令一起选择第一组R0~R7寄存器
	MOV IE, #0H ; 关闭所有中断
	NOP
	MOV SP, #ORIGIN_SP ; ORIGIN_SP EQU 40H ; 初始化堆栈起始地址
	NOP
	LCALL _hardware_init ; 调用你写的函数写驱动模块寄存器初始化你需要的硬件,如引脚、PLL时钟倍频分频、JTAG设置、看门狗复位、IO输出、引脚复用、软件配置判断、内存初始化、串口、SPI、I2C等初始化
	NOP
	LCALL _crt0Startup ; 调用crt0.c里面的C语言函数,其实这时候已经可以直接调用main函数了,但是有些main函数之前的准备工作是用C写的,所以要提前调用一下
		;;;;
		; extern int main(int, char * const []);
		; extern int sysExit(int exit_code);
		; #define sysMain main
		; int crt0Startup(int argc, char * const argv[])
		; {
		;     // 关闭所有中断、DMA缓存刷新、CPU工作模式选择、硬件频率进一步设置、串口的完整初始化(设置波特率)、中断初始化、时钟初始化(更新当前实时时间)、有操作系统的话初始化task、内核、使能中断、调用main函数、main结束后进行资源销毁,便于软复位后系统能再次正常运行
		;     // sysMain(argc, argv); // 跳转到main函数执行
		;     // sysExit();
		; }
	NOP
	LJMP _cpuStop ; 关闭PLL时钟分频倍频,将时钟设置为晶振的原始频率
	NOP
	RET
	NOP
	
	END

CSEG ; 绝对地址指示的代码段,可以当成一个函数的入口
RSEG ; 再定位段选择指令,它用来选择一个在前面已经定义过的再定位段作为当前段,例如先申明一个函数段,后面写这个函数段。PS: 程序代码放到代码段,数据对象放到数据段,段分两种,一是种绝对段,一种是再定位段。
SEGMENT ; 申明是哪种段,类似C语言的{}花括号,和END配和使用
AT ; 该段的起始地址
PUBLIC ; 给别的.asm文件暴露出函数接口,类似于C语言 int api_func(void);放在头文件中
$SAVE ; 存储最近的LIST和GEN的设置
$NOLIST ; 不使用最近的LIST配置
$RESTORE ; 恢复最近的LIST和GEN的设置
EXTRN CODE (YOUR_FUNCTION_NAME) ; EXTRN 是与PUBLIC 配套使用的,要调用其它模块的函数,就必须先在模块前声明

汇编语言段和RSEG用法
A51零散笔记
STC8头文件

  • 函数参数:
    可用作函数参数的,及时压栈和弹栈的寄存器有ACC累加器、B寄存器(为乘法和除法指令而设置)、PSW程序状态字(处理进位、非零、正负、溢出等)、DPH/DPL(数据地址指针,读外部RAM数据)、R0~7(工作寄存器);函数调用时,让这些寄存器放弃它们本来的用法,当作函数参数使用。压栈和弹栈时顺序要刚好相反

寄存器B
标志寄存器(PSW)
单片机DPH DPL是什么
求教解释R0~R7.还有,RS0,RS

举报

相关推荐

0 条评论