STC15 - C51 - Memory Models

news/2024/11/28 2:51:05/

文章目录

    • STC15 - C51 - Memory Models
    • 概述
    • 笔记
    • 内存用量的优化思路
    • END

STC15 - C51 - Memory Models

概述

在STC上测试呢, 想看看变量(不同类型的定义)被编译器分配在哪个内存范围(idata, pdata, xdata)?
同时, 总结一下降低内存用量的思路(如果像上位机那样内存管够, 就不用考虑内存用量的优化).

笔记

MDK5的自带帮助中, 有关于内存模型的说明.
在这里插入图片描述
MDK5中可以设置默认的内存模型.
在这里插入图片描述
在这里插入图片描述
内存模型有3种可选 : Small, Compact, Large.
选定了内存模型后, 可以让编译器按照指定的内存模型, 来分配变量到默认的内存范围.
在这里插入图片描述
内存模型 - Small : 默认将变量都分配到idata(总共128bytes)
内存模型 - Compact : 默认将变量都分配到pdata(总共256bytes)
内存模型 - Large : 默认将变量都分配到xdata(具体是否超过对应MCU的内部扩展RAM或外部扩展RAM, 需要看具体硬件连接情况), 对于IAP15F2K61S2, 内部扩展RAM范围 0x0 ~ 0x6ff, 但是IAP15自己要用0x400~0x6ff, 所以可用的内部扩展RAM范围为0x0 ~ 0x3ff

选定内存模型后, 变量被编译器自动分配到默认的内存范围. 但是可以单独指定函数入参和局部变量, 全局变量到底存在哪里.

C51的函数调用时, 参数并不在栈内, 而是全局变量.
可以单独指定函数参数的存储位置.

#pragma small         /* Default to small model */extern int calc (char i, int b) large reentrant;
extern int func (int i, float f) large;
extern void *tcp (char xdata *xp, int ndx) compact;int mtest (int i, int y)            /* Small model */{return (i * y + y * i + func(-1, 4.75));}int large_func (int i, int k) large /* Large model */{return (mtest (i, k) + 2);}

如上的函数定义, 只能告诉编译器将函数入参分配到哪个内存区, 但是对节约内存没有帮助. 多一个被调用的函数实现(假设函数有入参), 内存用量还是会增加.

在C51中的局部变量也不在栈中(其实也是全局变量). 可以单独指定局部变量分配在哪个内存范围.
在这里插入图片描述
我的测试程序, 变量空间> 0x256, 直接将内存模型选为Large了.
在一个函数内定义3个变量, idata, pdata, xdata, 看看编译后内存空间大小的变化.

int main(void)
{// int idata x1 = 0;// int pdata x2 = 0;// int xdata x3 = 0;

每次编译后, 就逐个注释掉3个变量定义, 再编译, 得到如下实验结果.

Program Size: data=25.1 xdata=969 code=28811 x1,x2, x3
Program Size: data=25.1 xdata=967 code=28803 x1,x2
Program Size: data=25.1 xdata=965 code=28796 x1
Program Size: data=23.1 xdata=965 code=28789 

可以看出:
idata变量被编译器分配在data区.
pdata变量被编译器分配在xdata区.
xdata变量被编译器分配在xdata区.

内存用量的优化思路

51MCU, 内部扩展RAM就那么多(1K+), 如果不规划内存用量, 多一个函数就多占用一些内存(函数入参, 函数内的局部变量), 函数写的越多, 内存占用越多. 最后直到程序编译不过去, 显示内存越界.
将不在一条调用链上的函数, 公用一些内存空间, 就能节省一些内存. 且不随着函数数量的增加, 内存占用量上升不明显.
中断函数的内存用量要是单独的.
顶层函数的内存用量最好是单独的.
被顶层函数调用的多个并排的底层函数(这些函数之间, 不存在同时被调用的可能), 内存可以是公用的.

自己做一个结构体, 将变量都放到里面.
不能公用的变量, 就是结构体中的一个变量或结构.
能公用的变量, 就是结构体中的联合中的一个变量或结构. 如下所示.

// D:\my_dev\my_local_git_prj\hardware\LS_stc_dev_board\src\stc15_exp_box4\v5\soft_prj\factory_mode\uart_cmd.c
typedef struct _tag_uart_cmd {int len;int i;int len1;int len2;TAG_CMD_ITEM code* pcmd_item;TAG_CMD_PARAM* pcmd_param;
} TAG_uart_cmd;// D:\my_dev\my_local_git_prj\hardware\LS_stc_dev_board\src\stc15_exp_box4\v5\soft_prj\factory_mode\hardware_init.c
typedef struct _tag_hardware_init{COMx_InitDefine COMx_InitStructure;					//结构定义
}TAG_hardware_init;typedef union un_app_cfg {// union中的内容不能为空TAG_hardware_init hardware_init;TAG_uart_cmd uart_cmd;TAG_uart_cmd_help uart_cmd_help;TAG_uart_cmd_test_ind_leds uart_cmd_test_ind_leds;TAG_uart_cmd_test_7seg uart_cmd_test_7seg;TAG_Exti Exti;TAG_uart_cmd_test_exint_2key uart_cmd_test_exint_2key;TAG_uart_cmd_test_key16_normal uart_cmd_test_key16_normal;TAG_uart_cmd_test_key16_adc uart_cmd_test_key16_adc;TAG_uart_cmd_test_ex_ram uart_cmd_test_ex_ram;TAG_uart_cmd_test_12864 uart_cmd_test_12864;TAG_uart_cmd_test_1602 uart_cmd_test_1602;TAG_EEPROM EEPROM;TAG_uart_cmd_test_e2prom uart_cmd_test_e2prom;TAG_uart_cmd_test_LVD uart_cmd_test_LVD;TAG_uart_cmd_test_RTC uart_cmd_test_RTC;TAG_uart_cmd_test_pm25 uart_cmd_test_pm25;Tag_uart_cmd_test_NTC uart_cmd_test_NTC;
} UN_APP_CFG;typedef struct _tag_app_global_var {// 需要被同时使用的变量, 放在union外面的结构中u8 g_tmp_buf[TMP_BUFFER_LEN];u8 g_param_buf[TMP_BUFFER_LEN];UFP_uart_cmd g_UFP_uart_cmd;BOOL is_cmd_was_process;BOOL is_cmd_process_ok;// 能不同时使用的变量都放un中的每个应用参数中UN_APP_CFG un;
} TAG_APP_GLOBAL_VAR;extern u8 bdata u8_flag;
extern TAG_APP_GLOBAL_VAR xdata app;

调用这些变量时, 示例如下:
不能公用的变量用法 app.is_cmd_process_ok
可以公用的变量用法 app.un.uart_cmd.pcmd_item
这种方法还挺管用的, 从1200字节, 优化到了965字节. 这样的话,使用片内扩展RAM(0x0~0x400)就可以满足逻辑运行的要求.

这种内存优化思路不好的地方, 没有直接写全局变量直观了(每个变量都好长, 需要好几个结构的.操作才能使用这个变量, 看起来也挺恶心).

u8 code fn_parse_user_input_form_uart1(void /*u8* pbuf, int len_buf*/)
{app.un.uart_cmd.len = 0;if ( (NULL == app.g_UFP_uart_cmd.fn_parse_user_input_form_uart1.pbuf)|| (app.g_UFP_uart_cmd.fn_parse_user_input_form_uart1.len_buf < 2)|| (COM1.RX_Cnt < 2)) {return FALSE;}memset(app.g_UFP_uart_cmd.fn_parse_user_input_form_uart1.pbuf, 0,app.g_UFP_uart_cmd.fn_parse_user_input_form_uart1.len_buf);RX1_Buffer[COM1.RX_Cnt - 2] = '\0';app.un.uart_cmd.len = strlen(RX1_Buffer);PrintString1("您输入的命令为[");PrintString1(RX1_Buffer);PrintString1("]\r\n");PrintString2("您输入的命令为[");PrintString2(RX1_Buffer);PrintString2("]\r\n");if ((app.un.uart_cmd.len <= 0)|| (app.un.uart_cmd.len >= (app.g_UFP_uart_cmd.fn_parse_user_input_form_uart1.len_buf - 1))) {return FALSE;}strcpy(app.g_UFP_uart_cmd.fn_parse_user_input_form_uart1.pbuf, RX1_Buffer); // 得到了没有\r\n, 以\0结尾的用户命令return TRUE;
}

甚至为了进一步降低内存用量, 直接将有些函数改成(void)无入参. 实际的入参由外部调用直接赋值, 函数内部直接使用函数外部赋值的全局变量, 看着更恶心了.
函数调用

			app.un.uart_cmd_test_pm25.SPI_Write_Nbytesaddr = app.un.uart_cmd_test_pm25.Flash_addr;app.un.uart_cmd_test_pm25.SPI_Write_Nbytesbuffer = app.un.uart_cmd_test_pm25.tmp; app.un.uart_cmd_test_pm25.SPI_Write_Nbytessize = 0x10;SPI_Write_Nbytes();		//写N个字节

函数实现

void	SPI_Write_Nbytes(void /* u32 addr, u8* buffer, u8 size */)
{if (app.un.uart_cmd_test_pm25.SPI_Write_Nbytessize == 0)	{return;}if (!app.un.uart_cmd_test_pm25.B_FlashOK)	{return;}while (CheckFlashBusy() > 0);		//Flash忙检测FlashWriteEnable();					//使能Flash写命令SPI_CE_Low();						// enable deviceapp.un.uart_cmd_test_pm25.SPI_WriteByteout = SFC_PAGEPROG;SPI_WriteByte();		// 发送页编程命令

不过, 对于MCU这种内存, 寸土寸金的地方, 牺牲一些直观性, 来保证内存用量可控, 还是值得的(暂时想不到其他方法可以大规模的降低内存用量).

END


http://www.ppmy.cn/news/2607.html

相关文章

测量电源纹波-正确测量方法

测量纹波需要注意的点&#xff1a; 1、用弹簧探针&#xff1b; 2、测量位置在输出电容两端&#xff1b; 3、示波器选择‘20M’&#xff1b; 4、示波器选择‘交流耦合’&#xff1b; 5、示波器探头‘X1’&#xff0c;示波器设置‘X1’&#xff1b;(10:1的探头&#xff0c;实际上…

关于操作PPL进程引发的一些问题

背景 之前发布过一篇关于procexp的利用&#xff0c;但是最近有brother说&#xff0c;最新的版本不能利用了&#xff0c;然后就去下载了最新版本的。 发现判断了是不是受保护进程&#xff0c;不是就拒绝。 这里简单看一下这个函数&#xff0c;发现对比的是_PS_PROTECTION,其实就…

[Go] go基础4

1. 并发编程 1.1 并发和并行 并发: 多个线程在同个核心的CPU上运行.并发的本质是串行. 并行: 多个线程在多个核心的CPU上运行. 1.2 协程和线程 协程: 独立的栈空间,共享堆空间,调度由用户控制,本质上有点类似用户及线程,这些用户及线程的调度也是自己实现的. 线程: 一个线…

CRC 循环冗余检验【计网必考】

CRC 循环冗余检验作为一个重点&#xff0c;也是数据链路层必考的一个考点&#xff0c;所以我把差错检测单独拿出来分析一起看一下。总结不易&#xff0c;一个简单的攒&#xff0c;Thanks♪(&#xff65;ω&#xff65;)&#xff89; 目录 一、介绍及工作原理 二、校验计算过程…

浅浅的分析Spring底层事务原理

Spring事务底层原理一、EnableTransactionManagement工作原理二、Spring事务基本执行原理三、Spring事务的过程四、Spring事务传播机制五、Spring事务传播机制分类&#xff08;1&#xff09;案例分析、情况1&#xff08;2&#xff09;案例分析、情况2&#xff08;3&#xff09;…

c++11中的declval和decltype

一、declval的介绍 std::declval定义在头文件中&#xff1a; template<class T> typename std::add_rvalue_reference<T>::type declval() noexcept;看定义它应该是返回一个右值引用(在T 是&#xff08;可有 cv 限定的&#xff09; void &#xff0c;此情况下返回…

Maya多边形物体批量材质传递工具v1.0发布及教程

一、插件介绍&#xff1a; 在大量场景制作时&#xff0c;当前期模型和材质没有同时完成&#xff0c;而用白模进行场景搭建后&#xff0c;能否后期&#xff0c;快速根据相同模型结构&#xff0c;快速识别物体并批量赋材质吗&#xff1f;答案是现在可以了。下面介绍的就是解决此…

ISO9001有什么好处

1、帮助大中小企业间公平竞争 有些中小企业不善于在国际市场中推销自己&#xff0c;当面对艰难竞争时&#xff0c;标准证书可以为其说话&#xff0c;给企业带来信誉。 2、帮助企业打开出口市场 从事标准化工作取得的收益比很多开展小型业务人员原本认为的要大。标准既重要且有趣…