1.前言+ z# X, g7 U6 G) S( t4 N& c; Z, Q
一直没有正经从完全无知开始去接触了解一款新的stm型号芯片。趁着这次11月份的新项目的开端需要用到stm32L0系列的单片机,搭载lora模块进行项目开发。这里开个系列博客记录下学习过程和调试过程。( O- c( X# y5 \6 q. R
7 w* L9 c! ^' H+ W5 @2.开发工具
( z' \5 d7 Z9 t: l) ^ ?. ^以前做stm32的开发都是基本用的stm32F系列,代码结构也是套用的公司以前的模板。
6 [/ u$ D; M. r& O本次L0系列采用了st公司提供的一个新的(对笔者来说是新的,之前没用过,其实以前出来比较久了)开发代码生成工具-STMCubeMx。) _# L& P$ V; O+ Y. _* G+ m" p( u
8 M1 t3 m7 ?& T- G2 `9 X& O
& N8 X' \. W( o4 O; I; m
# S. I# b8 V3 X这个就是安装完后的工具图标。具体安装下载过程这里不做赘述,不了解的朋友可自行百度。# F0 R$ O; B( _
- c: M! Q' Z$ h+ D1 \
3.简述开发工具生成的代码结构( |# S2 t; z9 o
Stm32CubeMx自动用户所选的IC型号以及需要用到的mcu资源,外设等自动生成相应的代码,生成的代码使用HAL库。其代码结构基本不需要进行什么修改(用户所选所设置皆正确的情况下),仅仅需要对一些HAL中的回调进行重写(如果你需要)。2 @* k$ L2 B- t+ v T
- g" z q0 v$ W5 x9 A& J
( w$ X- k% e/ W$ y( c( _9 q
如图,代码编译工具还是使用keil软件,根据图中代码结构可以很清晰的看到整个代码分为
2 X. {3 \& X) e. B9 A. fApplication和Drivers(附加CMSIS)
& X1 `' O$ z2 l5 _! z, lApplication里分为MDK-ARM和User。
0 T0 }5 T' a8 n. L" |MDK-ARM是代码工程的一个目录(基于编译,比如编译生成文件,中间文件.o之类的)
$ q! f v- B3 I( lUser是用户程序的主入口,如main.c等。$ P& C0 Y2 p+ A3 U$ U5 h( Y0 Q( i
需要注意的是User中还包含除main.c之外的两个文件,其中一个msp文件是工具自动生成的,后面分析。ic文件则是和中断回调相关的,这个相比都比较清楚。
2 }7 _& e* F4 L' |" G* R- g$ ~& b. i' c3 E8 S
) G$ T6 l7 O4 T5 h& ^7 l! [) \
4 I. V' s9 _# Z; K- {; T
在Drivers中包含HAL_Driver和CMSIS.
+ h0 J9 h: q4 U# M' LCMSIS则是和内核相关的,如M0,M3等之类的内核。
1 q, c1 _8 t; z3 i( MHAL_Driver则是st给用户提供一些接口,如flash,rtc等的操作接口。
3 Q% o: ]# b+ j% ~5 l; G0 t( ]1 i6 Z9 ?5 S. S, X" r8 Y4 h1 a! c
3.1 main.c简析
' a! s' o- C) w, R$ K. |最简单main.c里面的代码至少包括三部分:HAL_Init,SystemClock_Config,while(1);
8 G2 B; P Q* Q. K/ g, y- ^) f
0 U1 ]% J4 s- X4 x$ `5 ?7 w- int main(void)
7 q! i/ w V# \0 ~$ O8 a - {- V) }# Q& U# l7 B& ]' ~
- HAL_Init(); //HAL库的初始化
; ^ `1 Z. V4 x! W5 B' C - SystemClock_Config();//系统时钟配置初始化- A( O' C- g" Z1 H( A# M
- while(1)//主程序的运行
+ K1 m) ?0 X) t$ B- A - {
# v" J+ T( I; r4 S6 w0 e1 C - }. R; [! Z( c: M: L' \: w: X! j
- }
复制代码
$ Q4 K* `& i7 K, r3.2 代码运行流程以及HAL库的调用结构
2 w6 `* L6 b; W$ `+ w3.2.1 HAL_Init()9 N6 q2 _: n3 c* L& m: f( W7 e. n
- /**( n3 f: F% t. A4 J
- * @brief This function configures the Flash prefetch, Flash preread and Buffer cache,: v) ^% n6 K/ O6 A
- * Configures time base source, NVIC and Low level hardware
. x& a2 f* v0 }. R. L. S2 u6 b - * @note This function is called at the beginning of program after reset and before, }1 U( ~4 F6 ^: \ o. S
- * the clock configuration
5 z; Q. z6 r! `2 c/ N% s& l - * @note The time base configuration is based on MSI clock when exiting from Reset.
9 Z+ C% }/ R& |+ k Y! Q/ Z- Y - * Once done, time base tick start incrementing." Z/ @) u% G8 e9 h$ F( d# q
- * In the default implementation,Systick is used as source of time base.! N" G7 O7 H! X/ S6 i! {
- * the tick variable is incremented each 1ms in its ISR.. }5 [. U5 V2 ^3 ]
- * @retval HAL status8 G2 g. r) s* A% ^& b) `% h
- */
9 M4 p4 c+ X5 I: P/ l - HAL_StatusTypeDef HAL_Init(void)) k1 u/ c1 y* ?+ o" n
- {
t ?8 G, B1 ~1 w& t# l r( j - HAL_StatusTypeDef status = HAL_OK;; F' ]/ a2 R" O) b' \
: M# a- `: M- n6 z# D( }- /* Configure Buffer cache, Flash prefetch, Flash preread *// M* _/ P; Z: K* j$ z
- #if (BUFFER_CACHE_DISABLE != 0)/ ]3 q0 w4 [: j! r$ }
- __HAL_FLASH_BUFFER_CACHE_DISABLE();- @4 P0 ~; @9 E2 U& u
- #endif /* BUFFER_CACHE_DISABLE */
7 x! y; O( G8 G' N
# ] D+ z3 ]; z4 k- #if (PREREAD_ENABLE != 0)6 D' Q& B1 Q& Q- J/ X
- __HAL_FLASH_PREREAD_BUFFER_ENABLE();( F6 Z4 q4 U* r+ O3 X/ r8 t
- #endif /* PREREAD_ENABLE */. d6 y5 A1 x* E+ R* ?9 O0 m; c5 @, [
4 U4 [2 Q/ v3 E% B- #if (PREFETCH_ENABLE != 0), l8 Z8 t4 Y2 W$ z# w
- __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
5 K+ D5 y i/ w$ b - #endif /* PREFETCH_ENABLE */
) p7 _4 `3 D+ I( v: a& u. _; q# e
. i; D7 ^0 K0 F( c- /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */$ [1 h) u' h7 c) y- W# m% f: q+ I
- if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK): `7 ^) L' |9 J6 ?5 t: Q+ a4 g; C
- {# I2 Q9 x! s% P! [/ o9 E( u3 W
- status = HAL_ERROR;: i$ y+ ]. m* v# a, o/ I
- }7 q, Q( r( l0 b, ~6 O3 v6 u! }
- else
3 Q! u0 t. ?& r2 C9 F& H - {
2 }- p- Q0 x" O - /* Init the low level hardware */' z: L0 H& t2 h$ g; B
- HAL_MspInit();8 p! w" E8 m, J$ G# k( V0 C! x6 n
- }1 y" Y% V+ E8 L1 ]1 O' p
- 0 j3 Y z$ ~: E6 c# p$ O9 J
- /* Return function status */
$ R# Z2 P4 w" b* A7 V - return status;- ] g( q3 }9 ?* F/ }+ k5 R
- }
复制代码
( y# s( r; K+ \4 S这里抽出2个关键函数:
( `* P( N$ V1 N9 u9 eHAL_InitTick,HAL_MspInit6 V% z$ Z+ W8 Z/ p
可以看到mcu一启动则调用HAL_InitTick,它的功能是使用SysTick作为时基源并配置1ms刻度(复位后的默认时钟为MSI)
0 p: H2 u+ G7 u8 s. j9 D如果该函数出错,则调用HAL_MspInit。
1 M4 ]& t" f0 f. i( m/ N4 O/ o这里提下该函数HAL_MspInit是被__weak修饰,叫做弱函数。被这种修饰的函数,通常需要用户自己去实现。实现的位置在哪里?还记得前面提到msp文件吗,就在那里面,当然你也可以写到你想写的地方,msp这个文件只是为了方面管理整个代码工程中的需要重写的函数。
, h, h, f" g) c* Z: J3 ]$ u" A+ ^7 \$ |) b/ o
3.2.2 SystemClock_Config()8 G" z7 }1 A. T2 d g$ Z
- /**2 \, l" z: U2 m" v, _. Q: ~
- * @brief System Clock Configuration3 U( n N. R. Y& t# }( V$ B+ A
- * @retval None
& \" Y4 x9 g0 B4 U8 s R - */
/ y. y& E5 U7 W Y( V6 u - void SystemClock_Config(void)
) H' }6 m; V5 Y- @6 }9 D - {6 A6 l0 o; ]: @8 S
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};
# g0 A. k ~- g" [& U - RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};+ T5 j3 B: h( {% u! g2 f
4 A( E; m' p9 Q2 F9 r+ h# x- /** Configure the main internal regulator output voltage
( Q; H, H S/ ^7 W% v* u! F - */9 E/ \* t* A, Q. E
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);" j+ r* C A6 Z' H
- /** Initializes the CPU, AHB and APB busses clocks
; d4 L) I O+ d% K - */
& ?. g0 X/ ?$ K& X' h% D* S - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;$ W3 X1 p# ]- w# a9 v
- RCC_OscInitStruct.MSIState = RCC_MSI_ON;
- F3 c- V% B& ]+ o" \ - RCC_OscInitStruct.MSICalibrationValue = 0;4 [& j }% F ^! J
- RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
o& z3 o8 I* |" Y) V& f4 f - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;& v }% d6 t; Z& x$ R6 N# R& G. E
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)) t4 _; H a# S/ u% J5 [- V
- {
/ K2 k4 p* p+ \0 g - Error_Handler();$ H! a$ U' \) l( |, v L- \3 Y' A
- }6 t* R1 A4 s! v: f) Q9 ]5 U
- /** Initializes the CPU, AHB and APB busses clocks
; w+ I. t0 J. ]4 a) r1 S( p - */" C$ {/ a- d$ ]( K$ q
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK2 r( l( G; z) K7 I7 W2 f M/ D
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;! Q K* x9 x* P4 n) v5 I& V3 ?
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
9 P/ U$ x1 C% A9 [( g - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;, z: L* C' h5 F/ I( e2 W ^5 j3 S
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;$ k3 f/ L! b, F
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;$ u# ?- L- l" D- S4 N$ Y) b k+ {
. a3 r4 _* x0 A+ o D9 q. v6 |- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
( c5 \2 X- ~% h2 } - {! T6 g4 T: C" G2 `# k5 B; Y
- Error_Handler();
& H/ a, g/ `6 i p; ~' s m* P - }
0 |% l3 d7 |) R" w - }
复制代码
2 c8 Z6 ^. n% S5 t0 e; \5 Q7 j+ x这里可以看到对于mcu的时钟选择配置,完全对上刚开始创建工程时的选择。先简单将这个函数分隔下:3 M$ H F" r9 w
第一步:配置内部主稳压器的输出电压
' f' U. u$ K: Y5 U. d1 b2 F第二步:初始化RCC振荡器。HAL_RCC_OscConfig,这里选择了MSI时钟,频率是4.194Mhz
! W5 P$ W8 S' W3 h& s+ k: ~第三步:初始化CPU,AHB和APB总线时钟,HAL_RCC_ClockConfig
4 M& f) a! a7 N& a3 k9 P如图;
8 x' k: W! K, J1 _! T8 }
9 ~, t' E. `8 v6 U0 D4 J! p1 |9 ^4 n5 c% Y0 a$ f5 ]6 }; }* ~
; g# ~$ D+ A$ F( G- j$ M
ps:关于STM32l0的时钟相关,可以参考datasheet。也可以往官网下载(英文版)2 b( \+ I" Q' m; ?4 w
$ S$ x% h% Z$ p u# l4. 话说在前面系列6 ?, D/ Y5 |7 ?: |& [) M1 _; b
在使用St’m32CubeMx生成代码的时候,后期单独修改代码的时候,需要注意:
+ p3 K h! d) R2 ^1.stm32l0xx_hal_conf.h 头文件指定了哪些文件是否参与编译,文件内是按模块声明的。$ l" t- v$ S0 [
2.第一次调试测试单独功能的时候,可以使用工具生成单独模块的代码进行单独测试。% e" Q* P# @5 q2 q% e$ ^
: W* `# ?$ ~$ T! Z8 d& P
c1 e+ F" ~2 I$ c2 n! l Z6 U* X" \! M( d$ |* \ M8 S
|