嵌入式项目中打造自己的utils库-ENUM转字符串

前言

不知道大家在调试的时候,是否经常需要将一些类型变量的名称打印出来呢。这里提供了一个统一的方法,在使用时只需要很少的代码量,就可以把宏定义、枚举的变量名称打印出来了。
下面先上代码。

实现

utils.h

typedef struct {
    int32_t val;
    char* name;
} Enumerate;

char* enumerate_to_string(Enumerate* enumerate, uint8_t total, int32_t val);

#define ENUMERATE_DEF(object) static Enumerate _ENUM_##object[]
#define ENUMERATE_ITEM(val) {val, #val}

#define ENUM_TO_STRING(object, val) enumerate_to_string(_ENUM_##object, ARRAY_LENGTH(_ENUM_##object), val)

#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))

这次的代码因为要遍历数组,已经不能用简单的宏定义来完全实现了。
utils.c

char* enumerate_to_string(Enumerate* enumerate, uint8_t total, int32_t val)
{
    for(uint8_t i=0; i<total; i++) {
        if(enumerate[i].val == val) {
            return enumerate[i].name;
        }
    }
    return "EnumUnknow";
}

老规矩,示例:

enum {
    UNDEFINED = 0,
    OBJECT,
    ARRAY,
    STRING,
    NUMBER,
    FLOAT,
    BOOL,
};

ENUMERATE_DEF(JsonTypeStr) = {
    ENUMERATE_ITEM(UNDEFINED),
    ENUMERATE_ITEM(OBJECT),
    ENUMERATE_ITEM(ARRAY),
    ENUMERATE_ITEM(STRING),
    ENUMERATE_ITEM(NUMBER),
    ENUMERATE_ITEM(FLOAT),
    ENUMERATE_ITEM(BOOL),
};

uint8_t json_type = ARRAY;
printf("json_type:%s(%d)\n", ENUM_TO_STRING(JsonTypeStr, json_type), json_type);

结果:

json_type:ARRAY(2)

代码分析

这里的思路,是重新定义一个结构体数组,在这个结构体里保存枚举值,和值的名字。在使用时,遍历整个数组,找到对应值的名字。
同时为了减少手写代码的数量,利用宏定义的特性,将值的文本名称直接转成了字符串,就不用手动再去写一遍名称的字符串了。
使用上保留了原来c语言中定义枚举的语法,用 xxx={a1,a2,a3}; 的方式,使代码阅读更符合习惯。
在计算数组长度方面,c语言不比其他语言,java的array.length(),python的len(list),都可以快速准确得到数组长度。c语言中则需要这样来计算:

size_t array_len = sizeof(array) / sizeof(item);

用数组占的内存大小,除以每个项占的内存大小,就能得到一共有多少个项,就是数组长度了。在传递的参数都是数组的前提下,使用第一个元素可以简化代码:

size_t array_len = sizeof(array) / sizeof(array[0]);

这种用法有一定的隐患,没有对传入的参数做检查,所以也是没有大面积推广使用的原因吧。
同时要注意的,sizeof是一个操作符,和=,+这些是一样的,并不是函数,如果要计算一个数组的大小,需要传入正确的名字,通过传入指向数组的指针是不行的。

小知识:

在声明时,宏定义展开后增加了特定的前缀,这样可以有效避免和其他变量命名重复,使本变量更接近业务,更方便输入。

应用

就拿蓝牙断开原因来举例吧,运行在ESP32平台。
蓝牙断开原因在 idf\components\bt\host\bluedroid\api\include\api\esp_gatt_defs.h 中定义了:

/**
 * @brief Gatt Connection reason enum
 */
typedef enum {
    ESP_GATT_CONN_UNKNOWN = 0,                      /*!< Gatt connection unknown */               /* relate to BTA_GATT_CONN_UNKNOWN in bta/bta_gatt_api.h */
    ESP_GATT_CONN_L2C_FAILURE = 1,                  /*!< General L2cap failure  */                /* relate to BTA_GATT_CONN_L2C_FAILURE in bta/bta_gatt_api.h */
    ESP_GATT_CONN_TIMEOUT = 0x08,                   /*!< Connection timeout  */                   /* relate to BTA_GATT_CONN_TIMEOUT in bta/bta_gatt_api.h */
    ESP_GATT_CONN_TERMINATE_PEER_USER = 0x13,       /*!< Connection terminate by peer user  */    /* relate to BTA_GATT_CONN_TERMINATE_PEER_USER in bta/bta_gatt_api.h */
    ESP_GATT_CONN_TERMINATE_LOCAL_HOST = 0x16,      /*!< Connection terminated by local host */    /* relate to BTA_GATT_CONN_TERMINATE_LOCAL_HOST in bta/bta_gatt_api.h */
    ESP_GATT_CONN_FAIL_ESTABLISH = 0x3e,            /*!< Connection fail to establish  */         /* relate to BTA_GATT_CONN_FAIL_ESTABLISH in bta/bta_gatt_api.h */
    ESP_GATT_CONN_LMP_TIMEOUT = 0x22,               /*!< Connection fail for LMP response tout */ /* relate to BTA_GATT_CONN_LMP_TIMEOUT in bta/bta_gatt_api.h */
    ESP_GATT_CONN_CONN_CANCEL = 0x0100,             /*!< L2CAP connection cancelled  */           /* relate to BTA_GATT_CONN_CONN_CANCEL in bta/bta_gatt_api.h */
    ESP_GATT_CONN_NONE = 0x0101                     /*!< No connection to cancel  */              /* relate to BTA_GATT_CONN_NONE in bta/bta_gatt_api.h */
} esp_gatt_conn_reason_t;

我们来为它添加字符串:(上面定义原样复制出来,再做少许替换删减即可)

ENUMERATE_DEF(BleConnReasonStr) = {
    ENUMERATE_ITEM(ESP_GATT_CONN_UNKNOWN),
    ENUMERATE_ITEM(ESP_GATT_CONN_L2C_FAILURE),
    ENUMERATE_ITEM(ESP_GATT_CONN_TIMEOUT),
    ENUMERATE_ITEM(ESP_GATT_CONN_TERMINATE_PEER_USER),
    ENUMERATE_ITEM(ESP_GATT_CONN_TERMINATE_LOCAL_HOST),
    ENUMERATE_ITEM(ESP_GATT_CONN_FAIL_ESTABLISH),
    ENUMERATE_ITEM(ESP_GATT_CONN_LMP_TIMEOUT),
    ENUMERATE_ITEM(ESP_GATT_CONN_CONN_CANCEL),
    ENUMERATE_ITEM(ESP_GATT_CONN_NONE),
};

在工程中应用:

// 蓝牙事件回调函数
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
    esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;

    switch (event) {
    case ESP_GATTC_DISCONNECT_EVT: //断连事件
        //打印事件原因
        ESP_LOGI(TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %s(%x)", ENUM_TO_STRING(BleConnReasonStr, p_data->disconnect.reason), p_data->disconnect.reason);
        break;
    default:
        break;
    }
}

实际打印会长这样:

I (1126378) BLE: ESP_GATTC_DISCONNECT_EVT, reason = ESP_GATT_CONN_TIMEOUT(8)

是不是很方便,这样看日志就一目了然,不用对着错误码再去对照定义。使用时也不需要额外再写数字转字符串的函数,在很多地方添加就能使用。

End

这篇文章还喜欢吗,我们下次见。

18 声望
3 粉丝
0 条评论
推荐阅读
float类型中NaN和Inf是什么
做嵌入式的同学应该知道,c语言中int的大小,是和平台有关的,有的占4字节,有的占2字节。所以我们对有期望长度的变量,很少直接用int定义,而更多用uint8_t, uint16_t, uint32_t。但到了float,我们为什么没有fl...

银翼Neal阅读 639

Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节6阅读 1.4k

封面图
C 程序眼中的 Unicode
去年写了一篇文章「在 C 程序中处理 UTF-8 字符串」,介绍了如何使用 GLib 提供的 UTF-8 字符串处理函数来实现基本的 UTF-8 文本处理。不过,GLib 是一个功能比较全面的 C 程序库,C 字符串处理仅仅是它的一个很...

garfileo3阅读 5.7k评论 5

手写一个同步服务端时间的小工具
在前端开发的过程中,开发者经常会用到 new Date() 来获取当前时间,但是 new Date() 是获取的当前操作系统的时间,由于用户可以修改当前电脑时间,所以它是不准确的。

jump__jump2阅读 876

计算机如何表示整数
在计算机中,任何的数据都是用二进制: 0 和 1 来表示。整数也不例外。生活中的 10,在 8 个字节的整数中表示为 00001010。但是这样子只能表示正数和零。怎么表示负数呢?于是有了符号位的概念。在 8 个字节的整...

kang2阅读 3k评论 7

麒麟操作系统 (kylinos) 从入门到精通 - 研发环境 - 第二十一篇 C++/C语言开发环境搭建
类别:笔记本型号:中国长城 NF14C硬件平台:飞腾处理器(ArmV8 指令集)系统:银河麒麟操作系统 V10 SP1(2203) 关键词:信创,麒麟系统,linux,c++,c,内核飞腾,arm

码上世界1阅读 2.4k评论 1

封面图
浙大版《C语言程序设计》第四版(何钦铭颜晖) 第5章 函数 课后习题答案
你也可以上程序咖([链接]),打开大学幕题板块,不但有答案,讲解,还可以在线答题。一、选择题1.在 C 语言程序中,若对函数类型未加显式说明,则函数的隐含类型为( )。A. voidB. double C. charD. int答:D解析...

茹茹1阅读 1k

18 声望
3 粉丝
宣传栏