极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 21868|回复: 8

Arduino 是怎样 占用空间的

[复制链接]
发表于 2015-12-9 04:17:56 | 显示全部楼层 |阅读模式
看到一个帖子,

求助: 关于Arduino的内存!
http://www.geek-workshop.com/thread-10816-1-1.html

我想很多人都有这样的 困惑,下面是我的回帖,希望能对大家有帮助!


我和你一样,新兵蛋子一个,不过最近我研究了一下这个问题。下面谈谈感想。

楼上的网友说的基本都对,Arduino 328p 用的芯片 属于 哈弗结构,他把存储区分为 三个部分:

1. FLASH 程序存储区,
2. RAM 动态 内存,
3. ROM 区 。

他不同于 诺依曼 结构的  程序存储区  和  RAM  内存是在一起的。

由于 程序存储区  和  RAM  内存 是分开的,于是就有了  32K 的 FLASH 程序存储区 和  2K 的 RAM 动态内存,当然还有 ROM .

32K 的 FLASH 程序存储区 是存放 你的 经过 编译的 程序本体。 2K 的 RAM 动态内存 是存放 程序运行时 需要的 临时 变量。

由于 Arduino 本初的 想法是 给那些 非 计算机专业的 设计者们 使用的,所以他使用了人们习惯的思维方式来编程。于是他发明了很多自己专用的 指令(确切的说应该是 函数,或  宏替换 ),这样,就带来了 代码 不精练的 问题。当然 宏替换 没有问题。

这不等于说 不高效 和 不简练,因为 Arduino IDE  是 基于 C / C++ 的 编译器, C / C++ 在 编译时 还是做了很多 代码优化工作。

我们看 几个 例子:        使用   Arduino IDE  版本 1.6.6   编译

1.   编译一个  空 程序     ( Arduino 编辑器 的 初始界面 ,他也是一个程序,而且是 C 程序)

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}


Arduino IDE  版本 1.6.6   显示的 编译结果是:
项目使用了 450 字节,占用了 (1%) 程序存储空间。最大为 30,720 字节。
全局变量使用了9字节,(0%)的动态内存,余留2,039字节局部变量。最大为2,048字节。

这个程序 什么也没有做,他也要占用 这么多 空间。 那是因为 他要 初始化 各个端口 和 很多 寄存器,还要 运行 上面那段 看起来像是 空代码 的 C 代码。

你若不信,看看     Arduino IDE    他 隐藏 包含的  main.cpp  库函数

int main(void)                        //  这是一段 c  程序
{
        init();

        initVariant();

#if defined(USBCON)
        USBDevice.attach();
#endif
       
        setup();                            // 这是  Arduino IDE 定义的 函数,就是上面那个空程序中的    setup();
   
        for (;;) {
                loop();                   //  这个也是  Arduino IDE 定义的 函数,也是上面那个空程序中的    loop();  
                                             //  只不过他放在了   具有 空参数 的  for ( ; ; )  中,去做无限的 循环,版本不一样可能略有不同。
                if (serialEventRun) serialEventRun();
        }
        
        return 0;
}


这就是 我们看得到   Arduino IDE  编程器的 初始 界面 ,别看他是 空的,但是 上面的代码是要 运行的。

记住 这段 空代码 使用的 空间,我们再做 下面的 实验。我们用 Arduino 自带的 经典代码,点亮 一颗 LED 灯。


void setup() {

  pinMode(13, OUTPUT);         //   将   D13   端口 设置为  输出, 因为 他上面 连着 一颗  LED 灯。
}

void loop() {
  digitalWrite(13, HIGH);        //  将 D13 设为 高电平, 点亮 LED 灯。
  delay(1000);                      //  延时 1秒
  digitalWrite(13, LOW);       //   将 D13 设为 低电平, 关闭 LED 灯。
  delay(1000);                     //   再 延时 1秒
}

编译他:   IDE 版本不一样 结果会有不同。            
项目使用了 1,030 字节,占用了 (3%) 程序存储空间。最大为 30,720 字节。              
全局变量使用了9字节,(0%)的动态内存,余留2,039字节局部变量。最大为2,048字节。

这个程序 占用的空间是    1030 - 450 = 580 字节。   (本次编译 使用空间  1030 字节 -  空程序 使用空间 450 字节。)
变量 占用的 RAM 空间 还是  9 字节,这是因为  上面的 4 个 看起来像变量的数据 (13, OUTPUT ,HIGH,LOW )其实都是 宏 替换,占用的是  FLASH 程序存储区。

为了叙述方便,我们只改动 一条 语句。

void setup() {

  //   pinMode(13, OUTPUT);         //   注释掉 这一句,不让他 参与 编译
}

void loop() {
  digitalWrite(13, HIGH);        //  将 D13 设为 高电平, 点亮 LED 灯。
  delay(1000);                      //  延时 1秒
  digitalWrite(13, LOW);       //   将 D13 设为 低电平, 关闭 LED 灯。
  delay(1000);                     //   再 延时 1秒
}

再次 编译 他,看看  FLASH 程序存储区 占用情况。

项目使用了 900 字节,占用了 (2%) 程序存储空间。最大为 30,720 字节。
全局变量使用了9字节,(0%)的动态内存,余留2,039字节局部变量。最大为2,048字节。

就这一句的减少, 他比 上次 编译 节省了   1030 - 900 = 130 字节,而且他是 能工作的 ,只是 LED 灯 很暗 ,在很暗的地方可以看清他。


为什么 他还能亮呢? 因为 编译器在什么程序也不编译的时候,他还是要初始化 各个 端口的,这时 PB 端口 都被 初始化成 输入状态,AVR 的 芯片 在 端口为 输入状态时,给他写入 高电平,就启动了 上拉 电阻,电压从 正电源 通过 上拉电阻传递给了 LED ,尽管 电流非常小,但是他还是亮了。这也是 空程序 也要占用空间的原因。

那么  pinMode(13, OUTPUT); 语句为什么要占用 130 字节的空间呢,因为 Arduino 指令 格式 是 16 比特,也就是 2 字节,参数 13 要用 2 字节,OUTPUT 要用 2 字节,调用   pinMode( ) 函数 要用 4 字节,剩下的 是 函数  pinMode( ) 本身 要占用的 空间,由于 IDE 版本 不一样, 函数 本身 要占用的 空间 也不一样,大家可以 试试。

这个程序 不能正常 干活,因为 LED 灯看起来不亮,我们换条指令 让他 正常 干活。看下面的程序:

void setup() {

  //   pinMode(13, OUTPUT);         //   还是 注释掉 这一句,  改为 下面 一句
    bitSet(DDRB,5);                       //   设置  PB 端口的    DDR   输出寄存器 第 5 位 为输出状态,(就是 D13 端口)

}

void loop() {
  digitalWrite(13, HIGH);        //  将 D13 设为 高电平, 点亮 LED 灯。
  delay(1000);                      //  延时 1秒
  digitalWrite(13, LOW);       //   将 D13 设为 低电平, 关闭 LED 灯。
  delay(1000);                     //   再 延时 1秒
}

编译一下,奇迹出现了,
项目使用了 902 字节,占用了 (2%) 程序存储空间。最大为 30,720 字节。
全局变量使用了9字节,(0%)的动态内存,余留2,039字节局部变量。最大为2,048字节。

  bitSet(DDRB,5);     语句 只用了   2   字节 空间,而且和   pinMode(13, OUTPUT);   语句 干的事 同样的 活。

这就是  Arduino ,他用 自己的 语言 方便了 我们,使我们能 很快 入门,但又使我们的程序 冗长,并且执行效率也不高。

爱他,因为我们 很笨,恨他,因为我们嫌他 很笨。

回到 你的 问题,我确实不知道他能 装下 多少行 代码,但是我们知道的是,

1. 代码的 种类 要尽可能的少,因为 每条 相同的函数 本身只占 一个空间,而每次 调用他 只占 很少的 空间,不信你可以多加几条    pinMode(  ,  ); 语句,看看他占用的空间是不是成倍增加。

2. 最宝贵的是    RAM   空间,要尽量的 少用 全局 变量,因为 局部 变量 每次使用完后,他会释放 内存空间给其他变量使用。

3. 相同功能的库,不同发布者,占用的空间不一样,比如:驱动  OLED 屏的库文件。 u8g  占用的空间最少, SSD1306  次之,Adafruit_SSD1306-master  占用空间最多。

u8g 库 的 字体数据文件占用的空间可不少,但是又不能不用,其实每个字体文件中有不少 字 是不用的,我在研究怎样去掉不用的字。goolg 给了一个 造字工具,但是我无法下载他。请各位 大侠 帮帮忙。
回复

使用道具 举报

发表于 2015-12-10 09:14:45 | 显示全部楼层
本帖最后由 164335413 于 2015-12-10 09:22 编辑

void pinMode(uint8_t pin, uint8_t mode)
{
        uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);
    volatile uint8_t *reg;
    if(port == NOT_A_PIN)
                return;
        reg = portModeRegister(port);
        if (mode == INPUT)
        {
            uint8_t oldSREG = SREG;
            cli();

            *reg &= ~bit;
            SREG = oldSREG;
        }
            else      
            {
                uint8_t oldSREG = SREG;
                 cli();
                *reg |= bit;
                SREG = oldSREG;
        }
}
这是函数原型,有些函数在我们用来的确效率不高,但不用关心avr的引脚了,使用起来会很方便。
DDRB |=0x20;
这是avr的源码定义PB5为输出,感觉更高效不过在lib中DDRB寄存器 应该是 _SFR_IO8(0x17) 这才是真正的地址。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-12-10 18:01:29 | 显示全部楼层
我学习的深度远不如你,只是老来无事才进门。先拜你为师,待我有了提高,能达到与你同等层次时,再与你切磋。

说起 avr ,官方给出的定义很多,而且都是最底层的定义,这是 Arduino 的基础。

但是 Arduino  IDE 使用了 C 语言的 一个 子集 作为 自己的语言,一般称之为 Arduino C  ,当然她也是一门高级语言。所以他封装了底层的指令,并且他独创的采用了人们习惯好用的 端口编号。让人们省去了大量的学习时间,也使得非 计算机专业的人才能快速的入门。完成自己的创意。这是 Arduino 的初衷。也是我的最爱。

对于 学习 Arduino 的人来说,总想有些提高,在提高的路上又不必过于拘泥于底层代码,Arduino C 也提供了可供实现的平台,所以学习 Arduino C 不仅要学习 常用的 指令,也要学习 那些能提高代码质量的 看起来像 汇编语言的 那些 指令(其实有很多是宏替换指令),所以才有了本文 “ Arduino 是怎样 占用空间的 ”。

本帖的用意是    用最简单的方法  查看,检验,一个具有相同功能的 不同指令的 程序段 实际占用的 空间,让大家知道  有一个能够正确运行的程序很重要,而有一个 能高效运行的程序 更重要。并同时展示了 一些 最简单的方法,不用增加投资,只要用心学习就足够啦。

感谢你的回帖,让我又有了学习方向!
回复 支持 反对

使用道具 举报

发表于 2016-2-19 16:02:13 | 显示全部楼层
恩,这样对于初学者来说,入门门槛就低了一些,只要了解基础的电子尝试和C语言的基础部分就可以了。如果想要深入,那当然是再回归本质了。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-2-19 23:36:21 | 显示全部楼层
164335413 发表于 2016-2-19 16:02
恩,这样对于初学者来说,入门门槛就低了一些,只要了解基础的电子尝试和C语言的基础部分就可以了。如果想要 ...

arduino 确实是一个伟大的创举,不仅仅是软件开源,硬件开源,最重要的是编程语言,他使很多人不知不觉的就进入了单片机世界,极大的推动了人们参与创新的欲望。

但是,还是要平心而论,仅仅进门还是不够的,得心应手的运用它才是应追求的。

回你上一次的帖:

DDRB |=0x20;
这是 avr 的源码定义 PB5 为输出,感觉更高效不过在lib中 DDRB 寄存器 应该是 _SFR_IO8(0x17) 这才是真正的地址。

我找到了他的定义,在  iom328p.h 里。

以大家常用的 328 p 为例,DDRB 是 端口 B 的 输出方向寄存器,在 iom328p.h 里 是这样定义的:

#define DDRB         _SFR_IO8(0x04)            // 端口 B 方向寄存器,
#define DDB0  0                                  // 端口 B 方向寄存器 中的第 0 位,对应 D8 引脚
#define DDB1  1                               
#define DDB2  2
#define DDB3  3
#define DDB4  4
#define DDB5  5
#define DDB6  6
#define DDB7  7

0x20 = 0B100000,       位 5 (DDB5)是 1 ,也是 arduino 定义的 13 脚,
DDRB 是端口B 的方向寄存器,
  |=  或等于 运算,
他的作用是: 把 端口 B 的第五位 置位,使他具有输出功能。

看了你的帖子后,钻研了一个多月,才搞明白,所有对引脚的操作,其实都是对端口的操作,端口以及他所属的位,都有自己的名称,而每个端口都有自己的地址,最终操作的是地址。到了地址这一步也就到了硬件的最底层,
回复 支持 反对

使用道具 举报

发表于 2016-2-20 11:09:06 | 显示全部楼层
感谢楼主的分享~期待你研究一下内存的占用
回复 支持 反对

使用道具 举报

发表于 2016-2-21 09:43:40 | 显示全部楼层
Arduino的编译环境是WinAVR,arv的开发工具有好多种,代码不能通用,需要做修改。所以我在尝试给attiny88使用Arduino编程时遇到了问题,所以放弃了。只好用WinAVR,不过最后鉴于attiny88没有串口,只好用其他的了。
另外,找ARV案例的代码比Arduino少得多,而avr有很多都是在产品中用。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-2-21 23:40:24 | 显示全部楼层
164335413 发表于 2016-2-21 09:43
Arduino的编译环境是WinAVR,arv的开发工具有好多种,代码不能通用,需要做修改。所以我在尝试给attiny88使 ...

我学习的范围不如你学习的广泛,至于 attiny88 遇到的困难,我猜你的 ARDUINO 可能是找不到关联的文件,可以手工添加,没有 串口,可以用软件模拟串口,ARDUINO 有专门的函数。
回复 支持 反对

使用道具 举报

发表于 2016-2-22 09:02:41 | 显示全部楼层
老来疯 发表于 2016-2-21 23:40
我学习的范围不如你学习的广泛,至于 attiny88 遇到的困难,我猜你的 ARDUINO 可能是找不到关联的文件,可 ...

之前尝试过添加相关的文件和board信息,浪费了很长时间没有成功,所以换回了mega328。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|联系我们|极客工坊

GMT+8, 2026-6-15 17:25 , Processed in 0.042268 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表