1 位操作 位操作与位带操作并不相同,位操作就是对一个变量的每一位做运算,而逻辑位操作是对这个变量整体进行运算。 下面是六种常用的操作运算符: 按位取反 - void test01()" a! ]" e1 d- i) w4 ^9 \; B; _: H+ {
- {
, b$ z$ b9 g$ X3 x - int num = 7;
) @+ B$ Q/ ^0 U- c) b6 u8 ~+ O/ g; o - printf("~num = %d\n", ~num);//-82 y8 V6 Y" c6 E& t5 z
' l/ j9 r* |3 J9 y- // 0111 按位取反 1000 机器中存放的都是补码 . N/ X/ F# }. X: c$ q8 O. C
- //补码转换原码需要分有符号数和无符号数两种
; a' r4 W8 M" I; V- `5 H: e8 u - }
复制代码
5 C; t0 D- x, Z: M/ d+ F
, E3 t" n& U, F
按位与 - void test02()% y7 O' L" b' i* o( P! I8 ]
- {1 a6 n+ ~/ i7 H* F( k) @1 J. K) E
- int num = 128; C9 Z8 R: e% a4 q; X: C) {
- //换算为八位,1换算就是00000001, 这样只要所给数字的二进制最后一位是1.那么就是奇数,否则就是偶数
8 h# d( f' L+ m+ V4 Z! V - if ( (num & 1) == 0) - u/ Z; {" C$ F( [' T$ g& @
- {) R* x& L- e+ } A& b# J# {
- printf("num为偶数\n");' n0 l, ^: X+ e |1 j& g9 Y* I
- }
; ~( x8 i$ T5 e - else
M4 \9 ?# ^, { - {
( f0 ^, x. c. F8 t - printf("num为奇数\n");
2 s" A5 t7 ~, m- ?2 ^8 Z, x - }
- m: `0 B" |" _8 W) S5 p - }
复制代码 % J. v( }6 P4 k @' ]
按位异或 - void test03()
3 K5 e" q0 \9 Y: j0 C" d' n8 D - {: u1 L6 o( R4 \9 c! u9 g+ n' R& @
- //按位异或的意思是,两个数字相同为0,不同为1。我们可以利用按位异或实现两个数的交换% P# q7 i9 f g+ a0 l+ b; }( P
- num01 = 1; // 0001+ h( a% @/ q/ ~$ q; o9 o ~
- num02 = 4; // 0100
7 M- b; b! j. e' ~ - printf("num01 ^ num02 = %d", num01 ^ num02); // 5 两个二进制按位异或之后是: 0101- C+ j& ~& V( h7 Q6 Y9 x
( }( x, t( }9 x1 C8 e3 g* |5 m- printf("交换前\n");3 f* v4 j3 C8 w& W! @& n
- printf("num01 = %d\n", num1);- d1 S: ~( D, v: V( o2 [5 @3 s& q
- printf("num02 = %d\n", num2);4 Z6 \) F4 r: v3 J* w( F6 L6 S
6 a' w% z! q' d, J* j: Z8 P- num01 = num01 ^ num02;
5 t4 L0 \/ F/ j& A0 u* y0 E - num02 = num01 ^ num02;
* k3 s& h5 N6 E9 H3 x( [ - num01 = num01 ^ num02;7 Q5 a' R- ]4 g. O" P' w8 X$ P
- //不用临时数字实现两个变量交换# r( [8 e/ R! G" @8 k
- printf("交换后\n");0 Q' { v$ V- ~4 Z+ @! S; O
- printf("num01 = %d\n", num1);& m, }9 J5 j3 N+ ~6 k0 p, h+ J, W
- printf("num02 = %d\n", num2);0 t; |9 p8 ]- W' f$ Z5 [' q
- }
复制代码
, u4 s" D; L" h, s按位或9 [4 Y' J+ O1 ~; @/ H7 Z3 Z
计算方法:
) t1 |+ j4 _) K/ z; n4 e8 G$ S+ \' c( ` 参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当 相应位上全部为1时取1, 存在0时为0。 printf是格式化输出函数,它可以直接打印十进制,八进制,十六进制,输出控制符分别为%d, %o, %x, 但是它不存在二进制,如果输出二进制,可以手写,但是也可以调用stdlib.h里面的itoa函数,他不是标准库里面的函数,但是大多数编译器里面都有这个函数。 - #include <stdio.h>" F s) P( h; k) U! n; p1 J
- #include <stdlib.h>4 S$ ]8 w4 E5 F# [8 j' z) l
* q+ r$ p# n& S' b# N1 g+ |$ x- int main(): g7 S5 R5 \; B* M2 s- O
- {- k1 L8 |) h @: e+ t
- test04(); + B% w) c6 r' q7 J$ V
- }8 h3 R" h+ G% [: s
/ K/ K4 r/ P8 d& {- int test04()' C' h0 Z7 a) K2 m! D, R
- {/ u7 ]% ] Q0 ?6 y3 f3 h7 k# J- G
- int a = 6; //二进制0110
* {) N: _5 `) S' P4 t, L1 @ - int b = 3; //二进制0011
, M J5 a- S. ~" Q" a7 \4 } - int c = a | b; //a、b按位或,结果8,二进制111,赋值给c
# A! x, a$ [! _( U* Y' q# D) q. [ - char s[10];
* j% ~. z% l0 c1 q - itoa(c, s, 2);
/ w* K3 J$ V( E1 w) B - printf("二进制 --> %s\n", s);//输出:二进制 -->111+ w" r0 q( @( [ e& M
- }
复制代码
) Q M: g9 J& R
# w" T2 [" t) Y8 T/ r+ {
左移运算符 - void test05()
t# a% t' b( x' | - {$ q0 A m$ H `1 P/ V
- int num = 6;
& @1 y! s/ c, d- r( a4 j - printf("%d\n", num << 3);//左移三位,就是0000# c' a! F8 K8 W3 w# M
- }
复制代码 . ]1 Z3 B8 z9 K8 ^$ P
, a1 Z6 H! W# m/ ^. |9 w2 V
右移运算符 - void test06()
9 ?" l& q8 B% j" N* A) R - {- J* A; x P" u, `2 s+ W8 A
- int num = 6; //0110
- a. P; d# t( r' j% z3 M; H7 ]4 p - printf("%d\n", num >> 1); //右移一位,就是0011,输出3
3 f3 R. n1 P |% g9 T9 c - }
复制代码
/ S( ~7 q1 A$ ~( }5 f8 [! z$ G' |$ ]; E( _+ Y7 B
上面是用普通c代码举得栗子,下面我们看一下STM32中操作通常用的代码: (1)比如我要改变 GPIOA-> BSRRL 的状态,可以先对寄存器的值进行& 清零操作 - GPIOA-> BSRRL &= 0xFF0F; //将第4位到第7位清零(注意编号是从0开始的)
复制代码
; V/ C( e6 Q" @$ Y7 O 然后再与需要设置的值进行|或运算: - GPIOA-> BSRRL |= 0x0040; //将第4位到第7位设置为我们需要的数字
复制代码
7 c) J$ q. r3 C( L8 m5 j% }% r$ b (2)通过位移操作提高代码的可读性: - GPIOx->ODR = (((uint32_t)0x01) << pinpos);
复制代码 & l( F% u s; w( J% o9 T9 A; C
上面这行代码的意思就是,先将"0x01"这个八位十六进制转换为三十二位二进制,然后左移"pinpos"位,这个"pinpos"就是一个变量,其值就是要移动的位数。也就是将ODR寄存器的第pinpos位设置为1。 (3)取反操作使用: SR寄存器的每一位代表一个状态,如果某个时刻我们想设置一个位的值为0,与此同时,其它位置都为1,简单的作法是直接给寄存器设置一个值: / ?. k5 U" Q1 X2 k Q( f
这样的作法设置第 3 位为 0,但是这样的作法可读性较差。看看库函数代码中怎样使用的: - TIMx->SR = (uint16_t)~TIM_FLAG;
复制代码 " F: E% T* s$ \) _/ T5 l+ }$ K, d
而 TIM_FLAG 是通过宏定义定义的值: - #define TIM_FLAG_Update ((uint16_t)0x0001)
& |. y+ t. F' _7 s( D! u - #define TIM_FLAG_CC1 ((uint16_t)0x0002)
复制代码
. e; m" A5 |0 w6 V! n) m2 r- \2 ?1 J: t7 p& S1 G" C/ _
2 define宏定义 define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供 方便。 常见的格式: 5 v+ d! N( ~3 w( Z7 N' p
标识符意思是所定义的宏名,字符串可以是常数、表达式或者格式串等,例如: - #define PLL_Q 7 //注意,这个定义语句的最后不需要加分号
复制代码 1 Y; y9 o" l3 C
3 ifdef条件编译 在程序开发的过程中,经常会用到这种条件编译: - #ifdef PLL_Q
0 L8 S$ o0 @# a$ R& r - 程序段1$ n a/ x7 z( s! M( u4 A3 ^
- #else
`" a; p: `' V - 程序段20 _+ n! l8 M6 [! z/ C* R1 S% _( q
- #endif
复制代码 上面这段代码作用就是当这个标识符已经被定义过,那么就进行程序程序段1,如果没有则进行程序段2。当然,和我们设计普通的c代码是一样的,"#else"也可以没有,就是上面的代码减去"#else"和程序段2。 - #ifndef PLL_Q //意思就是如果没有定义这个标识符
复制代码 % l X' `3 a# t0 `2 v4 c( |
4 extern变量申明 C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义(一个变量只能定义一次,而extern可以申明很多次)使用例子如下: extern u16 USART_RX_STA; 上面例子意思就是申明 “USART_RX_STA” 这个变量在其他文件中已经定义了,"u16"的意思是16位的。 . m! w" j+ R1 w8 j# o, j
5 结构体 定义一个结构体的一般形式为: - struct 结构名
" A4 o3 a: c1 y3 z8 m+ J4 D' i% [ - {
: ?0 L! [' x3 Y' Z8 Y- r - 成员列表2 y* g! y" H0 }* j
- };
复制代码
/ [2 c( y. a }: u4 I4 @ 成员列表由若干个成员组成,每个成员都是该结构体的一个组成部分。对每个成员也必须作类型说明,其形式: ' g: ^- Q* |1 O) Q# [
结合上面的说明,我们可以构建一个简单的结构体例子: - struct sutdent8 b. G' v. |, S) g& Q
- {
) d4 R/ t% q; O5 m( u- W - int num;' C; W) K/ O$ I( I' ^
- char name[20]; //20个字节长的字符
9 p4 R, k( }# |" w' w* j, \ - char sex; p8 W" g# I9 [0 I3 U' N0 J+ _
- int age;
# W m9 Y4 B+ v4 J+ T - float score;
1 M# n6 x j7 z* D0 Z - char addr[30]; //30个字节长的字符: D& Z3 |# ?9 J# I
- }
复制代码 0 C2 Y3 ?) `, r. |3 V
+ y0 D5 G) Q$ y) [; }
而如果我们想定义结构体变量,那么我们在定义这个结构体的时候直接定义,或者定义完结构体再另外定义结构体变量,比如: - struct sutdent' Z: p4 p* x* x
- {8 U8 x' y4 s( ]# K: v2 G
- int num;
& A( L+ ~) P3 [" X/ E2 J - char name[20]; //20个字节长的字符
* H" g! `# u5 o* c - char sex;
0 {/ @% @2 H1 g - int age;
2 E: {1 P5 |& { - float score;8 V' t G& i- W/ V6 i$ o
- char addr[30]; //30个字节长的字符, i* q. |6 U! H
- }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码
$ _4 T" t l8 [. y0 t. q4 a7 M
有时候我们可能需要用到结构体的嵌套,比如: - struct date) X5 G" _( p$ b( ^
- {4 e" m5 E1 `$ n; H2 Z, X' C. ]' Y) |
- int year, month,day;$ t: [0 ~- [+ U
- };
9 k7 t0 d+ A* a Y - struct sutdent P, c8 n0 k, l/ A' [& t
- {
" t! I6 g4 y2 H0 W8 C3 W$ U R3 s - int num;
3 J3 o* D @7 O- m - char name[20]; //20个字节长的字符/ Q+ M0 U: Z3 {, O8 w3 V
- char sex;
' [: O. ]0 Q4 E. I0 b- Y - struct date birthday; //这里就用到了结构体的嵌套
" G) j- J+ F- G) _# i% c5 E- v - int age;1 T6 h4 Q* @) A# p. x! r- J
- float score;
, y5 z& R+ ?! P6 E+ Y2 R - char addr[30]; //30个字节长的字符9 z" b$ F. j) R' q4 N
- }student01,student02; //变量名表列(如果由结构体变量名,那么我们可以不写结构体名称)
复制代码' i$ p$ Q4 O, X% q2 U& x
如果需要引用结构体里面的成员内容,可以使用下面的方式: - student01.name = 小李;
6 q7 k8 O, b& S5 B ` - // 结构体变量名.成员名(注意这里用的是点),这里是对这个成员的赋值
复制代码) l+ W% b" p, f+ ^( F3 {7 Q
结构指针变量说明的一般形式为: 5 r0 M9 _, i; X! T, `
假如说我们想定义一个指向结构体"student"的指针变量pstu,那么我们可以使用如下代码: 2 t1 n3 ]6 F5 d8 l
如果我们要给一个结构体指针变量赋初值,那么我们可以使用如下的方式: - struct student; \$ ]& K7 i- e: A6 h/ i
- {3 W8 h# ~% L# }# A
- char name[66];
8 d+ Z6 D( P4 p+ h' u - int num;
- u( h5 K5 N/ L! X6 i( m3 j - char sex;8 f3 H G8 X" ~3 R/ y) g% L; a
- }stu;2 M( \$ c* Q+ H4 F) N2 [4 ?
% A3 s! Z. v9 K: c- 4 S8 d- d3 Z. B- ^6 x& U
- pstu = &stu;
复制代码
. {% |4 V2 s) I& t
注意上边的赋值方式,我们如果要进行赋值,那必须使用结构体变量,而不能使用结构体名,像下边这样就是错误的。 - struct student
3 Y8 e8 ~9 G* {- F% T/ I - {
, _, J/ b# q) I! [3 ? - char name[66];0 ]9 H6 d! v" Z; w$ a/ {
- int num;
l l; b' O& {4 ~6 k6 P, X - char sex;8 d2 s( M: K' _# z; Q: T* a# \9 s
- }stu;3 V8 E# f" J! w; Q1 R8 h$ H, D y0 m
1 X+ E2 z h; n5 i0 w0 ?& i- 1 T; F) Y3 L$ m/ K) J$ `% q. }( ?2 u5 r
- pstu = &student;
复制代码 & O# V8 E8 M0 ^, u% c% R0 g3 ^% k
这是因为结构名和结构体变量是两个不同的概念,结构名只能表示一个结构形式,编译系统并不会给它分配内存空间(就是说不会给它分配地址),而结构体变量作为一个变量,编译系统会给它分配一个内存空间来存储。 访问结构体成员的一般形式: - (*pstu).name; //(1)(*结构指针变量).成员名;9 V& s; g9 s) U9 i) T7 Z
- pstu->name; //(2)结构指针变量->成员名
复制代码 结构体的知识就简单说上边这些。 6 typedef类型别名 typedef用来为现有类型创建一个新的名字,或者称为类型别名,用来简化变量的定义(上边extern变量申明的例子中,"u16"就是对"uint16_t"类型名称的简化)。typedef在MDK中用得最多的就是定义结构体的类型别名和枚举类型。 我们定义一个结构体GPIO: 定义这样一个结构体以后,如果我们想定义一个结构体变量比如"GPIOA",那么我们需要使用这样的代码:
$ m1 f, K$ B: Q; G 虽然也可以达到我们的目的,但是这样会比较麻烦,而且在MDK中会有很多地方用到,所以,我们可以使用"typedef"为其定义一个别名,这样直接通过这个别名就可以定义结构体变量,来达到我们的目的: - typedef struct- |. u3 G. Q7 P1 P9 n$ D
- {
' G& a( i( Y% d$ J4 l - _IO uint32_t MODER;
' V+ j0 D W* t$ ? - _IO uint32_t OTYPER;
' P0 {3 q' g% e9 ?; I F6 y: ^ - }GPIO_typedef;
复制代码
: u$ K& x9 m- A7 [- M
这样定义完成之后,如果我们需要定义结构体变量,那么我们只需要这样: - GPIO_typedef _GPIOA,_GPIOB;
复制代码 1 n9 _( ~3 `, O
|