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

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

[复制链接]
STMCU小助手 发布时间:2022-6-4 15:09
1 位操作
    位操作与位带操作并不相同,位操作就是对一个变量的每一位做运算,而逻辑位操作是对这个变量整体进行运算。
    下面是六种常用的操作运算符:
640.png
按位取反
  1. void test01(), O% U9 `8 C# d- w
  2. {
      J) }) w! u! A. m
  3.   int num = 7;+ T- y4 v& k/ [  y
  4.   printf("~num = %d\n", ~num);//-89 N1 f. m' ^% [9 S2 }

  5. ! P% j2 q  }; T9 E3 m
  6. // 0111  按位取反   1000   机器中存放的都是补码   
    4 l+ R0 _; ]7 r3 i( \! t
  7. //补码转换原码需要分有符号数和无符号数两种
    7 z8 J/ ]' M3 N. E( \
  8. }
复制代码
" v6 Q: W2 S1 D; H8 H9 y

* ]( ?& s% s) f2 U/ R% U
按位与
  1. void test02()
    8 W' m8 f$ i! c
  2. {
    . r4 J' C9 e3 w* [
  3.   int num = 128;
    4 Z, R. \. G, h& B  q
  4. //换算为八位,1换算就是00000001, 这样只要所给数字的二进制最后一位是1.那么就是奇数,否则就是偶数
    # u4 D  c! f) @) v) @2 w
  5.   if ( (num & 1) == 0)    " E6 F, y* G! x$ w$ g3 `9 H3 r! D
  6.   {7 u, i" Z8 A, h* ]/ e4 U9 c0 m" F
  7.     printf("num为偶数\n");
    ! Q7 Y3 x+ Z! _, z
  8.   }; H- s" I/ Z2 s& q
  9.   else
    ! z5 Z/ _4 W' i
  10.   {
      V3 ?! c& U( E( y7 u9 y, D) R
  11.     printf("num为奇数\n");
    / y3 G! G4 ?6 X8 q1 B
  12.   }
    , c- c$ Z0 L. D; I2 s
  13. }
复制代码

# ~# n3 N; T  H
按位异或
  1. void test03()3 V- ?. ~% A( i% b, }
  2. {. O' V6 l1 M5 Z$ A
  3.   //按位异或的意思是,两个数字相同为0,不同为1。我们可以利用按位异或实现两个数的交换  x! o- T3 A! D$ _9 {8 w
  4.   num01 = 1; // 0001
    - O+ A8 a& J. u2 v, }
  5.   num02 = 4; // 0100
    - i1 k& Y# q& _  P( \, N
  6.   printf("num01 ^ num02 = %d", num01 ^ num02); // 5  两个二进制按位异或之后是: 01010 _, R7 \  H5 _) X+ P1 h
  7. ; d" ^- z7 L9 P6 B+ v9 G
  8.   printf("交换前\n");) b& w7 k$ ?# }! c* i
  9.   printf("num01 = %d\n", num1);
    4 i7 k1 |8 w. h- u
  10.   printf("num02 = %d\n", num2);
    , @+ r3 u4 `, M4 R  s: d: w3 p* k4 B
  11. ( G, `- {9 w  Q) ~+ B
  12.   num01 = num01 ^  num02;
    * I. F* C. V" V
  13.   num02 = num01 ^  num02;
    + {$ O+ V. Z3 i2 d5 C" P) f
  14.   num01 = num01 ^  num02;
    8 [$ n5 p  R1 ?3 b+ K
  15.   //不用临时数字实现两个变量交换
    + L+ l( t6 B# S
  16.   printf("交换后\n");& W5 S, {4 B9 ^8 n% n
  17.   printf("num01 = %d\n", num1);2 [3 [$ D+ R/ j. y, ]" Z
  18.   printf("num02 = %d\n", num2);
    : A8 T* g* T5 U( C7 N! G  s
  19. }
复制代码

, m, O5 \! G+ N' k  ^& T5 |" B
按位或7 p" _& ]. A# S. _$ l
    计算方法:
6 S, O  a8 m3 I0 m) n3 {    参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当 相应位上全部为1时取1, 存在0时为0。
    printf是格式化输出函数,它可以直接打印十进制,八进制,十六进制,输出控制符分别为%d, %o, %x, 但是它不存在二进制,如果输出二进制,可以手写,但是也可以调用stdlib.h里面的itoa函数,他不是标准库里面的函数,但是大多数编译器里面都有这个函数。
  1. #include <stdio.h>
    ' Z$ j. C# e- F6 C( p
  2. #include <stdlib.h>: q0 b9 H- f! x" i; T0 R

  3. 1 \6 w) Y5 p3 y" h- V
  4. int main()
    ' K; y( E. r5 K, k+ q5 @
  5. {3 D; J, W' }' M4 b% }% c; T
  6.   test04();   
    # a% b& _7 x# y- x$ [
  7. }
    , w+ @" [1 b6 q, V: |
  8. 8 `$ z( D2 N9 L4 z
  9. int test04()
    $ G0 Q3 z- A7 m6 k$ P* c
  10. {
    $ m' @- N/ q+ p9 i, j
  11.     int a = 6;                  //二进制01104 [6 Z' Z: l6 e: ~( Z+ }# F
  12.     int b = 3;                  //二进制0011% B! Q4 D: R  O( ^% x: p% v
  13.     int c = a | b;              //a、b按位或,结果8,二进制111,赋值给c# T- e' S& y9 a
  14.     char s[10];
    ) w1 f0 b2 s! [3 P1 X& R- _* J
  15.     itoa(c, s, 2);# h) \: a1 M) P! j3 K2 J
  16.     printf("二进制 --> %s\n", s);//输出:二进制 -->111
    $ G& R/ n. X, e* P" h( }1 B6 ^
  17. }
复制代码

/ o7 h$ W  E& l' m7 q
6 G2 P' V9 K$ t9 \
左移运算符
  1. void test05()9 Q8 F' ^+ W: k2 q( G
  2. {
      V' k& |& F7 O0 z) s
  3.   int num = 6;/ I7 _  _" m: ]3 g
  4.   printf("%d\n", num << 3);//左移三位,就是0000. X7 c* P9 u( K5 @( m
  5. }
复制代码

4 |6 D# b* W% g! D# ^# E) E/ r

0 j4 N7 w! K9 _7 A9 n8 d
右移运算符
  1. void test06()( t6 b, e0 L9 N5 _$ Q" B5 C
  2. {
    - w# }7 _6 f0 g$ N9 u
  3.   int num = 6; //0110
    $ V# F5 Q$ o$ f9 v% h
  4.   printf("%d\n", num >> 1); //右移一位,就是0011,输出3+ N. u4 g' p/ h  X+ ~) h2 l- ~4 d8 B
  5. }
复制代码

+ p# H2 Q( {3 v# ?! e

1 o+ E+ C- j( A" w7 P+ g
    上面是用普通c代码举得栗子,下面我们看一下STM32中操作通常用的代码:
    (1)比如我要改变 GPIOA-> BSRRL 的状态,可以先对寄存器的值进行& 清零操作
  1. GPIOA-> BSRRL &= 0xFF0F; //将第4位到第7位清零(注意编号是从0开始的)
复制代码
0 g/ p" I0 l/ o, ^
    然后再与需要设置的值进行|或运算:
  1. GPIOA-> BSRRL |= 0x0040; //将第4位到第7位设置为我们需要的数字
复制代码

5 M, D, b/ K: N3 W9 J, P
    (2)通过位移操作提高代码的可读性:
  1. GPIOx->ODR = (((uint32_t)0x01) << pinpos);
复制代码

, e% U  r( J; h6 q
    上面这行代码的意思就是,先将"0x01"这个八位十六进制转换为三十二位二进制,然后左移"pinpos"位,这个"pinpos"就是一个变量,其值就是要移动的位数。也就是将ODR寄存器的第pinpos位设置为1。
    (3)取反操作使用:
    SR寄存器的每一位代表一个状态,如果某个时刻我们想设置一个位的值为0,与此同时,其它位置都为1,简单的作法是直接给寄存器设置一个值:
  1. TIMx->SR=0xFFF7;
复制代码

9 n2 r3 F9 ~7 J$ L
    这样的作法设置第 3 位为 0,但是这样的作法可读性较差。看看库函数代码中怎样使用的:
  1. TIMx->SR = (uint16_t)~TIM_FLAG;
复制代码
6 w9 ^, ]3 H) j3 @; v* _
    而 TIM_FLAG 是通过宏定义定义的值:
  1. #define TIM_FLAG_Update                    ((uint16_t)0x0001) ! D! Z1 }' r* n* S
  2. #define TIM_FLAG_CC1                       ((uint16_t)0x0002)
复制代码
0 d2 g$ ~/ ^) P# ~) S$ u0 C% A
- D  J! O# N6 A  p
2 define宏定义
    define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供 方便。
    常见的格式:
  1. #define 标识符 字符串
复制代码
% W; P, [8 u/ x& y. y/ U
    标识符意思是所定义的宏名,字符串可以是常数、表达式或者格式串等,例如:
  1. #define PLL_Q 7  //注意,这个定义语句的最后不需要加分号
复制代码

0 A- _" ?: L+ r1 L: r1 z( k  ?
3 ifdef条件编译
    在程序开发的过程中,经常会用到这种条件编译:
  1. #ifdef PLL_Q
    1 ?. C4 j/ h- u4 z- K4 e
  2.    程序段1
    , K6 }+ D( X! B, ^/ S! \
  3. #else; A/ b( r; k- @7 B% D8 {+ H
  4.   程序段2
    & O6 [1 r6 Q; S6 x9 D- ~8 d
  5. #endif
复制代码
    上面这段代码作用就是当这个标识符已经被定义过,那么就进行程序程序段1,如果没有则进行程序段2。当然,和我们设计普通的c代码是一样的,"#else"也可以没有,就是上面的代码减去"#else"和程序段2。
  1. #ifndef PLL_Q    //意思就是如果没有定义这个标识符
复制代码

  Z7 \9 f1 d! }( L1 K
4 extern变量申明
    C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义(一个变量只能定义一次,而extern可以申明很多次)使用例子如下:

  • - I- f8 l6 Q' R
extern u16 USART_RX_STA;
    上面例子意思就是申明 “USART_RX_STA” 这个变量在其他文件中已经定义了,"u16"的意思是16位的。

5 E7 f' G0 f2 {! w6 n
5 结构体
    定义一个结构体的一般形式为:
  1. struct 结构名
    ; e& P- @* R1 C" T0 P5 K/ r
  2. {
    + ~3 y6 T7 M3 M1 j
  3.   成员列表- f8 u7 Y: S" h; X- ~& G
  4. };
复制代码

, C3 x% Q1 A- n4 B. S$ L; U% A/ a
    成员列表由若干个成员组成,每个成员都是该结构体的一个组成部分。对每个成员也必须作类型说明,其形式:
  1. 类型说明符  成员名;//比如:int num;
复制代码

% |* K; Z' x$ c7 M
    结合上面的说明,我们可以构建一个简单的结构体例子:
  1. struct sutdent. |' [$ I( W, A4 o) i- Q% I1 K
  2. {
    % d7 \" a6 n0 z. n) u+ ^% J5 O
  3.   int num;
    9 ]8 O0 n% j; `
  4.   char name[20];  //20个字节长的字符
    : |4 D5 U) O2 s3 G# A5 M+ m2 Z
  5.   char sex;/ S( K, Z! ^, W3 C- |* ~
  6.   int age;
    - P2 E  Z' g4 }/ q, v6 {
  7.   float score;
    / n8 ]$ ?8 C+ L: J* p% I
  8.   char addr[30]; //30个字节长的字符
    , z+ p: F4 X2 l1 x5 K- O$ x2 K
  9. }
复制代码
: Q+ S! ^! P# r5 e! K4 j  B. F* V

6 f9 R1 F9 G# D7 y) }; Y; g
    而如果我们想定义结构体变量,那么我们在定义这个结构体的时候直接定义,或者定义完结构体再另外定义结构体变量,比如:
  1. struct sutdent
    ( K& d8 l3 q3 s
  2. {: q( W* d6 ^( B2 Q: j+ ?
  3.   int num;8 e; e  \, f' I
  4.   char name[20];  //20个字节长的字符
    # ~+ R: k0 w. z9 B, [" o- U) y! U
  5.   char sex;
    ) C/ n8 L: W& p- z6 q$ L9 k" M
  6.   int age;- _1 H3 i9 r3 l  k- V; w
  7.   float score;0 Z, A" a$ ?% I' j0 q8 }) }
  8.   char addr[30]; //30个字节长的字符/ S9 t) ]$ _3 J( j
  9. }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码

( S: q9 l/ h  L6 a5 n# C/ N, `( D% ~* E4 A
    有时候我们可能需要用到结构体的嵌套,比如:
  1. struct date
    " D, |' r- I! X3 N- Q! W
  2. {
    & H# p. l% l5 y8 r+ f: w2 e
  3.   int year, month,day;
    5 ~  o# ~: ^" y; U7 O( S  H/ E
  4. };& C8 j: k2 A# Y* V1 v
  5. struct sutdent
    . s* J/ G& n: t* T  j4 l% C7 G
  6. {
    ; e5 u  F) l$ u1 g2 ?+ w; u
  7.   int num;
    0 _/ }4 ?3 j9 {9 @: h4 `3 L
  8.   char name[20];  //20个字节长的字符" |$ F' t5 ~, h6 T
  9.   char sex;
    ( @% Y7 z; b0 ~) F+ W
  10.   struct date birthday; //这里就用到了结构体的嵌套/ s$ O, s6 r2 B( `! o% {# V+ N! H
  11.   int age;
    0 `# Q! M& f2 ?: ]0 S* k- g
  12.   float score;; e; \! c0 a1 I
  13.   char addr[30]; //30个字节长的字符, ^! A) W% `, l0 d3 P7 R
  14. }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码

6 g1 E7 j$ E+ R; E* U
    如果需要引用结构体里面的成员内容,可以使用下面的方式:
  1. student01.name = 小李;
    & {! }* l0 \+ c0 G" H0 a% A
  2. // 结构体变量名.成员名(注意这里用的是点),这里是对这个成员的赋值
复制代码
; ?! y6 j5 y: d( e% @& @
    结构指针变量说明的一般形式为:
  1. struct 结构名 *结构指针变量名
复制代码

9 z& v$ t( `8 l& O4 x# m
    假如说我们想定义一个指向结构体"student"的指针变量pstu,那么我们可以使用如下代码:
  1. struct student *pstu;
复制代码
; }8 j- P7 l6 y7 R
    如果我们要给一个结构体指针变量赋初值,那么我们可以使用如下的方式:
  1. struct student
    0 B! g% }3 b4 B/ \* j1 `
  2. {7 Z6 {# b+ y3 @: F. E% D
  3.   char name[66];
    5 ?" g! E1 n) x9 `$ }
  4.   int num;
    5 ~- q% ?& c# `8 W% ?+ t
  5.   char sex;
    $ N! w' p# s+ c4 X3 c
  6. }stu;- C9 d+ W1 z2 q/ u6 X

  7. " t; S7 v! S, d
  8. , ~) c+ p+ K# r! j$ B, ^
  9. pstu = &stu;
复制代码
7 m1 f+ x3 D' V9 l' s
    注意上边的赋值方式,我们如果要进行赋值,那必须使用结构体变量,而不能使用结构体名,像下边这样就是错误的。
  1. struct student+ [0 b4 ~& {) z9 R: o
  2. {7 R3 C' x. K' z( R
  3.   char name[66];
    * t8 s' @) J% t+ h4 x4 l8 B4 [$ l% C
  4.   int num;
    0 Y$ T$ p; X/ b
  5.   char sex;8 [+ t$ J1 C$ c& y1 n
  6. }stu;
    7 `9 I8 i% N7 |( o

  7. " O+ G$ ?9 ?  I, D

  8. 5 r0 I0 ^$ d: @# B! S6 |& F9 K3 n) w; ]
  9. pstu = &student;
复制代码

$ f4 |8 Z( b  n% j7 ~' `( J
    这是因为结构名和结构体变量是两个不同的概念,结构名只能表示一个结构形式,编译系统并不会给它分配内存空间(就是说不会给它分配地址),而结构体变量作为一个变量,编译系统会给它分配一个内存空间来存储。
访问结构体成员的一般形式:
  1. (*pstu).name;   //(1)(*结构指针变量).成员名;; p+ Z6 e7 e" K+ {' l3 _& E: F1 S
  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;
复制代码

4 u. |5 T- _/ g" ]
    虽然也可以达到我们的目的,但是这样会比较麻烦,而且在MDK中会有很多地方用到,所以,我们可以使用"typedef"为其定义一个别名,这样直接通过这个别名就可以定义结构体变量,来达到我们的目的:
  1. typedef struct
    % P0 e' s; T: w/ `  @7 b
  2. {) t, x% f6 D) h
  3.   _IO uint32_t MODER;
    % w  }: [5 V4 q0 h) y
  4.   _IO uint32_t OTYPER;
    / \# S% `+ |+ K" D4 t2 \( [
  5. }GPIO_typedef;
复制代码

: h- k" k$ D& A4 t
    这样定义完成之后,如果我们需要定义结构体变量,那么我们只需要这样:
  1. GPIO_typedef _GPIOA,_GPIOB;
复制代码
) o0 q. L5 J/ U" z5 k5 T
赞 收藏 评论0 发布时间:2022-6-4 15:09

举报

0个回答

所属标签

相似技术帖

官网相关资源

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