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

单片机--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?MAIN
            DATA    0050H     0017H     UNIT         ?DT?UART0
            DATA    0067H     0008H     UNIT         ?DT?TIMER0
                    006FH     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?MAIN
            DATA    0050H     0017H     UNIT         ?DT?UART0
            DATA    0067H     0008H     UNIT         ?DT?TIMER0
            IDATA   006FH     0001H     UNIT         ?ID?MAIN
            IDATA   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 byte
    my_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地址 0x81

void main(void) {
    var1 = 0x55;  // 访问高128字节RAM
    var2 = 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地址 0x80

    ptr = (unsigned char idata *)0x81;  // 指向高128字节RAM地址 0x81
    *ptr = 0xAA;                        // 写入数据到高128字节RAM地址 0x81

    while (1);
}

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

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

示例代码:
#include <reg51.h>

void main(void) {
    __asm
        MOV R0, #0x80   ; 指向高128字节RAM地址 0x80
        MOV A, #0x55    ; 准备写入数据 0x55
        MOV @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字节RAM

void main(void) {
    alarm_cnt_100[0] = 0x55;   // 写入高128字节RAM
    alarm_cnt_100[1] = 0xAA;   // 写入高128字节RAM

    while (1);
}

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

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

INC 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.kler.cn/a/463675.html

相关文章:

  • 【小程序开发】- 小程序版本迭代指南(版本发布教程)
  • 滴滴数据分析80道面试题及参考答案
  • 明源地产ERP VisitorWeb_XMLHTTP.aspx SQL注入漏洞复现
  • 4. C语言 变量
  • 探索 Vue.js 的动态样式与交互:一个有趣的样式调整应用
  • Servlet解析
  • @colyseus/loadtest 插件详解
  • 代码随想录算法训练营第十七天-二叉树-654.最大二叉树
  • STM32-笔记19-串口打印功能
  • arm rk3588 升级glibc2.31到2.33
  • AIGC与未来的通用人工智能(AGI):从生成内容到智能革命
  • 华为云Welink数据怎么连接到小满CRM?
  • gesp(C++一级)(12)洛谷:B3953:[GESP202403 一级] 找因数
  • 电脑与手机
  • GPT分区 使用parted标准分区划分,以及相邻分区扩容
  • 苍穹外卖04——Redis初入门 在店铺打烊or营业状态管理功能中的使用
  • 条款35:考虑虚函数以外的其它选择(Consider alternatives to virtual functions)
  • 元宇宙金融新纪元:CZ协议全球启航
  • ctrip 小试牛刀记录
  • 分布式系统架构6:链路追踪
  • 基于SpringBoot的题库管理系统的设计与实现(源码+SQL+LW+部署讲解)
  • ESP32 I2S音频总线学习笔记(一):初识I2S通信与配置基础
  • MySQL 分库分表
  • 对称密码算法(分组密码算法 序列密码算法 密码杂凑算法)中的基本操作
  • 28.Marshal.PtrToStringAnsi C#例子
  • spring网关维度