# 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 | 间接寻址 |
code | Flash(最大 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 资源。
总结:
- const 声明的对象被 Keil C51 存放在数据区 RAM 中 (可以为 data、idatd、xdata,具体看分配);
- 要想将变量存放到代码区 ROM 中以节省 RAM 资源,需更换为 code 关键字去声明对象;
- 虽然可以使用指针访问并修改 const 声明的对象,但最好不要这么做,因为 Keil C51 不一定会如程序员预想那样处理代码;
- 无论是 const 还是 code 修饰的对象,都具有不可修改的特性,最大的区别可能是一个存放在数据区一个存放在代码区,因此在使用这些修饰符时,需要根据实际需求选择合适的修饰符。
# printf 输出打印
在 C51 上,printf 的转换说明符跟常用的是有区别的,如果配置不当,那么打印输出的结果是错乱的(eg:char 使用 '% d' 转换);既然不一样,那么对应的数据类型是分别使用哪些转换说明符呢?
参考: Keil > Help > uVision Help > Cx51 Compiler User’s Guide > Library Reference > Reference > printf
转换说明符 | 变量类型 | 描述 |
---|---|---|
%bd | char | 带符号字符型 |
%hd | short | 带符号短整型 |
%d | int | 带符号整形 |
%ld | long int | 带符号长整型 |
%bu | unsigned char | 无符号字符型 |
%hu | unsigned short | 无符号短整型 |
%u | unsigned int | 无符号整型 |
%lu | unsigned long int | 无符号长整形 |
%bx | unsigned char | 无符号十六进制字符型 |
%hx | unsigned short | 无符号十六进制短整型 |
%x | unsigned int | 无符号十六进制整型 |
%lx | unsigned 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 个步骤:
- 声明一个合适的存储空间;
- 利用
init_mempool()
初始化内存池; - 调用相关的内存管理 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 配置指定地址:
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 依赖函数表:
Function | Usage |
---|---|
__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/> __stdout | Reimplementing the printf family. (Tailoring input/output functions in the C and C++ libraries.). |
__backspace() <br/> fgetc() <br/> __stdin | Reimplementing 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 依赖函数表:
Function | Description |
---|---|
__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