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

STM32单片机的C语言竟然这么简单

[复制链接]
STMCU小助手 发布时间:2022-9-19 21:58
C语言是单片机开发中的必备基础知识,本文列举了部分STM32学**比较常见的一些C语言基础知识,希望能对大家有所帮助。

( Z) ~; P2 l, O6 J3 H8 N
01 位操作
下面我们先讲解几种位操作符,然后讲解位操作使用技巧。C语言支持以下六种位操作:$ q# i* X7 l  }( [; t
640.png (六种位操作)
6 b+ `0 j/ {2 n0 k- Z! k3 J, Z% T9 w
下面,重点讲解一下位操作在单片机开发中的一些实用技巧。
/ |, |* t' }/ X3 H8 ~4 @1.1 在不改变其他位的值的状况下,对某几个位进行设值。0 B" @- L7 v) T
这个场景在单片机开发中经常使用,方法就是我们先对需要设置的位用&操作符进行清零操作,然后用 | 操作符设值。
7 B0 P% [- [+ k2 e; h* V; q
+ m* u! Y6 ^. Y, k# G6 O- |比如,我要改变GPIOA的状态,可以先对寄存器的值进行&清零操作:

1 T$ N3 u% R$ ~' S7 }+ ^: X) J1 c# s8 {% ^' @
640 (1).png 2 W0 y+ P" L4 z1 J! [
8 y" r4 z; W, e+ k. J
然后再与需要设置的值进行|或运算:
/ |0 t1 O. \+ i1 |% u9 J9 x% Z/ h8 A: i! S% k+ ]' ]2 u
640 (2).png
" e% W9 b4 p* h8 L8 s
7 i* k! @# a6 A9 p6 t6 V! T, u' ^" e1.2 移位操作提高代码的可读性。6 f) K: s/ y$ V- q$ R
移位操作在单片机开发中非常重要,下面是delay_init函数的一行代码:
  1. SysTick->CTRL |= 1 << 1;
复制代码

+ I7 \" u( z' E# s$ Q这个操作就是将CTRL寄存器的第1位(从0开始算起)设置为1,为什么要通过左移而不是直接设置一个固定的值呢?- k# S( v' r4 X2 F

  j) Y# X4 d2 d% w其实这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第1位设置为1。如果写成:
  1. SysTick->CTRL |= 0X0002;
复制代码

: h) V/ |% F: Y) T& h
& N) u% X6 I' A

+ H3 X" m3 @. o- \& o这个虽然也能实现同样的效果,但是可读性稍差,而且修改也比较麻烦。
9 [. K7 B9 Q( x' g1.3 ~按位取反操作使用技巧
+ @4 ?5 M; q- n+ W. Q按位取反在设置寄存器的时候经常被使用,常用于清除某一个/某几个位。下面是delay_us函数的一行代码:
  1. SysTick->CTRL &= ~(1 << 0) ;    /* 关闭SYSTICK */
复制代码

1 a4 C4 F4 p3 F! f8 `/ w, R
2 [2 y; _6 }' M, e% T; y) o2 t) y
& P7 c3 t& i, L7 |5 n
该代码可以解读为:仅设置CTRL寄存器的第0位(最低位)为0,其他位的值保持不变。
' V6 i2 T/ S* w/ u% G. _. X
$ X( J. \! v5 u  C: x* s同样我们也不使用按位取反,将代码写成:

    $ c/ d" ?5 Z/ Q" ?& Z
    1. <div><font face="Tahoma"><font size="3"><font color="#000000">SysTick->CTRL &= 0XFFFFFFFE;        /* 关闭SYSTICK */</font></font></font></div><div></div>
    复制代码
    ; @' P0 K/ B( V3 _
5 c: G/ T4 [! ?- _6 D
可见,前者的可读性及可维护性都要比后者好很多。
+ U# J; x9 c1 N1 m
! }1 n. \* N5 g/ t2 @- g1.4 ^按位异或操作使用技巧
, j; F7 Z7 U$ ?$ W该功能非常适合用于控制某个位翻转,常见的应用场景就是控制LED闪烁,如下:
  1. GPIOB->ODR ^= 1 << 5;
复制代码
, y  Q) [( Y  J8 V* N1 n

. q' y+ S! P4 n. {1 _1 G% m/ s执行一次该代码,就会使PB5的输出状态翻转一次,如果我们的LED接在PB5上,就可以看到LED闪烁了。/ p* o/ ^5 \  e  i2 Y$ |" H2 O
02define宏定义
define是C语言中的预处理命令,它用于宏定义(定义的是常量),可以提高源代码的可读性,为编程提供方便。常见的格式:  d. f7 D7 y. U* `
+ c9 F4 d1 W* ^% B# L2 K; r; a! L+ Q
640 (3).png
& {" }9 }' [0 V5 }' B) `0 p* i, ?) x& D6 o
“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:& l, k+ S4 v0 P. c1 t' v& D% h* S

+ e# L% C# q$ D  [4 ]5 `3 m* u5 v' _
640 (4).png
. s8 o" g' J+ |9 a0 o定义标识符HSE_VALUE的值为8000000,数字后的U表示unsigned的意思。至于define宏定义的其他一些知识,比如宏定义带参数,这里就不多讲解了。! m" [1 t! Y( n! N1 a

( u! }' G1 _. Y4 n- y
+ t# @  {8 z! K
; \* D. l5 W5 L. r- H- P
2 y5 |# P: e3 \) R8 u* Y, L* P# ?4 _/ Z2 ?3 v  C& p4 `  h
03ifdef条件编译
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。" e3 m6 W% f# m, l4 Y8 W% y9 k
条件编译命令最常见的形式为:
, v2 G$ U: O6 f. \: h, V; B
  1. #ifdef 标识符    程序段1#else    程序段2#endif
复制代码
5 Z4 ^7 q6 J6 K1 q  f  Q
它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。2 Z2 K' l8 |1 x" X7 y" U: h

6 x( L1 `/ v' h+ c! `# z, {其中#else部分也可以没有,即:
' ^1 f! b0 B1 o' }
  1.   #ifdef! ?1 A" N6 d: A4 S7 \! O. B" s: j
  2.     程序段1/ j# T) |0 T& `5 C7 ?, ^
  3.     #endif
复制代码
条件编译在HAL库里面是用得很多,在stm32mp1xx_hal_conf.h这个头文件中经常会看到这样的语句:
  1. #if !defined  (HSE_VALUE)      #define HSE_VALUE            24000000U    #endif
复制代码

, v* U, B7 O* }8 J; r/ K
如果没有定义HSE_VALUE这个宏,则定义HSE_VALUE宏,并且HSE_VALUE的值为24000000U。条件编译也是C语言的基础知识吧。5 h' o" Q+ k+ `9 d1 G
' T/ O1 k/ A  S9 r, L
这里提一下,24000000U中的U表示无符号整型,常见的,UL表示无符号长整型,F表示浮点型。" S* H& T  y7 `. t

9 S* R: u( v( f这里加了U以后,系统编译时就不进行类型检查,直接以U的形式把值赋给某个对应的内存,如果超出定义变量的范围,则截取。
: I) ?: O, ~- D- f

) d9 E8 p& ]6 D3 i, U! H9 Z# D
04extern变量申明
C语言中extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。7 d1 g- W; f3 b* s4 o9 g
这里面要注意,对于extern申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:
  1. extern uint16_t g_usart_rx_sta;
复制代码

3 F8 g) }% {2 @% \: e5 G! U: T( b, l# @) R" W2 z4 k
% Z; I* X. b0 c, \5 S" t
这个语句是申明g_usart_rx_sta变量在其他文件中已经定义了,在这里要使用到。0 c  H) Y3 ]9 O+ D3 Y1 B9 \/ E
所以,你肯定可以找到在某个地方有变量定义的语句:
  1. uint16_t g_usart_rx_sta;
复制代码

8 {! r; S9 W; n9 x
0 _  e' ^# Q8 i- W9 m2 vextern的使用比较简单,但是也会经常用到,需要掌握。
& \  o2 Q* U5 d6 e1 d) W# y* C$ q9 P  x
05typedef类型别名
typedef用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef在HAL库用得最多的就是定义结构体的类型别名和枚举类型了。' M' J/ Q2 o7 I5 S( e6 j0 P
  1.     struct _GPIO
    * c' x$ d) Z; d) V7 R, Y
  2.     {
    % n# v/ v, V1 r1 H% `+ b
  3.         __IO uint32_t CRL;
    $ S7 x' y* `# \8 t! l
  4.         __IO uint32_t CRH;1 L: i2 m% w1 V4 l
  5.         …$ }7 o+ d: Z: p* S; {
  6.     };
复制代码
定义了一个结构体GPIO,这样我们定义结构体变量的方式为:5 Q6 g* U) r& k) K+ j3 h
  1. struct  _GPIO  gpiox;       /* 定义结构体变量gpiox */
复制代码

) i7 C- ]6 g: g/ k! e; k8 e8 q但这样很繁琐,HAL库中有很多这样的结构体变量需要定义。( c' U1 O: s" y* q4 T& q
这里我们可以为结体定义一个别名GPIO_TypeDef,这样我们就可以在其他地方通过别名GPIO_TypeDef来定义结构体变量了,方法如下:

  Z) {2 z% O$ x
  1.     typedef struct8 [& Y2 V2 x! \
  2.     {9 H! w' M! R  }4 `
  3.             __IO uint32_t CRL;
    ' c7 [1 D$ G2 q4 O! M" }$ T7 F
  4.             __IO uint32_t CRH;1 i8 i1 b3 X# G+ ?$ m+ B
  5.             …/ }5 f+ k: r& \: C
  6.     } GPIO_TypeDef;
复制代码
Typedef为结构体定义一个别名GPIO_TypeDef,这样我们可以通过GPIO_TypeDef来定义结构体变量:GPIO_TypeDef gpiox;% H4 r) A: w  ?$ A2 z6 e

% u6 @4 r  Q+ e这里的GPIO_TypeDef就跟struct _GPIO
是等同的作用了,但是GPIO_TypeDef使用起来方便很多。# [0 I( j7 M8 }. I! M& K/ C

% Q' P' m  \# @6 w: l: V7 m5 ^5 f  L) V. ~; j
赞 收藏 评论0 发布时间:2022-9-19 21:58

举报

0个回答

所属标签

相似技术帖

官网相关资源

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