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

【经验分享】stm32学习

[复制链接]
STMCU小助手 发布时间:2022-2-2 20:00
ARM处理器家族
  • 经典:ARM7,ARM9,ARM11
  • Cortex-M:单片机驱动(控制)
  • Cortex-A:开发式系统的高性能处理器(智能手机 ,数字电视)
  • Cortex-R:实时系统,实时的控制需求(汽车制动,动力系统)
    3 ~9 b' L+ ~: T
1 U7 f4 _, N6 M2 b8 b
想要操作stm32的外设通常可以使用寄存器或者stm32库,在当今stm32库已经十分成熟,并且操作十分简单。
寄存器:寄存器则是内置于各个 IP 外设中,是一种用于配置外设功能的存储器,就是一种内存,并且有相对应的地址。

! N, |/ X9 R3 s: _0 u' u! p
GPIO:通用输入输出接口
按键:通过输入电平的高低感知来得到按键的按下与否。
led灯:通过输出高低电平控制led的亮与灭。
GPIO也可以作为串口数据的收发管脚或AD接口等复用功能,根据数据手册配置相应的功能寄存器。

3 D% _3 r0 H+ f/ x$ U9 [/ I
GPIO寄存器MODER
32位两位为一组,分为了16个,分别对应每一组GPIO的16个 管脚。如PA0-PA15。
$ V8 {/ m2 N4 X& [0 }! K% ~: E5 v/ t
点灯:学习嵌入式开发最开始的操作就是点灯,以下使用寄存器操作和库函数操作来点灯。
寄存器:从技术参考手册中可以找到ODR 寄存器的地址偏移是: 0CH,这个偏移地址是基于端口的起始地址而言的。在 STM32 中,每个外设都有一个起始地址,叫做外设基址,外设的寄存器就以这个基地址为标准按照顺序排列,跟结构体里面的成员差不多。
假设我们需要点亮的灯是由GPIOB-0这个管脚去控制的,找到GPIOB对应的基地址,再加上ODR的偏移地址就可以去实现输出功能。

* t$ }+ q6 D; U, ]4 H 1746750-20190729102652344-890127595.png
# q/ c8 y$ ?$ ^6 P0 R: i% |7 s) j
$ \8 ]7 H8 E$ ?5 w, n  m 1746750-20190729102739578-1961042137.png
* ~, c0 a6 |, U; V4 h( @) ?, A/ L
其中 GPIOB 的起始地址是: 0X4001 0C00,这样就可以算出 GPIOB_ODR 寄存器的地址是: 0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义 GPIOB_ODR 这个寄存
/ C3 Y1 U2 m- C' A% y器了:
) ^5 V( g9 d/ c: f- \5 d2 J. D* h6 @# c+ x/ {! e% Z1 U
#define     GPIOB_ODR     *(volatile unsigned long *)0x40010C0C  u' {3 M' G; z/ |' S+ y5 N' ~
有了这个寄存器定义,我们就可以直接操作 GPIOB_ODR 了。虽然配置了 ODR 寄存器,但是这个时候还不能点亮 LED ,因为 STM32 的 IO 口还要配置方向,这个由端口配置寄存器来控制。端口配置寄存器分为高低两个,每 4bit 控制一个 IO 口,所以端口配置低寄存器: CRL 控制这 IO 口的低 8 位,端口配置高寄存器: CRH控制这 IO 口的高 8位。在 4 位一组的控制位中, CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0] 用来控制输出模式的速率,即输出时, IO 电平翻转的速度。6 ^( R5 N' K; e
输入有三种模式,输出有 4 中模式,我们在控制 LED 的时候选择通用推挽输出。9 a) D, ^+ n. G' ]* B7 A: R
输出速率有三种模式: 2M、10M 、50M,这里我们选择 2M。 7 W; A; s; @$ i0 X0 E
0 l+ L, O0 _& C
同 GPIOB_ODR 一样,我们也可以算出 GPIO_CRL 的地址为: 0x40010C00。那么设置PB0 为通用推挽输出,输出速率为 2M 的代码则如下所示:
6 {. T# a0 ?9 j/ M#define GPIOB_CRL *(volatile unsigned long *)0x40010C001 m0 N, V! a8 W% X) A# _

- s- _8 T# v; X7 p) _1 J! W& I// 配置 PB0 为通用推挽输出,输出速率为 2M! H3 z2 R& s# u8 X; g  {
GPIOB_CRL = (2<<0) | (0<<2); ( o7 _9 K7 g( t+ H2 f6 Q
1746750-20190729105851698-1624168984.png
% A1 b" x+ _! J8 V+ H8 y! f2 M, f/ V9 j; g+ t

7 X$ }- w( G3 P当我们设置了 IO 口的方向,并在相应的输出寄存器里面输入了值的时候,以为现在总算可以点亮 LED 了吧,其实还差最后一步。
  m9 F+ ~1 |: _: `STM32 外设很多,为了降低功耗,每个外设都对应着一个时钟,在系统复位的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。9 W1 Q5 {& B, {
STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC(reset and clockcontrol ), RCC 在 STM32 中文参考手册的第六章。
$ a9 ~' Q/ N5 L- P9 H; y  S8 G# R/ q# \" s; v4 J
STM32 的外设因为速率的不同,分别挂载到三条总系上: AHB 、APB2 、APB1 ,APB为高速总线, APB2 次之, APB1 再次之。
9 ^; D1 e& F+ h$ X0 j所有的 IO 口都挂载到 APB2 总线上,属于高速外设。时钟由 APB2 外设时钟使能寄存器 (RCC_APB2ENR) 来控制,其中 PB 端口的时钟由该寄存器的位 3 写 1 使能。9 j8 W0 O) W: t0 H( _. s
, ^1 o) V) b5 E# o, }7 ]. q+ |' x, P
1746750-20190729110046615-678393335.png : B% L5 T$ p: b3 T

. }/ p$ v! `  l* p* [5 a) ]1 x 同 ODR 和 CRL,我们可以算出 RCC_APB2ENR 的地址为: 0x40021018。那么使能PB 口的时钟代码则如下所示:# D* O6 y; L; V/ v& n6 R
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018% F) C  n% |8 H% W, |7 V( w
// 开启端口 B 时钟
7 w$ u9 W- b  |3 b  }5 U1 VRCC_APB2ENR |= 1<<3;* Q  s4 B" ?: s( L4 H

2 p; m" Y5 `/ ~9 I- v0 x我们虽然开了端口时钟,那这个时钟到底是多大?时钟到底是从哪里来的?
" c: t4 ]( `9 A, q0 P如果我们用的是库,那么有个库函数 SystemInit ,会帮我们把系统时钟设置成 72M 。现在我们没有使用库,那现在时钟是多少?
; n( _; s) ]3 h) J! Z5 _$ D6 H答案是 8M,当外部 HSE 没有开启或者出现故障的时候,系统时钟由内部低速时钟 LSI 提供,现在我们是没有开启 HSE,所以系统默认的时钟是 LSI=8M 。
3 x7 B( a4 L" g! ~$ f) q
5 \- f5 M. F6 E# g/ F. @如果你想自己先尝鲜,那么看 RCC 外设中的:时钟控制寄存器 (RCC_CR) 和时钟配置寄存器(RCC_CFGR) 这两个寄存器即可。
( @& `2 ]# t- S2 W" E8 @$ n8 r& n  }9 {; ?( _/ N' F
控制了电平,配置了方向,开启了时钟,经过这三步,我们总算可以控制一个 LED了。! o) k$ J9 P" B' }; w( Y
现在我们完整组织下用 STM32 控制一个 LED 的代码:; o8 B$ G; ~9 S- p0 N9 z) d
  1. #define RCC_APB2ENR *(volatile unsigned long *)0x40021018
    4 i+ u$ P$ m- a+ K2 f
  2. #define GPIOB_CRL *(volatile unsigned long *)0x40010C00
    . D# ~6 Q, H# t3 p7 Q& G6 V1 ^
  3. #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C1 ~) X" q( V; C# N
  4. int main( void )3 E! t0 L& r. n* M" G0 Z
  5. { // 开启端口 B 的时钟
    ' c! I1 L0 @3 w- A
  6. RCC_APB2ENR |= 1<<3;
    - Q* s# Q( i5 D0 g: p5 M
  7. 3 ^% s/ U- K' z7 F1 W  [7 e' [
  8. // 配置 PB0 为通用推挽输出模式,速率为 2M
    1 l! z; m3 C! f7 L
  9. GPIOB_CRL = (2<<0) | (0<<2);
    6 ~( f% D3 S! |+ `
  10. 6 |- ^6 Y) H. I6 u) r1 m! Z; ^
  11. // PB0 输出低电平,点亮 LED
    9 j" F2 y+ A. S; X! m7 [0 H
  12. GPIOB_ODR = 0<<0;
    / {! R, ~, A' u6 u- J3 N! W' f
  13. }
    + X( D4 r. W% O- t% ~0 ?
  14. ! I/ Z/ }% N+ X$ r& [6 `
  15. void SystemInit( void )
    . Q5 E( h) d3 T& H) P; p
  16. {
    1 R6 `6 n" s8 K5 m. e& N

  17. 5 r/ d! p8 s3 s% ?: O7 u
  18. }
复制代码

2 ~. l6 u# E2 H2 y
这种就是比较愚蠢的配置方式,每次配置一个外设就去从参考手册中去找,浪费时间且容易出错,但新手入门是需要理解并且操作学习一下的。
如果要以寄存器配置的话,会将所有的寄存器位置都先定义好,这样就不用一个个去找。实际开发过程中很少使用寄存器操作,一般都是库函数的操作,下面介绍库的操作。
" @) o6 b) w7 S3 C6 {9 b
keil的stm32环境自行去百度配置,篇幅太长就不写出了。
固件库分析:看看每个文件的作用是什么,这对我们能否清晰的调用库函数编程非常重要。
STM32 由 Cortex-M3 内核和内核之外的各种外设组成,库在编写的时候也遵循这中组成结构,把代码分成两大部分,一种是操作内核外设的,
另外一种是内核之外的外设,为了听起来不那么绕,下面我们把内核之外的外设用处理器外设来代替。
. b# Q  }" ?8 ^9 ~# ]8 L  ^
1746750-20190729135827753-1073564797.png

5 d9 T3 ]! Q- F9 [/ h7 K: u- u* G
  • 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 。2 q" k% v$ @& [6 k8 i. f- _
$ ^% |2 z7 H$ s& f8 r2 D
直接写库函数操作了:
  1. void LED_GPIO_Config(void); Y- c' d4 H% x7 \/ K! t- p& X
  2. {        
    5 P7 e" |0 l! D$ E
  3.     /*定义一个GPIO_InitTypeDef类型的结构体*/  M8 u$ _" x! J: |9 S
  4.     GPIO_InitTypeDef GPIO_InitStructure;  m! O- B9 a0 d) s% e2 P; h

  5. ; W2 C2 r: T- c7 d) S
  6.     /*开启GPIOC的外设时钟*/
    3 H, p" f6 U* M
  7.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); ( U3 k; r& J1 v  X/ y% \3 b

  8. & U8 D) H9 l! ~1 p: ^
  9.     /*选择要控制的GPIOB引脚*/                                                               
    ' G2 Q( l2 @1 W1 J
  10.       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;   
    5 s  k& _7 e" }, m& a0 J. U
  11. 4 Y) Z/ F' |7 w+ J6 E( C  R  V
  12.     /*设置引脚模式为通用推挽输出*/
    - r3 q7 j  Q. Z& j" {% k% F
  13.       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
    - d/ ?6 W' H& D* |

  14. $ H9 _% W" k; r7 X, |$ j
  15.     /*设置引脚速率为50MHz */   , E6 x/ a5 Q1 y& X
  16.       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    & W! W5 d2 }# U/ {- W6 }
  17. " H8 F9 I" Q& m
  18.     /*调用库函数,初始化GPIOB*/( `* z0 O/ f$ t1 [
  19.       GPIO_Init(GPIOB, &GPIO_InitStructure);         
    ' L; d( p/ X1 E1 c7 X, S. O8 }

  20. * [  d. L$ Q2 ~
  21.     /* 关闭led灯    */
    6 \7 u* l6 h# G! d! R
  22.     GPIO_SetBits(GPIOB, GPIO_Pin_0);     
    2 S3 N- V( @0 q) a8 s4 O
  23.     /* 开启led灯    */
    : J# E- h  v0 G$ B9 w7 V
  24.   GPIO_ResetBits(GPIOB, GPIO_Pin_0);
    ' y$ U. [) `4 k2 l6 R7 e
  25. }
复制代码
  1. typedef struct
    . C) h1 H% m% O  J
  2. {
    6 |$ P8 k! P5 z  N
  3. uint16_t GPIO_Pin;
    / x5 F- U2 H" p  d1 q! L( j# M
  4. /*指定将要进行配置的 GPIO 引脚** ^' E; o+ _- r2 q' g  p

  5. - k' D) O% z5 H  X" A  R
  6. GPIOSpeed_TypeDef GPIO_Speed;   
    9 ~4 k4 A1 N# ^, {+ S1 S
  7. /*指定 GPIO 引脚可输出的最高频率*/
    : `5 h: Q/ Y, N; O( K) x: C7 ^6 c8 S
  8.   p' s. q/ ]9 o% v+ }7 g
  9. GPIOMode_TypeDef GPIO_Mode;    5 y- \4 z+ u$ i
  10. /*指定 GPIO 引脚将要配置成的工作状态*/8 o7 a, ~  ^* i: G/ S8 E$ G3 V2 d
  11. * e7 r, h$ q" j% `: _, i2 T
  12. }GPIO_InitTypeDef;
复制代码
( T% U# K; A! U

! ~: D9 O  l/ K! s7 R" h( f
Mode和Speed相关结构体也是被设定好的,可以自行查找这里就不写出来了,直接赋值就可以了,十分方便并且相对于寄存器操作便于理解。
GPIO_Init(GPIO_TypeDef *, GPIO_InitTypeDef *);第一个参数,说明它将要对GPIOB 端口进行初始化。初始化的配置以第二个参数 GPIO_InitStructure 结构 体的成员值为准。这个结构体的成员,我们在调用GPIO_Init()前,已对它们 赋予了控制参数。
5 t; T6 O* p" V  |( Y

. N' |' U6 W$ s" U
% t4 D! C7 f8 |: a( a6 L7 c
0 }! l2 z8 B& i+ e& B. n$ u
2 R8 t3 S. Y$ G& V
赞 收藏 评论0 发布时间:2022-2-2 20:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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