你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

结构体、联合体结合使用在C语言、嵌入式中的一些实用技巧

[复制链接]
gaosmile 发布时间:2020-12-22 20:51

结构体、联合体是C语言中的构造类型,结构体我们平时应该都用得很多。但是,对于联合体,一些初学的朋友可能用得并不多,甚至感到陌生。我们先简单看一下联合体:
/ j4 f, {# B0 k% I$ R8 v

在C语言中定义联合体的关键字是union。

定义一个联合类型的一般形式为:

union 联合名+ P2 D: d% [$ M* A' ]7 [
{2 Y& j7 M, q  p7 \
成员表
+ X0 J% T% `% _( l  j- `};% s1 b7 C( d$ s$ m

成员表中含有若干成员,成员的一般形式为:类型说明符 成员名。其占用的字节数与成员中最大数据类型占用的字节数。

下面我们一起看一下结构体、联合体结合使用在C语言、嵌入式中的一些实用技巧。

1、应用于管理不同的数据

示例代码:

enum DATA_PKG_TYPE5 Q8 z+ z; Z4 ~* X7 y4 l7 I: c
{1 T( g# [0 R# x4 z2 i
    DATA_PKG1 = 1,& z. {& Y* e" i; V0 i; U# A
    DATA_PKG2,! p7 H* \, P( V# D2 A
    DATA_PKG3   
9 j$ [  k+ M" ~7 _8 }6 b};
; M9 _& v& Z. ?/ F# l# A1 r% E; N& P
2 F: I8 d% \8 c  t% {struct data_pkg1
5 A( h8 |  W! E! }+ A{
3 Q- I% u* h6 m, L+ Z5 j$ v8 v3 u* s    // ...# E; J- c4 `6 u5 H+ y+ ]
};
4 Z( G( p4 t1 N( f$ N2 S5 Z/ C  b, w
struct data_pkg2
! [" h& f+ v1 T{
5 x) {0 I. T( e+ |% X5 ~. c    // ...
  k. W1 I8 m5 L$ m2 M8 L* E/ G4 a};8 N8 v/ q1 C1 S/ w' I

2 s5 Z% E  G5 U1 ustruct data_pkg3
; a/ E- }" \0 y! w  {{
- F. s% E% [) ^2 l    // ...; t; r4 W+ T" G* O9 \& V
};4 C0 e9 j. A8 d" |% q" U; G  k9 n
* b1 G. c* ?9 E# V8 ]; C
struct data_pkg# y# \+ ]9 C( n1 |
{" ~& ^% W8 i/ D  k) k( }. p* J, m* T
    enum DATA_PKG_TYPE data_pkg_type;
% I2 K) c' L- N; Q& V: S    union
8 X- l8 V. b' Q$ z( s/ i! Q    {" u5 V1 D9 v/ a
       struct data_pkg1 data_pkg1_info;
1 _& E! p' L9 I3 p: ~       struct data_pkg2 data_pkg2_info;
# _( i  O5 `) v% l2 @2 r$ ?       struct data_pkg3 data_pkg3_info;0 y/ ?3 q0 E: N/ d, `) v- e
    }data_pkg_info;
$ _. _+ w" T' U1 r; E};  p* a  _2 p4 O: y/ W

这里把struct data_pkg1、struct data_pkg2、struct data_pkg3三个结构体放到了struct data_pkg这个结构体里进行管理,把data_pkg_type与union里的三个结构体建立一一对应关系,我们需要用哪一结构体数据就通过data_pkg_type来进行选中。

在进行数据组包的时候,先给data_pkg_type进行赋值,确定数据包的类型,再给对应的union里的结构体进行赋值;在进行数据解析的时候,通过data_pkg_type来选择解析哪一组数据。

思考一下,如果在union里面再嵌套一层union会怎么样?会变得更复杂?以前的话,我会觉得越嵌套会越复杂,我也很抵制这种不断嵌套的做法。但后来看了我同事鱼鹰(公众号:鱼鹰谈单片机)的设计之后,我惊呆了!这可太秀了,他就是这么嵌套使用把原本复杂的系统数据管理得明明白白的。我们看他怎么设计的(看个大概的图):

微信图片_20201222204958.png


) s6 Q' p0 d. S! X7 y

可以看到最左边和最右边这就建立起了一一对应关系,我们的模块很多,数据很多,但是在这样的设计中显得很清晰、很容易维护。

2、寄存器、状态变量封装

我们看一看TI的寄存器封装是怎么做的:

微信图片_20201222205004.jpg


2 U* Z& d% q4 F8 y& R4 F( H

所有的寄存器被封装成联合体类型的,联合体里边的成员是一个32bit的整数及一个结构体,该结构体以位域的形式体现。这样就可以达到直接操控寄存器的某些位了。比如,我们要设置PA0引脚的GPAQSEL1寄存器的[1:0]两位都为1,则我们只操控两个bit就可以很方便的这么设置:

GpioCtrlRegs.GPAQSEL1.bit.GPIO0 = 3
2 c2 P- U6 I7 V5 S* x; A; \

或者直接操控整个寄存器:

GpioCtrlRegs.GPAQSEL1.all |=0x03
9 k- Y, h; h: x& J

位域相关文章:【C语言笔记】位域

如果不是工作于芯片原厂,寄存器的封装应该离我们很远。但我们可以学习使用这种方法,然后用于我们的实际应用开发中。

下面就看一种实际应用:管理一些状态变量。

示例代码:

union sys_status) p6 q7 Q' V! d( E
{
; x# ]  w- D+ I1 i   uint32 all_status;
+ [1 G" A2 c/ Y3 N# ^   struct
( a2 Z4 o3 f3 c# q/ P1 ~9 s   {
0 l2 T  n" n! m( K      bool status1:  1; // FALSE / TRUE: }$ w2 a' u2 Q! e1 ?- N
      bool status2:  1; // $ k2 W2 |& H/ I
      bool status3:  1; //
9 M; O! f6 m% o! ~0 M, j1 S      bool status4:  1; //
) d: B+ N% K  h8 |* ?8 J" x% u      bool status5:  1; //
; ~& K+ Z1 ~; T" C" m3 x      bool status6:  1; //
* x  q( {8 W3 S; P7 z+ J2 w      bool status7:  1; //
, r6 e7 j: z8 L      bool status8:  1; //
5 [+ _( Y  `- E) g; E& [      bool status9:  1; //
( A% j5 l2 H' U      bool status10: 1; //
: [6 X5 z" C8 ~- F" P; s   // ..." g4 C# C8 n9 j
  }bit;; I8 R& Q, B% d
};
" x2 G: x$ n1 x6 g) {8 b7 ]3 M; r

之前记得群里有一位小伙伴问系统有几十个状态变量需要管理,怎么做比较好。如上例子就是比较好的一种管理方法。

3、数据组合/拆分、大小端(1)验证大小端#include <stdio.h>
% e" ~* J% V2 R7 d+ V
9 ^. E% T- y( ntypedef unsigned int  uint32_t;! f- e0 g7 ]) a8 @' a
typedef unsigned char uint8_t;
9 k* i. K  [2 |0 X3 Y
$ S$ {2 j8 V7 lunion bit32_data) p: |" b+ o$ C
{
5 A# ]$ W1 u# d0 k  T) ]; H    uint32_t data;2 ^6 }9 C7 u7 U8 |# T) m1 X! ~
    struct ) I% t3 T8 d9 r" e* e3 _+ r
    {- y  J/ m& Z& j' M# e, X& F0 T
        uint8_t byte0;( v! k9 c: t0 r! O$ G( @8 b0 p
        uint8_t byte1;
* \9 B* S9 W) z+ x, j        uint8_t byte2;
6 b5 \+ h; x( v, K6 t  X9 e        uint8_t byte3;1 f  o( w9 q. z2 o% ?' |
    }byte;
2 j* X8 T" j& v# E  Z) U5 w( a};
( E! I( k$ n& h+ ]( [) ]; D4 t8 }
9 ~2 l# S3 u# @3 J4 Uint main(void)
1 B  ~6 {% c6 E0 [$ P( F: P{
' g) \; t' a: C# y    union bit32_data num;+ s5 m- L4 l7 d5 U
    ; h4 ^+ m: d7 I5 G# {$ e
    num.data = 0x12345678;! q5 q% }5 t5 r; q/ A. s$ C
9 \) J  \4 ]$ ~8 Z
    if (0x78 == num.byte.byte0)
8 C& t6 e* u1 W+ P    {
& r- e8 o3 y6 d$ r  k' T* \% H      printf("Little endian\n");
8 ^& n1 `" E/ V6 q. P: p    }1 [* F3 F/ V/ R! [  u( Q4 k
    else if (0x78 == num.byte.byte3)+ W) V, C" }7 v9 Q5 ]. B
    {+ z5 |2 L; V# [1 c  Y( q) M0 ^) F
      printf("Big endian\n");# V# y' I6 h. ^3 `8 ^
    }else{}6 X5 M6 w  ?( j! x; E' K

# S, q: U5 Q9 n5 O! [4 ~& Z4 ]" g1 T) ]    return 0;5 l- k& c7 m, {- v) S# v
}
# \/ D+ q8 }9 U0 Q# j  o
运行结果:
微信图片_20201222205007.png
(2)数据组合、拆分

这其实也就是上一篇文章《面试题 | 获取整数各个字节》介绍的。在数据组合与拆分之前首先需要确实当前平台的大小端。比如小编使用的平台是小端模式。

① 把0x12345678拆分成0x78、0x56、0x34、0x12:#include <stdio.h>
% A1 v6 h, j) G3 z: Q* Z2 m# I; I! O" l* a
typedef unsigned int  uint32_t;( D& J  f: J. R2 S2 `% A
typedef unsigned char uint8_t;9 M3 R' C3 B1 k, i# W

9 Y2 X( k, r# T" P3 S8 sunion bit32_data: i2 E  T5 Q+ s+ j; k6 |# ^$ R
{7 q$ O) M3 F) y" }
    uint32_t data;
" C. d- E4 C9 [/ |% x6 j7 e    struct & Z2 |; J3 `- e5 {4 `" t( a
    {' ^: b7 ~0 O4 H
        uint8_t byte0;& B+ l% F. p' z- c3 o: o- i1 m9 U+ {
        uint8_t byte1;
, C8 a7 a% E' C5 A        uint8_t byte2;
4 l6 Z' ^9 x: w/ @9 m* L8 Z        uint8_t byte3;- |8 x6 }# u6 e' Q; C0 q& {
    }byte;8 s$ H) u( B# ~% g( v: r0 ?
};3 o6 I9 ?9 O; H1 Y* o

0 ?& i1 d8 q: ^9 gint main(void)7 i% w* I- a) K' V7 p+ t4 {
{: U( ]: R. t9 ~5 L7 w
    union bit32_data num;3 d1 @7 U, n5 \# M5 Q$ d; ^- M
    ' h/ s8 ?$ H& g2 b) A" ^) U$ U. i# `
    num.data = 0x12345678;0 F5 n/ f# l' u4 U7 b; D* D
0 U/ o0 }1 Z, Z, k" P2 K% d
    printf("byte0 = 0x%x\n", num.byte.byte0);
/ d2 [% u$ s$ @' Q0 p: r% |& s+ i. o. w    printf("byte1 = 0x%x\n", num.byte.byte1);
: J- t; q  t" l    printf("byte2 = 0x%x\n", num.byte.byte2);
6 i% |# C# n" G+ N    printf("byte3 = 0x%x\n", num.byte.byte3);
" _0 T6 Q& h* A, ~; K" K. U5 L' x8 m, Q4 T& l5 r3 d* j
    return 0;3 M4 g# ?/ l5 U7 k9 N2 L
}
1 o+ A2 u: ~+ J+ W8 u# e
运行结果:
微信图片_20201222205010.png
② 把0x78、0x56、0x34、0x12组合成0x12345678:#include <stdio.h>) n; g9 ?/ z; u! z$ N6 g+ @! p. Q

, p/ ^& J( }1 p( p! ntypedef unsigned int  uint32_t;" C) m+ {; ~: _+ H& F$ ]% q
typedef unsigned char uint8_t;
7 b6 O$ Z/ S" R* l4 z0 G" {% n' ~/ I# u) d. s
union bit32_data
4 q) o" V5 L; O2 T{
$ F3 u7 W" u# p# W" ~/ c% Q    uint32_t data;
9 q: L$ X% ^; i3 @3 j' ?9 E# B    struct
0 j& u% T% N) Z" q4 N9 S    {
1 O# s0 r% b# g( j8 e        uint8_t byte0;
7 ?( N1 q* L$ c6 E        uint8_t byte1;
6 T+ U8 k. W& ^% U6 T        uint8_t byte2;8 B4 t. p* }/ D; x
        uint8_t byte3;" d" E+ ?  T$ F4 s9 O
    }byte;: }+ f+ X5 w, b- T6 P, Z8 t1 [
};
$ Q4 }+ p/ U* f$ u& `+ u: v9 Z. j
1 ?* C4 _- e$ c& r% Vint main(void)
( \# |% F0 X3 c+ |: Y' ?, j{4 A+ t- k* z6 |( y! D7 O) a
    union bit32_data num;
- S. ]2 g$ n6 N6 _6 {; e9 F   
; ]0 K8 K" v8 O2 s2 r; B1 s) S' P    num.byte.byte0 = 0x78;2 e. K3 z1 e5 C4 K# h
num.byte.byte1 = 0x56;
* B) l% c6 w8 H5 i num.byte.byte2 = 0x34;
  X% ^; a5 {4 @. D8 [+ I* e num.byte.byte3 = 0x12;
+ \2 R( a1 B" V$ V5 L  N, z# M) `# z( W; }
    printf("num.data = 0x%x\n", num.data);
! M4 G1 m$ o0 h0 \: l
/ H2 m7 @/ h& I3 L% Z/ S3 z. _    return 0;
- m. ?$ }. K/ a- t}3 ^2 D' B! t3 w0 y9 I5 H4 `
运行结果:
微信图片_20201222205013.png

但是数据组合与拆分有更好的方法:移位操作。篇幅有限不再贴出代码,详细代码可参考:《面试题 | 获取整数各个字节》、《C语言、嵌入式位操作精华技巧大汇总》两篇文章。

4、结构体 & 缓冲区#define BUF_SIZE 16
+ R  v5 V" W: ?# e1 D  junion protocol_data1 m; k0 j! y0 d: p+ `; \8 j; @5 _
{
0 g: o" {( m! c- P2 W1 S uint8_t data_buffer[BUF_SIZE];
$ W  h3 @6 {2 V# {& o struct 5 r6 F* S+ E! N  B: T
{0 F. ]$ X4 ]0 V( Y8 c7 T( a1 w
  uint8_t data1;3 r, v, k, A; t: e% r
  uint8_t data2;5 P; ^& _- ^' \
  uint8_t data3;. Y) c+ C2 P# u2 Z
  uint8_t data4;; R1 M. I& O1 I# m4 u1 R
  // ...
8 o4 L0 k. p+ w% U. f3 n1 C }data_info;
* {& K* n6 z1 M' j3 `7 b3 A};
$ V% m/ N' _( P; x! T3 G

这种应用得很广泛,用于自定义通信协议。struct里面的内容可以设计得很简单,比如全是有用的数据,或是设计得很复杂,包含一些协议头尾、包长、有效数据、校验等内容。

但无论如何,我们组包发送的过程是填充结构体->发送data_buffer;反之接收数据解析的过程就是接收数据存于data_buffer->使用结构体数据。我们之前分享的《干货 | protobuf-c之嵌入式平台使用》也是这个思路。

5、传输浮点数据union f_data
7 w& d" \4 l( j* p: d* r( A{7 q$ v# G- r7 ~1 k5 n- @4 c' T. x+ ^% p
float f;
6 }6 n# g: I# p: J0 o& @1 F$ [# }6 J struct
! K3 r1 s1 G( q& ]9 u- |9 G9 t {
2 M/ K+ K7 H6 u9 Q  unsigned char byte[4];4 j, f: c+ Q# _! {- X
};- E& c3 {( q6 i
}& }7 ^( R  k  X

类似的,使用这样子的方法可以用于传输浮点数,更具体地不再展开,网络上有很多这一块的资料。感兴趣的朋友可以自己操作验证验证。

0 p3 H" G4 Z1 a+ c) P7 D
赞 收藏 评论0 发布时间:2020-12-22 20:51

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版