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

基于STM32(ARM)开发初级经验分享篇二

[复制链接]
攻城狮Melo 发布时间:2023-4-12 18:58
在数字电路中时钟是整个电路的心脏,电路的的一举一动都是根据时钟节拍下进行的,随着信息量逐渐提高,对硬件信息处理能力提出了更大的需求,时钟作为数字硬件的关键成员,其性能需要我们关注,尤其在高速电路设计中对模拟转换芯片对时钟性能有很高的需求,因此正确选择时钟是很关键的一步,前提是我们要了解时钟的关键参数咯。在数字电路中最常见的时钟元件有晶振和锁相环、时钟缓冲器等,本节对系统时钟进行重点讲解。
$ C7 K3 b5 S; y2 y# N
STM32 系统时钟专题讲解
  x) o9 p# G! U9 p0 s4 n& e' @7 B
4 ^- I+ L* v  K$ G/ l
时钟对于整个硬件系统来说是十分重要的,每一个外设包括CPU,如果没有外部时钟的驱动就无法工作,时钟就相当于硬件的脉搏,在时钟驱动下完成指令执行。CPU和外设工作的快慢和工作效率常用时钟周期,主频来进行评定。为了让每一个外设包括CPU的工作效率达到最高,我们必须要对时钟系统进行设置。频率越高,系统越快。
* T: k$ {8 h3 g! Q1 X9 U+ r4 ~
时钟系统是由振荡器(时钟源)、定时唤醒器、分频器等组成的电路。常用的信号源有晶体振荡器和RC振荡器。
9 p; Y2 @0 j6 x( |
时钟是嵌入式系统的脉搏,处理器内核在时钟驱动下完成指令执行,状态变换等动作,外设部件在时钟的驱动下完成各种工作,比如串口数据的发送、A/D转换、定时器计数等等。因此时钟对于计算机系统是至关重要的,通常时钟系统出现问题也是致命的,比如振荡器不起振、振荡不稳、停振等。

# C/ f7 c+ x# m. h# _4 D
微信图片_20230412185234.png
& _6 ?" Q$ N0 C6 S
时钟源的频率一般是比较小的,需要使用倍频器倍频后共给CPU和外设使用,而外设的频率很多都没有CPU的频率高,所以又需要分频器进行降频,以适应外设的工作频率。

- c9 }( g; ]5 H
振荡器是用来产生重复电子讯号的电子元件。其构成的电路叫振荡电路,能将直流电转换为具有一定频率交流信号输出的电子电路或装置。
' D# C2 |7 x( E3 d0 m$ A& l* D2 m
晶体振荡器:
石英晶体振荡器是高精度和高稳定度的振荡器,被广泛应用于彩电、计算机、遥控器等各类振荡电路中,以及通信系统中用于频率发生器、为数据处理设备产生时钟信号和为特定系统提供基准信号。其优点是相对来说振荡频率比较稳定,精度也较高,精度不易受温度、湿度等环境的影响,但价格相对较高,使用时还必须配备两个起振电容。

. J0 G  i' W* k9 i3 L6 M
STM32中主要有四个时钟源:
HSI:高速内部时钟,RC振荡器,频率为16MHz;该时钟在系统一上电的时候暂时使用,此时外部管脚还没有初始化,还无法使用外部高速时钟,等系统初始化之后,通过配置时钟就可以切换到外部时钟源HSE,此后一直使用,倘若系统故障或瘫痪,仍然可以切换到HSI暂时使用,所谓内部是指在STM32芯片内部已经集成了时钟源。

: V6 g6 a$ l2 w. S* y: \
HSE:高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz,我们开发板是25MHz,所谓外部是指通过管脚外接一个时钟源
9 A4 B# t* R; f. A1 y+ A
LSI:低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使用。
" z3 @& I9 V% L: G. x0 x4 [
LSE:低速外部时钟,接频率为 32.768kHz(非常精确)的石英晶体。这个主要是 RTC(Real_Time Clock,实时时钟)的时钟源,保证时间的精确性,如我们日常的事件一秒一秒的计时,都用此时钟。

2 K+ w8 h' a. D1 Y9 V  O( }
由此看出,低速的时钟源都是给一些特定的外设使用的,而高速的时钟源供系统以及大部分外设使用

/ R$ I! C: k" ~  E
复位和时钟控制RCC( restoration and clock control)单元:主要是配置时钟系统的分频、倍频,以及使能外设时钟(即打开外设时钟的开关)等,所有的控制都在RCC单元中。

+ i9 B2 J6 A  Y/ O" t
时钟树:

- Y1 t2 Q" k& R2 p% M3 R7 }
微信图片_20230412185238.png

1 M- F6 m; x, U/ c
1:低速内部时钟,主要给独立看门狗进行使用
2:低速外部时钟,外接晶振,大小精确,主要供RTC使用
3:高速内部时钟,16MHz,SW是一个通道选择器,如果选择的是HSI,那么该时钟就被使用称为SYCCLK。SW作为选择器,有三种选择,即HSE、HSI、PLLCLK(经过倍频后的时钟),芯片正常工作以后常选择PLLCLK(HSE倍频后)作为时钟源,进入系统后还可以经过分频系统降频
4:高速外部时钟,通常范围是4-26MHz,STM32f407接的是一个25MHz的晶振,也可以通过SW进行选择成为SYCCLK
5:STM32f407的CPU可以承受的最大主频为168MHz,如果高速时钟源直接接入CPU,那么CPU的使用效率是很低的,所以应当经过PLL倍频因子倍频后再接入,使CPU工作效率达到最高,高速内部时钟和高速外部时钟都可以经过PLL单元的倍频再进入CPU,因此某些参考书中认为PLL单元也是一个时钟源是有道理的

, j" y+ i+ v* y! E' A' r
可通过多个预分频器配置 AHB 频率、高速 APB (APB2) 和低速 APB (APB1)。AHB 域的最大频率为 168 MHz。高速 APB2 域的最大允许频率为 84 MHz。低速 APB1 域的最大允许频率为 42 MHz。
) |- t5 r, A* L8 m& J
APB和AHB总线,类似于个人PC系统里的北桥和南桥总线。南桥总线上挂接的都是鼠标、键盘这些慢速的设备,北桥上挂接显卡等高速设备。南桥频率低,北桥频率高。另外,南桥最后也要接到北桥上。这些感觉都类似于APB和AHB。AHB,是Advanced High performance Bus的缩写,译作高级高性能总线,这是一种“系统总线”。AHB主要用于高性能模块(如CPU、DMA和DSP等)之间的连接。AHB 系统由主模块、从模块和基础结构(Infrastructure)3部分组成,整个AHB总线上的传输都由主模块发出,由从模块负责回应。APB,是Advanced Peripheral Bus的缩写,这是一种外围总线。APB主要用于低带宽的周边外设之间的连接,例如UART、1284等,它的总线架构不像 AHB支持多个主模块,在APB里面唯一的主模块就是APB 桥。再往下,APB2(高速APB)负责AD,I/O,高级TIM,串口1;APB1(低速APB)负责DA,USB,SPI,I2C,CAN,串口2345,普通TIM。
  j3 |# D7 y& V
STM32时钟配置实例:
微信图片_20230412185243.png
% g+ ~% e0 d, C, p0 G% K2 E3 w" s
配置外部时钟:
在时钟配置界面进行外部高速时钟配置时,发现该配置不可使用(文字图标体现为灰色),那是因为连接外部晶振的管脚还未进行初始化,因此先配置管脚。
9 c9 u/ p; }' T  E  z3 H+ ^5 F
微信图片_20230412185247.png

( C- Q% N# e6 n& H- R
在RCC配置页面选择外部高速时钟,选择晶振,之后连接晶振的管脚会自动高亮,如下图所示。

0 ]8 @* b2 ]2 G3 I% v: s1 `- _8 b! t
微信图片_20230412185253.png
2 S8 |' a; Y2 \3 c) Z
微信图片_20230412185258.png

8 ?1 E1 Z1 c; o+ O" \6 y/ a9 i4 e
根据计算,设置分频和倍频参数的值,使得外设和内核都可以达到自己的最大工作频率。

/ B* o& }$ j/ h& \4 P8 Z6 I# L
微信图片_20230412185305.png
- w. Z7 P" P' }* V
系统刚上电时启动时使用的是内部高速时钟源HSI,此设置在刚上电产生的复位中断处理函数中进行,即“=SystemInit”在这个函数中配置了一些列RCC的寄存器就是为了使用初始时钟的,之后跳转到main函数中,即“=mian”,才进行外部高速时钟源的配置。
8 o9 C6 l$ z' |- e7 H* l
微信图片_20230412185329.png
5 S$ G" n% d, Y/ F0 W8 o
微信图片_20230412185332.png

2 U( ?/ U  w% J, D) Z* G2 a5 ?
在SystemClock_Config();函数中会进行电源的使能以及分频和倍频参数的设置,以及对系统定时器的重载数值寄存器进行填值等功能,来操控RCC的寄存器实现功能。
; v: L# N: y# L' f7 n& x' ^
微信图片_20230412185335.png
% W  C% ~: N0 v1 g) R
SysTick定时器:能够定时、计数的器件称为定时器
SysTick, 称作系统滴答定时器,简称滴答定时器。是一个定时设备,位于Cortex内核中,可以对输入的时钟进行计数,当然,如果时钟信号是周期性的,计数也就是计时。
. k% J/ x: I9 c8 U, I( a$ q# F$ S
系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。根据这个中断,系统就可以实现时间片的计算从而切换进程。
- o3 I. l1 ]& C  `" ^2 G5 f* J
滴答定时器是一个24位定时器,也就是最多能计数2^24。在使用的时候,我们一般给计数器送一个初始的计数值,计数器向下计数,每来一个时钟信号,计数初值就减一,计数值减到0的时候,就会发出一次中断。然后重新从计数初值再减一计数,循环不断。

" b0 O) [5 S9 M9 Z; l
SysTick定时器的时钟来源是HCLK(168MHz),到达定时器的时钟可以选择分频,也可以选择不分频。默认情况下不进行分频,是168MHz。
" y/ E  x+ O" M
微信图片_20230412185338.png
) f# B  B8 ]# [# Q
微信图片_20230412190635.png
2 \$ Y, n5 ]9 N! n
重载数值寄存器需要用户自己填写,系统上电后,重载数值寄存器会将自己的value赋给定时器,重载数值寄存器的值设定好后,系统整个工作过程中中不会被改变。定时器每接收到一个CLK,就会将重载数值寄存器赋给自己的value值减1,一般时钟源越快,减到0的速度也越快。减到0经历的时长为vaue值常以一个CLK周期,在168MHz的情况下,一个CLK的周期为1/(169x1000000)秒。一旦减到0后就可以触发异常中断,同时重载数值寄存器会再一次将自己的value赋给定时器。所有的定时器的工作原理基本相同,SysTick定时器是系统使用的定时器,而用户专门使用的定时器为Timer。
$ Z1 c: C" |( z. R  a
微信图片_20230412185342.png
# {2 w) ~" ?8 C& t
需要注意的是系统定时器的中断配置在一初始化时就默认使能了,且默认1ms触发一次中断,因为系统的定时器常被操作系统使用,而且我们也不会去屏蔽它。如图,在初始化外部时钟源时,在SystemClock_Config();函数中会自动填充重载数值寄存器的值为时钟源频率除以1000,换算后可得系统每1ms触发一次中断。

& N3 k$ B; F6 L" L
微信图片_20230412185349.png
, C+ E2 Z/ Z4 \1 r
系统定时器触发异常中断时会进入异常处理函数SysTick_Handler(),此函数入口在中断向量表中存在,本质上上调用HAL_SYSTICK_IRQHandler () ;是真正的中断处理函数。而HAL_SYSTICK_IRQHandler ()调用了一个回调函数,而该回调函数是弱生成的,用户可以使用,在函数内部用户可以重写逻辑功能,若是带有操作系统,操作系统会重写这个回调函数,维持系统的时基,对多任务的进程进行计数。
. F1 j" o2 R. _7 S4 c: a; N$ H" Z
微信图片_20230412185355.png

& D3 ~2 D" i' l3 L  W- r
微信图片_20230412185401.png
" I/ ?, A! b: J9 _0 A
微信图片_20230412185405.png

5 L9 J4 ]: F/ x: r7 {
微信图片_20230412185410.png
+ q$ j' z$ k8 j  e0 W
由于系统默认每1ms触发一次中断,用户想要想要达到1s后进行中断处理功能,可让系统中断触发1000次,因此在重写HAL_SYsTICK_Callback ( void)回调函数时可进行如下编程:
  1. void HAL_SYsTICK_Callback (void){    ) X& s& n1 X  t
  2.   static uint32_t i=0;   
    6 H6 _8 `! {8 m
  3.   i++;   
    $ B- |/ d/ ^. a- [1 r* T& M
  4.   if(i==1000){        
    7 [" c  L3 N5 k9 u% `" k! ]+ a0 Y
  5.   printf("systick IRQ\n");//此处可改为用户目标处理函数        
    8 t. ^# |; H. f+ A* Z1 |, Y
  6.   } 7 i* P! i  A( [* m5 E, x9 J& o
  7. }
复制代码
3 ]5 K& K. i' j" C/ f
补充知识:static关键字在C语言中的用法:

& o' G; a  y: B8 D; B" z6 r* L+ p
在修饰变量时,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。以上述代码为例,每次触发中断进入回调函数中,i的值不为0,而是保留位上次回调函数中i的值。
, j4 g+ m- |+ V7 m  j8 P4 i
static修饰全局变量的时,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。
! h6 F7 l: }( E! t
static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。

: Q! V" \6 X' J3 h: W
HAL_Delay()的实现:
  1. //利用SysTick实现精准的延时
    # l; ^1 m6 B# P- [' Q7 F9 O- s6 z
  2. ( C" w8 W9 i8 B( k
  3. __weak void HAL_Delay(uint32_tDelay) {
    ) C2 E* |2 f: B% t
  4.   uint32_t tickstart = HAL_GetTick();//获取当前的uwTick值赋值给tickstart
      n  e" G2 e, e8 _) i. O# P
  5.   uint32_t wait = Delay;将用户要延时的值(单位ms)赋值给wait 2 _' h% C9 ?# N: @
  6.   while((HAL_GetTick() - tickstart) < wait) ; E9 `7 Y0 X( Y7 E0 {, r/ y+ i. v# s
  7.   //只有当条件为假的时候退出,否则一直循环 ( O; N; }  @0 x; O" }- u
  8.   //当最新的uwTick值与用户想要进入延时获得的uwTick的差小于
    ' G5 l+ a. G4 {+ ?1 _3 v
  9.   //用户输入的延时时间,说明延时时间未到,继续循环 : Y( k; h/ y  L/ N* U
  10.   { }
    % D6 H: e0 y; [
  11. } 8 y+ K1 m6 i. U; A# u
  12. " ^/ @( A# k! ]3 B/ X
  13. //下面代码均在文件 stm32f4xx_hal.c 中
    1 C6 W, b3 `9 ~9 h. f
  14. static __IO uint32_t uwTick; //定义计数全局变量 8 Z4 v& g- @3 x5 B. d
  15. 8 A* y* s" Z& g) y' J
  16. __weak uint32_t HAL_GetTick(void){   
    # h  f4 _) r! L
  17.   return uwTick; 4 m- P8 z5 D! i' A* D7 _2 x2 M
  18. } - O1 y- m8 ]) w6 [7 @% Q7 ]
  19. 4 ], c" F3 i- Y7 L) U: S. L4 z
  20. //全局变量 uwTick 递增
    7 U) b; R( [6 y3 C, e( q4 n; z+ E
  21. __weak void HAL_IncTick(void) {
    $ M3 ?: g; r# X" W6 v# A2 F9 d
  22.   uwTick++; 0 u8 V; x+ e4 x
  23. } ! }% B1 H5 X2 t& k" J8 `

  24. * x; l8 ?7 j0 X. t$ H6 B+ p! {
  25. //Systick 中断服务函数:文件 stm32f4xx_it.c 中
    . {. `" f# h  n. U+ Z/ N
  26. void SysTick_Handler(void) {  . }  k2 w, b4 Z& I5 s0 I  g
  27.   HAL_IncTick();  
    # u" |% M# N/ V
  28.   HAL_SYSTICK_IRQHandler(); . z: ]/ D) E+ n% m' O5 J  E" q
  29. }
    # T8 N0 l/ H" W% }( @1 [: H1 x
  30. + s) o9 W$ j! ?& I( W2 b
  31. 每隔 1ms,uwTick增加 1 8 ?# F' H/ M' |% |* S, B
  32. 由于系统默认的值每1ms会进行一次系统时中断, . H5 \6 D1 u: r
  33. 也就是每1ms会进行一次时钟中断处理 7 E( n( V# O  J/ s+ \/ O
  34. 在中断处理函数中,系统每次都会调用函数HAL_IncTick();   J' K3 R& K& b  O1 {
  35. 而函数HAL_IncTick();的功能是将uwTick的值加1
    1 _8 w/ Z0 I9 s6 K- L% e9 r' q9 u* T

  36. 2 M7 b1 F0 e0 ^0 C$ g
复制代码

% L/ T) o9 m3 J* W
由于系统默认的值每1ms会进行一次系统时中断,也就是每1ms会进行一次时钟中断处理,在中断处理函数SysTick_Hanqler ()中,系统每次都会调用函数HAL_IncTick();,而函数HAL_IncTick();的功能是将uwTick的值加1,由此可知,uwTick的值的单位是1ms。7 c. i* _/ e6 w& U0 ]) ]$ h

+ O% e1 p- A9 d. d1 B( B$ }4 x
由此可见,HAL_Delay()功能实现的本质就是要保障SysTick_Hanqler ()的正常运行以更新uwTick的值,因此一定要保证系统时钟中断的优先级在整个系统中处于足够的高的地位,不能有外部中断的优先级比系统时钟中断的优先级高。
/ D7 A9 d' f! _& _
HAL_Delay()  的局限:HAL库的延时函数有一个局限性,在中断服务函数中使用HAL_Delay会引起混乱,因为它是通过中断方式实现,而 Systick 的中断在一般操作系统优先级是最低的,所以在中断中运行 HAL_Delay会导致死锁的现象。

4 Z" G0 @% R/ S4 N4 q转载自: [color=var(--weui-FG-2)]骆驼听海如有侵权请联系删除* _0 H: Z! W& V: q1 w

  b/ M, z: W& k) `9 ?0 p# X7 N' e- C
. @  ]( v& ^) I) E. ~/ V6 [: z7 Y
收藏 评论0 发布时间:2023-4-12 18:58

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版