完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
本帖最后由 正点原子运营官 于 2020-4-15 18:19 编辑
1)实验平台:ALIENTEK NANO STM32F411 V1开发板 2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子 4.5 STM32 NVIC 中断优先级管理 CM4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有 256级的可编程中断设置。但 STM32F411 并没有使用 CM4 内核的全部东西,而是只用了它的一部分。STM32F411xC/E 则总共有 96 个中断,以下仅以 STM32F411RCT6 为例讲解。 STM32F411RCT6 的 96 个中断里面,包括 10 个内核中断和 86 个可屏蔽中断,具有 16 级可编程的中断优先级。而我们常用的就是这 86 个可屏蔽中断。在 MDK 内,与 NVIC 相关的寄存器,MDK 为其定义了如下的结构体: typedef struct { __IO uint32_t ISER[8]; /*!< Interrupt Set Enable Register */ uint32_t RESERVED0[24]; __IO uint32_t ICER[8]; /*!< Interrupt Clear Enable Register */ uint32_t RSERVED1[24]; __IO uint32_t ISPR[8]; /*!< Interrupt Set Pending Register */ uint32_t RESERVED2[24]; __IO uint32_t ICPR[8]; /*!< Interrupt Clear Pending Register */ uint32_t RESERVED3[24]; __IO uint32_t IABR[8]; /*!< Interrupt Active bit Register */ uint32_t RESERVED4[56]; __IO uint8_t IP[240]; /*!< Interrupt Priority Register, 8Bit wide */ uint32_t RESERVED5[644]; __O uint32_t STIR; /*!< Software Trigger Interrupt Register */ } NVIC_Type; STM32F411 的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便的使用 STM32F411 的中断。下面重点介绍这几个寄存器: ISER[8]:ISER 全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。上面说了 CM4 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是STM32F411 的可屏蔽中断只有 86 个,所以对我们来说,有用的就是三个(ISER[0~2],总共可以表示 86 个中断。而 STM32F411 只用了其中的前 86 个。ISER[0]的 bit0~bit31 分别对应中断 0~31。ISER[1]的 bit0~32 对应中断 32~63;ISER[2]的 bit0~26 对应中断 64~85;这样总共 86 个 中断就分别对应上了。你要使能某个中断,必须设置相应的 ISER 位为 1,使该中断被使能(这 里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。具 体每一位对应哪个中断,请参考 stm32f411xe.h 里面的第 84 行处。 ICER[8]:全称是:Interrupt Clear-Enable Registers,是一个中断除能寄存器组。该寄存器组 与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ICER 一样。 这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。 ISPR[8]:全称是:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。 ICPR[8]:全称是:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断接挂。写 0 无效。 IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。 IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32F411 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32411只用到了其中的前 86 个。IP[85]~IP[0]分别对应中断 85~0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是 只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前, 子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。这里简单介绍一下 STM32F411 的中断分组:STM32 将中断分为 5 个组,组 0~4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表 4.5.1 所示: 表 4.5.1 AIRCR 中断分组设置表 通过这个表,我们就可以清楚的看到组 0~4 对应的配置关系,例如组设置为 3,那么此时所有的 86 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。这里需要注意两点:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看 哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级 中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。 结合实例说明一下:假定设置中断优先级组为 2,然后设置中断 3(RTC 中断)的抢占优先级 为 2,响应优先级为 1。中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:中断 7>中断 3>中断 6。 上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互打断!通过以上介绍,我们熟悉了 STM32 中断设置的大致过程。接下来我们介绍如何使用 HAL 库函数实现以上中断分组设置以及中断优先级管理,使得我们以后的中断设置简单化。NVIC 中断管理相关函数主要在 HAL 库关键文件 stm32f4xx_hal_cortex.c 中定义。 首先要讲解的是中断优先级分组函数 HAL_NVIC_SetPriorityGrouping,其函数申明如下: void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup); 这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分 组确定就最好不要更改,否则容易造成程序分组混乱。这个函数我们可以找到其函数体内容如 下: void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup) { /* Check the parameters */ assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup)); /* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */ NVIC_SetPriorityGrouping(PriorityGroup); } 从函数体以及注释可以看出,这个函数是通过调用函数 NVIC_SetPriorityGrouping 来进行 中断优先级分组设置。通过查找(参考 3.5.3 小节 MDK 中“Go to definition of”的使用方法), 我们可以知道函数 NVIC_SetPriorityGrouping 是在文件 core_cm4.h 头文件中定义的。接下来, 我们分析一下函数 NVIC_SetPriorityGrouping 函数定义。定义如下: __STATIC_INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) { uint32_t reg_value; uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); reg_value = SCB->AIRCR; reg_value& = ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); reg_value = (reg_value | ((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) | (PriorityGroupTmp << 8U) ); SCB->AIRCR = reg_value; } 从函数内容可以看出,这个函数主要作用是通过设置 SCB->AIRCR 寄存器的值来设置中断 优先级分组,这在前面寄存器讲解的过程中已经讲到。 关于函数 HAL_NVIC_SetPriorityGrouping 的函数体内容解读我就给大家介绍到这里。接下来我 们来看看这个函数的入口参数。大家继续回到函数 HAL_NVIC_SetPriorityGrouping 的定义可以 看到,函数的最开头有这样一行函数; assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup)); 其中函数 assert_param 是断言函数,它的作用主要是对入口参数的有效性进行判断。也就 是说我们可以通过这个函数知道入口参数在那些范围内有效的。而其入口参数通过在 MDK 中 双击选中“IS_NVIC_PRIORITY_GROUP”,然后右键“Go to defition of...”可以查看到为: #define IS_NVIC_PRIORITY_GROUP(GROUP) (((GROUP) == NVIC_PRIORITYGROUP_0) || ((GROUP) == NVIC_PRIORITYGROUP_1) || ((GROUP) == NVIC_PRIORITYGROUP_2) || ((GROUP) == NVIC_PRIORITYGROUP_3) || ((GROUP) == NVIC_PRIORITYGROUP_4)) 从这个内容可以看出,当 GROUP 的值为 NVIC_PRIORITYGROUP_0 ~NVIC_PRIORITYGROUP_4 的时候,IS_NVIC_PRIORITY_GROUP 的值才是为真。这也就是 我 们 上 面 表 4.5.1 讲 解 的 , 分 组 范 围 为 0-4 , 对 应 的 入 口 参 数 为 宏 定 义 值 NVIC_PriorityGroup_0~NVIC_PriorityGroup_4。比如我们设置整个系统的中断优先级分组为 2, 那么方法是: HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); 这样就确定了中断优先级分组为 2,也就是 2 位抢占优先级,2 位响应优先级,抢占优先级 和响应优先级的值的范围均为 0-3。 讲到这样,大家对怎么进行系统的中断优先级分组设置,以及具体的中断优先级设置函数 HAL_NVIC_SetPriorityGrouping 的内部函数实现都有了一个详细的理解。接下来我们来看看在 HAL 库里面,是怎样调用 HAL_NVIC_SetPriorityGrouping 函数进行分组设置的。 打开 stm32f4xx_hal.c 文件可以看到,文件内部定义了 HAL 库初始化函数 HAL_Init,这个 函数非常重要,其作用主要是对中断优先级分组,FLASH 以及硬件层进行初始化,我们在 3.1 小节对其进行了比较详细的讲解。这里我们只需要知道,在系统主函数 main 开头部分,我们都 会首先调用 HAL_Init 函数进行一些初始化操作。在 HAL_Init 内部,有如下一行代码: HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); 这行代码的作用是把系统中断优先级分组设置为分组 4,这在我们前面已经详细讲解。也 就是说,在主函数中调用 HAL_Init 函数之后,在 HAL_Init 函数内部会通过调用我们之前讲解 的 HAL_NVIC_SetPriorityGrouping 函数来进行系统中断优先级分组设置。所以,我们要进行中 断优先级分组设置,只需要修改 HAL_Init 函数内部的这行代码即可。中断优先级分组的内容我 们就给大家讲解到这里。 设置好了系统中断分组,那么对于每个中断我们又怎么确定他的抢占优先级和响应优先级 呢?官方 HAL 库文件 stm32f4xx_hal_cortex.c 中定义了三个单个中断优先级设置函数。函数如 下: void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority); void HAL_NVIC_EnableIRQ(IRQn_Type IRQn); void HAL_NVIC_DisableIRQ(IRQn_Type IRQn); 第一个函数 HAL_NVIC_SetPriority 是用来设置单个优先级的抢占优先级和响应优先级的 值。 第二个函数 HAL_NVIC_EnableIRQ 是用来是使能这个中断通道。 第三个函数 HAL_NVIC_DisableIRQ 是用来清除某个中断使能的,也就是中断失能。 这三个函数的使用都非常简单,对具体的调用方法,大家可以参考我们后面外部中断实验 讲解。 这里大家还需要注意,中断优先级分组和中断优先级设置是两个不同的概念。中断优先级 分组是用来设置整个系统对于中断分组设置为那个分组,分组号为 0-4,设置函数为 HAL_NVIC_SetPriorityGrouping,确定了中断优先级分组号,也就确定了系统对于单个中断的 抢占优先级和响应优先级设置各占几个位(对应表 4.5.1)。设置好中断优先级分组,确定了分 组号之后,接下来我们就是要对单个优先级进行中断优先级设置。也就是这个中断的抢占优先 级和响应优先级的值,设置方法就是我们上面讲解的三个函数。 最后我们总结一下中断优先级设置的步骤: ①系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和响应优先级的 分配位数。设置函数为 HAL_NVIC_PriorityGroupConfig。对于 HAL 库,在文件 stm32f4xx_hal.c 内部定义函数 HAL_Init 中有调用 HAL_NVIC_PriorityGroupConfig 函数进行相关设置,所以我 们只需要修改 HAL_Init 内部对中断优先级分组设置即可。 ②设置单个中断的中断优先级别和使能响应中断通道,使用到的函数主要为函数 HAL_NVIC_SetPriority 和函数 HAL_NVIC_EnableIRQ。 4.6 HAL 库中寄存器地址名称映射分析 之所以要讲解这部分知识,是因为经常会遇到客户提到不明白 HAL 库中那些结构体是怎么 与寄存器地址对应起来的。这里我们就做一个简要的分析吧。 首先我们看看 51 中是怎么做的。51 单片机开发中经常会引用一个 reg51.h 的头文件,下 面我们看看他是怎么把名字和寄存器联系起来的: sfr P0 =0x80; sfr 也是一种扩充数据类型,点用一个内存单元,值域为 0~255。利用它可以访问 51 单片 机内部的所有特殊功能寄存器。如用 sfr P1 = 0x90 这一句定义 P1 为 P1 端口在片内的寄存 器。然后我们往地址为 0x80 的寄存器设值的方法是:P0=value; 那么在 STM32 中,是否也可以这样做呢??答案是肯定的。肯定也可以通过同样的方 式来做,但是 STM32 因为寄存器太多太多,如果一一以这样的方式列出来,那要好大的篇 幅,既不方便开发,也显得太杂乱无序的感觉。所以 MDK 采用的方式是通过结构体来将 寄存器组织在一起。下面我们就讲解 MDK 是怎么把结构体和地址对应起来的,为什么我 们修改结构体成员变量的值就可以达到操作对应寄存器的值。这些事情都是在 stm32f40x.h 文件中完成的。我们通过 GPIOA 的几个寄存器的地址来讲解吧。 首先我们可以查看《STM32F411xC/E 参考手册》中的寄存器地址映射表(P163): 图 4.6.1 GPIO 寄存器地址映像 从这个表我们可以看出,因为 GPIO 寄存器都是 32 位的,所以每组 GPIO 的 10 个寄存器中,每个寄存器占有 4 个地址,一共占用 40 个地址,地址偏移范围为(0x00~0x24)。 这个地址偏移是相对 GPIOA 的基地址而言的。GPIOA 的基地址是怎么算出来的呢?因为 GPIO 都是挂载在 AHB1 总线之上,所以它的基地址是由 AHB1 总线的基地址加上 GPIOA 在 AHB1 总线上的偏移地址决定的。同理依次类推,我们便可以算出 GPIOA 基地址了。下 面我们打开 stm32f411xe.h 定位到 GPIO_TypeDef 定义处: typedef struct { __IO uint32_t MODER; __IO uint32_t OTYPER; __IO uint32_t OSPEEDR; __IO uint32_t PUPDR; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t LCKR; __IO uint32_t AFR[2]; } GPIO_TypeDef; 然后定位到: #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 可以看出,GPIOA 是将 GPIOA_BASE 强制转换为 GPIO_TypeDef 指针,这句话的意思是, GPIOA 指向地址 GPIOA_BASE,GPIOA_BASE 存放的数据类型为 GPIO_TypeDef。然后双 击“GPIOA_BASE”选中之后右键选中“Go to definition of ”,便可一查看 GPIOA_BASE 的宏定义: #define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000) 依次类推,可以找到最顶层: #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000) #define PERIPH_BASE ((uint32_t)0x40000000) 所以我们便可以算出 GPIOA 的基地址位: GPIOA_BASE= 0x40000000+0x00020000+0x0000=0x40020000 下面我们再跟《STM32F411xC/E 参考手册》比较一下看看 GPIOA 的基地址是不是 0x40020000。截图 P28 存储器映射表我们可以看到,GPIOA 的起始地址也就是基地址确实 是 0x40020000: 图 4.6.2 GPIO 存储器地址映射表 同样的道理,我们可以推算出其他外设的基地址。 上面我们已经知道 GPIOA 的基地址,那么那些 GPIOA 的 10 个寄存器的地址又是怎么 算出来的呢??在上面我们讲过 GPIOA 的各个寄存器对于 GPIOA 基地址的偏移地址,所 以我们自然可以算出来每个寄存器的地址。 GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相对 GPIOA 基地址的偏移值 这个偏移值在上面的寄存器地址映像表中可以查到。 那么在结构体里面这些寄存器又是怎么与地址一一对应的呢?这里涉及到结构体成员 变量地址对齐方式方面的知识,这方面的知识大家可以在网上查看相关资料复习一下,这 里我们不做详细讲解。在我们定义好地址对齐方式之后,每个成员变量对应的地址就可以 根据其基地址来计算。对于结构体类型 GPIO_TypeDef,他的所有成员变量都是 32 位,成 员变量地址具有连续性。所以自然而然我们就可以算出 GPIOA 指向的结构体成员变量对应 地址了 表 4.6.3 GP表 4.6.3 GPIOA 各寄存器实际地址表IOA 各寄存器实际地址表 我们可以把 GPIO_TypeDef 的定义中的成员变量的顺序和 GPIOx 寄存器地址映像对比可以发现,他们的顺序是一致的,如果不一致,就会导致地址混乱了。 这就是为什么固件库里面:GPIOA->BRR=value;就是设置地址为 0x40020000 +0x18(BRR 偏移量)=0x40020018 的寄存器 BSRR 的值了。它和 51 里面 P0=value 是设置地 址为 0x80 的 P0 寄存器的值是一样的道理。 看到这里你是否会学起来踏实一点呢??STM32 使用的方式虽然跟 51 单片机不一样, 但是原理都是一致的。 4.7 MDK 中使用 HAL 库快速组织代码技巧 这一节主要讲解在使用 MDK 中使用 HAL 库开发的一些小技巧,仅供初学者参考。这节的 知识大家可以在学习第一个跑马灯实验的时候参考一下,对初学者应该很有帮助。我们就用最 简单的 GPIO 初始化函数为例。 现 在 我 们 要 初 始 化 某 个 GPIO 端 口 , 我 们 要 怎 样 快 速 操 作 呢 ? 在 头 文 件 stm32f4xx_hal_gpio.h 头文件中,定义 GPIO 初始化函数为: void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init); 现在我们想写初始化函数,那么我们在不参考其他代码的前提下,怎么组织代码呢? 首先,我们可以看出,函数的入口参数是 GPIO_TypeDef 类型指针和 GPIO_InitTypeDef 类 型 指 针 , 因 为 GPIO_TypeDef 入 口 参 数 比 较 简 单 , 所 以 我 们 通 过 第 二 个 入 口 参 数 GPIO_InitTypeDef 类型指针来讲解。双击 GPIO_InitTypeDef 后右键选择“Go to definition…”,如 下图 4.7.1: 图 4.7.1 查看类型定义方法 于是定位到 stm32f4xx_hal_gpio.h 中 GPIO_InitTypeDef 的定义处: typedef struct { uint32_t Pin; uint32_t Mode; uint32_t Pull; uint32_t Speed; uint32_t Alternate; }GPIO_InitTypeDef; 可以看到这个结构体有 5 个成员变量,这也告诉我们一个信息,一个 GPIO 口的状态是由 模式(Mode),速度(Speed)以及上下拉(Pull)来决定的。我们首先要定义一个结构体变量, 下面我们定义: GPIO_InitTypeDef GPIO_InitStructure; 接着我们要初始化结构体变量 GPIO_InitStructure。首先我们要初始化成员变量 Pin,这个时 候我们就有点迷糊了,这个变量到底可以设置哪些值呢?这些值的范围有什么规定吗? 这里我们就要找到 HAL_GPIO_Init()函数的声明处,同样,双击 HAL_GPIO_Init(),右键点 击“Go to definition of …”,这样光标定位到 stm32f4xx_hal_gpio.c 文件中的 HAL_GPIO_Init 函数 体开始处,我们可以看到在函数的开始处有如下几行: void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) { …… /* Check the parameters */ assert_param(IS_GPIO_ALL_INSTANCE(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Init->Pin)); assert_param(IS_GPIO_MODE(GPIO_Init->Mode)); assert_param(IS_GPIO_PULL(GPIO_Init->Pull)); …//此处省略部分代码 assert_param(IS_GPIO_AF(GPIO_Init->Alternate)); …//此处省略部分代码 } 顾名思义,assert_param 是断言语句,是对函数入口参数的有效性进行判断,所以我们可 以从这个函数入手,确定我们的入口参数的范围。第一行是对第一个参数 GPIOx 进行有效性判 断,双击“IS_GPIO_ALL_INSTANCE”右键点击“go to defition of…” 定位到了下面的定义: #define IS_GPIO_ALL_INSTANCE(INSTANCE) (((INSTANCE) == GPIOA) || ((INSTANCE) == GPIOB) || ((INSTANCE) == GPIOC) || ((INSTANCE) == GPIOD) || ((INSTANCE) == GPIOE)) 很明显可以看出,GPIOx 的取值规定只允许是 GPIOA~GPIOE。 同样的办法,我们双击“IS_GPIO_PIN”右键点击“go to defition of...”,定位到下面的定 义: #define IS_GPIO_PIN(PIN) ((((PIN) & GPIO_PIN_MASK ) != 0x00U) && (((PIN) & ~GPIO_PIN_MASK) == 0x00U)) 同时,宏定义标识符 GPIO_PIN_MASK 的定义为: #define GPIO_PIN_MASK 0x0000FFFFU 从上面可以看出,PIN 取值只要低 16 位不为 0 即可。这里需要大家注意,因为一组 IO 口 只有16个IO,实际上PIN的值在这里只有低16位有效,所以PIN的取值范围为0x0001~0xFFFF。 那么是不是我们写代码初始化就是直接给一个 16 位的数字呢?这也是可以的,但是大多数情况 下,我们不会直接在入口参数处设置一个简单的数字,因为这样代码的可读性太差,HAL 库会 将 这 些 数 字 的 含 义 通 过 宏 定 义 定 义 出 来 , 这 样 可 读 性 大 大 增 强 。 我 们 可 以 看 到 在 GPIO_PIN_MASK 宏定义的上面还有数行宏定义: #define GPIO_PIN_0 ((uint16_t)0x0001) #define GPIO_PIN_1 ((uint16_t)0x0002) #define GPIO_PIN_2 ((uint16_t)0x0004) ...//此处省略部分定义 #define GPIO_PIN_14 ((uint16_t)0x4000) #define GPIO_PIN_15 ((uint16_t)0x8000) #define GPIO_PIN_All ((uint16_t)0xFFFF) 这些宏定义 GPIO_PIN_0~GPIO_PIN_ALL 就是 HAL 库事先定义好的,我们写代码的时候 初始化结构体,成员变量 Pin 的时候入口参数可以是这些宏定义标识符。 同理,对于成员变量 Pull,我们用同样的方法,可以找到其取值范围定义为: #define IS_GPIO_PULL(PULL) (((PULL) == GPIO_NOPULL) || ((PULL) == GPIO_PULLUP) || ((PULL) == GPIO_PULLDOWN)) 也 就 是 PULL 的 取 值 范 围 只 能 是 标 识 符 GPIO_NOPULL , GPIO_PULLUP 以 及 GPIO_PULLDOWN。 对于其他成员变量 Mode 以及 Speed,方法都是一样的,这里基于篇幅考虑我们就不重复 讲解。讲到这里,我们基本对 HAL_GPIO_Init 的入口参数有比较详细的了解了。于是我们可以 组织起来下面的代码: GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_9; //PA9 GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速 GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为 USART1 HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9 接着又有一个问题会被提出来,这个初始化函数一次只能初始化一个 IO 吗?我要同时初 始化很多个 IO 口,是不是要复制很多次这样的初始化代码呢? 这里又有一个小技巧了。从上面的 GPIO_PIN_X 的宏定义我们可以看出,这些值是 0,1,2,4 这样的数字,所以每个 IO 口选定都是对应着一个位,16 位的数据一共对应 16 个 IO 口。这个 位为 0 那么这个对应的 IO 口不选定,这个位为 1 对应的 IO 口选定。如果多个 IO 口,他们都 是对应同一个 GPIOx,那么我们可以通过|(或)的方式同时初始化多个 IO 口。这样操作的前 提是,他们的 Mode,Speed,Pull 参数值相同,因为这些参数并不能一次定义多种。所以初始 化多个具有相同配置的 IO 口的方式可以是如下: GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10//PA9,PA10, GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速 GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为 USART1 HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PA9 ,PA10, 对于那些参数可以通过|(或)的方式连接,这既有章可循,同时也靠大家在开发过程中不 断积累。 大家觉得上面讲解有点麻烦,每次要去查找 assert_param()这个函数去寻找,那么有没有更 好的办法呢?大家可以打开 GPIO_InitTypeDef 结构体定义: typedef struct { uint32_t Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ uint32_t Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIO_mode_define */ uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins. This parameter can be a value of @ref GPIO_pull_define */ uint32_t Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIO_speed_define */ uint32_t Alternate; /*!< Peripheral to be connected to the selected pins. This parameter can be a value of @ref GPIO_Alternate_function_selection */ }GPIO_InitTypeDef; 从上图的结构体成员后面的注释我们可以看出 Pin 的意思是 “Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define” 从这段注释可以看出 Pin 的取值需要参考注释 GPIO_pins_define,大家可以在 MDKK 中搜 索注释 GPIO_pins_define,就可以找到上面我们提到的 Pin 的取值范围宏定义。如果要确定详 细的信息我们就得去查看手册了。对于去查看手册的那个地方,你可以在函数 HAL_GPIO_Init() 函数体中搜索 Pin 关键字,然后查看库函数设置 Pin 是设置哪个寄存器的哪个位,然后去中文 参考手册查看该寄存器相应位的定义以及前后文的描述。 这一节我们就讲解到这里,希望能对大家的开发有帮助。 4.8 手把手教你入门 STM32CubeMx 图形配置工具 上一章节我们讲解 stm32Cube 的时候提到 stm32Cube 包含 2 个部分:一部分是上一章我们 讲解的嵌入式软件包(包括 HAL 库),另一部分是图形化配置工具 STM32CubeMX。本小节 我们将给大家讲解 STM32CubeMX 相关知识,带领大家入门 STM32CubeMX 图形化配置工具。 之所以我们要把 STM32CubeMX 讲解放在本小节,是因为 STM32CubeMX 最基本也是最重要 的用途是配置时钟系统,所以我们要先讲解 STM32F4 的时钟系统之后,才能教大家学习 STM32CubeMX。这部分内容我们分 3 个小节来讲解: 4.8.1 STM32CubeMX 简介 4.8.2 STM32CubeMX 运行环境搭建 4.8.3 使用 STM32CubeMX 工具配置工程模板 4.8.1 STM32CubeMX 简介 STM32CubeMX 是 ST 意法半导体近几年来大力推荐的 STM32 芯片图形化配置工具,允许 用户使用图形话向导生成 C 初始化代码,可以大大减轻开发工作,时间和费用。STM32CubeMX 几乎覆盖了 STM32 全系列芯片。它具有如下特性: 1 直观的选择 MCU 型号,可指定系列、封装、外设数量等条件 2 微控制器图形化配置 3 自动处理引脚冲突 4 动态设置时钟树,生成系统时钟配置代码 5 可以动态设置外围和中间件模式和初始化 6 功耗预测 7 C 代码工程生成器覆盖了 STM32 微控制器初始化编译软件,如 IAR,KEIL,GCC。 8 可以独立使用或者作为 Eclipse 插件使用 对 于 STM32CubeMX 和 STM32Cube 的 关 系 这 里 我 们 还 需 要 特 别 说 明 一 下 , STM32Cube 包 含 STM32CubeMX 图 形 工 具 和 STM32Cube 库 两 个 部 分 , 使 用 STM32CubeMX 配置生成的代码,是基于 STM32Cube 库的。也就是说,我们使用 STM32CubeMX 配置出来的初始化代码,和 STM32Cube 库兼容,例如硬件抽象层代码就 是使用的 STM32 的 HAL 库。不同的 STM32 系列芯片,会有不同的 STM32Cube 库支持, 而 STM32Cube 库即可。它们之间的关系如下图 4.8.1.1; 图 4.8.1.1 STM32CubeMX 和 STM32Cube 库的关系 4.8.2 STM32CubeMX 运行环境搭建 STM32CubeMX 运行环境搭建包含两个部分。首先是 Java 运行环境安装,其次是 STM32CubeMX 软件安装。对于 Java 运行环境,大家可以到 Java 官网 www.java.com 下载最新 的 Java 软件,也可以直接从我们光盘复制安装包,目录为:5,软件资料1,软件Java 安装包。 这里大家需要注意,STM32CubeMX 的 Java 运行环境版本必须是 V1.7 及以上,如果你的电脑 安装过 V1.7 以下版本,请先删掉后重新安装最新版本。 对于 Java 运行环境安装,我们这里就不做过多讲解,大家直接双击安装包,根据提示安装 即可。安装完成之后提示界面如下图 4.8.2.1: 图 4.8.2.1 Java 安装成功提示界面 安装完 Java 运行环境之后,为了检测是否正常安装,我们可以打开 Windows 的命令输入 框,输入:java -version 命令,如果显示 Java 版本信息,则安装成功。提示信息如下图 4.8.2.2: 图 4.8.2.2 查看 Java 版本 在安装完 Java 运行环境之后,接下来我们安装 STM32CubeMX 图形化工具。该软件大家 同样可以直接从光盘复制,目录为:5,软件资料1,软件STM32CubeMX,也可以直接从 ST 官方下载,下载地址为:www.st.com/stm32cube。 接下来我们直接双击 STM32CubeMX 安装包,根据提示信息安装即可。安装完成之后提示信息 如下图 4.8.2.3 所示: 图 4.8.2.3 STM32CubeMx 安装完成界面 安装完成之后,我们打开软件,如果软件安装成功,打开软件之后的界面如下图 4.8.2.4 所 示: 图 4.8.2.4 STM32CubeMX 打开后的显示界面 在安装好 STM32CubeMX 之后,接下来我们要在软件中指定 STM32Cube 软件包。在 STM32CubeMX 操作界面,依次点击 Help->Updater Settings,弹出界面如下图 4.8.2.5 所示: 图 4.8.2.5 Updater Settings 操作界面 在上图 4.8.2.5 中,我们只需要点击 Browse 按钮,定位到我们 3.1 小节讲解的 stm32cubefx 存放目录即可。这里大家注意,stm32cubefx 文件夹名字遵循 STM32Cube_FW_Fx_Vm.n 格式, 我们指定的“Repository Folder”下面必须存在一个或者多个 STM32Cube_FW_Fx_Vm.n 格式程 序包,在 STM32CubeMX 生成工程的时候,会根据我们选择的芯片型号,去这个目录加载必要 的库文件。一般情况下,我们会新建一个目录,然后把我们需要使用的各种 stm32cubefx 支持 包解压放到该目录之下,然后把该目录指定为“Repository Folder”即可。操作方法如下图所示: 图 4.8.2.6 指定程序库目录 Cube 软件对文件路径有中文支持的不是很友好,会显示框框,这个需要注意。另外,我们 也可以直接在 STM32CubeMX 中点击 Help->Manage embedded software packages 下载需要的程 序库,但是由于速度比较慢,而且在下载过程中很容易中断,所以我们不推荐直接在 CubeMX 中下载。 接下来我们将讲解怎么使用 STM32CubeMX 新建一个完整的 STM32F4 工程。 4.8.3 使用 STM32CubeMX 工具配置工程模板 大多数情况下,我们都只使用 STM32CubeMX 来生成工程的时钟系统初始化代码以及外设 的初始化代码,因为用户控制逻辑代码是无法在 STM32CubeMX 中完成的,需要用户自己根据 需求来实现。使用 STM32CubeMX 配置工程的一般步骤为: 1)工程初步建立和保存 2)RCC 设置 3)时钟系统(时钟树)配置 4)GPIO 功能引脚配置 5)生成工程源码 6)编写用户代码 接下来我们将按照上面 6 个步骤,依次教大家使用 STM32CubeMX 工具生成一个完整的工程。 4.8.3.1 工程初步建立和保存 工程建立的方法有两种方法,第一种方法是打开 STM32CubeMX 之后在主界面点击 NewProject 按钮,第二种方法是在菜单栏依次点击 File->New Project。操作方法如下图 4.8.3.1.1 所示: 图 4.8.3.1.1 新建工程 点击新建工程按钮之后,会弹出 MCU 选择窗口。我们依次在选项卡 Core,Serise,Line和 Package 之下选择与我们使用的芯片 STM32F411RCT6 对应的参数,然后选择对应的芯片型号,最后双击确定。操作方法如下图 4.8.3.1.2 所示: 图 4.8.3.1.2 选择 MCU 为了避免在软件使用过程中出现意外导致工程没有保存,所以我们选择好芯片型号之后, 先对工程进行保存。依次点击菜单栏 File->Save Project,然后保存工程到某个文件夹下面即可。操作过程如下图 4.8.3.1.3 所示: 图 4.8.3.1.3 保存工程 保存完成之后,大家进入 Template 目录后发现目录中多了一个 Template.ioc 文件,下次我 们点击这个文件就可以直接打开这个工程。 工程新建好之后会直接进入 Pinout 选项卡,这个时候界面会展示芯片完整引脚图,如下图 4.8.3.1.4 所示 图 4.8.3.1.4 STM32CubeMX 中芯片引脚图 在引脚图中,我们可以对引脚功能进行配置。图中黄色的引脚主要是一些电源和 GND 引 脚。如果某个引脚被使用,那么会显示为绿色。 4.8.3.2 RCC 设置 对 STM32 芯片而言,RCC 配置的重要性不言而喻。在 STM32CubeMX 中,RCC 相关设置 却 非 常 简 单 , 因 为 它 把 系 统 独 立 出 来 配 置 。 在 操 作 界 面 , 依 次 点 击 选 项 卡Pinout->Peripherals->RCC 便可进入 RCC 配置栏,操作步骤如下图 4.8.3.2.1 所示: 图 4.8.3.2.1 进入 RCC 配置栏 从上图可以看出,RCC 配置栏实际上只有 5 个配置项。选项 High Speed Clock(HSE)用来配置 HSE,第二个选项 Low Speed Clock(LSE)用来配置 LSE,选项 Master Clock Output 1 用来选择是否使能 MCO1 引脚时钟输出,选项 Master Clock Output 2 用来选择是否使能 MCO2 引 脚时钟输出,最后一个选项 Audio Clock Input(I2S_CKIN)用来选择是否从 I2S_CKIN(PC9)输入 I2S 时钟。这里大家要注意,因为选项 Master Clock Output 2 和选项 Audio Clock Input (I2S_CKIN)都是使用的 PC9 引脚,所以如果我们使能了其中一个,那么另一个选项会自动显示为红色,也就是不允许配置,这就是 STM32CubeMX 的自动冲突检测功能。 本 小 节 我们 只 使 用 到 HSE , 所 以 我们 设 置 选 项 High Speed Clock( HSE ) 的 值 为 Crystal/Ceramic Resonator(使用晶振/陶瓷振荡器)即可。这里还需要说明一下,值 Bypass Clock Source 的意思是旁路时钟源,也就是不使用晶振/陶瓷振荡器,直接通过外部提供一个可靠的 4-26Mhz 时钟作为 HSE。配置好的 RCC 配置选项如下图 4.8.3.2.2 所示: 图 4.8.3.2.2 RCC 选项配置 从上图还可以看出,在我们打开了 HSE 之后,右边的引脚图中,相应的引脚会由灰色变为 绿色,表示该引脚已经被使用。配置完 RCC 之后,接下来我们来看看配置时钟系统树的方法。 4.8.3.3 时钟系统(时钟树)配置 在使用 STM32CubeMX 配置时钟树之前,大家需要充分理解 STM32 时钟系统,这在我们 前面 4.3 小节有非常详细的讲解,只有熟练掌握了 STM32 时钟系统,那么在软件中配置时钟树 才会得心应手。 点击 Clock Configuration 选项卡即可进入时钟系统配置栏,如下图 4.8.3.3.1 所示: 图 4.8.3.3.1 时钟系统配置栏 进入 Clock Configuration 配置栏之后可以看到,界面展现一个完整的 STM32F411 时钟系统 框图。这个时钟系统框图跟我们之前时钟系统章节讲解的时钟系统框图实际是一模一样的,只 不过调整了一下显示顺序。从这个时钟树配置图可以看出,配置的主要是外部晶振大小,分频 系数,倍频系数以及选择器。在我们配置的工程中,时钟值会动态更新,如果某个时钟值在配 置过程中超过允许值,那么相应的选项框会红色提示。 这里,我们将配置一个和我们之前讲解的 STM32_Clock_Init 函数实现的一模一样的配置。 Stm32_Clock_Init 函数主要实现的是以 HSE 为时钟源,配置主 PLL 相关参数,然后系统时钟选 择 PLL 为时钟源,最终配置系统时钟为 96Mhz 的过程。同时,还配置了 AHB,APB1,APB2 和 Systick 的相关分频系数。由于图片比较大,我们把主要的配置部分分两部分来讲解,第一部 分是配置系统时钟,第二部分是配置 AHB,APB1 和 APB2 的分频系数。首先我们来看看第一部分配置如下图 4.8.3.3.2 所示: 图 4.8.3.3.2 系统时钟配置图 我们把系统时钟配置为 4 个步骤,分别用标号①~④表示,详细过程为: 1 时钟源参数设置:HSE 或者 HSI 配置。这里我们选择 HSE 为时钟源,所以我们之前 必须在 RCC 配置工程中我们开启了 HSE。 2 时钟源选择:HSE 还是 HSI。这里我们配置 HSE 即可。 3 PLL 倍频系数 M 配置。倍频系数 M 我们设置为 4 4 主 PLL 倍频系数 N 配置。倍数系数 N 我们设置为 96。 5 主 PLL 分频系数 P 配置。分频系数 P 我们配置为 2。 6 系统时钟时钟源选择:PLL,HSI 还是 HSE。这里毫无疑问,我们选择 PLL,选择器 选择 PLLCLK 即可。 经过上面的 6 个步骤,就会生成标准的 96Mhz 系统时钟。接下来我们只需要配置 AHB, APB1,APB2 和 Systick 的分频系数,就可以完全实现函数 Stm32_Clock_Init 配置的时钟系统。 配置如下图 4.8.3.3.3 所示: 图 4.8.3.3.3 AHB,APB1 和 APB2 总线时钟配置 AHB,APB1 和 APB2 总线时钟以及 Systick 时钟的最终来源都是系统时钟 SYSCLK。其中 AHB 总线时钟 HCLK 是由 SYSCLK 经过 AHB 预分频器之后来的,如果我们要设置 HCLK 为 96Mhz,那么我们只需要配置图中标号⑦的地方为 1 即可。得到 HCLK 之后,接下来我们将在 图标好⑧~⑩处同样的方法依次配置 Systick,APB1 以及 APB2 分频系数为 2 和 1 即可。配置完 成之后,那么 HCLK=96Mhz,Systick 时钟为 96/1Mhz=96Mhz,PCLK1=96Mhz/2=48MHz, PCLK2=96Mhz/1=96Mhz,这和我们使用 Stm32_Clock_Init 函数配置的时钟是一模一样的。 配置完时钟之后,这个时候如果我们直接使用软件生成工程,那么我们就可以从工程中提 取系统时钟初始化配置相关代码。配置时钟系统实际上是 STM32CubeMX 一个很重要的功能。 为了验证我们工程的正确性,下一节我们将手把手教大家进行 IO 口配置,配置一个和我们 NANO STM32F4 开发板跑马灯实验初始化代码一样的效果。 4.8.3.4 GPIO 功能引脚配置 本小节,我们将讲解怎么使用 STM32CubeMX 工具配置 STM32 的 GPIO 口。在 NANO STM32F4 开发板的 PC0~PC7 引脚有连接八个 LED 灯,本小节将配置 PC0 和 PC1 这两个 IO 口 的相关参数。STM32CubeMX 可以直接在芯片引脚图上配置 IO 参数。这里我们回到 STM32CubeMX 的 Pinout 选项,在搜索栏输入 PC0 和 PC1 即可找到 PC0 和 PC1 在引脚图中的位置如下图 4.8.3.4.1 所示: 图 4.8.3.4.1 PC0/PC1 引脚位置图 接下来,我们在图 4.8.3.4.1 引脚中点击 PC0,在弹出的下拉菜单中,选择 IO 口的功能为 GPIO_Output。操作方法如下图 4.8.3.4.2 所示: 图 4.8.3.4.2 配置 GPIO 模式 同样的方法,我们配置 PC1 选择功能为 GPIO_Oput 即可。这里我们需要说明一下,如果我们要配置 IO 口为外部中断引脚或者其他复用功能,我们选择相应的选项即可。配置完 IO 口 功能之后,还要配置 IO 口的速度等参数。这些参数是在 Configuration 选项卡中配置的。进入 IO 口配置界面之后,界面会列出所有使用到的 IO 口的参数配置。这里,我们选中 PC0 栏,就 会显示框下方显示对应的 IO 口详细配置信息,然后我们对参数进行配置后点击 Apply 保存即可。配置方法如下图 4.8.3.4.3 所示: 图 4.8.3.4.3 配置 GPIO 口详细参数 这个界面里的 IO 参数含义这里就不做过多讲解,在大家学习完第一个实验跑马灯实验之 后,对参数的含义理解会更加清晰。配置完成之后,我们点击 OK 后界面回到 Configuration 选 项卡界面。 对于选项卡 Power Consumption Calculator,它的作用是对功耗进行计算,这里我们并没有 使用到,就不详细讲解了。 4.8.3.5 生成工程源码 经过上面 4 个步骤,一个完整的系统已经配置完成。接下来,我们将使用 STM32CubeMX生成我们需要的工程源码。在 STM32CubeMX 操作界面,依次点击菜单 Project Manager->Project 即可生成源码,操作方法如下图 4.8.3.5.1 所示: 图 4.8.3.5.1 点击 Generate Code 选项 点击之后,弹出的界面会要求配置生成的工程名称,保存目录以及使用的编译软件类型。 我们依次填写名称和目录即可,对于编译软件我们选择 MDK5,还有我们需选择使用的 Cube 库版本。操作过程如下图 4.8.3.5.2 所示: 图 4.8.3.5.2 工程参数设置 注意:如果在 Firmware Package Name and Version 栏中对应的 Cube 版本号不一致,请添加 自己的 Cube 包路径。配置完成后。点击 GENERATE CODE,生成工程,如图 4.8.3.5.3 所示: 点击后开始生成源码。源码生成完成之后,就保存在我们 Project Location 选项配置的目录中,同时弹出生成成功提示界面,我们可以点击界面的“Open Folder”按钮打开工程保存目录,也可以点击界面的“Open Project”按钮直接使用 IDE 打开工程。提示界面如下图 4.8.3.5.3 所示: 图 4.8.3.5.3 代码生成后提示界面 至此,一个完整的 STM32F4 工程就已经生成完成。生成后的工程目录结构如下图 4.8.3.5.4 所示: 图 4.8.3.5.4 STM32CubeMX 生成的工程目录结构 Drivers 文件夹存放的是 HAL 库文件和 CMSIS 相关文件。 Inc 文件夹存放的是工程必须的部分头文件。 MDK-ARM 下面存放的是 MDK 工程文件 Src 文件夹下面存放的是工程必须的部分源文件 Template.ioc 是 STM32CubeMX 工程文件,双击该文件工程就会在 STM32CubeMX 中杯打开。 4.8.3.6 编写用户程序 在编写用户程序之前,首先我们打开生成的工程模板进行编译,发现没有任何错误和警告。 工程模板结构如下图 4.8.3.6.1 所示: 图 4.8.3.6.1 使用 STM32CubeMX 生成的工程模板 该工程模板结构跟我们前面 3.3 小节新建的工程模板实际是类似的,只不过一些分组名称 不一样,同时我们将时钟系统配置源码放在 SYSTEM 分组的 sys.c 中,而该模板直接放在 main.c 源文件中。这里我们对该模板就不做过多讲解。我们直接打开 main.c 源文件可以看到,该文件 定义了两个关键函数 SystemClock_Config 函数用来配置配置时钟系统,和我们模板中的 Stm32_Clock_Init 函数作用一样。MX_GPIO_Init 函数用来初始化 PC0 和 PC1 相关配置,这在 我们的模板中,我们直接放在 main 函数中。接下来我们看看生成的工程模板的 main 函数,这 里我们删掉了源码注释,关键源码如下: int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); While(1) { } } 该函数 while 语句之前实现的功能和我们 3.3 小节的工程模板是一致的。这里,我们直接把 我们 3.3 小节新建的工程模板中 main 函数 while 语句中的源码复制到此处的 while 语句中,然 后复制 Delay 函数声明和定义到 main 函数之前。这里大家需要注意,STM32CubeMX 生成的 main.c 文件中,有很多地方有“/*USER CODE BEGIN X*/”和“/*USER CODE END X*/”格 式的注释,我们在这些注释的 BEGIN 和 END 之间编写代码,那么重新生成工程之后,这些代 码会保留而不会被覆盖。复制完代码之后,main 函数关键源码如下: /* USER CODE BEGIN 0 */ void Delay(__IO uint32_t nCount); void Delay(__IO uint32_t nCount) { while(nCount--){} } /* USER CODE END 0*/ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); /* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_SET); //PC0 置 1 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_SET); //PC1 置 1 Delay(0x7FFFFF); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_0,GPIO_PIN_RESET); //PC0 置 0 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_1,GPIO_PIN_RESET); //PC1 置 0 Delay(0x7FFFFF); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } 这个时候,我们对工程进行编译,发现没有任何警告和错误。同时,我们使用 3.4 小节的方法下载程序到 NANO STM32F4 开发板中(下载是时候,请注意配置 MDK),运行结果和 3.3 小节新建工程运行结果一模一样。 本小节使用 STM32CubeMX 新建的工程模板在我们光盘目录: “4,程序源码标准例程-HAL 库函数版本实验 0-3 Template 工程模板-使用 STM32CubeMX 配置”中有存放,大家在编写用 户代码过程中可以参考该工程的 main.c 文件。 这里我们需要说明一下,大多数情况下,我们使用 STM32CubeMX 主要用来配置时钟系统 和外设初始化代码。这里我们讲解新建一个工程模板,是为了系统全面的讲解 STM32CubeMX 生成工程的步骤。本小节就给大家讲解到这里。通过本章的学习,大家对 STM32CubeMX 的使 用就有了初步的认识和理解,后面我们会在实战篇通过几个实验来巩固这方面的知识。 |
|
|
相关推荐
|
|
1477 浏览 1 评论
1137 浏览 0 评论
1001 浏览 0 评论
STM32F405驱动DS1302时钟模块,输出时间错乱该怎么排查?
5380 浏览 2 评论
stm32f405rgt6驱动DS1302ZN出现时间错乱问题
4168 浏览 1 评论
/9
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-12-15 11:32 , Processed in 0.668949 second(s), Total 65, Slave 47 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191

淘帖