1 位操作 位操作与位带操作并不相同,位操作就是对一个变量的每一位做运算,而逻辑位操作是对这个变量整体进行运算。 下面是六种常用的操作运算符: 按位取反 - void test01()& r. L$ T) `/ o
- {
1 z0 Z# O4 }1 Q6 @* u" Y - int num = 7;$ a4 e& V+ A0 n9 x+ N6 C* s$ N
- printf("~num = %d\n", ~num);//-8
' f& I0 s$ J; h6 H8 e( }3 K+ z5 z - , p5 k; d9 A! ^8 n+ g
- // 0111 按位取反 1000 机器中存放的都是补码 7 z S y( n) c8 X% L; Y G
- //补码转换原码需要分有符号数和无符号数两种
: [1 u, y d' J - }
复制代码
, O% X6 [7 Z, _1 l; @! W0 j C) D: P i! H& W8 V
按位与 - void test02()
! k/ ~6 P9 o0 m6 l. K( ^* ~ - {
+ ]' L2 Y; N4 ? o5 ]. Q; p - int num = 128;4 a7 \! ^8 d& @" ]6 z& d
- //换算为八位,1换算就是00000001, 这样只要所给数字的二进制最后一位是1.那么就是奇数,否则就是偶数: z" J' W1 R2 D j
- if ( (num & 1) == 0)
# p7 R! s0 P8 b6 h ^: N/ a - {
' ]& l. z6 Y2 n/ [- X - printf("num为偶数\n"); ^6 Z! a% X' \* w; k' D
- }; A* |, B( d7 ^) g: f5 ^
- else
c; d! W; m% o4 w/ x! h) m; X' a - {) [) k* c) [$ A( b) F, w
- printf("num为奇数\n");
" }+ E9 Y' T5 x: K% n& A - }
u! k! u9 C4 B, @, i7 f - }
复制代码
8 x) e" ^" I6 ?- r4 F6 e( }按位异或 - void test03()
5 k/ f& B4 y: v1 A0 v; O6 f* {4 Z - {
* E0 \# N) b, w/ i, @ - //按位异或的意思是,两个数字相同为0,不同为1。我们可以利用按位异或实现两个数的交换
^7 R- v! e! T* x; S0 y8 ] - num01 = 1; // 00010 J; F: M2 h$ F" m$ J
- num02 = 4; // 0100
' j: t7 ^' s/ l6 E3 \. m - printf("num01 ^ num02 = %d", num01 ^ num02); // 5 两个二进制按位异或之后是: 01018 z! L/ a% T+ V8 p: J3 I
- 9 D: k6 v, Z- L# |) ]
- printf("交换前\n");' K' N, q: g4 q6 W, W+ Y
- printf("num01 = %d\n", num1);
1 J$ ]' K0 }' E% ]. x4 h3 {7 N! t, J - printf("num02 = %d\n", num2);9 f* E4 j, G6 e3 Q l1 F: R/ @
- ( q, P0 a' p9 f
- num01 = num01 ^ num02;; h' h8 K- O" }" L6 h \( E7 l
- num02 = num01 ^ num02;
6 s0 y/ p V1 p2 c. ?7 Y6 S. x - num01 = num01 ^ num02;! L2 ~( E5 b( z% F2 |" \: x
- //不用临时数字实现两个变量交换: W6 `$ u: o+ R0 P3 X
- printf("交换后\n");
( l" x" o7 k$ B4 \% \ - printf("num01 = %d\n", num1);, n3 m+ W9 y8 K" F
- printf("num02 = %d\n", num2);
" ]9 Q2 M3 F5 f6 u- O* z - }
复制代码 / 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函数,他不是标准库里面的函数,但是大多数编译器里面都有这个函数。 - #include <stdio.h>: P b. i R/ K- O: E
- #include <stdlib.h># T8 f# z/ f5 g/ i1 k' r6 W
- " X2 s6 p/ H f, a
- int main()
1 l1 j2 W N# D+ x% C2 T - {
9 j; F9 ?5 y! u) t6 n3 m - test04(); ; W ~& S7 e! ^! o2 W5 J5 P$ b
- }6 N; U! D* i* ~; c; }4 ?. ~0 X0 S7 ?
- G R0 d( l" Q3 T- int test04()
" G2 K: m# [, S# C - {3 j; A: A. ], u4 E7 C1 D
- int a = 6; //二进制0110
* k5 T+ @7 Z8 e9 T. s! U) S - int b = 3; //二进制0011. n1 V" N4 t+ b
- int c = a | b; //a、b按位或,结果8,二进制111,赋值给c8 r9 B4 m7 r( i
- char s[10];: c& J+ h) U8 p
- itoa(c, s, 2);
& @ ]" [+ D+ U1 h - printf("二进制 --> %s\n", s);//输出:二进制 -->111! i% T9 E4 }% p( S
- }
复制代码 $ Q" A5 N% \, M# q( ?' e( b- [
8 M3 {: W$ p) t
左移运算符 - void test05()
" a: ~ P) O" z# D" C - {
; a$ O4 L7 o( s8 h6 i( l1 h: A9 E4 H - int num = 6;
) T/ U/ S1 B# r7 x5 F0 j H- X+ B+ K - printf("%d\n", num << 3);//左移三位,就是0000
# t8 q0 n0 h! B/ A1 K0 j/ j - }
复制代码
1 ~6 a- m: d B5 Q7 j0 x
3 ]5 G! S6 L! H6 @* w. s) U
右移运算符 - void test06()
4 d. ^: }6 F* n8 M. L3 H8 u - {+ J% p; i5 D+ }9 O
- int num = 6; //0110
4 `! ~" ?0 F" s0 [ - printf("%d\n", num >> 1); //右移一位,就是0011,输出37 P8 `4 R+ Y) ?! t) S7 w: f2 D" P/ t1 L
- }
复制代码
( `2 K' U y0 W* S1 h& g# z
' J9 P6 h9 t/ j* q0 X$ _
上面是用普通c代码举得栗子,下面我们看一下STM32中操作通常用的代码: (1)比如我要改变 GPIOA-> BSRRL 的状态,可以先对寄存器的值进行& 清零操作 - GPIOA-> BSRRL &= 0xFF0F; //将第4位到第7位清零(注意编号是从0开始的)
复制代码
" \8 r0 A" [4 l$ ^8 [$ | 然后再与需要设置的值进行|或运算: - GPIOA-> BSRRL |= 0x0040; //将第4位到第7位设置为我们需要的数字
复制代码 5 r* t# U' b" i( J( ^# U0 F/ n
(2)通过位移操作提高代码的可读性: - GPIOx->ODR = (((uint32_t)0x01) << pinpos);
复制代码 # E% d5 T) D% J
上面这行代码的意思就是,先将"0x01"这个八位十六进制转换为三十二位二进制,然后左移"pinpos"位,这个"pinpos"就是一个变量,其值就是要移动的位数。也就是将ODR寄存器的第pinpos位设置为1。 (3)取反操作使用: SR寄存器的每一位代表一个状态,如果某个时刻我们想设置一个位的值为0,与此同时,其它位置都为1,简单的作法是直接给寄存器设置一个值:
7 X% R) `; Y5 U0 X% w9 K 这样的作法设置第 3 位为 0,但是这样的作法可读性较差。看看库函数代码中怎样使用的: - TIMx->SR = (uint16_t)~TIM_FLAG;
复制代码 2 R6 B8 L. o. x4 c" r9 K
而 TIM_FLAG 是通过宏定义定义的值: - #define TIM_FLAG_Update ((uint16_t)0x0001)
+ @1 V& ?4 K1 f/ X- |. { - #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 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供 方便。 常见的格式:
3 S, b" H4 H/ _! X0 k 标识符意思是所定义的宏名,字符串可以是常数、表达式或者格式串等,例如: - #define PLL_Q 7 //注意,这个定义语句的最后不需要加分号
复制代码 7 W& V/ N% l" @5 i
3 ifdef条件编译 在程序开发的过程中,经常会用到这种条件编译: - #ifdef PLL_Q9 O3 A, D- s+ A" c
- 程序段1/ l! [6 T2 L# u0 Y/ \- e" z+ P
- #else
* _( U# \7 R0 Y* D - 程序段28 L' H* Z) O/ r! m- A( Y
- #endif
复制代码 上面这段代码作用就是当这个标识符已经被定义过,那么就进行程序程序段1,如果没有则进行程序段2。当然,和我们设计普通的c代码是一样的,"#else"也可以没有,就是上面的代码减去"#else"和程序段2。 - #ifndef PLL_Q //意思就是如果没有定义这个标识符
复制代码 1 u! O0 E2 c3 h7 Q. ~
4 extern变量申明 C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义(一个变量只能定义一次,而extern可以申明很多次)使用例子如下: extern u16 USART_RX_STA; 上面例子意思就是申明 “USART_RX_STA” 这个变量在其他文件中已经定义了,"u16"的意思是16位的。 5 ? j% Z% Q w; B' W) D
5 结构体 定义一个结构体的一般形式为: - struct 结构名
5 P. t- G7 x& S @ - {+ k) d" |) X$ ?2 ]: H/ E; Q
- 成员列表
' W j% x6 J$ ^2 a - };
复制代码
8 s: f0 O) s4 C 成员列表由若干个成员组成,每个成员都是该结构体的一个组成部分。对每个成员也必须作类型说明,其形式: + S! s% H- ?! K6 E8 q( W3 p- {0 p
结合上面的说明,我们可以构建一个简单的结构体例子: - struct sutdent1 K$ G* {" n2 Q
- {9 R) `/ `+ y$ G: E+ R
- int num;
2 v8 h- R5 U5 ?: m$ @" E' T L& W - char name[20]; //20个字节长的字符
+ |2 q7 J0 S$ [' Q; J2 ^ - char sex;
+ t: g. q' b3 F, Q - int age;
8 {% g6 {! s7 s* V2 | - float score;- l% Y5 A- t: E9 G( Z+ [, @
- char addr[30]; //30个字节长的字符* H9 |6 |" k# i9 A
- }
复制代码 7 t+ k0 w7 \; }# J
+ m0 e, I/ r1 V, v) C
而如果我们想定义结构体变量,那么我们在定义这个结构体的时候直接定义,或者定义完结构体再另外定义结构体变量,比如: - struct sutdent
3 ], R, C" Q; v5 A" |& x; a - {
: [* v G) L0 g8 [ - int num;
/ U( y9 J/ A" \- ` - char name[20]; //20个字节长的字符
- \7 B1 m+ \3 T. ^ - char sex;5 @- P$ {4 O: U% ~. u) m# l. q, j
- int age;) F) E9 A# r* p- u K. ?( X
- float score;4 p: ^* s- H' k o3 N+ o% b. }
- char addr[30]; //30个字节长的字符
- [+ @2 ~+ j' w9 y - }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码
9 Z7 S5 ~4 T7 L/ h7 d" \( P3 D
有时候我们可能需要用到结构体的嵌套,比如: - struct date
% ^! Z: L7 P7 p2 {0 l - {# p5 {9 J; Z' F; t( ^$ [) T
- int year, month,day;
7 Y" q' [$ K& o) J# A - };
: s. o6 {3 ?( t! w - struct sutdent2 d5 Y" t: n8 y8 C
- {
7 W& {& L2 s2 [/ X7 m) c4 w - int num;
1 b' H* Z! C! i. W* v0 ~ - char name[20]; //20个字节长的字符5 p3 B3 l r, ] ^/ W/ _3 C
- char sex;
: p. S6 \4 S2 H4 U$ s7 F - struct date birthday; //这里就用到了结构体的嵌套- s/ Z5 x) q3 m& E
- int age;
L6 G! B9 M4 u& D9 C - float score;
4 G3 O" Q% _0 ~* a7 v, n1 W3 r - char addr[30]; //30个字节长的字符
7 p5 P/ _, I5 Q, ?, w- Y - }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码
- R4 l* h1 f6 j
如果需要引用结构体里面的成员内容,可以使用下面的方式: - student01.name = 小李;
& d$ V$ J4 Y2 T8 ]6 H+ O* |6 s - // 结构体变量名.成员名(注意这里用的是点),这里是对这个成员的赋值
复制代码
! J; C$ m ^% e9 M) k
结构指针变量说明的一般形式为: - Q7 s/ Y: o. Y- S' O
假如说我们想定义一个指向结构体"student"的指针变量pstu,那么我们可以使用如下代码:
I& M# u1 B; m) A 如果我们要给一个结构体指针变量赋初值,那么我们可以使用如下的方式: - struct student
/ v" w6 i" {0 ? - {
' B# j" Y; T/ m' e( X5 G - char name[66];
( T6 z+ D4 a. D+ o - int num;
$ \- R- S: U" ~) R8 q' l& B - char sex;8 g% A7 f) R3 Z- y; z+ W
- }stu;
7 T9 L0 ^8 A% K" X - 5 x0 X$ W2 w4 A) v6 |
- 7 Q' s8 k+ [8 I5 a/ a- _/ j1 D
- pstu = &stu;
复制代码
8 V5 b5 e5 O5 D9 w% }+ m
注意上边的赋值方式,我们如果要进行赋值,那必须使用结构体变量,而不能使用结构体名,像下边这样就是错误的。 - struct student4 ^6 j6 Y7 J- @* j6 r( h1 o: d
- {: S: T! |" e- u, ?# w' w
- char name[66];5 K# ?) R& z4 r% h: q
- int num;4 x( V" A, l2 L" h% l
- char sex;
! g7 g2 z! g5 @ - }stu;
7 s5 @: ?) N n+ `/ {+ M. E: I8 c
& }) Q6 S3 F1 ]
: X0 f1 W4 m8 x' v r/ h- {- pstu = &student;
复制代码 0 c. b2 B9 ?+ U i, s
这是因为结构名和结构体变量是两个不同的概念,结构名只能表示一个结构形式,编译系统并不会给它分配内存空间(就是说不会给它分配地址),而结构体变量作为一个变量,编译系统会给它分配一个内存空间来存储。 访问结构体成员的一般形式: - (*pstu).name; //(1)(*结构指针变量).成员名;
1 q8 Z/ F1 F; A V' ?5 S8 Y - pstu->name; //(2)结构指针变量->成员名
复制代码 结构体的知识就简单说上边这些。 6 typedef类型别名 typedef用来为现有类型创建一个新的名字,或者称为类型别名,用来简化变量的定义(上边extern变量申明的例子中,"u16"就是对"uint16_t"类型名称的简化)。typedef在MDK中用得最多的就是定义结构体的类型别名和枚举类型。 我们定义一个结构体GPIO: 定义这样一个结构体以后,如果我们想定义一个结构体变量比如"GPIOA",那么我们需要使用这样的代码:
; {6 H2 f: { e {% v& b 虽然也可以达到我们的目的,但是这样会比较麻烦,而且在MDK中会有很多地方用到,所以,我们可以使用"typedef"为其定义一个别名,这样直接通过这个别名就可以定义结构体变量,来达到我们的目的: - typedef struct3 M3 X, e. S: h$ [: Z+ L5 \
- {/ y; j( W# W/ |- V; q4 X
- _IO uint32_t MODER;
; u1 p$ i2 h# Y8 K) A1 @ - _IO uint32_t OTYPER;$ Y7 O! p, w) G" z' J% _* K
- }GPIO_typedef;
复制代码) }4 K% I3 Q& C, g" Y) e
这样定义完成之后,如果我们需要定义结构体变量,那么我们只需要这样: - GPIO_typedef _GPIOA,_GPIOB;
复制代码
4 y5 K E& T, o9 b7 W. s+ x5 B; ] |