[MM32生态] 分享几种管理C程序中标志位的方法

[复制链接]
 楼主| 盗铃何须掩耳 发表于 2022-8-23 09:55 | 显示全部楼层 |阅读模式
在嵌入式开发中,难免会涉及到非常多的标志位处理,特别是玩单片机、裸机开发的朋友,比如跟一些模块配合联调会遇到各种信号是否到位、成功等等状态,而这些信号大多都是bool类型,1个bit即可进行标识。
当然,如果仅仅是几个标志,直接拿个uint8_t的整形来进行标识也不会影响什么,但如果特别多的话似乎就比较废RAM了。然而,为了更好的管理这些标志位等,有个如下几种方式供大家更好的管理这些标志位 :
 楼主| 盗铃何须掩耳 发表于 2022-8-23 09:58 | 显示全部楼层
位域直接标识
采用位域是管理这些标志位比较直接且方便的方式,代码如下所示:
  1. 1typedef union _tag_SystemFlag
  2. 2{
  3. 3    uint16_t all;
  4. 4    struct
  5. 5    {
  6. 6        uint16_t Run         :1;
  7. 7        uint16_t Alarm       :1;
  8. 8        uint16_t Online      :1;
  9. 9        uint16_t TimerOver   :1;
  10. 10        uint16_t Reserver    :12;
  11. 11    }bit;
  12. 12
  13. 13} uSystemFlag;
  14. 14
  15. 15uSystemFlag  unSystemFlag;
  16. 16
  17. 17int main(int argc, char *argv[]) {
  18. 18
  19. 19    unSystemFlag.all = 0x00; //系统标志清除
  20. 20
  21. 21    unSystemFlag.bit.Run       = 1; //置位
  22. 22    unSystemFlag.bit.Alarm     = 1;
  23. 23    unSystemFlag.bit.Online    = 1;
  24. 24    unSystemFlag.bit.TimerOver = 1;
  25. 25
  26. 26    unSystemFlag.bit.Run       = 0; //清零
  27. 27    unSystemFlag.bit.Alarm     = 0;
  28. 28    unSystemFlag.bit.Online    = 0;
  29. 29    unSystemFlag.bit.TimerOver = 0;
  30. 30
  31. 31    return 0;
  32. 32}

这些标志位的操作无非就是置位、清零,以及读取三种方式。

但如代码中这样的操作方式在语句或语义表达上还是不够直观。

我经常谈到,代码可以不写注释,不过你的每个变量、函数名称等需要足够的直观,所以很多朋友习惯把这些标志封装起来。
 楼主| 盗铃何须掩耳 发表于 2022-8-23 10:21 | 显示全部楼层
枚举+移位

为了更好的表达一般会对标志位进行进一步的封装,如下代码所示:
  1. 1typedef enum _tag_Flag {
  2. 2cEmRun = 0,
  3. 3cEmAlarm,
  4. 4cEmOnline,
  5. 5cEmTimerOver
  6. 6}emSystemFlag;
  7. 7
  8. 8uint16_t SystemFlag ;
  9. 9//置位
  10. 10void SetFlag(emSystemFlag flag)
  11. 11{
  12. 12    SystemFlag |=  ((uint16_t)0x01) << flag;
  13. 13}
  14. 14//清除
  15. 15void ClrFlag(emSystemFlag flag)
  16. 16{
  17. 17    SystemFlag &=  ~(((uint16_t)0x01) << flag);
  18. 18}
  19. 19//获得状态
  20. 20uint8_t  GetFlag(emSystemFlag flag)
  21. 21{
  22. 22    return (((SystemFlag & (((uint16_t)0x01) << flag)) != 0)? true:false);  
  23. 23}
  24. 24
  25. 25int main(int argc, char *argv[]) {
  26. 26
  27. 27    SetFlag(cEmAlarm);
  28. 28
  29. 29    if(GetFlag(cEmAlarm) == true)
  30. 30    {
  31. 31        printf("ClrFlag\r\n");
  32. 32        ClrFlag(cEmAlarm);
  33. 33    }
  34. 34    else
  35. 35    {
  36. 36        printf("SetFlag\r\n");
  37. 37        SetFlag(cEmAlarm);
  38. 38    }
  39. 39    return 0;
  40. 40}

当然,封装成函数是相对比较耗时的,不过代码也会更加的易懂,如果确实容忍不了函数封装带来的时间消耗,把函数修改为宏代码片段或者内敛函数(前提是编译器支持)也是可行的。
 楼主| 盗铃何须掩耳 发表于 2022-8-23 10:22 | 显示全部楼层
宏列表

或许这里才是本文的重中之重~

以前跟大家介绍过,用宏自动化的生成各种代码片段,以使得代码更加的紧凑。当然可读性会相对降低一点,但对于重复性代码就不需要太多考虑了。
  1. 1#include <stdio.h>
  2. 2#include <stdlib.h>
  3. 3
  4. 4typedef unsigned char uint8_t;
  5. 5typedef unsigned int uint16_t;
  6. 6typedef signed char int8_t;
  7. 7typedef int  int16_t;
  8. 8
  9. 9#define true  1
  10. 10#define false 0
  11. 11
  12. 12
  13. 13//宏列表
  14. 14#define TAG_LIST(tag) \
  15. 15tag(Run)\
  16. 16tag(Alarm)\
  17. 17tag(Online)\
  18. 18tag(TimerOver)
  19. 19
  20. 20
  21. 21//枚举处理
  22. 22#define DEFINE_TAG(_tag) _tag,
  23. 23enum Flag {
  24. 24None = 0,
  25. 25TAG_LIST(DEFINE_TAG)
  26. 26EmMAX
  27. 27};
  28. 28#undef DEFINE_TAG
  29. 29
  30. 30//位定义变量
  31. 31uint16_t SysFlag = 0x0000;
  32. 32
  33. 33
  34. 34//通用方法定义
  35. 35uint8_t GetFlags(uint16_t mask)
  36. 36{
  37. 37    return ((SysFlag & mask) != 0)? true:false;
  38. 38}
  39. 39
  40. 40void SetFlags(uint16_t mask)
  41. 41{
  42. 42     SysFlag |=  mask;
  43. 43}
  44. 44
  45. 45void ClrFlags(uint16_t mask)
  46. 46{
  47. 47     SysFlag &=  ~mask;
  48. 48}
  49. 49
  50. 50
  51. 51//自动生成三类函数定义
  52. 52#define FLAG_Operater(flag) \
  53. 53uint8_t  get##flag()  {\
  54. 54return GetFlags(1 << flag);\
  55. 55}\
  56. 56void set##flag() {\
  57. 57SetFlags(1 << flag);\
  58. 58}\
  59. 59void clr##flag() {\
  60. 60ClrFlags(1 << flag);\
  61. 61}
  62. 62
  63. 63//反向函数关联
  64. 64TAG_LIST(FLAG_Operater)
  65. 65
  66. 66int main(int argc, char *argv[]) {
  67. 67
  68. 68    setRun();
  69. 69    setAlarm();
  70. 70
  71. 71    if(getAlarm() == true)
  72. 72    {
  73. 73        printf("set \r\n");
  74. 74    }
  75. 75    else
  76. 76    {
  77. 77        printf("clr \r\n");
  78. 78    }
  79. 79
  80. 80    return 0;
  81. 81}

如果以前有过类似代码处理的朋友,应该看这段代码还是比较轻松的吧,如果有点生疏,可以一层一层展开了解。

其主要的功能是,通过宏替换和代码拼接符号,自动的生成通用的代码片段。这样做的好处是,不再需要我们在代码中定义一大堆setflag、clrflag、getflag等函数。

通过上面的代码当我们向TAGLIST宏中添加一个标识符,即可生成一系列相关的操作函数等。

这样一方面可以及简化代码,同时也能避免一些人工编码带来的错误。
海滨消消 发表于 2022-8-29 15:31 | 显示全部楼层
为啥你发的代码底色要设置成黑色
tpgf 发表于 2022-9-5 15:50 | 显示全部楼层
位域直接标识算是宏定义的变通吗
qcliu 发表于 2022-9-5 16:10 | 显示全部楼层
咱们最常用的就是宏了
drer 发表于 2022-9-5 16:20 | 显示全部楼层
感觉枚举和移位比较的慢
coshi 发表于 2022-9-5 16:30 | 显示全部楼层
不推荐第二种方式 比较耗时
kxsi 发表于 2022-9-5 16:35 | 显示全部楼层
我比较喜欢自己进行标识
wiba 发表于 2022-9-5 16:44 | 显示全部楼层
感觉位域的省代码 而且还省时间
littlelida 发表于 2022-9-8 21:22 | 显示全部楼层
优缺点怎么样?
这总结到是长见识了
Bowclad 发表于 2022-9-9 18:43 | 显示全部楼层
第二种有点耗时
Henryko 发表于 2022-9-14 21:48 | 显示全部楼层
第一种比较推荐
Stahan 发表于 2022-9-16 20:32 | 显示全部楼层
自己进行标识感觉挺好的
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

385

帖子

0

粉丝
快速回复 返回顶部 返回列表