发帖
8 0 0

【宏元编程】——这辈子第一次听见的C语言编程技巧

起个名字好难啊
论坛元老

36

主题

60

回帖

5376

积分

论坛元老

积分
5376
技术杂谈 296 8 2025-8-5 10:14:58

初遇

在给 Ai-WB2 做WS2812驱动库的时候,一直在想:

💡 之前做好了 IR 模拟的驱动,如果加入 SPI 的话,还要改一遍 Demo,有什么没办法 尽可能少干扰 Demo 代码?

  • 方法 1:

    步骤1:在 IR 模拟驱动函数中直接添加 SPI的驱动函数
    步骤2:使用宏来选择编译函数段
    

❌这个方法简单快捷,但是会破坏代码的整体结构,而且如果还有其他实现方法,那是不是要继续添加,所以这个方法不是我想要的简洁。

  • 方法2:

    另外做一个 SPI 库,只需要调用SPI的驱动即可。
    

    ❌ 用户在ws2812 库时,肯定会找茬:一个ws2812 ,你干嘛要做两个驱动?这不是浪费资源吗?。
    确实,IR 和SPI两个驱动只能二选一,并且希望选其中之一时,另外的驱动不参与编译,这样才能节省Flash 消耗。

💡 回忆

以前在搞小安派的时候,在 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

image.png

宏元编程,我这辈子都是第一次听说。 你们呢?

🧑‍⚖️宏元编程实践

宏函数创建

于是乎,我在 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_spiws28x_ir的区别,函数后半部分的名字都是一样的。

宏元函数调用

⚠️ 调用宏元函数时,第一个参数永远是函数名字的后缀,后面才是实际函数的形参。例如:

_WS281X_FUNC_DEFINE(init, ws2812_strip); // init 初始化

总结

利用这个编程技巧,就可以把同一个功能不同的实现方式做到解耦。有新的功能时,只需要新增相关的宏定义即可。

──── 0人觉得很赞 ────

使用道具 举报

2025-8-5 11:19:04

我在这

【已解决】熬了两晚终于点亮了1.3寸7789V的屏幕

用到了,但不知道叫什么名字。又学到了。

2025-8-5 11:30:01
涨知识了
2025-8-5 13:48:56
涨知识了
2025-8-5 19:15:09
_WS281X_FUNC_DEFINE 改为 _WS281X_FUNC_CALL 更合适吧
2025-8-5 19:32:00

涨知识了
2025-8-5 19:39:25
涨知识了
2025-8-6 08:51:55
lazy 发表于 2025-8-5 11:19
我在这
【已解决】熬了两晚终于点亮了1.3寸7789V的屏幕
用到了,但不知道叫什么名字。又学到了。

厉害
2025-8-12 09:41:29
这个方法在XTrack码表开源的消息系统里也有用到,真的很妙
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 29583 个
  • 话题数: 42921 篇