Arduino 宏:完整指南(含示例)

  • Arduino 中的宏允许您在编译之前优化代码和内存。
  • 使用#define 允许您创建自适应函数、常量和结构。
  • 高级宏允许您连接标识符并访问 PROGMEM。
  • 规范地使用宏可以提高效率,同时又不牺牲可读性。

Arduino 内核 Zephyr OS beta-1

Arduino的 由于可以轻松创建功能原型和项目,彻底改变了电子世界。然而,对于那些想要进一步提高编程水平、优化资源、保持代码整洁、提高效率的人来说, 成为一个关键工具。

在本文中,我们将深入探讨 Arduino中的宏:它们是什么,如何使用,它们的优点和局限性。我们将通过从网上最好的资源中收集最全面、最有用的信息来实现这一点,并以清晰、现代的方式重写,以真正实用。

Arduino 中的宏是什么?

宏是预处理器指令 在 C/C++ 中,允许您在编译代码之前替换文本。宏并不像传统函数那样执行指令,而是通过替换源文本的部分内容来工作,这 直接影响最终二进制代码的生成方式.

预处理器 它在实际编译之前运行,并负责应用这些替换。在 Arduino 中,这允许 定义常量,有条件地包含文件,甚至创建小 在线功能 节省时间和内存。

基本示例: 像这样的定义 #define LED_PIN 13 导致所有代码被自动替换 LED_PIN13 编译之前。

这看起来可能微不足道,但它提供了一个 编写更灵活、更易于维护的代码的强大方法.

使用宏的优点

在 Arduino 项目中实现宏可以带来许多具体的好处:

  • 提高代码可读性: 通过重复使用符号名称,可以更容易地理解每个元素的用途。
  • 优化性能: 通过不生成函数调用,宏可以更快地执行操作。
  • 减少内存使用量: 在资源有限的董事会中尤其有用,例如 Arduino UNO.
  • 允许有条件的调整: 根据所使用的 Arduino 板的类型,可以编译不同的代码片段。

基本宏:使用#define

指示 #定义 它是最常用的。它既可用于 定义常量值 如果到 创建注入的自动函数 在预编译时。

示例 1:定义一个引脚

#define PINLED 13

void setup() {
  pinMode(PINLED, OUTPUT);
}

void loop() {
  digitalWrite(PINLED, HIGH);
  delay(500);
  digitalWrite(PINLED, LOW);
  delay(500);
}

示例 2:宏作为内联函数

int itemCounter = 0;
#define COUNT_ITEM() do { itemCounter++; } while(0)

void setup() {
  Serial.begin(9600);
  COUNT_ITEM();
  COUNT_ITEM();
}

void loop() {
  Serial.println(itemCounter);
}

正如你所见,该模式的使用 执行 { … } while(0) 确保宏即使在条件结构中使用也能安全运行。

## 运算符和宏连接

## 运算符是一个强大的预处理工具。 允许连接标识符。当您想要动态生成变量名时,这非常有用。

实际例子:

#define GENERAR_VARIABLE(no) \
  int var##no = no;

void setup() {
  GENERAR_VARIABLE(3); // crea int var3 = 3
}

重要警告: 该操作器并不兼容所有 Arduino 板型号。例如,它可能在 Uno 或 Esplora 上运行良好,但在 Mega 上却失败。此外,您不能直接使用 ## 将宏创建嵌套在其他宏中。

宏和内存节省

在 Arduino 中使用宏的一个主要优点是 节省 RAM 内存。 Arduino 的容量有限,因此将文本字符串直接加载到 RAM 中可能会成为一个重大问题。

避免这种情况的先进技术包括使用 强制内联 并从程序存储器(PROGMEM)加载字符串:

#include <HardwareSerial.h>
#define MYSERIAL Serial
#define FORCE_INLINE __attribute__((always_inline)) inline

FORCE_INLINE void printFromFlash(const char *str) {
  char ch = pgm_read_byte(str);
  while (ch) {
    MYSERIAL.write(ch);
    ch = pgm_read_byte(++str);
  }
}

#define SERIAL_LOG(x) (MYSERIAL.print(x))
#define SERIAL_LOGLN(x) (MYSERIAL.println(x))

使用这些宏可能会影响项目的正常运行,特别是在具有显示器或多个传感器的应用程序中。

宏与函数结合

宏还可以根据作为参数传递的类型来动态调用函数。一个清晰且非常生动的例子是:

#define FUNC_LENTA(tipo) \
  { funcion_##tipo##_lenta(); }

#define FUNC_RAPIDA(tipo) \
  { funcion_##tipo##_rapida(); }

void funcion_caminar_lenta() {
  Serial.println("Andando despacio");
}

void funcion_caminar_rapida() {
  Serial.println("Andando rápido");
}

void setup() {
  Serial.begin(9600);
  FUNC_LENTA(caminar);
}

void loop() {
  FUNC_RAPIDA(caminar);
}

借助##运算符和宏,我们可以避免重复结构并集中动态逻辑。.

带有输出参数的宏

也可以使用宏来封装小对象或转换:

#define BOOL_OUT() (bool){false}
#define NUM_OUT(a,b) (float){a+b}
#define STR_OUT(msg) (String){msg}

void loop() {
  Serial.println(BOOL_OUT());
  Serial.println(NUM_OUT(1.2, 3.4));
  Serial.println(STR_OUT("Mensaje"));
}

使用宏的良好做法和预防措施

过度或不小心使用宏可能会导致 难以调试的错误。例如,进行不正确的替换或定义与外部库中的名称冲突的名称。

避免问题的一些基本规则:

  • 避免不必要的空格或换行符 在宏内。
  • 不包括评论 在使用多行的复杂宏中。
  • 使用唯一名称 或使用前缀(例如项目名称)以避免冲突。
  • 用真正的常量或函数替换宏 只要有可能。现代 C++ 允许更清洁、更安全的替代方案。

另一方面,过度使用宏会降低代码的清晰度。目标应该是在不影响可维护性的前提下提高效率和模块化。

条件指令和自适应编译

可扩展项目中最实用的功能之一是使用宏来 条件生成代码,当您想让同一个草图在不同的电路板上工作时,这非常有用。

典型示例:

#ifdef ARDUINO_MEGA
  #define LEDPIN 53
#else
  #define LEDPIN 13
#endif

它还可用于控制调试或显示编译器消息 #pragma 消息 甚至在某些条件下产生错误 #错误.

内部编译器宏

AVR 的 GCC 预处理器(用于 Arduino)包括几个 提供系统信息的特殊宏,在开发过程中非常有用:

  • __线__:当前行号。
  • __文件__:当前文件的名称。
  • __TIME__ 和 __DATE__:编译时间和日期。
  • __func__:当前函数的名称。

它们允许版本控制、日志结构,并且无需侵入主代码即可促进维护和错误跟踪。

宏提供了一种强大而灵活的方式来构建 Arduino 项目。它们允许你定义常量, 节省内存, 调整代码 根据执行环境并创建可重复使用的块而无需重复行。当然,它们需要纪律、清晰度和知识,以避免细微的错误或可读性的丧失。如果正确使用,它们对于中级和高级开发人员来说是一笔无价的财富。


成为第一个发表评论

发表您的评论

您的电子邮件地址将不会被发表。 必填字段标有 *

*

*

  1. 负责数据:MiguelÁngelGatón
  2. 数据用途:控制垃圾邮件,注释管理。
  3. 合法性:您的同意
  4. 数据通讯:除非有法律义务,否则不会将数据传达给第三方。
  5. 数据存储:Occentus Networks(EU)托管的数据库
  6. 权利:您可以随时限制,恢复和删除您的信息。