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

【经验分享】stm32学习

[复制链接]
STMCU小助手 发布时间:2022-2-2 20:00
ARM处理器家族
  • 经典:ARM7,ARM9,ARM11
  • Cortex-M:单片机驱动(控制)
  • Cortex-A:开发式系统的高性能处理器(智能手机 ,数字电视)
  • Cortex-R:实时系统,实时的控制需求(汽车制动,动力系统); y) `0 P: s3 _! O1 T. R1 O
8 {6 g! H( f4 l) I
想要操作stm32的外设通常可以使用寄存器或者stm32库,在当今stm32库已经十分成熟,并且操作十分简单。
寄存器:寄存器则是内置于各个 IP 外设中,是一种用于配置外设功能的存储器,就是一种内存,并且有相对应的地址。

# w: g1 r: r+ c, {3 p- f& `! \
GPIO:通用输入输出接口
按键:通过输入电平的高低感知来得到按键的按下与否。
led灯:通过输出高低电平控制led的亮与灭。
GPIO也可以作为串口数据的收发管脚或AD接口等复用功能,根据数据手册配置相应的功能寄存器。
2 }* {) Z8 f/ q! H! r! R
GPIO寄存器MODER
32位两位为一组,分为了16个,分别对应每一组GPIO的16个 管脚。如PA0-PA15。

( |" _0 J5 O6 C  a3 P; ?
点灯:学习嵌入式开发最开始的操作就是点灯,以下使用寄存器操作和库函数操作来点灯。
寄存器:从技术参考手册中可以找到ODR 寄存器的地址偏移是: 0CH,这个偏移地址是基于端口的起始地址而言的。在 STM32 中,每个外设都有一个起始地址,叫做外设基址,外设的寄存器就以这个基地址为标准按照顺序排列,跟结构体里面的成员差不多。
假设我们需要点亮的灯是由GPIOB-0这个管脚去控制的,找到GPIOB对应的基地址,再加上ODR的偏移地址就可以去实现输出功能。

3 o) q" r7 i) E$ k+ l' |. T 1746750-20190729102652344-890127595.png
4 |, D, q% P. x3 t* N" E# N! K
" _9 w* {# {! L# j) z: g 1746750-20190729102739578-1961042137.png
+ p  E# R4 h& u) O, H$ `- C) I" |7 `: @9 W% D, ^$ f3 b
其中 GPIOB 的起始地址是: 0X4001 0C00,这样就可以算出 GPIOB_ODR 寄存器的地址是: 0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义 GPIOB_ODR 这个寄存; e0 `3 |+ X# x' m8 I: [
器了:1 Z5 v! k0 d, L& T
- O! S0 y/ z) F: Y
#define     GPIOB_ODR     *(volatile unsigned long *)0x40010C0C
7 I, [. n9 E" J( q% x  e有了这个寄存器定义,我们就可以直接操作 GPIOB_ODR 了。虽然配置了 ODR 寄存器,但是这个时候还不能点亮 LED ,因为 STM32 的 IO 口还要配置方向,这个由端口配置寄存器来控制。端口配置寄存器分为高低两个,每 4bit 控制一个 IO 口,所以端口配置低寄存器: CRL 控制这 IO 口的低 8 位,端口配置高寄存器: CRH控制这 IO 口的高 8位。在 4 位一组的控制位中, CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0] 用来控制输出模式的速率,即输出时, IO 电平翻转的速度。+ X. P0 d, {! p  U. D7 w" F3 t# f3 [
输入有三种模式,输出有 4 中模式,我们在控制 LED 的时候选择通用推挽输出。  A& k8 r* b. w
输出速率有三种模式: 2M、10M 、50M,这里我们选择 2M。 8 _+ c, B7 N  V1 ^  u" R- `

5 V* |/ J7 D$ t! `) S同 GPIOB_ODR 一样,我们也可以算出 GPIO_CRL 的地址为: 0x40010C00。那么设置PB0 为通用推挽输出,输出速率为 2M 的代码则如下所示:' J4 k/ [7 _9 r6 T4 g- S
#define GPIOB_CRL *(volatile unsigned long *)0x40010C00* ?# t- h/ q8 q9 r/ T  Y+ |" v9 _
2 P% d- z2 j% l
// 配置 PB0 为通用推挽输出,输出速率为 2M
# j2 {7 Q1 q; s  a0 ]" U5 nGPIOB_CRL = (2<<0) | (0<<2);
3 K& `$ w; W* u5 S$ b( x$ O 1746750-20190729105851698-1624168984.png
6 f' I( q9 A( v* m& Q0 y  n- x
, t$ t1 I' x4 h5 `4 z# K( ^9 R+ j1 T0 ^1 x7 P( ?
当我们设置了 IO 口的方向,并在相应的输出寄存器里面输入了值的时候,以为现在总算可以点亮 LED 了吧,其实还差最后一步。, c2 `2 u; U! _' n: t
STM32 外设很多,为了降低功耗,每个外设都对应着一个时钟,在系统复位的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。8 z4 L0 R5 s1 l2 k3 l& k
STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC(reset and clockcontrol ), RCC 在 STM32 中文参考手册的第六章。 5 K: m( j( F- c- O. q* u% r2 b! T: O  N

6 t: G4 u& f  X0 o2 |1 d3 U STM32 的外设因为速率的不同,分别挂载到三条总系上: AHB 、APB2 、APB1 ,APB为高速总线, APB2 次之, APB1 再次之。8 J, L; y) @- M: `
所有的 IO 口都挂载到 APB2 总线上,属于高速外设。时钟由 APB2 外设时钟使能寄存器 (RCC_APB2ENR) 来控制,其中 PB 端口的时钟由该寄存器的位 3 写 1 使能。
$ `" ]8 ]# J; @& N( w4 e
7 C$ D$ A, C$ q$ l' W 1746750-20190729110046615-678393335.png & t. e0 B4 a3 }: J  O5 P) E

4 g. @$ `. e& w, l- r$ p' t* D4 n 同 ODR 和 CRL,我们可以算出 RCC_APB2ENR 的地址为: 0x40021018。那么使能PB 口的时钟代码则如下所示:/ k: d3 B3 j( T8 H2 Z* u' R& H
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018; H) q6 Q2 v% s9 P: L4 @8 W# ~
// 开启端口 B 时钟; w% G4 g9 U& W6 v5 J
RCC_APB2ENR |= 1<<3;; }6 ^. w$ F9 @' u6 T

6 H4 U, H; _) Z0 Q$ X7 k我们虽然开了端口时钟,那这个时钟到底是多大?时钟到底是从哪里来的?
, }  j# L' s9 G- ~& t8 ]如果我们用的是库,那么有个库函数 SystemInit ,会帮我们把系统时钟设置成 72M 。现在我们没有使用库,那现在时钟是多少?, m+ u$ Q6 L/ w. W2 I/ {, `; b
答案是 8M,当外部 HSE 没有开启或者出现故障的时候,系统时钟由内部低速时钟 LSI 提供,现在我们是没有开启 HSE,所以系统默认的时钟是 LSI=8M 。
) V4 F; g$ g) [0 O
2 [& S4 r* C3 H如果你想自己先尝鲜,那么看 RCC 外设中的:时钟控制寄存器 (RCC_CR) 和时钟配置寄存器(RCC_CFGR) 这两个寄存器即可。
0 G' m$ B% ], u. m& o! A4 C
, T! r6 P! ?, {2 d5 t0 L控制了电平,配置了方向,开启了时钟,经过这三步,我们总算可以控制一个 LED了。
  E/ `  C5 C# _4 G6 u现在我们完整组织下用 STM32 控制一个 LED 的代码:9 [& p1 p" h( ?4 D: C2 G" o4 o, n0 D
  1. #define RCC_APB2ENR *(volatile unsigned long *)0x40021018# I! L# ?+ M! w1 Y7 ~8 `; c
  2. #define GPIOB_CRL *(volatile unsigned long *)0x40010C00
    6 P7 {& C5 ~) H( F6 M* w
  3. #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C0 A/ h1 S1 k- e4 m; A
  4. int main( void )
    3 I9 f3 s2 I% J+ M& O" G
  5. { // 开启端口 B 的时钟
    ) s# V5 Y: _1 r' R: K4 `9 s7 E
  6. RCC_APB2ENR |= 1<<3;
    & X. {5 s9 e/ T* s

  7. / ]4 T/ i( V9 [% f& m
  8. // 配置 PB0 为通用推挽输出模式,速率为 2M
    . Q+ B3 w+ Z/ n
  9. GPIOB_CRL = (2<<0) | (0<<2);0 c. y* {# M' K- f9 a: L

  10. ) w$ ?: e4 E( t4 F. I# ^
  11. // PB0 输出低电平,点亮 LED1 ^! w- p8 P& x, e0 S. P
  12. GPIOB_ODR = 0<<0;" Y4 g# C; L3 D' Z
  13. }
      L6 n6 D( R- Q6 d# D4 y
  14. * ]8 }' `: e% B! U8 K4 v
  15. void SystemInit( void )& O5 O; t7 `7 [8 \3 \
  16. {. |  W- M6 R: Y" A) s8 s
  17.   b& I; U# u7 q3 I" c, \
  18. }
复制代码
1 z4 Q9 x" J* O
这种就是比较愚蠢的配置方式,每次配置一个外设就去从参考手册中去找,浪费时间且容易出错,但新手入门是需要理解并且操作学习一下的。
如果要以寄存器配置的话,会将所有的寄存器位置都先定义好,这样就不用一个个去找。实际开发过程中很少使用寄存器操作,一般都是库函数的操作,下面介绍库的操作。
. U. t2 o6 I* ]# p
keil的stm32环境自行去百度配置,篇幅太长就不写出了。
固件库分析:看看每个文件的作用是什么,这对我们能否清晰的调用库函数编程非常重要。
STM32 由 Cortex-M3 内核和内核之外的各种外设组成,库在编写的时候也遵循这中组成结构,把代码分成两大部分,一种是操作内核外设的,
另外一种是内核之外的外设,为了听起来不那么绕,下面我们把内核之外的外设用处理器外设来代替。

+ E* F0 V* o  R5 z3 v/ @
1746750-20190729135827753-1073564797.png

) c! Z( G: r5 }- F+ r+ K! Q
  • startup_stm32f10x_hd.s:这个是由汇编编写的启动文件,是 STM32 上电启动的第一个程序,启动文件主要实现了:1、初始化堆栈指针SP;2、设置 PC 指针 =Reset_Handler ;3、设置向量表的地址,并初始化向量表,向量表里面放的是 STM32 所有中断函数的入口地址4、调用库函数SystemInit ,把系统时钟配置成 72M ,SystemInit 在库文件 stytem_stm32f10x.c 中定义;5、跳转到标号 _main,最终去到 C 的世界。
  • system_stm32f10x.c:这个文件的作用是里面实现了各种常用的系统时钟设置函数,有 72M,56M, 48,36,24,8M,我们使用的是把系统时钟设置成 72M。
  • stm32f10x.h:这个头文件非常重要,可以说是上帝之手。这个头文件实现了: 1、处理器外设寄存器的结构体定义 2、处理器外设的内存映射 3、处理器外设寄存器的位定义。
  • stm32f10x_xxx.h:外设 xxx 应用函数库头文件,这里面主要定义了实现外设某一功能的结构体,比如通用定时器有很多功能,有定时功能,有输出比较功能,有输入捕捉功能,而通用定时器有非常多的寄存器要实现某一个功能,比如定时功能,我们根本不知道具体要操作哪些寄存器,这个头文件就为我们打包好了要实现某一个功能的寄存器,是以结构体的形式定义的,比如通用定时器要实现一个定时的功能,我们只需要初始化TIM_TimeBaseInitTypeDef 这个结构体里面的成员即可,里面的成员就是定时所需要操作的寄存器。 有了这个头文件,我们就知道要实现某个功能需要操作哪些寄存器,然后再回手册中精度这些寄存器的说明即可。
  • stm32f10x_xxx.c :外设 xxx 应用函数库,这里面写好了操作 xxx 外设的所有常用的函数,我们使用库编程的时候,使用的最多的就是这里的函数。
  • cor_cm3.h:这个头文件实现了: 1、内核结构体寄存器定义 2、内核寄存器内存映射 3、内存寄存器位定义。跟处理器相关的头文件 stm32f10x.h 实现的功能一样,一个是针对内核的寄存器,一个是针对内核之外,即处理器的寄存器。
  • misc.c:内核应用函数库文件,对应 stm32f10x_xxx.c 。在 CM3 这个内核里面还有一些功能组件,如 NVIC、SCB、ITM 、MPU、CoreDebug ,CM3 带有非常丰富的功能组件,但是芯片厂商在设计 MCU 的时候有一些并不是非要不可的,是可裁剪的,比如 MPU、ITM 等在STM32 里面就没有。其中 NVIC 在每一个 CM3 内核的单片机中都会有,但都会被裁剪,只能是 CM3 NVIC 的一个子集。在 NVIC 里面还有一个 SysTick,是一个系统定时器,可以提供时基,一般为操作系统定时器所用。
  • misc.h:内核应用函数库头文件,对应 stm32f10x_xxx.h 。9 u" \0 h7 M: }* w- K8 E3 M
: j6 m. _9 Z2 G% [& d& ]
直接写库函数操作了:
  1. void LED_GPIO_Config(void)# _/ M1 ]% t% |0 C/ V/ _0 N
  2. {          P6 ]2 h$ S( R& _
  3.     /*定义一个GPIO_InitTypeDef类型的结构体*/
    6 s' f' K3 S, E$ d, q+ R, Z
  4.     GPIO_InitTypeDef GPIO_InitStructure;
    9 i# a) U0 c2 [! t0 ]! f
  5. " r4 V* a' L( h* D& b: m+ Z
  6.     /*开启GPIOC的外设时钟*/
    ! B) Y6 U, B% O( Z
  7.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
    ! I" k4 j* R$ w
  8. $ e: X  y# n* {0 N4 K! V0 T+ {
  9.     /*选择要控制的GPIOB引脚*/                                                               
    3 L8 h. l/ p1 J& P, u- r7 x4 S
  10.       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;    - F3 ~$ F  E% h! `; m% w

  11. , h. d' U' j& ]: D0 a
  12.     /*设置引脚模式为通用推挽输出*/
    5 `3 m9 V' @/ Y2 G. B, N/ c" l
  13.       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   & V3 k# P$ w5 ?% F3 O# b, m

  14. * L- E8 W2 `4 A# [5 v  l
  15.     /*设置引脚速率为50MHz */   
    . }+ s1 A9 }: z# b: `
  16.       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 0 B2 m4 h3 o) c7 h

  17. & }7 _' N. w$ Z  j
  18.     /*调用库函数,初始化GPIOB*/
    7 X' k1 ?' Q, [5 C7 p* ]; L
  19.       GPIO_Init(GPIOB, &GPIO_InitStructure);         
    6 \/ B7 I$ a: k; [. P) ]- `

  20. . h! D* g$ _- Y- [
  21.     /* 关闭led灯    */% M7 G& Q0 ~. Z+ R  o
  22.     GPIO_SetBits(GPIOB, GPIO_Pin_0);     + F* w0 ^: a$ d9 f
  23.     /* 开启led灯    */
    & L) O1 l% C) W6 a: z# K2 o5 L
  24.   GPIO_ResetBits(GPIOB, GPIO_Pin_0);
    : r) C8 I! a/ @7 F6 m
  25. }
复制代码
  1. typedef struct6 j: k) @, T' ^; ?8 ~* b/ |
  2. {
    : K. d% l% g: Z1 l& J8 k
  3. uint16_t GPIO_Pin;
    8 ?. n5 ]8 l* r( C7 `2 D
  4. /*指定将要进行配置的 GPIO 引脚*  h. D1 ^9 \( x) X6 u1 H: a: u
  5. " k& [& C2 E4 ~- B3 w. [
  6. GPIOSpeed_TypeDef GPIO_Speed;    $ s( N9 ~: y) E( F- _; q; H9 K! c
  7. /*指定 GPIO 引脚可输出的最高频率*/" T, {: \9 g- b3 E+ O  p$ T7 A

  8. ' c# g3 \& L% X4 M1 ^
  9. GPIOMode_TypeDef GPIO_Mode;    1 ?9 m2 o4 Q3 k" N
  10. /*指定 GPIO 引脚将要配置成的工作状态*/
    2 f- A9 U2 z& e9 }+ l

  11. 1 H# g& R2 ?' `6 R( p& U6 t
  12. }GPIO_InitTypeDef;
复制代码

4 e5 u3 Y# |! b1 U4 p+ z% U( C4 x. V2 ]
Mode和Speed相关结构体也是被设定好的,可以自行查找这里就不写出来了,直接赋值就可以了,十分方便并且相对于寄存器操作便于理解。
GPIO_Init(GPIO_TypeDef *, GPIO_InitTypeDef *);第一个参数,说明它将要对GPIOB 端口进行初始化。初始化的配置以第二个参数 GPIO_InitStructure 结构 体的成员值为准。这个结构体的成员,我们在调用GPIO_Init()前,已对它们 赋予了控制参数。
8 H9 y" N; j( X/ u
! [& v2 s8 K( q* e" y- T
! h5 K) M. ^. _- g
) E/ H" I# m; }2 S* ^( j' K

) k- _( O. A+ u7 {
赞 收藏 评论0 发布时间:2022-2-2 20:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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