Keil C51 - ERROR L107: ADDRESS SPACE OVERFLOW

概述

在给一个做好的板子写出厂测试程序. 一共要写20个测试功能.
MCU为STC15F2K60S2. 编译器为 MDK5(keil C51)
开始很正常, 大概完成一半功能实现后, 编译报错如下:

*** ERROR L107: ADDRESS SPACE OVERFLOW
    SPACE:   DATA    
    SEGMENT: ?DT?_TX2_WRITE2BUFF?USART
    LENGTH:  0001H
*** ERROR L107: ADDRESS SPACE OVERFLOW
    SPACE:   DATA    
    SEGMENT: ?DT?_ADC_POWERCONTROL?ADC
    LENGTH:  0001H
Program Size: data=131.1 xdata=510 code=14336
Target not created.
Build Time Elapsed:  00:00:01

可以看到 data 超过了127, xdata不到2048, code不到64K.
在网上查了资料, L07的错误原因: 不是RAM超了, 就是ROM超了.
我这的情况是data超过了127bytes.
网上解决方法:

* 将内存模式从Small换成Compact或Large, 再编译通过.

在这里插入图片描述
这种方法不适合我现在的场景.
现在我挂了一片32KB的外部RAM, 只能用small模式正常访问. 如果换成Compact或Large, 无法正常访问外部RAM, 读写校验时报错, 看反汇编, 发现操作外部RAM的DPTR, 操作了2次, 每操作1次, DPTR++. small模式只操作了1次.

因为是用C写的, 也没有特别的方法能保证操作外部RAM时和small模式一样.
所以, 必须用small模式来编译.

官方demo也是在small模式下编译的, 换成其他2个内存模式后, 访问外部RAM也是不正常的.
另外以后如果用了RTOS, RTOS也是在small模式下编译的.
所以, 不能改内存模式. 内存模式只能用small

* 将不用的变量去掉.减少内存用量.

想让程序编译过, 现在只能将data小于127才能编译过.
当前情况, 外存用的都很少, ROM空间也剩下的很多. 只要将data用量优化后小于127, 就能编译通过.
如果ROM用量超过了, 就说明MCU选型不对, 就不能选51的MCU了. 或者说, 程序逻辑要改(e.g. 不能包含巨大的测试素材的数组)
像我这个出厂测试程序就是测试这块STC15F2K60S2的板子, MCU不能改, 只要不放很大的素材在ROM中, 64KB写个出厂测试程序还是够的.

现在编译选项采用无优化
在这里插入图片描述

从map文件, 可以看到data用量是被谁占用的.

在完全编译, 出现报错后, 内存用量可以在map文件中看到.
需要先打开产生产生map文件的编译选项.
在这里插入图片描述
现在去看map文件
在这里插入图片描述
用vscode打开map文件

MEMORY MODEL: SMALL


INPUT MODULES INCLUDED:
  .\Objects\STARTUP.obj (?C_STARTUP)
  .\Objects\main.obj (MAIN)

可以看到, 当前内存模型是small

LINK MAP OF MODULE:  .\Objects\krgy_factory_mode (?C_STARTUP)


            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     0014H     UNIT         _DATA_GROUP_
            DATA    001CH     0004H     UNIT         ?DT?_TEST_XRAM_READ_WRITE?UART_CMD_TEST_EX_RAM
            BIT     0020H.0   0001H.1   UNIT         _BIT_GROUP_
                    0021H.1   0000H.7                *** GAP ***
            DATA    0022H     0009H     UNIT         ?DT?_MY_ASSERT?APP_CFG
            DATA    002BH     0005H     UNIT         ?DT?_FN_PARSE_USER_INPUT_FORM_UART1?UART_CMD
            DATA    0030H     0004H     UNIT         ?DT?_EXT_INILIZE?EXTI
            DATA    0034H     0004H     UNIT         ?DT?_GPIO_INILIZE?GPIO
            DATA    0038H     0004H     UNIT         ?DT?_USART_CONFIGURATION?USART
            DATA    003CH     0004H     UNIT         ?DT?_GET_ADC10BITRESULT?ADC
            DATA    0040H     0003H     UNIT         ?DT?_IN1_PROCESS_UART_CMD?UART_CMD
            DATA    0043H     0003H     UNIT         ?DT?_FN_PROC_CMD_DEFAULT?UART_CMD
            DATA    0046H     0003H     UNIT         ?DT?_FN_PROC_CMD_HELP?UART_CMD_HELP
            DATA    0049H     0003H     UNIT         ?DT?_FN_PROC_CMD_TEST_IND_LEDS?UART_CMD_TEST_IND_LEDS
            DATA    004CH     0003H     UNIT         ?DT?CLEAR_7SEG_LED?UART_CMD_TEST_7SEG
            DATA    004FH     0003H     UNIT         ?DT?_FN_PROC_CMD_TEST_7SEG?UART_CMD_TEST_7SEG
            DATA    0052H     0003H     UNIT         ?DT?_FN_PROC_CMD_TEST_EXINT_2KEY?UART_CMD_TEST_EXINT_2KEY
            DATA    0055H     0003H     UNIT         ?DT?_FN_PROC_CMD_TEST_KEY16_NORMAL?UART_CMD_TEST_KEY16_NORMAL
            DATA    0058H     0003H     UNIT         ?DT?IO_KEYSCAN_KEY16_NORMAL_READ_ONCE?UART_CMD_TEST_KEY16_NORMAL
            DATA    005BH     0003H     UNIT         ?DT?IO_KEYSCAN_KEY16_NORMAL?UART_CMD_TEST_KEY16_NORMAL
            DATA    005EH     0003H     UNIT         ?DT?_FN_PROC_CMD_TEST_KEY16_ADC?UART_CMD_TEST_KEY16_ADC
            DATA    0061H     0003H     UNIT         ?DT?_FN_PROC_CMD_TEST_EX_RAM?UART_CMD_TEST_EX_RAM
            DATA    0064H     0003H     UNIT         ?DT?_DELAY_MS?DELAY
            DATA    0067H     0003H     UNIT         ?DT?_PRINTSTRING1?USART
            DATA    006AH     0003H     UNIT         ?DT?_PRINTSTRING2?USART
            DATA    006DH     0003H     UNIT         ?DT?_ADC_INILIZE?ADC
            DATA    0070H     0002H     UNIT         ?DT?_SEND_595?UART_CMD_TEST_7SEG
            DATA    0072H     0002H     UNIT         ?DT?_SET_595_VALUE?UART_CMD_TEST_7SEG
            DATA    0074H     0002H     UNIT         ?DT?INIT_7SEG?UART_CMD_TEST_7SEG
            DATA    0076H     0002H     UNIT         ?DT?INIT_KEY16_NORMAL?UART_CMD_TEST_KEY16_NORMAL
            DATA    0078H     0002H     UNIT         ?DT?_SHOW_595_U16?UART_CMD_TEST_KEY16_ADC
            DATA    007AH     0002H     UNIT         ?DT?_ADC_STAND_RANGE?UART_CMD_TEST_KEY16_ADC
            DATA    007CH     0002H     UNIT         ?DT?TESTXRAM?UART_CMD_TEST_EX_RAM
            DATA    007EH     0002H     UNIT         ?DT?UART_CMD_TEST_EX_RAM
            IDATA   0080H     0001H     UNIT         ?STACK

            * * * * * * *  X D A T A   M E M O R Y  * * * * * * *

"D A T A M E M O R Y"区域内存, 就是data被谁占用了.

不能优化data内存的项

REG 0000H 0008H ABSOLUTE “REG BANK 0”

  • “REG BANK 0” 这个是寄存器组, 必须有的, 我们也操作不到, 只能是这样.

DATA 0008H 0014H UNIT DATA_GROUP

DATA_GROUP 是函数调用链深度用到data, 这个也改不了.

BIT 0020H.0 0001H.1 UNIT BIT_GROUP

位变量用到的data, 这个不用去改.

0021H.1 0000H.7 *** GAP ***

位变量用到的data, 这个不用去改.

IDATA 0080H 0001H UNIT ?STACK

栈用掉的data, 这个改不了.

可以改的data选项

DATA    001CH     0004H     UNIT         ?DT?_TEST_XRAM_READ_WRITE?UART_CMD_TEST_EX_RAM

类型是DATA, 名称为?DT?X?Y
这种都可以改.
Y代表哪个C文件名称
X代表在Y.C中哪个函数名称

这个工程, 我用了STC15官方的库函数实现.
自己的实现和STC15库函数的实现都是可以改的.
先优先改自己的函数, 实在不行再优化第三方的函数

优化data的方法

有些关键函数不能改. e.g. 内存访问操作, my_assert格式化封装的函数
改了之后, 使用起来极其不方便(e.g. my_assert要打印__FILE_, __LINE, 必须正常将函数入参传进来, 而不能使用全局变量). 或者根本就起作用(e.g. 访问外部RAM, 只能是从xdata地址到data变量的直接访问)
这样的关键函数很少. 如果有这样的函数, 只能保留.

将函数内部的变量改为xdata

在C51中, 函数内部的局部变量, 用的并不是栈空间, 而是全局内存空间.
将函数内部的局部变量改为xdata, 这是最方便的.先做这一步, 再看map文件, 将这种函数局部变量都优化掉.

u8 wait_until_user_input_cmd_from_uart1_special(void)
{
	// const char* pDst
	BOOL xdata is_cmd_process_ok = FALSE;

将函数入参去掉, 用全局变量代替

如果优化完函数局部变量, 由于函数数量多, 导致还是L107错误.
这时, 只能是将函数入参优化成全局变量传递, 只剩这一招了.

函数参数也占用全局内存空间, 即使函数只有一个u8类型的参数. 也会占用1字节的data空间.
编译器默认会将函数参数分配为data类型
如果有100个以上的函数都要调用, 那么data区的127个字节空间就危险了.

如果xdata空间足够, 可以初步将函数参数由全局变量代替.
在函数调用前, 对全局变量参数赋值.
在函数中, 读写这些全局变量.

将全局变量可以复用的地方, 用union的结构变量

如果xdata空间紧张, 或者考虑到后续xdata空间不够用的原因, 需要再对全局变量空间进行优化.
用union类型的全局变量结构体来代替单独的全局变量, 只要保证这些全局变量不会同时用到, 就可以放到union中用来节省xdata空间.

可以将每个函数的入参, 封装成一个结构体_tag_fn_A, _tag_fn_B.
我代码中的一个例子

typedef struct _tag_function_param_uart_cmd__wait_until_user_input_cmd_from_uart1_special
{
	// u8 wait_until_user_input_cmd_from_uart1_special(const char* pDst)
	const char* pDst;
}TAG_FUNCTION_PARAM_uart_cmd__wait_until_user_input_cmd_from_uart1_special;

typedef union _un_function_param_uart_cmd {
	TAG_FUNCTION_PARAM_uart_cmd__wait_until_user_input_cmd_from_uart1_special wait_until_user_input_cmd_from_uart1_special;
}UN_FUNCTION_PARAM_uart_cmd;

extern UN_FUNCTION_PARAM_uart_cmd xdata g_UN_FUNCTION_PARAM_uart_cmd;

我封装了一个union类型的全局变量, 如果其他函数的入参要放进来, 就在 UN_FUNCTION_PARAM_uart_cmd 中加入其他函数的参数结构
.函数调用前, 对全局变量赋值

		g_UN_FUNCTION_PARAM_uart_cmd.wait_until_user_input_cmd_from_uart1_special.pDst = "next";
		if (wait_until_user_input_cmd_from_uart1_special())
		{
			PrintString1("开始检查下一项\r\n");
			break;
		}

进了函数之后, 对全局参数进行读写

u8 wait_until_user_input_cmd_from_uart1_special(void)
{
	// const char* pDst
	BOOL xdata is_cmd_process_ok = FALSE;
	
	wait_until_user_input_cmd_from_uart1();
	do {
		if (NULL == g_UN_FUNCTION_PARAM_uart_cmd.wait_until_user_input_cmd_from_uart1_special.pDst)
		{
			break;
		}

将每个文件所有的函数入参, 封装为一个union. 自己从逻辑上保证不会同时用到这个union(而是调用每个函数前, 才会去填充这个union, 然后再调用函数, 函数内部再取全局union中的函数对应参数), 这样的话, 内存就省很多.

备注

这种优化data的方法已经试过了, 好使.
如果预估到自己的工程如果按照正常的写法会不断消耗data, 但是工程的功能还远没有写完(e.g. 功能现在只完成了一半), 就已经出现了L107错误.
这时, 最好参考map文件, 一次性的将消耗data的函数都优化完.免的临时报佛脚, 只优化眼前的报错, 稍后新写了一段代码, 又出现了L107错误.
新加的函数实现的内部变量和参数, 函数调用, 都按照这种优化data的思路来写.

END

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐