一、序
本文主要是简述如何创建一个STM32的基础工程,以及在工程文件中所添加文件(头文件以及原文件)的意义。本文不使用RTE,使用的芯片为STM32F103ZET6,KEIL,使用LL相关库函数。因为文件的含义是根据本人的理解,可能存在错误的地方,欢迎讨论。
二、STM32文件架构
我们先来看下ARM的文件架构图
从图中我们不难看出,其实最简单的路径就是直接从Application Code(应用层代码)调用CMSIS-Core来实现对CPU-Core、Core-Peripherais、Other--Peripherais来运算和控制。这种方法可不可行呢,肯定是可行的。但是这样会存在一些问题。
第一:CMSIS-CORE中是对CORE和Peripherais的一些定义。这些定义一般是操作硬件的寄存器的结构体。例如你想要操作某一个外设,你就必须知道这个外设的每个寄存器的位置在哪里,以及每个寄存器的作用,并且确保你不会在设置寄存器的值的时候出错。这种方式称为寄存器开发
第二:即使不怕困难完成了第一步,那么接下来你就会面临第二个问题:移植。你通过第一步完成的程序的移植性是很低的。为什么呢?你每切换一款芯片都会可能导致不兼容,因为即使采用的同一个框架,每个厂家都可以根据自己的需求去改变外设的数量以及位置。这就可能导致你在应用层设置的寄存器地址和结构是不通用,甚至同一个厂家同一个架构下也会有不同型号的芯片,那就更加不用提更换厂家甚至是更换芯片架构的情况了。
为了解决寄存器开发的不足,Silicon Vendor(生产制造商)提供了一套叫做HAL(硬件抽象层)的东西。什么是HAL?按照个人理解就是把操作硬件也就是操作寄存器的动作有机的封装起来,让我们不再需要去关注怎么操作寄存器也就是硬件,而是使用了一些有意义的宏或者函数来代替,方便我们的编写程序。这个解决了上面的问题一。对于问题二是怎么解决的呢,刚才也说到HAl使用了一些有意义的宏或者函数来代替,这些宏或者函数在这个厂家对每一个同架构的芯片都是一样。例如在STM32F1的串口初始化的LL库函数为LL_USART_Init(USART1),那么即使你切换到STM32F3也是在应用层调用LL_USART_Init(USART1),只需要把STM32的HAL文件替换为STM32F3的HAL文件就行,如果他们USART1外设的寄存器位置不一样,那么在HAL文件的定义中就已经修改好了,应用层的代码无需修改。这样就大大的减少了移植的难度。
现在我们的路径变为了Application Code(应用层代码)------>DEVICE HAL -------->CMSIS-Core -------->CPU-Core、Core-Peripherais、Other--Peripherais。这种方式我们称为库函数开发。
三、库函数开发的基础工程文件(不带系统)
根据上面的描述,我们把文件分成四个大的部分(不带系统):CMSIS-CORE,STARTUP,HAL,Application Code
其中CMSIS-CORE也会细分三个部分:Peripherais-CORE,Peripherais-Device,compiler:
Peripherais-CORE:
具体文件:
1.cmsis_compiler.h(compiler)
2.cmsis_armcc.h (compiler)
3.cmsis_version.h (compiler)
4.core_cm3.h (Peripherais-CORE)
5.stm32f1xx.h (Peripherais-Device)
6.stm32f103xe.h (Peripherais-Device)
1.cmsis_compiler.h
该文件是一个编译声明文件,主要作用:
1.声明使用哪个编译器编译。本文采用的Keil的环境所以使用的是armcc的编译方式
2.cmsis_armcc.h
该文件是一个编译器头文件,主要作用:
1.申明编译器的部分特殊寄存器的操作
3.cmsis_version.h
该文件是一个编译器版本文件,主要作用:
1.申明编译器的版本
4.core_cm3.h
该文件的是一个申明M3架构的内核外设的文件,主要作用:
1.申明core_register
2.申明NVIC_register
3.申明SCB_register
4.申明Systick_register
5.申明Debug_register
6.申明MPU_register(可选)
5.stm32f1xx.h
该文件使用制造商提供的头文件,主要作用是:
1.根据Keil的宏定义去添加对于F1系列芯片的外设申明文件
6.stm32f103xe.h
该文件的使用制造商的头文件,主要作用是:
1.申明stm32f103系列芯片的所有外部外设的中断、位置,结构体,以及寄存器操作的相关宏定义
Startup:
1.startup_stm32f103xe.s
2.system_stm32f1xx.c
3.system_stm32f1xx.h
1.startup_stm32f103xe.s
该文件是一个启动文件,主要的作用有:
1.初始化堆栈
2.异常以及中断的定义,以及部分异常的实现
3.调用SystemInit函数初始化时钟(system)
4.调动main函数进入application code
2.system_stm32f1xx.c
该文件主要是系统时钟初始换函数实现。主要作用:
1.初始化时钟
3.system_stm32f1xx.h
该文件主要是系统时钟初始换函数申明。主要作用:
1.申明时钟初始化函数
HAL:本文采用的ST的LL库,下面采用的也是LL库的示例。目前示例的工程的点亮一个LED
1. stm32f1xx_ll_bus.h
该文件主要是申明对外设总线时钟操作宏定义。主要作用:
1.定义总线时钟相关的操作
2.stm32f1xx_ll_gpio.c 和stm32f1xx_ll_gpio.c
这两个文件是LL库对GPIO外设文件,主要作用:
1.对GPIO外设数量、位置、结构、基本操作的申明以及定义
3.其他:LL库线的C文件以及H文件还有很多,我们可以根据我们自己的需要进行添加,不一定需要把整个LL库都添加进去。例如:ADC,USART等等等等。该工程只是点亮LED,所以不需要其他文件。LL库的头文件以及源文件有以下这些(STM32F1系列)
Application Code:应用层代码,主要是我们业务逻辑。该工程只有main.c文件,用于点亮LED
main.c的主要代码如下:
#include "stm32f1xx_ll_gpio.h"
#include "stm32f1xx_ll_bus.h"
int main(void)
{
//因为LED脚位是PB0,所以使能GPIOB组的时钟
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB);
//GPIO结构体初始化
LL_GPIO_InitTypeDef GPIO_Initstruct= {0} ;
//GPIO输出模式
LL_GPIO_StructInit(&GPIO_Initstruct);
//GPIO输出模式
GPIO_Initstruct.Mode = LL_GPIO_MODE_OUTPUT;
//初始化GPIO
LL_GPIO_Init(GPIOB, &GPIO_Initstruct);
while(1);
}
整体文件架构图:
PS:以上就是该项目的本地树结构。该结构只是个人的风格,每个人可以根据自己的风格创建自己的结构
三、如何在KEIL中建立该工程并编译烧录
1、KEIL安装,网上有很多优秀的文章,自行参考,不在赘述
2、建立基本工程(选择芯片STMF103ZE)
3、建立工程文件架构、添加源文件。参考其他文章。个人喜欢和本地目录的架构保持一致,添加好文件后的Keil工程架构如图:
4.添加头文件到编译路径
魔术棒----》c/c++------》Include Paths
5.添加全局的宏定义
STM32F103xE和USE_FULL_LL_DRIVER
STM32F103xE:用于告诉stm32f1xx文件,最后添加f1系列具体哪个型号的头文件进来
USE_FULL_LL_DRIVER:用来启动LL库的编译。
6.编写application code(main.c)文件
7.编译
8.烧录(需要连接电脑,模拟器,芯片)具体你使用的是哪个模拟器相关设置请参考其他文章
9.LED指示灯正常亮起。
四、总结
至此,一个最最最基本的用LL库去点亮LED的工程已经建好了。本文只是在描述stm32工程文件的架构思路,在实际应用中你们可以使用的不同框架以及型号的芯片,不要照搬,要懂得根据自己的实际情况是使用合适的文件。本文仅作为参考