1.前言& C: ~3 q/ `+ \: X7 [
一直没有正经从完全无知开始去接触了解一款新的stm型号芯片。趁着这次11月份的新项目的开端需要用到stm32L0系列的单片机,搭载lora模块进行项目开发。这里开个系列博客记录下学习过程和调试过程。) N$ s+ B1 w/ l* P( i$ t4 g7 \
! R* E7 X$ U/ _! [* {$ l$ t6 s2.开发工具
# F v7 P9 k5 J; B2 g2 n/ I以前做stm32的开发都是基本用的stm32F系列,代码结构也是套用的公司以前的模板。
' O# I4 U# L8 N8 w本次L0系列采用了st公司提供的一个新的(对笔者来说是新的,之前没用过,其实以前出来比较久了)开发代码生成工具-STMCubeMx。
, U4 C; y; u: }. ~+ Y9 a8 _9 l: B* V2 Q2 ^' L+ o' N
2 d! ?1 d- T. J
. t3 {& X. u' T这个就是安装完后的工具图标。具体安装下载过程这里不做赘述,不了解的朋友可自行百度。
4 ^! S7 ]$ \% m; y1 e- P, y8 c e5 l t% F4 _- j9 I1 S$ X. l# c
3.简述开发工具生成的代码结构
* O- G5 m# K8 _$ H" s: MStm32CubeMx自动用户所选的IC型号以及需要用到的mcu资源,外设等自动生成相应的代码,生成的代码使用HAL库。其代码结构基本不需要进行什么修改(用户所选所设置皆正确的情况下),仅仅需要对一些HAL中的回调进行重写(如果你需要)。
3 p; k6 E% r/ ^/ Z7 n# b0 ~; o. g" F0 _
$ K |. ?$ h: m& @( |
如图,代码编译工具还是使用keil软件,根据图中代码结构可以很清晰的看到整个代码分为8 a1 |) s: B; @/ Z% t$ u
Application和Drivers(附加CMSIS)8 \3 m! q+ r& S
Application里分为MDK-ARM和User。
1 ~ Y p, i$ L$ _2 rMDK-ARM是代码工程的一个目录(基于编译,比如编译生成文件,中间文件.o之类的)
! e7 z+ t2 }+ t. @User是用户程序的主入口,如main.c等。
. U6 A1 f& F. A6 O需要注意的是User中还包含除main.c之外的两个文件,其中一个msp文件是工具自动生成的,后面分析。ic文件则是和中断回调相关的,这个相比都比较清楚。2 G9 A4 d5 @- T# O; X6 A
# g2 ~8 x1 e! n; p) M& V
2 ~! b+ \ V( u% ~
: z# a3 O+ X: r* e0 L- |; v8 Z在Drivers中包含HAL_Driver和CMSIS.. I e6 A7 W+ d1 d/ ^) o, {. f
CMSIS则是和内核相关的,如M0,M3等之类的内核。, B6 ? O* R1 X( c4 p! u
HAL_Driver则是st给用户提供一些接口,如flash,rtc等的操作接口。
1 @; U$ U" u- A7 w0 |/ Q
# y' A3 d. q# f9 m, c* u3.1 main.c简析
' r* y1 V# C$ Z7 n _最简单main.c里面的代码至少包括三部分:HAL_Init,SystemClock_Config,while(1);+ b: L& ~( g/ R8 L1 V7 R% e9 K
1 y/ q4 q& ^$ @- int main(void)% _6 s; Q( O6 N. Y1 S d9 o+ j
- {
& A' A! k' ~( m3 a9 g2 d6 z7 L' O - HAL_Init(); //HAL库的初始化. t: v @" K% [# r" }% u% B2 h
- SystemClock_Config();//系统时钟配置初始化
' w# X! u; _: R j7 O' R' \! P - while(1)//主程序的运行
, D l+ w S" ^ - {" t4 T7 a4 v: e1 r# b
- }
9 c5 D) V- C1 R! B, l% |1 T4 W - }
复制代码 - n. w c+ \- H9 G+ t$ Z
3.2 代码运行流程以及HAL库的调用结构
& a" U# M% X6 m3.2.1 HAL_Init()# y" Q1 k4 u6 ?
- /**
# W8 }+ a# V# h" b5 u4 l - * @brief This function configures the Flash prefetch, Flash preread and Buffer cache,* M+ q' I$ b) ^8 f
- * Configures time base source, NVIC and Low level hardware+ V6 s7 {5 I" [& B2 D7 ~% x b
- * @note This function is called at the beginning of program after reset and before+ G6 q( ?) @; J" j3 K2 D8 O# P
- * the clock configuration
/ g% b1 i1 A* Z/ l - * @note The time base configuration is based on MSI clock when exiting from Reset.
7 u8 U" K# m, O$ t - * Once done, time base tick start incrementing., I$ P' W& O6 a1 n6 e' I
- * In the default implementation,Systick is used as source of time base.( e2 y2 k" Y7 @8 q
- * the tick variable is incremented each 1ms in its ISR.8 t& r* ?6 s! I' }2 s8 S
- * @retval HAL status
3 L* B& i! b2 ^% S: u! h - */( q! `, t$ S2 U# I- \$ R5 x
- HAL_StatusTypeDef HAL_Init(void)( o1 {5 W; c) t6 `/ E
- {
: x( S' q2 u! H8 s+ N8 L) X - HAL_StatusTypeDef status = HAL_OK;
8 x% z! m. K0 d4 I( ^) a3 \ - 7 I9 V% E! K L. j
- /* Configure Buffer cache, Flash prefetch, Flash preread */
], D+ u: A3 B- I, C - #if (BUFFER_CACHE_DISABLE != 0)
2 s4 a& Q( c. D( m: M - __HAL_FLASH_BUFFER_CACHE_DISABLE();
+ H) f' S7 E4 w - #endif /* BUFFER_CACHE_DISABLE */! v, ~& L; k" j/ E+ J i% ?, m
- / E# ~* B$ ]/ ?2 y
- #if (PREREAD_ENABLE != 0)
* w" E0 f9 e, w* ~ - __HAL_FLASH_PREREAD_BUFFER_ENABLE();, F, o( @" G) W9 R* ~) |) }- y
- #endif /* PREREAD_ENABLE */
5 v& i" }. l! _3 x! h& W
) h6 D n; M/ C* G$ v- #if (PREFETCH_ENABLE != 0)
& o6 q0 s7 B! n - __HAL_FLASH_PREFETCH_BUFFER_ENABLE();2 A7 a: q/ a( m3 d- i* Y% h
- #endif /* PREFETCH_ENABLE */
5 U2 z: G& E8 r& v% {9 G4 N+ w - 6 z+ W, j" X0 g; F& x3 O. y; G
- /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */# s6 H: O8 u9 Y. P; H
- if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)8 z$ U% l4 q! U: C+ }
- {
$ e7 n; x; c" x ]2 d1 X8 j - status = HAL_ERROR;9 w: k4 t5 Y) R) a. t5 A
- }, W3 S" q2 Q" s/ Z5 c- L! n0 [
- else
! b y) [1 [) V! r# o. g+ Y' m6 c - {& h0 v4 f" S1 F: E+ A0 u$ i! m
- /* Init the low level hardware */
' P. T! k' U3 b - HAL_MspInit();6 m4 ?5 Z4 f! y( g# c. j
- }' c' P" o$ ]& i; l3 V! u
- " t" C% G* M8 U+ j
- /* Return function status */( A$ Z$ A0 f; z0 z! A4 m* h. }
- return status;
' q2 J& a0 m: I& e - }
复制代码
2 ^6 U* E; A7 Q: Q" i& e这里抽出2个关键函数:; |6 G* u- w* h/ R7 x% l' S( @$ \
HAL_InitTick,HAL_MspInit8 i, E+ k) Q4 \4 P T A% H
可以看到mcu一启动则调用HAL_InitTick,它的功能是使用SysTick作为时基源并配置1ms刻度(复位后的默认时钟为MSI)1 t$ Y2 O. ~, j6 u; B3 x0 q
如果该函数出错,则调用HAL_MspInit。2 Q9 a* G& |% v6 K/ F) `
这里提下该函数HAL_MspInit是被__weak修饰,叫做弱函数。被这种修饰的函数,通常需要用户自己去实现。实现的位置在哪里?还记得前面提到msp文件吗,就在那里面,当然你也可以写到你想写的地方,msp这个文件只是为了方面管理整个代码工程中的需要重写的函数。7 o8 Q# G/ x: Y4 T& _/ W, M
0 \6 N0 }, Q/ y+ r1 @1 a
3.2.2 SystemClock_Config()
# w; W* I& x. j4 T! m2 A- /**
3 k, G9 @) H' y% i7 } - * @brief System Clock Configuration
4 p/ V+ D3 g: H, C( }; R M - * @retval None
) |3 }8 |: x' M4 m F) A - */0 b# N( _9 N7 e& j! d( y
- void SystemClock_Config(void)
8 T+ N# y# s8 p: s n% C - {; `" {9 V$ c7 v# s' D2 K
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};6 `; _% a, f+ `8 ?
- RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};: r B, v5 C. f) |! _# h
6 N9 u6 ]: b; s+ j- /** Configure the main internal regulator output voltage 1 n: I7 ]5 \: _+ J
- */+ R3 f4 U/ K) b" b [
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);5 e6 t- Q) v' M; R4 c/ x1 ~
- /** Initializes the CPU, AHB and APB busses clocks 8 m# f- {1 O3 C* C' ]
- */
( t2 n0 f" @/ M7 I. E - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
/ u2 t/ o' D) `, y- B2 ^ - RCC_OscInitStruct.MSIState = RCC_MSI_ON;3 M! w3 w5 |1 X" }; T1 x0 z
- RCC_OscInitStruct.MSICalibrationValue = 0;/ w! B- l! y, C2 j1 k2 {/ e3 b
- RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
# Q2 t6 {/ O9 G9 ^ - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
6 b4 w. r( h; m/ N' q% ~- [ i/ O! i - if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
8 y& k* ^- @/ H% J) n- S8 T" P - {$ E9 ?! g" V: B2 [$ u6 ]& v
- Error_Handler();/ N. ]7 w A, ~
- }$ B( A `9 T3 k9 E4 x$ V
- /** Initializes the CPU, AHB and APB busses clocks
/ D1 b0 Z) i0 C" h - */" `( W2 l: \! K: w
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK% O8 n( ]( ~) p' w
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; {5 j% o2 J, v
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
, u+ r2 A) ` @# j" h3 f' F- W* p) j - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;' s$ J1 l( D2 `' U, U$ e ^
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;$ \1 i0 m1 ]7 ^9 U# M1 ~* P% E' f
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;& G' w3 q( v9 G, o( ^: \
- 2 o G9 i0 E7 ?1 F
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
# c) A6 q+ J/ A - {
4 f. S6 z6 j3 C: O6 J - Error_Handler();
& O6 D0 f6 L* z/ C4 Z6 ~ - }" |* n/ L# E1 @. R7 m6 D
- }
复制代码
+ X9 y) P' a6 ]2 X* V这里可以看到对于mcu的时钟选择配置,完全对上刚开始创建工程时的选择。先简单将这个函数分隔下:
# T% X+ V4 F# u( E- O0 i第一步:配置内部主稳压器的输出电压
2 e: U. K' m/ M. J1 J5 X第二步:初始化RCC振荡器。HAL_RCC_OscConfig,这里选择了MSI时钟,频率是4.194Mhz
0 o# y6 c" Y" z) s第三步:初始化CPU,AHB和APB总线时钟,HAL_RCC_ClockConfig+ a m$ h3 l3 y1 C q- P- i
如图;1 ^7 v+ U& R. W {7 w8 P
- n# n4 O$ m1 Z
3 T! B+ f/ G3 S: ^9 g( N; ?3 E% ?
: g# d9 f; T3 _1 w" G+ V# dps:关于STM32l0的时钟相关,可以参考datasheet。也可以往官网下载(英文版)
/ u8 I# Z. L; Y _
9 ]; ^6 B g& w% s5 G7 W9 _4 t( ]4 r* m4. 话说在前面系列9 Y3 s8 X0 R- g* l* o" K) N1 D" K
在使用St’m32CubeMx生成代码的时候,后期单独修改代码的时候,需要注意:
# P/ C7 E3 r, _% a- ]5 w9 T1.stm32l0xx_hal_conf.h 头文件指定了哪些文件是否参与编译,文件内是按模块声明的。$ B7 P: t0 U4 h0 j+ u
2.第一次调试测试单独功能的时候,可以使用工具生成单独模块的代码进行单独测试。
3 G& ?+ U+ R5 e+ L* [! E& Y6 d2 i: T1 J! J1 ^, p
p c' v" D. {- K; S* o
" H6 _9 v8 c' [% E4 ? |