初遇
在给 Ai-WB2 做WS2812驱动库的时候,一直在想:
💡 之前做好了 IR 模拟的驱动,如果加入 SPI 的话,还要改一遍 Demo,有什么没办法 尽可能少干扰 Demo 代码?
❌这个方法简单快捷,但是会破坏代码的整体结构,而且如果还有其他实现方法,那是不是要继续添加,所以这个方法不是我想要的简洁。
💡 回忆
以前在搞小安派的时候,在 bouffalo_sdk
中见过一种方式,在 bsp\common\lcd
中,lcd.h
做了很多 宏定义
判断,例如:
#if defined LCD_DBI_GC9307
#include "mipi_dbi/gc9307_dbi.h"
#define LCD_INTERFACE_TYPE LCD_INTERFACE_DBI
#define LCD_W GC9307_DBI_W
#define LCD_H GC9307_DBI_H
#define LCD_COLOR_DEPTH GC9307_DBI_COLOR_DEPTH
#define _LCD_FUNC_DEFINE(_func, ...) gc9307_dbi_##_func(__VA_ARGS__)
#elif defined LCD_DBI_ILI9488
#include "mipi_dbi/ili9488_dbi.h"
#define LCD_INTERFACE_TYPE LCD_INTERFACE_DBI
#define LCD_W ILI9488_DBI_W
#define LCD_H ILI9488_DBI_H
#define LCD_COLOR_DEPTH ILI9488_DBI_COLOR_DEPTH
#define _LCD_FUNC_DEFINE(_func, ...) ili9488_dbi_##_func(__VA_ARGS__)
❓这些宏定义实现了什么?先不 一 一 深究,其中 _LCD_FUNC_DEFINE(_func, ...) ili9488_dbi_##_func(__VA_ARGS__)
这个宏值得一看。
去看看 对应的 lcd.c
文件中,这个宏是怎么调用的。
_LCD_FUNC_DEFINE(init);//初始化
_LCD_FUNC_DEFINE(async_callback_enable, enable);//启动
‼️一个宏,竟然能实现这么多函数功能?而且完全实现了代码解耦,这不就是我想要的?我很好奇这种编程技巧叫啥,于是:交给AI

宏元编程,我这辈子都是第一次听说。 你们呢?
🧑⚖️宏元编程实践
宏函数创建
于是乎,我在 WS2812 库的 ws2812.c
(为了方便外部宏选择性编译) 文件中,创建了宏:
#if defined WS281X_IR_MODE
#pragma message "ws2812 using IR mode"
#include "ws281x_ir.h"
#define _WS281X_FUNC_DEFINE(_func, ...) ws281x_ir_##_func(__VA_ARGS__)
#elif defined WS281X_SPI_MODE
#pragma message "ws2812 using SPI mode"
#include "ws281x_spi.h"
#define _WS281X_FUNC_DEFINE(_func, ...) ws281x_spi_##_func(__VA_ARGS__)
#else
#error "Please select a ws281x mode,CONFIG_WS2812_MODE=SPI_MODE or IR_MODE"
#endif
‼️这些宏在没有定义WS281X
的驱动模式时,不会编译通过,并且二选一编译,其余的驱动文件不会被编译到固件中。
驱动函数编写
⚠️ 宏元编程对驱动函数的命名有限制,为了达到不用驱动使用同一个宏函数实现功能的目前,函数名的后半部分必须保持一致,例如:
/********** SPI 驱动*************/
void ws281x_spi_init(ws2812_strip_t *ws2812_strip);
void ws281x_spi_set_pixel_color(uint8_t index, uint8_t r, uint8_t g, uint8_t b);
void ws281x_spi_show_leds(void);
/************ IR 驱动 ******************/
void ws281x_ir_init(ws2812_strip_t *ws2812_strip);
void ws281x_ir_set_pixel_color(uint8_t index, uint8_t r, uint8_t g, uint8_t b);
void ws281x_ir_show_leds(void);
从上面的代码可以看出,除了驱动不一样之外ws281x_spi
和ws28x_ir
的区别,函数后半部分的名字都是一样的。
宏元函数调用
⚠️ 调用宏元函数时,第一个参数永远是函数名字的后缀,后面才是实际函数的形参。例如:
_WS281X_FUNC_DEFINE(init, ws2812_strip); // init 初始化
总结
利用这个编程技巧,就可以把同一个功能不同的实现方式做到解耦。有新的功能时,只需要新增相关的宏定义即可。