为了C++构建STM32的工程模板尽可能降低软件的逻辑处理部分与硬件的耦合,可以使业务相关功能脱离硬件运行。所以应该抽象出与硬件相关的操作。由于内容过多,不能一一列举,具体内容请查看源文件。
创建core文件夹–系统常用定义
创建cpu_type文件
该文件对常见的CPU类型进行枚举定义,记录各个系列的芯片ID的起始地址,flash容量地址等信息。如下图所示,记录了F0、F1、F2、F3、F4系列的芯片唯一ID的起始地址以及。
最后根据当前编译的芯片(在编译配置文件config.mk中定义)预编译ID地址、Flash地址等信息从而适配各个系列的MCU。
创建sys_type文件
该文件主要对一些常用的常量进行定义如LOW为0, HIGH为1,PI为3.1415926535897932384626433832795等等,也在此文件下定义了CPU时钟、CPU、时间、等结构体。
创建sys_core文件
该文件对一些常用操作定义为宏/函数,方便快速调用,例如读指定bit位,指定bit位置1,指定bit位清零,4字节8位无符号整型数转换为32位无符号整型数等等。
创建port_gpio文件
该文件对GPIO端口进行重新定义,为每个GPIO重新编号为PinId,后面无论哪个系列的芯片都可以通过这个PinId的获取到该引脚的
- 中断端口#define GETEXTIPORT(a) ((uint32_t)a >>4 )(得到的就是EXTI_PortSourceGPIOA–EXTI_PortSourceGPIOK)
- 中断线#define GETEXTILINE(a) ((uint32_t)1<<( a & 0x0f )) (得到的就是EXTI_LINE_0–EXTI_LINE_31)
- 引脚编号#define GETPIN(a) (uint16_t)(1<<(a&0x0f)) (得到的就是GPIO_Pin_0 — GPIO_Pin_15)
- 引脚复用功能编号 #define GETPINSOURCR(a) (a & 0x0f) (得到的就是GPIO_PinSource0 — GPIO_PinSource15 )
- 引脚端口号#define GETPORT(a) ((GPIO_TypeDef*)(((((a)&0xf0))<<6)+PORT_BASE)) (得到的就是GPIOA_BASE — GPIOF_BASE)
其中PORT_BASE根据不同类型的芯片已经在cpu_type完成定义,在使用过程中,例如我们有一个gpio的类对象为_pin,该对象具备id属性(port_gpio定义的引脚编号,这个编号在创建这个对象时就完成了初始化)那么我们在做配置中断线时直接调用SYSCFG_EXTILineConfig(GETEXTIPORT(_pin->id), GETPINSOURCR(_pin->id)); 就可以完成该GPIO的中断线配置。这个就是更换芯片该代码也可复用,如果更换引脚也只需要在初始化_pin这个对象时id修改为相应的引脚就可以了。
创建config文件夹–工程配置
创建sys_config文件
该文件作为系统配置文件,配置是否打开全局串口,debug串口选择,是否使用IAP,是否使用SW接口等。
创建mcu_config文件
mcu配置文件,该文件对使用到的该系列的mcu定义flash区域、引脚数、flash大小、内存大小等等。
创建bsp文件
该文件定义出所有的硬件抽象出的类对象,例如GPIO对象,CMcuGpio PA0(PA0_ID);CMcuGpio PA1(PA1_ID); 利用这些GPIO对象又可以根据工程需要创建如CExti exti4(&PD4);CUsart uart5(UART5, &PC12, &PD2);这些中断、串口对象等等,在项目需要时只需要选择配置相应的GPIO就可以了。
创建hardware文件夹–硬件相关
前面所描述的基本与硬件差异性不大,稍作预编译就可以通用于各个系列的mcu,而hardware文件夹下可以根据类型不同分别创建cortex-m0,cortex-m3,cortex-m4等等文件夹,在config.mk工程配置文件进行配置,调用工程当前使用mcu相匹配的内核进行编译。
创建mcu_define文件
定义外设时钟源、外设中断源、GPIO引脚配置等结构体,且定义了一个可以根据芯片ID获取外设索引的函数uint8_t getIndex(PinId_t pin_id, const AF_FUN_S *emap)。外设索引根据芯片手册获取,每种芯片定义一个外设索引的文件(只要配置完成就不再需要修改)例如下图的串口引脚索引(f407ve系列的索引文件stm32f407VE_define.h)。
假设现在我们需要配置一个串口使用,首先按照在索引文件stm32f407VE_define.h文件中找到想使用的串口1,发现可以使用PA9、PA10或者PB6、PB7。假设硬件接着PA9、PA10。那么我们配置只需要在bsp中定义一个CUsart usart1(USART1, &PA9, &PA10);这个CUsart这个内我们先不介绍。现在我们就有了一个usart1的串口实例了。如果CUsart类提供了初始化的方法Init()。那我们就可以在这个方法中实现对串口的初始化,首先使用uint8_t getIndex(PinId_t pin_id, const AF_FUN_S *emap)函数找到串口所有就是PA9/PA10在UART_MAP中的位置,可以发现PA9的索引为0,PA10为1。接着使用GPIO类提供引脚工作模式配置的_rx_pin->Mode(UART_MAP[index]._pinMode, UART_MAP[index]._pinAf)直接配置串口rx使用引脚的工作模式,tx同样如此_tx_pin->Mode(UART_MAP[index]._pinMode, UART_MAP[index]._pinAf);。接着打开串口时钟,这里就要用到rcc文件提供的功能。
创建rcc文件
rcc文件比较简单,只提供了根据根据设备地址开启或关闭时钟(void rcc_clock_cmd(uint32_t dev, FunctionalState state))和获取系统时钟的功能。如下图所示,其中dev_to_rcc_table同样定义在索引文件stm32f407VE_define.h中。
接着上面打开串口时钟,直接调用 rcc_clock_cmd((uint32_t)mUSARTx, ENABLE); //打开串口时钟,其中mUSARTx有类CUsart提供。接着开始配置串口的中断,那就需要用到下面的nvic文件提供的功能。
创建nvic文件
nvic文件主要是对mcu的中断进行配置,提供了中断优先级分组配置,根据设备地址和其对应的第几个中断索引,查找出对应的中断号,设置其中断优先级,开启设备地址和索引对应的中断号的中断 等功能。
接着上面的配置串口中断,设置串口优先级nvic_dev_set_priority((uint32_t)mUSARTx, 0, preemption_priority, sub_priority);使能串口中断nvic_dev_enable((uint32_t)mUSARTx, 0);接着初始化一些串口的工作模式后就完成了对串口的配置,只有串口提供的接口完善且正确,那么即使下次更换串口PB6/PB7,全工程只需修改CUsart usart1(USART1, &PB6, &PB7);就可以了