当前位置: 首页 > news >正文

单片机--51- RAM

1.概览某个51单片机对空间区域的划分:

2.RAM被分配的区域是256bytes,

通常8051单片机ram是128bytes

8052的ram是256bytes(其中高128位的地址和sfr区域地址重合,物理区域不同)

extern uint32_t alarm_cnt_1;

uint32_t alarm_cnt_1=0;

在这种常规的生命和定义下,变量会被保存在0x0008h--0x007e的地址内,

超出就会报警;

存储类位置特点
data内部RAM的低128字节直接寻址,访问速度最快,但空间有限。
idata内部RAM的全部区域可访问整个内部RAM,但高128字节需间接寻址,访问速度稍慢。
xdata外部RAM(扩展存储器)存储容量大,但访问需要额外的MOVX指令,速度较慢。
code程序存储器(Flash/ROM)用于存储常量或程序代码,访问速度较快,但只读。

直接寻址和间接寻址,是由编译器解释编译后,自动执行的,不需要手动造作。

关键字使用示例:

int var100[1] ; 默认分配地址(例如ram256bytes,通常也只分配到低128ram,)

int idata var100[1] ; 分配到整个RAM区域(通常低128ram不够用,使用idata,可以充分使用高128ram)

int xdata var100[1] ; 分配到外部ram

int code var100[1] ; 分配到rom

char idata var1 _at_ 0x80; 定义一个变量var1,会被分配到地址0x80,如果中间有没有使用的地址,也会跳过

_at_:关键字,指定地址

例如

指定地址:
char idata var1 _at_ 0x80;  map文件:        TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME-----------------------------------------------------* * * * * * *   D A T A   M E M O R Y   * * * * * * *REG     0000H     0008H     ABSOLUTE     "REG BANK 0"DATA    0008H     0048H     UNIT         ?DT?MAINDATA    0050H     0017H     UNIT         ?DT?UART0DATA    0067H     0008H     UNIT         ?DT?TIMER0006FH     0011H                  *** GAP ***IDATA   0080H     0001H     ABSOLUTE     IDATA   0081H     0001H     UNIT         ?STACK不指定地址:
char idata var1;  map文件:     TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME-----------------------------------------------------* * * * * * *   D A T A   M E M O R Y   * * * * * * *REG     0000H     0008H     ABSOLUTE     "REG BANK 0"DATA    0008H     0048H     UNIT         ?DT?MAINDATA    0050H     0017H     UNIT         ?DT?UART0DATA    0067H     0008H     UNIT         ?DT?TIMER0IDATA   006FH     0001H     UNIT         ?ID?MAINIDATA   0070H     0001H     UNIT         ?STACK

有关这些关键字的报警:

SRC\main.c(103): error C274: 'var1': absolute specifier illegal

使用xdata修饰,使用xdata修饰是保存在外部RAM,有些单片机上也有外部ram,有些没有:

extern uint32_t xdata alarm_cnt_2;

uint32_t xdata alarm_cnt_2=0;

            TYPE    BASE      LENGTH    RELOCATION   SEGMENT NAME
            -----------------------------------------------------

            * * * * * * *   D A T A   M E M O R Y   * * * * * * *
            REG     0000H     0008H     ABSOLUTE     "REG BANK 0"
            DATA    0008H     0058H     UNIT         ?DT?MAIN
            DATA    0060H     0017H     UNIT         ?DT?UART0
            DATA    0077H     0008H     UNIT         ?DT?TIMER0
            IDATA   007FH     0001H     UNIT         ?STACK

            * * * * * * *  X D A T A   M E M O R Y  * * * * * * *
            XDATA   0000H     0010H     UNIT         ?XD?MAIN

也可以强制把变量数据保存在0X80--0xff区域,但是禁止这样做,很容易和sfr发生冲突。

好的,了解您想将一个 int 型变量保存在 51 单片机的 0x80 - 0xFF 地址范围。虽然我们之前强调了 0x80 - 0xFF 这个区域主要用于 SFR (特殊功能寄存器),不推荐直接存储变量数据,但为了满足你的需求,我将展示如何在技术上尝试实现,并再次强调风险和替代方案。重要警告(再次提醒):极不推荐: 除非有绝对特殊的原因,否则不要将变量直接存储在 SFR 区域。这极易导致程序崩溃、硬件错误,并严重降低代码的可读性和可维护性。理解风险: 这样做会直接覆盖 SFR 的值,可能会导致不可预测的硬件行为。数据污染: SFR有自己的用途,随意覆盖,会导致程序错误。不可移植: SFR 的地址和功能依赖于具体的单片机型号,代码的移植性极差。替代方案: 请尽可能使用内部 RAM (DATA) 或外部 RAM (XDATA) 来存储变量数据。技术实现(仅供参考,风险自担):由于 0x80 - 0xFF 主要是 SFR 区域,直接存储 int 类型变量是行不通的,我们需要拆解int数据,逐字节进行操作。声明 int 型变量:先定义您要存储的 int 型变量:int my_int = 0x1234; // 示例 int 值
content_copy
download
Use code with caution.
C使用指针声明一个 unsigned char 类型的指针, 然后将指针指向 0x80-0xff 的某个地址,然后用指针来操作该地址的内存空间:unsigned char* ptr;ptr = (unsigned char*)0xA0;//假设使用0xA0地址
content_copy
download
Use code with caution.
C使用间接寻址将 int 型变量拆分为两个字节 (低字节和高字节) ,然后分别使用间接寻址写入到对应的地址:unsigned char* ptr = (unsigned char*)0xA0;*ptr = (unsigned char)(my_int & 0x00FF); // 写入低字节
ptr++; // 地址加1
*ptr = (unsigned char)((my_int >> 8) & 0x00FF); // 写入高字节
content_copy
download
Use code with caution.
C取出数据:从对应的地址读取数据:ptr = (unsigned char*)0xA0;
int tmp_int = 0;
tmp_int = *ptr | (*(ptr+1)<<8)
content_copy
download
Use code with caution.
C使用 sfr 关键字 (不推荐,仅作演示):声明 SFR 别名: 为了使用方便,我们也可以使用sfr关键字,来定义一个指向SFR地址的变量,但请注意这只是定义了一个变量名,该变量名会和SFR的地址重合,使用这个变量并不会修改SFR。volatile sfr my_address = 0xA0;
content_copy
download
Use code with caution.
C类型转换: 注意我们这里定义的是SFR变量名,而不是unsigned char类型的指针,my_address 是一个特殊功能寄存器,不能直接作为内存地址来使用。不推荐: 虽然这种方式可以把数据写入到指定地址,但是需要非常小心,避免修改SFR的值,这是一种非常危险的操作。示例: 使用这种方法,可以通过以下方式把int类型数据拆分到两个地址:volatile sfr my_address_low = 0xA0;volatile sfr my_address_high = 0xA1;unsigned int my_int = 0x1234;my_address_low = (unsigned char)my_int; // low byte
my_address_high = (unsigned char)(my_int >> 8); // high byte
content_copy
download
Use code with caution.
C读取数据:unsigned int temp_data = 0;temp_data = (unsigned int)my_address_low | ((unsigned int)my_address_high<<8);
content_copy
download
Use code with caution.
C代码示例 (极其危险,请勿在实际项目中使用):#include <reg52.h>
#include <stdio.h>// 非常不推荐!!!
volatile sfr my_address_low = 0xA0;  // 使用SFR 0xA0 地址,非常不建议这样做
volatile sfr my_address_high = 0xA1; // 使用SFR 0xA1 地址,非常不建议这样做
int my_int = 0x1234;  // 要存储的 int 型变量void main() {unsigned char* ptr;ptr = (unsigned char*)0xA0;// 将 int 分解成两个字节,并分别存入*ptr = (unsigned char)(my_int & 0x00FF);ptr++; // 地址加1*ptr = (unsigned char)((my_int >> 8) & 0x00FF);// 将存储在0xA0和0xA1的数据读取出来int tmp_int = 0;ptr = (unsigned char*)0xA0;tmp_int = *ptr | (*(ptr+1)<<8);printf("read data from 0xA0: %x\n",tmp_int);// 使用SFR别名,非常不建议这样做my_address_low = (unsigned char)my_int; // low bytemy_address_high = (unsigned char)(my_int >> 8); // high byte// 读取数据unsigned int temp_data = 0;temp_data = (unsigned int)my_address_low | ((unsigned int)my_address_high<<8);printf("read data from sfr address: %x\n",temp_data);while(1); // 避免程序运行到其他区域
}
content_copy
download
Use code with caution.
C解释:我们定义一个unsigned char*类型的指针, 指向0xA0的地址,这样我们就可以使用指针来操作这个地址的内存单元了。将int变量的值分解为低字节和高字节,分别写入到0xA0 和 0xA1地址。我们可以通过指针或者SFR变量名读取这个值。我们使用printf来打印读取到的值。非常重要: 这个示例仅用于演示, 绝对不推荐在实际项目中使用。风险:破坏 SFR 功能: 直接修改 SFR 可能导致硬件功能异常,例如端口输出错误、串口无法工作、定时器不准确等。代码难以维护: 这种方式代码可读性非常差,不利于团队协作和后续维护。兼容性问题: 不同型号的单片机 SFR 地址可能不同,代码的兼容性很差,移植性基本为0。程序崩溃: 如果错误的修改了某些SFR, 很可能导致程序崩溃。难以定位错误: 排查这种因为错误的访问SFR导致的错误非常困难,增加了调试难度。正确做法(再次强调):内部 RAM (DATA): 优先使用内部 RAM (地址 0x00 - 0x7F) 存储变量。外部 RAM (XDATA): 如果内部 RAM 不够用,考虑使用外部 RAM (XDATA)。结构体: 可以使用结构体来存储多个变量。联合体: 可以使用联合体,将不同的类型数据存储到同一片区域。动态内存分配: 如果数据量不确定,需要使用动态内存分配。总结:我为您展示了如何在技术上将 int 型变量存储在 51 单片机的 SFR 区域,但这是一种非常危险和不规范的做法,请务必谨慎使用。在大多数情况下,应该使用内部 RAM 或外部 RAM 来存储数据,使用 SFR 来控制硬件。如果您还有其他问题,请随时提出,我会尽力为您解答。

关于ram的问答:

问:char idata alarm_cnt_100[120] 解释;

答:

在标准51单片机中,char idata alarm_cnt_100[120]; 是一个声明数组的语句。以下是其组成部分及含义的详细解释:


语句分解与解释

  1. char

    • 表示数组的元素类型是 char,即每个数组元素占用 1字节(8位)。
    • 在51单片机中,char 默认是有符号类型(取值范围:-128127),如果需要无符号,可以使用 unsigned char
  2. idata

    • idata 是存储类说明符,表示这个数组存储在 内部数据存储区(Internal Data RAM) 中。
    • 在51单片机中,内部数据RAM分为:
      • 直接地址区(低128字节,0x00~0x7F)
      • 高128字节RAM(0x80~0xFF,需间接访问)
    • 使用 idata,编译器会在低128字节或高128字节RAM中分配存储空间。
  3. alarm_cnt_100[120]

    • 定义了一个名为 alarm_cnt_100 的数组,数组长度为 120
    • 该数组包含 120 个元素,每个元素占用 1 字节,总共需要 120字节 的存储空间。

综合意义

该语句的含义是:
在51单片机的内部RAM(idata区)中分配一个名为 alarm_cnt_100 的数组,它包含 120 个 char 类型的元素,每个元素占用 1 字节,总共占用 120 字节的RAM空间。


注意事项

  1. 内存占用问题

    • 标准51单片机内部RAM容量有限(通常为256字节,低128字节可直接访问,高128字节需间接访问)。
    • 如果定义的数组大小(如120字节)接近内部RAM容量,可能会导致RAM不足,进而出现程序运行错误。
  2. idata 的作用

    • 如果不显式指定 idata,编译器会自动选择合适的存储区域(可能是 dataidata)。
    • 使用 idata 明确指定存储区,可以优化资源分配,避免与其他内存区域冲突。
  3. 适配问题

    • 如果RAM不足,建议:
      • 优化数组大小。
      • 使用外部RAM(xdata 存储类)代替内部RAM。

简单使用示例

以下是一个使用 alarm_cnt_100 数组的示例:

#include <reg51.h>char idata alarm_cnt_100[120];  // 在内部RAM中定义一个120字节的数组void main() {unsigned char i;// 初始化数组for (i = 0; i < 120; i++) {alarm_cnt_100[i] = i;  // 将数组元素依次赋值为 0~119}// 使用数组中的某个值P0 = alarm_cnt_100[10];  // 将第11个元素的值输出到P0端口while (1);
}

补充:idata 与其他存储类的区别

存储类位置特点
data内部RAM的低128字节直接寻址,访问速度最快,但空间有限。
idata内部RAM的全部区域可访问整个内部RAM,但高128字节需间接寻址,访问速度稍慢。
xdata外部RAM(扩展存储器)存储容量大,但访问需要额外的MOVX指令,速度较慢。
code程序存储器(Flash/ROM)用于存储常量或程序代码,访问速度较快,但只读。

合理选择存储类,可以更高效地利用单片机的内存资源!

问:idata 内部RAM的全部区域 可访问整个内部RAM,但高128字节需间接寻址,访问速度稍慢。 这个是程序自己会进行间接寻址吗,

答:

是的,如果使用 idata 存储类,编译器会根据变量的地址自动选择适当的访问方式,包括是否需要间接寻址。你无需手动处理间接寻址,编译器会负责生成相应的代码。


具体解释

  1. 低128字节区域(地址 0x00~0x7F)

    • 如果变量被分配在低128字节的内部RAM中,编译器会使用 直接寻址 方式访问,因为低128字节支持直接寻址,效率较高。
  2. 高128字节区域(地址 0x80~0xFF)

    • 如果变量被分配在高128字节区域(0x80~0xFF),由于该区域只能通过 间接寻址 访问,编译器会自动生成使用间接寻址的代码。例如通过寄存器 R0R1 访问。
    • 你在代码中直接写访问操作即可,编译器会在后台处理具体的访问方式。

示例代码

以下是一个简单例子说明 idata 的访问:

#include <reg51.h>// 定义变量,明确使用 idata 存储类
char idata var1 = 0xAA;    // 编译器可能将其分配到低128字节
char idata var2 = 0xBB;    // 编译器可能将其分配到高128字节void main(void) {P0 = var1;  // 访问低128字节时,编译器生成直接寻址的代码P1 = var2;  // 如果 var2 位于高128字节,编译器生成间接寻址的代码while (1);
}

编译器如何处理?

编译器会根据变量的地址自动生成不同的指令:

  1. 低128字节(直接寻址示例):

    MOV A, var1   ; 从低128字节的地址直接读取到累加器
    MOV P0, A     ; 输出到P0端口
    
  2. 高128字节(间接寻址示例):

    MOV R0, #var2 ; 将高128字节的地址加载到R0
    MOV A, @R0    ; 使用间接寻址从高128字节地址读取数据到累加器
    MOV P1, A     ; 输出到P1端口
    

注意事项

  1. 访问速度差异

    • 低128字节采用直接寻址,效率更高。
    • 高128字节需使用间接寻址,指令多一步,速度稍慢。
  2. 编译器分配策略

    • 大多数情况下,编译器会优先将变量分配到低128字节。
    • 当低128字节空间不足时,才会使用高128字节。
  3. 手动优化

    • 如果对性能要求较高,可以通过优化变量定义的存储类(如尽量使用 data)或检查编译器分配的内存映射表。

总结:
你只需指定 idata 存储类,编译器会根据变量的地址自动选择适当的访问方式(直接或间接寻址)。用户无需手动处理间接寻址的细节,这些都由编译器生成的代码自动完成。

问:可以手动指定到高128ram吗

答:

在51单片机中,你可以通过手动方式将变量分配到高128字节RAM。具体方法有以下几种:


方法 1:直接使用 idata 并控制地址分配

编译器在分配 idata 区域的变量时,会按照其策略从低地址到高地址分配。如果你想明确将变量放入高128字节RAM,可以通过 汇编插入特殊编译选项 控制。

示例代码:
#include <reg51.h>char idata var1 _at_ 0x80;  // 将变量 var1 明确放入高128字节RAM地址 0x80
char idata var2 _at_ 0x81;  // 将变量 var2 明确放入高128字节RAM地址 0x81void main(void) {var1 = 0x55;  // 访问高128字节RAMvar2 = 0xAA;while (1);
}

注意
_at_ 是某些编译器(如 Keil C51)的特性,可以指定变量的绝对地址。不是所有 C 编译器都支持这个关键字。


方法 2:通过间接寻址手动操作高128字节RAM

如果编译器不支持直接指定变量地址,你可以通过手动使用指针间接访问高128字节RAM。

示例代码:
#include <reg51.h>void main(void) {unsigned char idata *ptr;  // 定义一个指针变量指向 idata 区ptr = (unsigned char idata *)0x80;  // 指向高128字节RAM地址 0x80*ptr = 0x55;                        // 写入数据到高128字节RAM地址 0x80ptr = (unsigned char idata *)0x81;  // 指向高128字节RAM地址 0x81*ptr = 0xAA;                        // 写入数据到高128字节RAM地址 0x81while (1);
}

方法 3:结合汇编代码手动分配地址

在 C 语言中插入汇编代码,直接操作高128字节RAM的地址。

示例代码:
#include <reg51.h>void main(void) {__asmMOV R0, #0x80   ; 指向高128字节RAM地址 0x80MOV A, #0x55    ; 准备写入数据 0x55MOV @R0, A      ; 写入数据到高128字节RAM__endasm;while (1);
}

编译器内存管理工具

大部分 C 编译器(如 Keil C51)提供了内存分配的配置文件,可以通过修改链接文件(如 .xcl.m51 文件)手动分配变量的内存地址。

在 Keil 中,你可以检查 MAP 文件,了解变量被分配到的具体地址。


注意事项

  1. 硬件限制

    • 高128字节RAM仅支持间接寻址,直接指定的变量会依赖编译器生成间接寻址代码。
  2. 地址冲突

    • 高128字节RAM的地址与 SFR 地址重叠,务必确保你的变量地址不会与 SFR 冲突。
  3. 适用范围

    • 不建议频繁手动操作变量地址,尤其在较复杂程序中可能导致管理混乱。仅在内存紧张或特定需求(如优化性能)时使用。

通过这些方法,你可以将变量手动分配到高128字节RAM,并有效利用51单片机的内存资源。

问:char idata alarm_cnt_100[120] 在占用了高128ram区域会和sfr冲突吗

答:

在标准51单片机中,char idata alarm_cnt_100[120] 不会与 SFR(特殊功能寄存器)冲突,即使它占用了高128字节的 RAM 区域。原因如下:


高128字节RAM与SFR的关系

  • 地址重叠
    高128字节RAM(地址范围 0x80 ~ 0xFF)与 SFR 地址范围相同,但两者是通过不同的访问方式区分的:

    • 直接寻址(Direct Addressing):访问的是 SFR。
    • 间接寻址(Indirect Addressing):访问的是高128字节RAM。
  • 关键点:只要编译器生成的代码正确地使用了间接寻址方式操作高128字节RAM,变量与 SFR 不会冲突。


idata 存储类的行为

当你声明 char idata alarm_cnt_100[120]

  1. 编译器会尝试将变量分配到 idata 区(包括低128字节和高128字节RAM)。
  2. 如果变量被分配到高128字节RAM,编译器会生成间接寻址代码来访问这些变量。

例如:

  • 变量分配在高128字节地址 0x80 ~ 0xFF
  • 对这些地址的访问始终使用寄存器(如 R0R1)和 @ 符号的间接寻址。

由于访问高128字节RAM不涉及直接寻址,变量不会干扰或覆盖 SFR 的内容。


示例解释

假设你定义了如下代码:

char idata alarm_cnt_100[120];  // 变量可能部分分配到高128字节RAMvoid main(void) {alarm_cnt_100[0] = 0x55;   // 写入高128字节RAMalarm_cnt_100[1] = 0xAA;   // 写入高128字节RAMwhile (1);
}

编译器会生成类似以下的汇编代码:

MOV R0, #0x80     ; 指针指向高128字节RAM的起始地址
MOV A, #0x55      ; 准备数据
MOV @R0, A        ; 写入高128字节RAM地址 0x80INC R0            ; 指针指向高128字节RAM的下一个地址
MOV A, #0xAA      ; 准备数据
MOV @R0, A        ; 写入高128字节RAM地址 0x81

这种方式完全避免了与 SFR 的直接寻址冲突。


注意事项

  1. 编译器生成代码的正确性

    • 确保编译器(如 Keil C51)能够正确地处理高128字节RAM和 SFR 的访问方式。
    • 大多数主流编译器都能很好地区分间接寻址(高128字节RAM)和直接寻址(SFR)。
  2. RAM分配策略

    • 如果 idata 的低128字节不足,编译器才会使用高128字节RAM。
    • 你可以通过编译器生成的 MAP 文件 查看 alarm_cnt_100 的实际分配地址。
  3. 硬件限制

    • 某些兼容性较差的51单片机可能不支持完整的高128字节RAM,需检查具体型号的硬件手册。

结论

char idata alarm_cnt_100[120] 占用高128字节RAM时不会与 SFR 冲突,因为编译器会通过间接寻址区分高128字节RAM和 SFR 区域。只需确保编译器生成的代码正确,并避免手动混用直接和间接寻址操作即可安全使用。

资料:

为什么8051单片机内部RAM存储单元是128个,只有低128位而没有高128位?_百度知道 (baidu.com)

51单片机内部RAM的问题、STC单片机内部扩展RAM的应用 - 51单片机 (51hei.com)

怎么才能用51单片机中的高128位内部ram啊? - 24小时必答区


http://www.mrgr.cn/news/82234.html

相关文章:

  • js的一些处理
  • 探索Wiki:开源知识管理平台及其私有化部署
  • vue学习第一阶段
  • uniapp小程序使用rich-text富文本图片溢出问题
  • 深入浅出梯度下降算法:快速抵达函数最小值的方法
  • SpringCloud源码分析-nacos与eureka
  • 【蓝桥杯研究生组】第15届Java试题答案整理
  • C++ —— 字符型和字符串
  • 作业:C高级:Day4
  • Ceph 手动部署(CentOS9)
  • Coding Our First Neurons
  • javaEE-多线程进阶-JUC的常见类
  • 设计模式 创建型 工厂模式(Factory Pattern)与 常见技术框架应用 解析
  • 升级Cypress到10.8.0
  • 【老白学 Java】Border 布局管理器
  • C++并发:在线程间共享数据
  • spring boot 异步线程池的使用
  • SpringCloud源码分析-Lettue Redis
  • shell脚本的【算数运算、分支结构、test表达式】
  • 03-类和对象(上)
  • SQL偏移类窗口函数—— LAG()、LEAD()用法详解
  • 单片机-蜂鸣器实验
  • vue视频录制 限制大小,限制时长
  • 思科无线控制器 AC5508 初始化
  • 操作系统课后题总复习
  • SpringCloud源码-Ribbon