# 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

# 半主机模式

在嵌入式开发中,少不了 printf 等 C 库函数操作,一般来说,这些都是标准库上的操作,然在资源紧凑的单片机中,能不引入完整的 C 标准库就尽量不引入;而 keil 中,提供紧凑型的 C 库,称之为 MicroLIB ,因此我们在建项目的时候都会在:工程属性的 “Target “ -> ” Code Generation “ 中勾选 ” Use MicroLIB “ 选项,这样避免在以后中使用了 printf、sprintf 等函数而出现异常,同时由于 MicroLIB 库是由官方亲自进行裁剪过,所以相对于 C 标准库编译出来的大小会小很多。

虽说可以使用 MicroLIB 库,但由于裁剪的问题,注定它无法实现一些比较高级的功能,或者无法重定义该 I/O 功能,所以在某些时候,必须使用 C 标准库,那么在 keil 中如何实现,这就是下文要讨论的了。

在应用程序代码中经常使用标准 I/O 库函数,如 printf ()、scanf () 或 fgetc () 来执行输入 / 输出操作。 这些函数在标准 ARM 编译器 c 运行时库中的结构是:

这样一来,要重新定位输入 / 输出,您可以:

  • 避免高级库函数。
  • 重新定义底层库函数。
  • 重新定义系统 I/O 功能。

而对于重新定义低级库函数还是系统 I/O 函数是更好的解决方案,这取决于您的用例。例如,UART 每次只写一个字符,而默认的 fputc () 使用缓冲,因此重新定义不带缓冲区的函数可能适合 UART。但是,在可能进行缓冲区操作的地方,重新定义系统 I/O 函数可能更合适。

半主机:Semihosting 是一种机制,通过调试器使 ARM 目标上运行的代码能够与主机上的输入 / 输出设施通信进行交互。当目标板脱离调试器单独运行时,则需要退出半主机模式,否则使得设备运行时会进入软件中断 BAEB 处。

为了能让目标板在单独运行时进行输入 / 输出,而不进入软件中断 BAEB 处。因此在 keil 中,可以使用如下操作禁止进入半主机模式:

  • In C source code:

    #if __ARMCC_VERSION >= 6000000
    	//https://developer.arm.com/documentation/100073/0622/The-Arm-C-and-C---Libraries/Support-for-building-an-application-with-the-C-library/Indirect-semihosting-C-library-function-dependencies
        __asm(".global __use_no_semihosting");
    #elif __ARMCC_VERSION >= 5000000
    	// https://developer.arm.com/documentation/dui0475/m/the-arm-c-and-c---libraries/support-for-building-an-application-with-the-c-library/indirect-semihosting-c-library-function-dependencies?lang=en
    	#pragma import(__use_no_semihosting)
    #else
        #error Unsupported compiler
    #endif
  • In armasm assembly language source code:

    IMPORT __use_no_semihosting
    

在禁用半主机模式后,对于标准 C 库一般会重定义实现以下 Dependencies on low-level functions 函数:

struct __FILE
{
    int handle; /* Add whatever you need here */
};
/* 
 * Re-implementing the printf family. 
 * The printf family consists of _printf(), printf(), _fprintf(), fprintf(), vprintf(), and vfprintf().
 */
// https://www.keil.com/support/man/docs/armlib/armlib_chr1358938930943.htm
FILE __stdout;
int ferror(FILE *f)
{
    /* Your implementation of ferror */
    return EOF;
}
int fputc(int ch, FILE *f)
{
}
/* 
 * Re-implementing the scanf family. 
 * The scanf() family consists of scanf() and fscanf().
 */
// https://www.keil.com/support/man/docs/armlib/armlib_chr1358938931161.htm
FILE __stdin;
int __backspace(FILE *f) { return 0; }
// https://www.keil.com/support/man/docs/armlib/armlib_chr1358938931957.htm
int fgetc(FILE *f)
{
}
void _ttywrch(int ch)
{
    printf("%c", ch);
    (void)ch;
}
// https://www.keil.com/support/man/docs/armlib/armlib_chr1359122862227.htm
void _sys_exit(int x)
{
    (void)x;
    while (1)
        ; /* endless loop */
}
// https://www.keil.com/support/man/docs/armlib/armlib_chr1359122859512.htm

附:

Indirect Semihosting 依赖函数表:

FunctionUsage
__user_setup_stackheap()Sets up and returns the locations of the stack and the heap.
__raise()Catching, handling, or diagnosing C library exceptions, without C signal support. (Tailoring error signaling, error handling, and program exit.)
__default_signal_handler()Catching, handling, or diagnosing C library exceptions, with C signal support. (Tailoring error signaling, error handling, and program exit.)
__Heap_Initialize()Choosing or redefining memory allocation. Avoiding the heap and heap-using C library functions supplied by Arm®.
ferror() <br/> fputc() <br/> __stdoutReimplementing the printf family. (Tailoring input/output functions in the C and C++ libraries.).
__backspace() <br/> fgetc() <br/> __stdinReimplementing the scanf family. (Tailoring input/output functions in the C and C++ libraries.).
fwrite() <br/> fputs() <br/> puts() <br/> fread() <br/> fgets() <br/> gets() <br/> ferror()Reimplementing the stream output family. (Tailoring input/output functions in the C and C++ libraries.).

Direct Semihosting 依赖函数表:

FunctionDescription
__user_initial_stackheap()Sets up and returns the locations of the stack and the heap. If you are using a scatter file at the link stage, you might have to re-implement this function. <br/>The linker issues an error when no semihosting is requested and __user_initial_stackheap() is not re-implemented.
_sys_exit() <br/> _ttywrch()Error signaling, error handling, and program exit.
_sys_command_string() <br/> _sys_close() <br/> _sys_iserror() <br/> _sys_istty() <br/> _sys_flen() <br/> _sys_open() <br/> _sys_read() <br/> _sys_seek() <br/> _sys_write() <br/> _sys_tmpnam()Tailoring input/output functions in the C and C++ libraries.
clock() <br/> _clock_init() <br/> remove() <br/> rename() <br/> system() <br/> time()Tailoring other C library functions.

参考:

AC5:https://developer.arm.com/documentation/dui0475/m

AC6:https://developer.arm.com/documentation/100073/0622

更新于 阅读次数

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

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝