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

【经验分享】STM32开发中常用的C语言知识

[复制链接]
STMCU小助手 发布时间:2022-6-4 15:09
1 位操作
    位操作与位带操作并不相同,位操作就是对一个变量的每一位做运算,而逻辑位操作是对这个变量整体进行运算。
    下面是六种常用的操作运算符:
640.png
按位取反
  1. void test01()& r. L$ T) `/ o
  2. {
    1 z0 Z# O4 }1 Q6 @* u" Y
  3.   int num = 7;$ a4 e& V+ A0 n9 x+ N6 C* s$ N
  4.   printf("~num = %d\n", ~num);//-8
    ' f& I0 s$ J; h6 H8 e( }3 K+ z5 z
  5. , p5 k; d9 A! ^8 n+ g
  6. // 0111  按位取反   1000   机器中存放的都是补码   7 z  S  y( n) c8 X% L; Y  G
  7. //补码转换原码需要分有符号数和无符号数两种
    : [1 u, y  d' J
  8. }
复制代码

, O% X6 [7 Z, _1 l; @! W
0 j  C) D: P  i! H& W8 V
按位与
  1. void test02()
    ! k/ ~6 P9 o0 m6 l. K( ^* ~
  2. {
    + ]' L2 Y; N4 ?  o5 ]. Q; p
  3.   int num = 128;4 a7 \! ^8 d& @" ]6 z& d
  4. //换算为八位,1换算就是00000001, 这样只要所给数字的二进制最后一位是1.那么就是奇数,否则就是偶数: z" J' W1 R2 D  j
  5.   if ( (num & 1) == 0)   
    # p7 R! s0 P8 b6 h  ^: N/ a
  6.   {
    ' ]& l. z6 Y2 n/ [- X
  7.     printf("num为偶数\n");  ^6 Z! a% X' \* w; k' D
  8.   }; A* |, B( d7 ^) g: f5 ^
  9.   else
      c; d! W; m% o4 w/ x! h) m; X' a
  10.   {) [) k* c) [$ A( b) F, w
  11.     printf("num为奇数\n");
    " }+ E9 Y' T5 x: K% n& A
  12.   }
      u! k! u9 C4 B, @, i7 f
  13. }
复制代码

8 x) e" ^" I6 ?- r4 F6 e( }
按位异或
  1. void test03()
    5 k/ f& B4 y: v1 A0 v; O6 f* {4 Z
  2. {
    * E0 \# N) b, w/ i, @
  3.   //按位异或的意思是,两个数字相同为0,不同为1。我们可以利用按位异或实现两个数的交换
      ^7 R- v! e! T* x; S0 y8 ]
  4.   num01 = 1; // 00010 J; F: M2 h$ F" m$ J
  5.   num02 = 4; // 0100
    ' j: t7 ^' s/ l6 E3 \. m
  6.   printf("num01 ^ num02 = %d", num01 ^ num02); // 5  两个二进制按位异或之后是: 01018 z! L/ a% T+ V8 p: J3 I
  7. 9 D: k6 v, Z- L# |) ]
  8.   printf("交换前\n");' K' N, q: g4 q6 W, W+ Y
  9.   printf("num01 = %d\n", num1);
    1 J$ ]' K0 }' E% ]. x4 h3 {7 N! t, J
  10.   printf("num02 = %d\n", num2);9 f* E4 j, G6 e3 Q  l1 F: R/ @
  11. ( q, P0 a' p9 f
  12.   num01 = num01 ^  num02;; h' h8 K- O" }" L6 h  \( E7 l
  13.   num02 = num01 ^  num02;
    6 s0 y/ p  V1 p2 c. ?7 Y6 S. x
  14.   num01 = num01 ^  num02;! L2 ~( E5 b( z% F2 |" \: x
  15.   //不用临时数字实现两个变量交换: W6 `$ u: o+ R0 P3 X
  16.   printf("交换后\n");
    ( l" x" o7 k$ B4 \% \
  17.   printf("num01 = %d\n", num1);, n3 m+ W9 y8 K" F
  18.   printf("num02 = %d\n", num2);
    " ]9 Q2 M3 F5 f6 u- O* z
  19. }
复制代码
/ G2 P7 ^% V5 e5 K: n# R
按位或
% w+ {! {( B% l$ X
    计算方法:
( p1 e8 w' n, d& B8 ?: |    参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当 相应位上全部为1时取1, 存在0时为0。
    printf是格式化输出函数,它可以直接打印十进制,八进制,十六进制,输出控制符分别为%d, %o, %x, 但是它不存在二进制,如果输出二进制,可以手写,但是也可以调用stdlib.h里面的itoa函数,他不是标准库里面的函数,但是大多数编译器里面都有这个函数。
  1. #include <stdio.h>: P  b. i  R/ K- O: E
  2. #include <stdlib.h># T8 f# z/ f5 g/ i1 k' r6 W
  3. " X2 s6 p/ H  f, a
  4. int main()
    1 l1 j2 W  N# D+ x% C2 T
  5. {
    9 j; F9 ?5 y! u) t6 n3 m
  6.   test04();    ; W  ~& S7 e! ^! o2 W5 J5 P$ b
  7. }6 N; U! D* i* ~; c; }4 ?. ~0 X0 S7 ?

  8. - G  R0 d( l" Q3 T
  9. int test04()
    " G2 K: m# [, S# C
  10. {3 j; A: A. ], u4 E7 C1 D
  11.     int a = 6;                  //二进制0110
    * k5 T+ @7 Z8 e9 T. s! U) S
  12.     int b = 3;                  //二进制0011. n1 V" N4 t+ b
  13.     int c = a | b;              //a、b按位或,结果8,二进制111,赋值给c8 r9 B4 m7 r( i
  14.     char s[10];: c& J+ h) U8 p
  15.     itoa(c, s, 2);
    & @  ]" [+ D+ U1 h
  16.     printf("二进制 --> %s\n", s);//输出:二进制 -->111! i% T9 E4 }% p( S
  17. }
复制代码
$ Q" A5 N% \, M# q( ?' e( b- [
8 M3 {: W$ p) t
左移运算符
  1. void test05()
    " a: ~  P) O" z# D" C
  2. {
    ; a$ O4 L7 o( s8 h6 i( l1 h: A9 E4 H
  3.   int num = 6;
    ) T/ U/ S1 B# r7 x5 F0 j  H- X+ B+ K
  4.   printf("%d\n", num << 3);//左移三位,就是0000
    # t8 q0 n0 h! B/ A1 K0 j/ j
  5. }
复制代码

1 ~6 a- m: d  B5 Q7 j0 x

3 ]5 G! S6 L! H6 @* w. s) U
右移运算符
  1. void test06()
    4 d. ^: }6 F* n8 M. L3 H8 u
  2. {+ J% p; i5 D+ }9 O
  3.   int num = 6; //0110
    4 `! ~" ?0 F" s0 [
  4.   printf("%d\n", num >> 1); //右移一位,就是0011,输出37 P8 `4 R+ Y) ?! t) S7 w: f2 D" P/ t1 L
  5. }
复制代码

( `2 K' U  y0 W* S1 h& g# z

' J9 P6 h9 t/ j* q0 X$ _
    上面是用普通c代码举得栗子,下面我们看一下STM32中操作通常用的代码:
    (1)比如我要改变 GPIOA-> BSRRL 的状态,可以先对寄存器的值进行& 清零操作
  1. GPIOA-> BSRRL &= 0xFF0F; //将第4位到第7位清零(注意编号是从0开始的)
复制代码

" \8 r0 A" [4 l$ ^8 [$ |
    然后再与需要设置的值进行|或运算:
  1. GPIOA-> BSRRL |= 0x0040; //将第4位到第7位设置为我们需要的数字
复制代码
5 r* t# U' b" i( J( ^# U0 F/ n
    (2)通过位移操作提高代码的可读性:
  1. GPIOx->ODR = (((uint32_t)0x01) << pinpos);
复制代码
# E% d5 T) D% J
    上面这行代码的意思就是,先将"0x01"这个八位十六进制转换为三十二位二进制,然后左移"pinpos"位,这个"pinpos"就是一个变量,其值就是要移动的位数。也就是将ODR寄存器的第pinpos位设置为1。
    (3)取反操作使用:
    SR寄存器的每一位代表一个状态,如果某个时刻我们想设置一个位的值为0,与此同时,其它位置都为1,简单的作法是直接给寄存器设置一个值:
  1. TIMx->SR=0xFFF7;
复制代码

7 X% R) `; Y5 U0 X% w9 K
    这样的作法设置第 3 位为 0,但是这样的作法可读性较差。看看库函数代码中怎样使用的:
  1. TIMx->SR = (uint16_t)~TIM_FLAG;
复制代码
2 R6 B8 L. o. x4 c" r9 K
    而 TIM_FLAG 是通过宏定义定义的值:
  1. #define TIM_FLAG_Update                    ((uint16_t)0x0001)
    + @1 V& ?4 K1 f/ X- |. {
  2. #define TIM_FLAG_CC1                       ((uint16_t)0x0002)
复制代码
! e3 w, o1 X# P& L$ v2 V

- x. L, V& A- W1 b  ~
2 define宏定义
    define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供 方便。
    常见的格式:
  1. #define 标识符 字符串
复制代码

3 S, b" H4 H/ _! X0 k
    标识符意思是所定义的宏名,字符串可以是常数、表达式或者格式串等,例如:
  1. #define PLL_Q 7  //注意,这个定义语句的最后不需要加分号
复制代码
7 W& V/ N% l" @5 i
3 ifdef条件编译
    在程序开发的过程中,经常会用到这种条件编译:
  1. #ifdef PLL_Q9 O3 A, D- s+ A" c
  2.    程序段1/ l! [6 T2 L# u0 Y/ \- e" z+ P
  3. #else
    * _( U# \7 R0 Y* D
  4.   程序段28 L' H* Z) O/ r! m- A( Y
  5. #endif
复制代码
    上面这段代码作用就是当这个标识符已经被定义过,那么就进行程序程序段1,如果没有则进行程序段2。当然,和我们设计普通的c代码是一样的,"#else"也可以没有,就是上面的代码减去"#else"和程序段2。
  1. #ifndef PLL_Q    //意思就是如果没有定义这个标识符
复制代码
1 u! O0 E2 c3 h7 Q. ~
4 extern变量申明
    C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义(一个变量只能定义一次,而extern可以申明很多次)使用例子如下:

  • . a3 I) {# D  c) Z& s& e
extern u16 USART_RX_STA;
    上面例子意思就是申明 “USART_RX_STA” 这个变量在其他文件中已经定义了,"u16"的意思是16位的。
5 ?  j% Z% Q  w; B' W) D
5 结构体
    定义一个结构体的一般形式为:
  1. struct 结构名
    5 P. t- G7 x& S  @
  2. {+ k) d" |) X$ ?2 ]: H/ E; Q
  3.   成员列表
    ' W  j% x6 J$ ^2 a
  4. };
复制代码

8 s: f0 O) s4 C
    成员列表由若干个成员组成,每个成员都是该结构体的一个组成部分。对每个成员也必须作类型说明,其形式:
  1. 类型说明符  成员名;//比如:int num;
复制代码
+ S! s% H- ?! K6 E8 q( W3 p- {0 p
    结合上面的说明,我们可以构建一个简单的结构体例子:
  1. struct sutdent1 K$ G* {" n2 Q
  2. {9 R) `/ `+ y$ G: E+ R
  3.   int num;
    2 v8 h- R5 U5 ?: m$ @" E' T  L& W
  4.   char name[20];  //20个字节长的字符
    + |2 q7 J0 S$ [' Q; J2 ^
  5.   char sex;
    + t: g. q' b3 F, Q
  6.   int age;
    8 {% g6 {! s7 s* V2 |
  7.   float score;- l% Y5 A- t: E9 G( Z+ [, @
  8.   char addr[30]; //30个字节长的字符* H9 |6 |" k# i9 A
  9. }
复制代码
7 t+ k0 w7 \; }# J

+ m0 e, I/ r1 V, v) C
    而如果我们想定义结构体变量,那么我们在定义这个结构体的时候直接定义,或者定义完结构体再另外定义结构体变量,比如:
  1. struct sutdent
    3 ], R, C" Q; v5 A" |& x; a
  2. {
    : [* v  G) L0 g8 [
  3.   int num;
    / U( y9 J/ A" \- `
  4.   char name[20];  //20个字节长的字符
    - \7 B1 m+ \3 T. ^
  5.   char sex;5 @- P$ {4 O: U% ~. u) m# l. q, j
  6.   int age;) F) E9 A# r* p- u  K. ?( X
  7.   float score;4 p: ^* s- H' k  o3 N+ o% b. }
  8.   char addr[30]; //30个字节长的字符
    - [+ @2 ~+ j' w9 y
  9. }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码

9 Z7 S5 ~4 T7 L/ h7 d" \( P3 D
    有时候我们可能需要用到结构体的嵌套,比如:
  1. struct date
    % ^! Z: L7 P7 p2 {0 l
  2. {# p5 {9 J; Z' F; t( ^$ [) T
  3.   int year, month,day;
    7 Y" q' [$ K& o) J# A
  4. };
    : s. o6 {3 ?( t! w
  5. struct sutdent2 d5 Y" t: n8 y8 C
  6. {
    7 W& {& L2 s2 [/ X7 m) c4 w
  7.   int num;
    1 b' H* Z! C! i. W* v0 ~
  8.   char name[20];  //20个字节长的字符5 p3 B3 l  r, ]  ^/ W/ _3 C
  9.   char sex;
    : p. S6 \4 S2 H4 U$ s7 F
  10.   struct date birthday; //这里就用到了结构体的嵌套- s/ Z5 x) q3 m& E
  11.   int age;
      L6 G! B9 M4 u& D9 C
  12.   float score;
    4 G3 O" Q% _0 ~* a7 v, n1 W3 r
  13.   char addr[30]; //30个字节长的字符
    7 p5 P/ _, I5 Q, ?, w- Y
  14. }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码

- R4 l* h1 f6 j
    如果需要引用结构体里面的成员内容,可以使用下面的方式:
  1. student01.name = 小李;
    & d$ V$ J4 Y2 T8 ]6 H+ O* |6 s
  2. // 结构体变量名.成员名(注意这里用的是点),这里是对这个成员的赋值
复制代码

! J; C$ m  ^% e9 M) k
    结构指针变量说明的一般形式为:
  1. struct 结构名 *结构指针变量名
复制代码
- Q7 s/ Y: o. Y- S' O
    假如说我们想定义一个指向结构体"student"的指针变量pstu,那么我们可以使用如下代码:
  1. struct student *pstu;
复制代码

  I& M# u1 B; m) A
    如果我们要给一个结构体指针变量赋初值,那么我们可以使用如下的方式:
  1. struct student
    / v" w6 i" {0 ?
  2. {
    ' B# j" Y; T/ m' e( X5 G
  3.   char name[66];
    ( T6 z+ D4 a. D+ o
  4.   int num;
    $ \- R- S: U" ~) R8 q' l& B
  5.   char sex;8 g% A7 f) R3 Z- y; z+ W
  6. }stu;
    7 T9 L0 ^8 A% K" X
  7. 5 x0 X$ W2 w4 A) v6 |
  8. 7 Q' s8 k+ [8 I5 a/ a- _/ j1 D
  9. pstu = &stu;
复制代码

8 V5 b5 e5 O5 D9 w% }+ m
    注意上边的赋值方式,我们如果要进行赋值,那必须使用结构体变量,而不能使用结构体名,像下边这样就是错误的。
  1. struct student4 ^6 j6 Y7 J- @* j6 r( h1 o: d
  2. {: S: T! |" e- u, ?# w' w
  3.   char name[66];5 K# ?) R& z4 r% h: q
  4.   int num;4 x( V" A, l2 L" h% l
  5.   char sex;
    ! g7 g2 z! g5 @
  6. }stu;
    7 s5 @: ?) N  n+ `/ {+ M. E: I8 c

  7. & }) Q6 S3 F1 ]

  8. : X0 f1 W4 m8 x' v  r/ h- {
  9. pstu = &student;
复制代码
0 c. b2 B9 ?+ U  i, s
    这是因为结构名和结构体变量是两个不同的概念,结构名只能表示一个结构形式,编译系统并不会给它分配内存空间(就是说不会给它分配地址),而结构体变量作为一个变量,编译系统会给它分配一个内存空间来存储。
访问结构体成员的一般形式:
  1. (*pstu).name;   //(1)(*结构指针变量).成员名;
    1 q8 Z/ F1 F; A  V' ?5 S8 Y
  2. pstu->name;   //(2)结构指针变量->成员名
复制代码
    结构体的知识就简单说上边这些。
6 typedef类型别名
    typedef用来为现有类型创建一个新的名字,或者称为类型别名,用来简化变量的定义(上边extern变量申明的例子中,"u16"就是对"uint16_t"类型名称的简化)。typedef在MDK中用得最多的就是定义结构体的类型别名和枚举类型。
    我们定义一个结构体GPIO:
G_L7~%JN`QSARXKVP)1Y]GY.png
    定义这样一个结构体以后,如果我们想定义一个结构体变量比如"GPIOA",那么我们需要使用这样的代码:
  1. struct _GPIO GPIOA;
复制代码

; {6 H2 f: {  e  {% v& b
    虽然也可以达到我们的目的,但是这样会比较麻烦,而且在MDK中会有很多地方用到,所以,我们可以使用"typedef"为其定义一个别名,这样直接通过这个别名就可以定义结构体变量,来达到我们的目的:
  1. typedef struct3 M3 X, e. S: h$ [: Z+ L5 \
  2. {/ y; j( W# W/ |- V; q4 X
  3.   _IO uint32_t MODER;
    ; u1 p$ i2 h# Y8 K) A1 @
  4.   _IO uint32_t OTYPER;$ Y7 O! p, w) G" z' J% _* K
  5. }GPIO_typedef;
复制代码
) }4 K% I3 Q& C, g" Y) e
    这样定义完成之后,如果我们需要定义结构体变量,那么我们只需要这样:
  1. GPIO_typedef _GPIOA,_GPIOB;
复制代码

4 y5 K  E& T, o9 b7 W. s+ x5 B; ]
收藏 评论0 发布时间:2022-6-4 15:09

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版