1.前言 H, l$ [$ J& O6 o
一直没有正经从完全无知开始去接触了解一款新的stm型号芯片。趁着这次11月份的新项目的开端需要用到stm32L0系列的单片机,搭载lora模块进行项目开发。这里开个系列博客记录下学习过程和调试过程。
" W, G3 k9 r$ U1 ^6 R( D% `0 k) C, n
2.开发工具
+ S7 V8 [6 E. r S }以前做stm32的开发都是基本用的stm32F系列,代码结构也是套用的公司以前的模板。
' I# k8 V* f- v3 y& ?本次L0系列采用了st公司提供的一个新的(对笔者来说是新的,之前没用过,其实以前出来比较久了)开发代码生成工具-STMCubeMx。: o3 E' f ~3 q, g1 Q% g4 c
/ a; M8 D1 ^4 p
. y! i; N% J8 y+ o, J& S# @2 w, U0 r' T6 z3 _, H0 {1 \5 ^
这个就是安装完后的工具图标。具体安装下载过程这里不做赘述,不了解的朋友可自行百度。/ [" q0 ]1 g$ I
* ~, e- N- a0 o- p" J
3.简述开发工具生成的代码结构
6 X! @4 z$ T' O2 _5 IStm32CubeMx自动用户所选的IC型号以及需要用到的mcu资源,外设等自动生成相应的代码,生成的代码使用HAL库。其代码结构基本不需要进行什么修改(用户所选所设置皆正确的情况下),仅仅需要对一些HAL中的回调进行重写(如果你需要)。4 W9 ?5 w4 a R) v! O& K
# N1 J6 J5 l; N& X! D7 s6 C( i- o1 L( \4 `! s4 J& C
如图,代码编译工具还是使用keil软件,根据图中代码结构可以很清晰的看到整个代码分为
7 B: a0 h$ R+ u* H! w( wApplication和Drivers(附加CMSIS)( G7 R0 w8 J& q$ u9 ^
Application里分为MDK-ARM和User。
- b, j' [- u7 o* m. X' d! V2 kMDK-ARM是代码工程的一个目录(基于编译,比如编译生成文件,中间文件.o之类的)
( g! N8 k: F9 h1 K# N( tUser是用户程序的主入口,如main.c等。8 \ ]: B5 X: w c
需要注意的是User中还包含除main.c之外的两个文件,其中一个msp文件是工具自动生成的,后面分析。ic文件则是和中断回调相关的,这个相比都比较清楚。
. R' P5 P+ L* \! W+ H6 ]( ?5 o2 s8 R5 R# B7 r: i- j8 u
& ?: x/ W' h1 F3 D5 m- ^5 j3 }& c% E0 {2 @( [# @
在Drivers中包含HAL_Driver和CMSIS.
+ P a# x3 h$ DCMSIS则是和内核相关的,如M0,M3等之类的内核。# a/ \/ X# q# l; ?. n
HAL_Driver则是st给用户提供一些接口,如flash,rtc等的操作接口。
2 u+ }. b! C$ _# j% R& E: h+ J/ [6 f& x7 e
3.1 main.c简析
$ w c/ `9 i5 M0 o0 E' Y最简单main.c里面的代码至少包括三部分:HAL_Init,SystemClock_Config,while(1);9 h) _- x, d' V* F5 {
% y8 ?! m; b" r, Y) D
- int main(void); ^' L0 F; f( `
- {
3 m3 [4 ?. |8 o6 u - HAL_Init(); //HAL库的初始化( G* K! P# ]7 D- Q
- SystemClock_Config();//系统时钟配置初始化. o. ~% @ V4 u+ W, z
- while(1)//主程序的运行
' x9 l3 F ^( b {3 t+ u - {5 K8 t/ u$ K- f9 |1 R' M6 ^
- }
' G% |" o( z$ N2 s% a7 P - }
复制代码
; g; d) H$ u+ k# m. c+ U- l3.2 代码运行流程以及HAL库的调用结构6 v- e9 E2 |, J
3.2.1 HAL_Init()
. s6 k1 O) L, X4 t- /**
# ^1 u* _" T) P& h; R W - * @brief This function configures the Flash prefetch, Flash preread and Buffer cache,* ?5 Y3 ^" F ~
- * Configures time base source, NVIC and Low level hardware6 P0 A0 N' ]- ~) O. y
- * @note This function is called at the beginning of program after reset and before# t2 _8 ]9 k' f1 L
- * the clock configuration
. I2 z) r, |# I - * @note The time base configuration is based on MSI clock when exiting from Reset.
# U1 C) x4 f) `) O) @7 ? - * Once done, time base tick start incrementing.
& Q8 \+ j, v% V* G) s7 p - * In the default implementation,Systick is used as source of time base.
) l) l: K0 ]1 j [5 F. L - * the tick variable is incremented each 1ms in its ISR.
" [% ^2 v) p$ a y) H9 X. h6 x - * @retval HAL status
$ `; M- I3 ~: A6 G" ? - */! W" R3 |% U$ ]2 X- u9 }
- HAL_StatusTypeDef HAL_Init(void)
% Y/ b2 H0 R# G% b - { i% e+ e- r, \2 U# P) J$ F$ z5 s
- HAL_StatusTypeDef status = HAL_OK;) z2 m9 T& g6 J9 j7 `' h
) C3 L% v+ k$ K7 i# g$ ~- /* Configure Buffer cache, Flash prefetch, Flash preread */, L* u. b0 y9 t* ~$ S7 U- Q- T
- #if (BUFFER_CACHE_DISABLE != 0)% d' q1 Q$ w4 E0 ^, V& b
- __HAL_FLASH_BUFFER_CACHE_DISABLE();
5 i/ C6 V8 f* p% [' v' m5 P0 n7 l0 `5 Q - #endif /* BUFFER_CACHE_DISABLE */- N/ h, i) l! F2 u5 r- h4 j
- 2 n/ Z: g4 T) K; a* ~5 ~
- #if (PREREAD_ENABLE != 0)
5 r I8 ?4 _0 @1 T) A - __HAL_FLASH_PREREAD_BUFFER_ENABLE();
9 X( v' G4 S3 I y3 g+ S - #endif /* PREREAD_ENABLE */. `, d' b; Y+ O1 W# R4 [
! F. U5 s! e3 n- #if (PREFETCH_ENABLE != 0)* }' r) c! i: K$ Y- [) Y5 \
- __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
# R; i, m# H* r/ U& y7 u4 ~ - #endif /* PREFETCH_ENABLE */
' b* i) d: m/ c. t" e5 C
9 F( W( _' \9 y3 H& o" F6 |/ U- /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
4 g: T( z! B1 Z! g4 l - if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
b! b, w1 `3 @4 M5 @0 d - {7 i, c o) q2 S" a' A
- status = HAL_ERROR;" ]8 ~6 v* C: I& ]% J
- }
8 q) x" @: r. \; ? - else: I N. ~7 r0 ?4 F
- {) W4 Z. r% d, I0 o; {
- /* Init the low level hardware */
, I7 `; K9 _, E5 F9 n - HAL_MspInit();
) ` q. m3 B7 c" a - }2 c6 J. w2 b+ n9 g
- 0 s4 x5 k/ r# P7 v) K# Z7 N
- /* Return function status */
4 q- H& L' L9 [& d0 ]! | - return status;
7 N( `. U) n2 A8 }: A - }
复制代码 2 ^ u* t! G8 J
这里抽出2个关键函数:
4 f9 R# O" r% m) UHAL_InitTick,HAL_MspInit
2 n. J5 `& ~) P, A1 s; S可以看到mcu一启动则调用HAL_InitTick,它的功能是使用SysTick作为时基源并配置1ms刻度(复位后的默认时钟为MSI)$ h) E7 n. B4 D0 J, G# M/ Q" I
如果该函数出错,则调用HAL_MspInit。( m% T$ Z4 p# K6 d r) c/ h; S
这里提下该函数HAL_MspInit是被__weak修饰,叫做弱函数。被这种修饰的函数,通常需要用户自己去实现。实现的位置在哪里?还记得前面提到msp文件吗,就在那里面,当然你也可以写到你想写的地方,msp这个文件只是为了方面管理整个代码工程中的需要重写的函数。- [# F* r2 _9 `5 i
) O y; y6 {$ U O0 x3.2.2 SystemClock_Config()* Y- F& G1 I' T2 L7 a
- /**9 A& O. H* ^" o4 y y
- * @brief System Clock Configuration
- p6 Y1 G" i! X. N. J6 \9 G6 c - * @retval None& c# p* Z' o3 ? V* N" ~, Z+ I0 a
- */* j* t+ \( [- r
- void SystemClock_Config(void)$ ]- T5 ^2 O7 P* p: r+ M
- {( J3 _3 q* O3 D+ h" s
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};
2 T" y. n" i5 q: a3 Y - RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
2 @, g3 q j- i. K: W' A8 X
- A8 o! N1 ?; u* V b- /** Configure the main internal regulator output voltage ; n+ W. E$ @6 Q9 Z( ~! T7 Z. n+ b
- */0 W1 i: W6 R R: `
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);) g9 x$ s& o4 W$ [) ^' t
- /** Initializes the CPU, AHB and APB busses clocks
3 D; L: Y9 D5 n" ` - */
j: x6 h- z* h j w - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
' S' f/ @/ W R3 e( d2 R - RCC_OscInitStruct.MSIState = RCC_MSI_ON;
# G& o& |4 B/ v$ } - RCC_OscInitStruct.MSICalibrationValue = 0;, v: ^5 p F1 B5 i
- RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
$ \% u& A+ M: R! A9 L" N \& p - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;# `! J. d# {/ B* f* `
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
: b. Y" J3 T# C9 b. W& N - {) k4 T8 W8 M- {% D3 o/ G9 n& z
- Error_Handler();( P- e! _; F! E" b1 O
- }: F, W4 ]) M0 a6 S
- /** Initializes the CPU, AHB and APB busses clocks
: k, _; c" m# F' Y0 Y" z& M$ K - */
2 I- C9 ^$ x q) i - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK9 z5 c. X8 ~. N# v/ N3 w6 f0 ?2 r
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
& N. O1 U% n% d0 s - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
1 v. y9 Z. l z9 H p3 ? - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
1 q% T) Y* q' I; l5 x9 |, U O - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
W. d4 S e1 |9 | - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
9 _+ A; O& l6 z: t* L/ Z8 D: R - 4 y* a. w) ~8 [6 W
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) l s$ S6 L6 W7 O
- {
; |! W: f1 d0 p# I2 C0 {/ n2 X - Error_Handler();
" U s& J% L& P - }! }. T7 r! h6 d- _ N: `
- }
复制代码
. B- r) ^7 E- Q; M* Z1 ^5 d& M/ s& k% d这里可以看到对于mcu的时钟选择配置,完全对上刚开始创建工程时的选择。先简单将这个函数分隔下:
+ e$ u) e3 `5 n1 s4 O第一步:配置内部主稳压器的输出电压) m8 g0 a8 Z& K3 Z8 s
第二步:初始化RCC振荡器。HAL_RCC_OscConfig,这里选择了MSI时钟,频率是4.194Mhz
3 s8 q% |, w4 y) v. h第三步:初始化CPU,AHB和APB总线时钟,HAL_RCC_ClockConfig
# g9 G" {' P" C3 W7 S如图;
) ]& E5 U6 `6 I! w7 `$ w3 Q5 r3 M$ c3 c
1 K2 n5 ~& V0 w' j5 Q) f7 |
7 G9 P1 o/ X- G. J5 X
ps:关于STM32l0的时钟相关,可以参考datasheet。也可以往官网下载(英文版)5 o" Q9 L& A# }# s4 q; a q
# n. T/ U. q- D$ `4. 话说在前面系列- e4 N3 X' G/ \1 s; l
在使用St’m32CubeMx生成代码的时候,后期单独修改代码的时候,需要注意:
& M5 X$ @* s- I1.stm32l0xx_hal_conf.h 头文件指定了哪些文件是否参与编译,文件内是按模块声明的。
- G. n8 S1 K9 G1 |6 [2 i, \2.第一次调试测试单独功能的时候,可以使用工具生成单独模块的代码进行单独测试。
( e) z5 n) t! q* Q; s+ }: m$ w0 ]! x2 L* I% V& ?
* u, s3 _9 Z" X6 h% \
3 T# I9 _& u7 ~, p2 K: @2 q |