1.前言
# [% s: D6 Q2 [! Y4 j一直没有正经从完全无知开始去接触了解一款新的stm型号芯片。趁着这次11月份的新项目的开端需要用到stm32L0系列的单片机,搭载lora模块进行项目开发。这里开个系列博客记录下学习过程和调试过程。
% }7 d# n/ B# c$ K+ z% K) s
$ o: t% l5 G! y2.开发工具4 l E+ q( V, M2 A& C) w3 b9 g
以前做stm32的开发都是基本用的stm32F系列,代码结构也是套用的公司以前的模板。# w1 z4 C7 V" p. ~- \+ r% ~
本次L0系列采用了st公司提供的一个新的(对笔者来说是新的,之前没用过,其实以前出来比较久了)开发代码生成工具-STMCubeMx。1 I8 Z2 q" M, n8 ]6 E; Z
/ z7 C* E% g9 O; M {/ i. A, h; i4 Y0 Q8 a7 D b& P
9 r0 W, o! `5 e1 F0 M# F( o; t
这个就是安装完后的工具图标。具体安装下载过程这里不做赘述,不了解的朋友可自行百度。
U l- p) f% R5 O" a
5 N$ r. s0 J8 K4 Y( U3.简述开发工具生成的代码结构' R A! y8 b. B3 E8 L
Stm32CubeMx自动用户所选的IC型号以及需要用到的mcu资源,外设等自动生成相应的代码,生成的代码使用HAL库。其代码结构基本不需要进行什么修改(用户所选所设置皆正确的情况下),仅仅需要对一些HAL中的回调进行重写(如果你需要)。) s5 C: W* O5 I/ \- ` {
+ l& z0 G, W5 {, }5 E2 Q
( k3 B) s4 G# X" E1 f如图,代码编译工具还是使用keil软件,根据图中代码结构可以很清晰的看到整个代码分为 j: l$ o9 d# q' B& B
Application和Drivers(附加CMSIS)
" h; U3 |2 T- h3 U. }Application里分为MDK-ARM和User。
2 t0 U6 L/ @7 r5 Q) b; nMDK-ARM是代码工程的一个目录(基于编译,比如编译生成文件,中间文件.o之类的)
2 I: b' d) w5 u# ?User是用户程序的主入口,如main.c等。
; s! `$ T, q9 m需要注意的是User中还包含除main.c之外的两个文件,其中一个msp文件是工具自动生成的,后面分析。ic文件则是和中断回调相关的,这个相比都比较清楚。
0 H; l& \0 V+ ? v4 s3 a4 R' [* v% B4 I& X+ y8 C5 T! a
" h; Y: C. T( w5 i( ^, _' U& z4 U
在Drivers中包含HAL_Driver和CMSIS.+ C' z4 W8 G; U }% j
CMSIS则是和内核相关的,如M0,M3等之类的内核。8 k3 ?' u i' j% T# i
HAL_Driver则是st给用户提供一些接口,如flash,rtc等的操作接口。$ b) ]3 h& @! F: o: ]7 U, V: l% \) V
1 \. `: Q `& _2 D7 z+ }
3.1 main.c简析
9 R" m: C# Z W7 R最简单main.c里面的代码至少包括三部分:HAL_Init,SystemClock_Config,while(1);
4 n5 N8 w: E8 X& Y) j+ g* j6 X
4 H/ b, t$ b3 \- int main(void)$ [9 z1 G; ^& N K, V3 o3 T7 M
- {1 z9 n0 q5 C5 { R2 {5 c$ t6 I
- HAL_Init(); //HAL库的初始化$ v3 c- g" G4 D( W1 M: v7 [/ {
- SystemClock_Config();//系统时钟配置初始化
" n7 A# a7 ^# s - while(1)//主程序的运行" c2 _. S3 _3 P" I4 I$ J; Q, d
- {3 S7 j2 U$ n, C) W! q& H
- }
5 P* u3 `# P& S3 }2 G* ^" s* y - }
复制代码
$ M! c" }) L' G0 E3.2 代码运行流程以及HAL库的调用结构
- _ q! T' U, Y u3.2.1 HAL_Init() n9 k |8 V2 Z) d. I* u; a( R
- /**2 V$ i3 {2 H! c
- * @brief This function configures the Flash prefetch, Flash preread and Buffer cache,
7 y z, X; J$ r" ] j3 e3 k$ Z% [ - * Configures time base source, NVIC and Low level hardware
+ s$ m$ P/ l' K( x @ - * @note This function is called at the beginning of program after reset and before2 U: s! \6 G* ^8 }
- * the clock configuration! k4 l1 j/ l2 n1 T% D
- * @note The time base configuration is based on MSI clock when exiting from Reset.
7 U* _& y. F. Z2 i6 z( b) b - * Once done, time base tick start incrementing.* K# D; V5 P% W. y- G% j
- * In the default implementation,Systick is used as source of time base.
; ?' W% i/ a& [& n* C4 M - * the tick variable is incremented each 1ms in its ISR.
& G% T" {: F' c. W/ v) ], ~" g1 n( v - * @retval HAL status
2 ]# w3 f) {2 c9 d" T$ H1 q - */, g: l) q6 V6 m7 b
- HAL_StatusTypeDef HAL_Init(void)- M8 P+ o" m$ C! d
- {
! e5 Q. G$ G& b" [* M - HAL_StatusTypeDef status = HAL_OK;
7 k- ~# O' i: K# O
- C( l9 }- M# F5 ^# A- /* Configure Buffer cache, Flash prefetch, Flash preread */
, M' H7 x- P/ T - #if (BUFFER_CACHE_DISABLE != 0)6 b7 [9 f3 E7 s6 `* b' @' q
- __HAL_FLASH_BUFFER_CACHE_DISABLE();
1 O" ` B6 y4 H# G - #endif /* BUFFER_CACHE_DISABLE */: f3 [7 F# i3 M2 x
- / h# @( u! E8 ^' d" }( J
- #if (PREREAD_ENABLE != 0)+ i; y \ |# B5 C. s# Q
- __HAL_FLASH_PREREAD_BUFFER_ENABLE();2 ]/ U& S$ {. ]( Z) O) p* P
- #endif /* PREREAD_ENABLE */
: A9 l W' r8 d$ Z% {
. a( H+ x9 Z) h/ a; U# l8 b/ W# m- #if (PREFETCH_ENABLE != 0)
) J3 D* e6 |' L% B1 h - __HAL_FLASH_PREFETCH_BUFFER_ENABLE();( [- M7 ?1 ?- s& d
- #endif /* PREFETCH_ENABLE */0 Y: Y n# t* }9 w- _
- 6 [/ @6 C7 J/ w' `. f9 [& S- w
- /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */ i+ d7 @) R8 P- s0 e
- if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
0 f$ M J: u8 [9 r) c5 C8 s3 `& I" F% J - {; z3 p: X2 l- Z7 H; f$ F9 E
- status = HAL_ERROR;
2 |1 O8 l& g& _4 J - }
' Z2 u! T+ T, H3 T+ f3 F8 c - else
. _2 E, i( X; [- v3 J ~( r - {
3 d: W& J% n# p! K& V/ @ - /* Init the low level hardware */
4 I2 w* u7 S k7 T1 ^' q - HAL_MspInit();
9 f, H* D+ z- }/ {" I - }
* B2 H N8 [1 k0 X - ) \) ]6 c8 k. g! u: S- W# z$ S: w
- /* Return function status */
' }/ I2 Q. Z) ]& ~# ^& k - return status; O6 _8 Q+ I. Y) `$ G
- }
复制代码
) u* |. l" u: Y- [这里抽出2个关键函数:. _% X- t$ Y/ k
HAL_InitTick,HAL_MspInit6 W: L7 a+ p: |$ m" Q6 X
可以看到mcu一启动则调用HAL_InitTick,它的功能是使用SysTick作为时基源并配置1ms刻度(复位后的默认时钟为MSI)" J( N1 Y' q+ V* V" L0 y
如果该函数出错,则调用HAL_MspInit。
?6 ]) x, U a* y" i" G9 {这里提下该函数HAL_MspInit是被__weak修饰,叫做弱函数。被这种修饰的函数,通常需要用户自己去实现。实现的位置在哪里?还记得前面提到msp文件吗,就在那里面,当然你也可以写到你想写的地方,msp这个文件只是为了方面管理整个代码工程中的需要重写的函数。" P' Z+ c3 ~) V/ ?
+ S/ f7 v/ k/ i. v$ q6 ~3 w3.2.2 SystemClock_Config()
' h. i5 D o: o$ V- X, W- /**
4 L, y1 W+ V, J0 V6 }; { - * @brief System Clock Configuration6 u& T% ?; Z& x
- * @retval None
" ~8 ]3 G M' r! { I; ^ - */
! {$ j9 o% G7 d, {7 i - void SystemClock_Config(void)
% `4 Z/ I3 `+ P% ` - {# z4 U' F; l6 U0 i+ T
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};& w6 G7 T$ L# d- I% X) d' i
- RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
5 h. B1 v( p: D! Y" C" G; n2 l$ D - - U1 c7 d$ I! x" p
- /** Configure the main internal regulator output voltage ) n0 D1 N, d6 c3 W2 L" Q
- *// m- y. F% ~; ^" P# \3 O3 z a1 ^. c
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);! [# |' u1 i6 D: D; D
- /** Initializes the CPU, AHB and APB busses clocks 8 O' F5 Y1 v$ H# E& s+ M4 K6 L( l$ E- q$ F
- */: l' Z/ r; |1 k: Q& y
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;5 s" E! l7 g5 R3 T5 p
- RCC_OscInitStruct.MSIState = RCC_MSI_ON;
6 q' \% o: N8 c2 }2 e; Z6 b - RCC_OscInitStruct.MSICalibrationValue = 0;5 \6 ?: u: k2 }
- RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;3 x# P* a2 x6 N7 A5 E# ?. L
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;# S' }' x* F& R8 K- \
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)0 u4 }' c4 P1 b5 v
- {
, ^( N$ b+ ~; o* G8 C) g( O - Error_Handler(); j# ^) Q) e1 {! |& j2 `
- }" c/ E2 D8 v4 Y
- /** Initializes the CPU, AHB and APB busses clocks
' ~$ }4 K- s8 N3 {- ~6 C3 Z( s - *// B T1 @3 b' D) M }
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
" }; k2 Y7 Q- h& d5 G" [; q) G - |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;( Q2 {4 y7 s& v% L9 c$ N$ B9 R
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;2 f9 r* d# u' `. C0 d3 {
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
; i3 l9 M% p7 |3 q! t8 l0 ? - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
- X$ ?' J+ @& p/ D' f - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;0 p; C+ O. n3 x) }- i9 K
- + F; n4 o3 C6 Z
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
; { X: a4 L; V: n# n! v1 A* ] - {+ w$ a4 |0 x9 \* K: f* _
- Error_Handler();
: \- C, `7 ^+ w. H* l# {) Y/ T - }$ k9 J! I0 ^) V2 [0 B) X
- }
复制代码
0 p- q' ? [1 i9 I6 F8 Q这里可以看到对于mcu的时钟选择配置,完全对上刚开始创建工程时的选择。先简单将这个函数分隔下:! }' D2 ? |, }: y# M& U
第一步:配置内部主稳压器的输出电压
0 {4 T+ {, t3 ?4 b第二步:初始化RCC振荡器。HAL_RCC_OscConfig,这里选择了MSI时钟,频率是4.194Mhz4 m* i3 D( _4 t2 _" K/ N/ |+ n5 m
第三步:初始化CPU,AHB和APB总线时钟,HAL_RCC_ClockConfig8 V. p8 x$ j, T9 A
如图;4 M B# Q; @, `( r$ b% ?; _6 [: d
5 M" M9 r( ^ u5 b7 t- s
9 {! c7 D1 _$ n( _
6 S3 e. Q. X5 n; kps:关于STM32l0的时钟相关,可以参考datasheet。也可以往官网下载(英文版)% m& ]5 \$ Y1 M+ O9 R0 R7 k1 N! T
6 Z' |0 E8 X' T: P2 p3 T6 O
4. 话说在前面系列
5 \0 q& p7 D9 H" f5 H在使用St’m32CubeMx生成代码的时候,后期单独修改代码的时候,需要注意:. d( h: R( ^. |6 s
1.stm32l0xx_hal_conf.h 头文件指定了哪些文件是否参与编译,文件内是按模块声明的。# V8 |; o/ v5 s& F. |% y
2.第一次调试测试单独功能的时候,可以使用工具生成单独模块的代码进行单独测试。$ P9 L, i: A) c i0 ~5 T
7 K1 G% ^9 `7 p% a! L. ?# Q
W; k9 r' l3 Y( Q/ _3 N! R( [ ^4 u# w/ t+ v
|