# for C51

# 数据存储类型(data,idata,xdata,code)

参考: Keil > Help > uVision Help > Cx51 Compiler User’s Guide > Language Extensions > Memory Types

关键字存储空间地址范围访问方式
data内部 RAM(最大 128 bytes)0x00~0x7F直接寻址
idata内部 RAM(最大 256 bytes)0x00~0xFF间接寻址
bdata内部 RAM(指定 16 bytes)0x20~0x2F位寻址 / 直接寻址
pdata扩展 RAM(最大 256 bytes)0x00~0xFF间接寻址
xdata扩展 RAM(最大 64K bytes)0x0000~0xFFFF间接寻址
codeFlash(最大 64K bytes)0x0000~0xFFFF间接寻址
  • data

    data 内存类型只能用于声明变量,不能声明函数。

    该内存使用 8 位地址直接寻址片上 RAM。

    优点是访问速度最快,缺点是数据量的大小受到限制(只有 128 字节或更少)。

  • idata

    idata 内存类型只能用于声明变量,不能声明函数。

    该内存使用 8 位地址间接寻址片上 RAM。

    最大可访问 256 字节,其中低位上的数据空间地址和 data 的相应地址重叠,只是访问方式不同。

  • bdata

    bdata 内存类型只能用于声明变量,不能声明函数。

    bdata 可使用 8 位地址直接访问,属于片上位可寻址 RAM。

    用 bdata 类型声明的变量是位可寻址的,可以使用位指令进行读写。

  • pdata

    pdata 内存类型只能用于声明变量,不能声明函数。

    该内存使用 8 位地址间接寻址以 256 字节为一页的 扩展 RAM。

    pdata 在大小上是有限的(最大 256 字节)。

  • xdata

    xdata 内存类型只能用于声明变量,不能声明函数。

    该内存使用 16 位地址间接寻址扩展 RAM。

    xdata 的容量限制为 64K 或更少。

  • code

    code 内存类型可用于声明常量或函数。

    该存储器使用 16 位地址进行片上或外部扩展访问。

    code 内存限制为 64K。

    • 对于常量 (ROM 变量)。对象限制为 64K,并且不能跨越 64K 边界。代码声明的常量变量位于 code 内存类中。

    • 对于程序代码 (函数)。程序函数默认存储在 code 内存类中。代码内存类型说明符不是必需的。

# const 和 code 的修饰使用

参考: Keil > Help > uVision Help > Cx51 Compiler User’s Guide > Language Extensions > Type Qualifiers > const

const:const 关键字所修饰的变量(注意是变量而非常量),是用来表明该变量的操作行为为只读处理,表示其值在程序执行期间不可改变(即程序员向编译器承诺这个变量不会修改),但你说它只读不可修改,用指针指向该变量然后访问修改,它却又可以修改,所以一般严谨的来说它是直接访问不可修改。前面也说了,const 修饰后的并非常量,因此在 C51 中存储在 RAM 上,至于在哪个存储区域 data? idata? xdata? 跟你所指定的分配或基于内存模型配置进行隐式的分配有关。

code:code 用于声明存储在只读存储器中。code 关键字通常与函数或程序的定义一起使用,也可以用来修饰常量,限制其只能存储在 ROM 中,从而节省了宝贵的 RAM 资源。

总结:

  1. const 声明的对象被 Keil C51 存放在数据区 RAM 中 (可以为 data、idatd、xdata,具体看分配);
  2. 要想将变量存放到代码区 ROM 中以节省 RAM 资源,需更换为 code 关键字去声明对象;
  3. 虽然可以使用指针访问并修改 const 声明的对象,但最好不要这么做,因为 Keil C51 不一定会如程序员预想那样处理代码;
  4. 无论是 const 还是 code 修饰的对象,都具有不可修改的特性,最大的区别可能是一个存放在数据区一个存放在代码区,因此在使用这些修饰符时,需要根据实际需求选择合适的修饰符。

# printf 输出打印

在 C51 上,printf 的转换说明符跟常用的是有区别的,如果配置不当,那么打印输出的结果是错乱的(eg:char 使用 '% d' 转换);既然不一样,那么对应的数据类型是分别使用哪些转换说明符呢?

参考: Keil > Help > uVision Help > Cx51 Compiler User’s Guide > Library Reference > Reference > printf

转换说明符变量类型描述
%bdchar带符号字符型
%hdshort带符号短整型
%dint带符号整形
%ldlong int带符号长整型
%buunsigned char无符号字符型
%huunsigned short无符号短整型
%uunsigned int无符号整型
%luunsigned long int无符号长整形
%bxunsigned char无符号十六进制字符型
%hxunsigned short无符号十六进制短整型
%xunsigned int无符号十六进制整型
%lxunsigned long int无符号十六进制长整型
#include <stdio.h>
void tst_printf (void) {
  char a = 1;
  int b  = 12365;
  long c = 0x7FFFFFFF;
  unsigned char x = 'A';
  unsigned int y  = 54321;
  unsigned long z = 0x4A6F6E00;
  float f = 10.0;
  float g = 22.95;
  char buf [] = "Test String";
  char *p = buf;
  printf ("char %bd int %d long %ld\n",a,b,c);
  printf ("Uchar %bu Uint %u Ulong %lu\n",x,y,z);
  printf ("xchar %bx xint %x xlong %lx\n",x,y,z);
  printf ("String %s is at address %p\n",buf,p);
  printf ("%f != %g\n", f, g);
  printf ("%*f != %*g\n", (int)8, f, (int)8, g);
}

# 内存池

在 C51 中,malloc 等内存管理函数并不能像在 PC 上一样直接调用,这涉及到了内存池的概念。

要想使用内存管理操作,这里需要 3 个步骤:

  1. 声明一个合适的存储空间;
  2. 利用 init_mempool() 初始化内存池;
  3. 调用相关的内存管理 API。

参考: Keil > Help > uVision Help > Cx51 Compiler User’s Guide > Library Reference > Reference > init_mempool

eg:

#include <stdlib.h>
static char xdata mempool[512];
void tst_init_mempool (void) {
  int i;
  char xdata *str;
  init_mempool (&mempool, sizeof(mempool));   // 初始化内存池
  str = malloc (100);   // 申请内存
  for (i = 0; i < 26; i++)
    str[i] = 'A' + i;
  str[i] = '\0';
  printf("%s\n", str);
  free (str);   // 释放内存
}

# for MDK

# 设置 noinit

设置 noinit 的 ram 区域有什么用?

对于 noinit 的 ram,等同于不断电的 EEPROM,可以在非上电复位后,依然保存该值,你可能会说,不是有备用域寄存器吗,但是你是否想到这样操作起来会比较麻烦,需要配置相应的操作寄存器;而对于直接把 ram noinit,那么可以跟以前一样直接读写数据,也不怕非上电复位导致数据清零;常见的例子有 bootloader 带参数信息跳转、强制复位前的信息保留等。

ARM: Uninialized Variables Get Initialized

1、AC5 编译器

可以在分散文件中定义 UNINIT 的分区段:

LR_IROM1 0x08000000 0x00080000  { ; load region size_region
  ER_IROM1 0x08000000 0x00080000  { ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 UNINIT 0x00000100  { ;no init section
        *(NO_INIT)
   }
  RW_IRAM2 0x20000100 0x0000FFF0  { ;all other rw data
        .ANY(+RW +ZI)
  }
}

然后再在 c 代码上面调用指定:

unsigned char  value[256] __attribute__((section("NO_INIT"), zero_init));

2、AC6 编译器

也可以像下面这样利用 Target 配置指定地址:

QQ截图20240104152114

3、区别

如果使用分散表来设置,那么 AC5 跟 AC6 调用是有不同的:

// Arm Compiler 5
unsigned long NI_longVar __attribute__( ( section( "NoInit"),zero_init) ) ;
// Arm Compiler 6
unsigned long NI_longVar __attribute__( ( section( ".bss.NoInit")) ) ;

而如果是直接指定内存地址:

// Arm Compiler 5
static uint32_t BootFlag __attribute__((section(".ARM.__at_0x2001FFFC")));
// Arm Compiler 6
static uint32_t BootFlag __attribute__((section(".bss.ARM.__at_0x2001FFFC")));

这种方法需要在分散表中定义 UNINIT 或在 Target 配置上勾选 NoInit

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝