嵌入式启航 嵌入式启航
首页 作品展示 个人主页
首页 作品展示 个人主页
binarybard
binarybard
嵌入式软件工程师,专注于MCU+RTOS技术栈,了解各种MCU底层。
binarybard
binarybard
嵌入式软件工程师,专注于MCU+RTOS技术栈,了解各种MCU底层。

分类

  • 默认分类 1
  • 开发环境 1
  • keil_C51 5

最新文章

  • 51单片机函数调用与可重入函数
    2026-01-10
  • 51单片机中断系统与响应优化
    2026-01-03
  • 51单片机bank功能与扩展关键字
    2025-12-27
  • 51单片机内存模型
    2025-12-20
  • 51单片机函数指针覆盖分析问题
    2025-12-13

51单片机内存模型

binarybard 2025年12月20日 keil_C51 0 条评论

如果你开发过8051内核单片机,那么你对于一些关键字一定不会陌生:data、idata、xdata、code、bit 。这几个是Keil_C51编译器不同于标准C语言而扩展的关键字,可是为什么需要这些关键字,又该怎么使用,这都和8051内核的内存模型有关系。

站在今天的眼光来看可以说8051单片机的内存模型真的很糟糕,一点也不利于高效开发,不过我们还是不要站在巨人的肩膀上批判巨人。8051的成功也是奠定了嵌入式开发的基石,现在仍然有很多设备上面运行着它,了解它的内存模型对于我们掌握51单片机编程有重要的意义。

我不打算直接都列出来告诉你那个关键字对应哪个区域,这里我以8051历史演进角度来讲,我相信你看完之后能够有一个更深刻印象。(真实演变过程不一定是我讲的这样,但这样想确实容易理解。)

我们现在只看红色框框里的部分,记住最初的8051内核就是这样的,他们才是真正的一家人。最开始把8位可寻址256字节空间一分为二,0x00-0x7F是data区域也就是现在意义上的RAM,0x80-0xFF是sfr区域也就是特殊功能寄存器每一个位都对应着一个实际硬件,比如说IO口。

这些特殊功能寄存器也叫做内存映射寄存器,和地址一一映射就是为了C语言方便操作,否则想要改变寄存器的值就必须使用汇编语言,sfr就有点像是一个指向固定地址的指针常量。

这个时候一切访问操作的都是直接访问 MOV A, 30H ; 直接把地址0x30的数据读到A。

data区域中的0x20-0x2F比较特殊既可以按照字节访问也可以单独访问每一个bit,如果你声明一个bit变量就一定会存储在这16个字节的某一位。由此也带了一个问题,如果你定义一个结构体类型里面有一个bit类型变量,编译的时候会直接爆出C150错误,告诉你聚合结构不能出现bit类型,这是8051架构限制的。

typedef struct {
  uint8_t a;
  bit b;
} test_t;

bit和现在标准C的bool不一样,bool虽然只能是0和1但是实际上是占用了至少一个字节空间的,而bit则是真的1位变量,这当然是为了节约资源毕竟一共只有可怜的128字节RAM。不过这种节约是不足以满足使用者的需求的,为了满足应对发展的变化,内核开发者不得不扩展RAM区域,可是256个地址已经分配完了,怎么办?

最后想了一个间接访问的方法:MOV R0, #30H;MOV A, @R0 ; 通过R0的内容(0x30)访问内存,把SFR地址0x80-0xFF复用,规定使用(@R0/@R1)这种的是间接访问,看似是相同的地址但是实际上访问的是完全不同的物理位置(不要关心硬件怎么实现,不重要),idata的i就是indirect间接的意思。

当然了如果你使用间接访问方法访问0x00-0x7F实际上还是data区域,到了这时候一共有256字节RAM,就是我上面图片里蓝色框里面的部分。这也是你经常在图片上看到idata区域地址连续,可里面却又分出了data部分,整个区域访问方式不尽相同的原因,因为他们本就是后来拼一块的。也是到了这里才出现了data、idata关键字,在以前访问方式一致的时候是不需要关键字进行区分的。

之后的故事我想你一定猜到了,128升级成为256实际上没有提升多少,很快也变得不够用了,这个时候出现了一种外部扩展的方式,xdata也就应运而生,并且也引入了新指令 MOVX,这里面的x代表External。

上面是一个51单片机扩展RAM的原理图,其中P0端口8个引脚用于低8位地址以及数据传输,P2端口可以进行配置不一定全部使用,虽然都是使用MOVX指令进行访问但也分成了8位和16位寻址。(U3是锁存器,因为低8位地址数据共用,所以需要先输出地址,然后u3锁定输出的地址,这时候把P0让出来给数据传输用。)

8位寻址从某种意义上来说和pdata类型算是一种对应关系,使用 MOVX @R0,A;MOVX A,@R0这种形式访问,8位寻址方式256字节,高于8位部分在外部看来就是高位地址,但是对于单片机认为是换页操作。

如果使用pdata需要在启动文件中将PPAGEENABLE 设置为1;PPAGE是说你使用了多少页,示例中的RAM6264有8KB也就是32个256字节,PPAGE就是32,这个数字一般是2的n次方但最大不超过256。PPAGE_SFR则设置了使用哪一个端口输出页面信息默认是P2(sfr空间A0H映射)。

不过也有一个问题就是如果同时需要访问的地址超过256字节,就需要换页操作,这个操作虽然不需要你手动编写,编译器自己会在合适位置生成 MOV PPAGE_SFR, #页码(相当于控制单片机引脚输出地址吗),但是换页也是有开销的。

另一种16位寻址方式则不同,直接固定占用P0,P2的全部端口,使用数据指针以 MOV DPTR, #0x1234;MOVX A, @DPTR;MOVX @DPTR, A形式访问。好处是64KB以内都认为是连续的空间,可以任意访问,但是很明显8位单片机操作16地址访问速度一定会有下降。

img

现在你再去看keil中设置memory model的地方我想你一定很清楚了,Small、compact、large分别对应着默认情况下变量以什么形式在RAM中,以及用什么指令访问。

不过发展到了现代还是有一些不同的,就比如说大部分的厂商其实已经把所谓的外部RAM封装到了芯片里面,也不会占用P0、P2端口,他们在硬件层面改变了连接方式。这和compact模型下使用pdata时PPAGE_SFR可以在启动代码里换成其他引脚是一样的道理,不是一定要使用sfr space的A0H地址进行映射,至于具体使用哪个地址就要看各个芯片厂商自己的处理。

所以现在外部RAM被封装到芯片内部的情况下,如果你不知道应该在启动代码里把PPAGE_SFR配置成什么地址以提供换页操作,pdata关键字就不应该使用。我遇到的有一些芯片厂商确实没有提供,当然你也可能遇到厂商处理的非常好,你能够不进行配置随意使用。

硬件演化过程中为了保证兼容性必须保证添加了新新指令的时候不能够影响原来的指令正常使用。经过漫长的堆叠,我们现在看起来有些地方像是拼到一块的,不那么融洽,这很正常。

最后你可能注意到code rom部分也和memory model一样有三个模型可选,这个比较简单我不便于单独展开就在这里简单说一下,因为有一个指令ACALL其中16位里面有5位用来做操作码了,只有11位配合PC计数的高五位进行程序跳转,也就是只能在一个2K区块内改变,如果超过就要使用LCALL。

整个程序小于2K那使用ACALL肯定可以,大于2k的情况下全部使用LCALL也可以覆盖整个空间64KB,如果想要节省一些就是混合使用,分别对应了三个模型。这个不用过于在意,如果不符合要求编译的时候就会爆出错误,如果不放心也可以使用LARGE。

上一篇
51单片机函数指针覆盖分析问题
下一篇
51单片机bank功能与扩展关键字

评论已关闭

© 2025-2026 嵌入式启航.
豫ICP备2025159703号    备案图标 豫公网安备41172102000273号