Arduino的 由于可以轻松创建功能原型和项目,彻底改变了电子世界。然而,对于那些想要进一步提高编程水平、优化资源、保持代码整洁、提高效率的人来说, 宏 成为一个关键工具。
在本文中,我们将深入探讨 Arduino中的宏:它们是什么,如何使用,它们的优点和局限性。我们将通过从网上最好的资源中收集最全面、最有用的信息来实现这一点,并以清晰、现代的方式重写,以真正实用。
Arduino 中的宏是什么?
宏是预处理器指令 在 C/C++ 中,允许您在编译代码之前替换文本。宏并不像传统函数那样执行指令,而是通过替换源文本的部分内容来工作,这 直接影响最终二进制代码的生成方式.
预处理器 它在实际编译之前运行,并负责应用这些替换。在 Arduino 中,这允许 定义常量,有条件地包含文件,甚至创建小 在线功能 节省时间和内存。
基本示例: 像这样的定义 #define LED_PIN 13
导致所有代码被自动替换 LED_PIN
由 13
编译之前。
这看起来可能微不足道,但它提供了一个 编写更灵活、更易于维护的代码的强大方法.
使用宏的优点
在 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 项目。它们允许你定义常量, 节省内存, 调整代码 根据执行环境并创建可重复使用的块而无需重复行。当然,它们需要纪律、清晰度和知识,以避免细微的错误或可读性的丧失。如果正确使用,它们对于中级和高级开发人员来说是一笔无价的财富。