本帖最后由 两只袜子 于 2023-6-14 16:47 编辑
模式配置: 同RCC_APB2ENR一样,GPIOB的起始地址是:0X4001 0C00,我们也可以算出GPIO_CRL的地址为:0x40010C00。那么设置PB0为通用推挽输出,输出速率为2M的代码则如下所示: 同上,从手册中我们看到ODR寄存器的地址偏移是:0CH,可以算出GPIOB_ODR寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义GPIOB_ODR这个寄存器了,代码如下: #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
//PB0 输出低电平GPIOB_ODR = 0<<0; 第一层级:基地址宏定义完成用STM32控制一个LED的完整代码: #define RCC_APB2ENR *(volatile unsigned long *)0x40021018#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void){ // 开启端口B 的时钟 RCC_APB2ENR |= 1<<3;
// 配置PB0 为通用推挽输出模式,速率为2M GPIOB_CRL = (2<<0) | (0<<2);
// PB0 输出低电平,点亮LED GPIOB_ODR = 0<<0;}
void SystemInit(void){} 第二层级:基地址宏定义+结构体封装 外设寄存器结构体封装上面我们在操作寄存器的时候,操作的是寄存器的绝对地址,如果每个寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占32个或者16个字节,这种方式跟结构体里面的成员类似。 所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可。 下面我们先定义一个GPIO寄存器结构体,结构体里面的成员是GPIO的寄存器,成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样。 typedef struct { volatile uint32_t CRL; volatile uint32_t CRH; volatile uint32_t IDR; volatile uint32_t ODR; volatile uint32_t BSRR; volatile uint32_t BRR; volatile uint32_t LCKR;} GPIO_TypeDef;在《STM32 中文参考手册》8.2 寄存器描述章节,我们可以找到结构体里面的7个寄存器描述。在点亮LED的时候我们只用了CRL和ODR这两个寄存器,至于其他寄存器的功能大家可以自行看手册了解。 在GPIO结构体里面我们用了两个数据类型,一个是uint32_t,表示无符号的32位整型,因为GPIO的寄存器都是32位的。这个类型声明在标准头文件stdint.h 里面使用typedef对unsigned int重命名,我们在程序上只要包含这个头文件即可。 另外一个是volatile作用就是告诉编译器这里的变量会变化不因优化而省略此指令,必须每次都直接读写其值,这样就能确保每次读或者写寄存器都真正执行到位。 外设封装 STM32F1系列的GPIO端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每个端口都含有GPIO_TypeDef结构体里面的寄存器,我们可以根据手册各个端口的基地址把GPIO的各个端口定义成一个GPIO_TypeDef类型指针,然后我们就可以根据端口名(实际上现在是结构体指针了)来操作各个端口的寄存器,代码实现如下: #define GPIOA ((GPIO_TypeDef *) 0X4001 0800)#define GPIOB ((GPIO_TypeDef *) 0X4001 0C00)#define GPIOC ((GPIO_TypeDef *) 0X4001 1000)#define GPIOD ((GPIO_TypeDef *) 0X4001 1400)#define GPIOE ((GPIO_TypeDef *) 0X4001 1800)#define GPIOF ((GPIO_TypeDef *) 0X4001 1C00)#define GPIOG ((GPIO_TypeDef *) 0X4001 2000)外设内存映射 讲到基地址的时候我们再引人一个知识点:Cortex-M3存储器系统,这个知识点在《Cortex-M3权威指南》第5章里面讲到。CM3的地址空间是4GB,如下图所示: 我们这里要讲的是片上外设,就是我们所说的寄存器的根据地,其大小总共有512MB,512MB是其极限空间,并不是每个单片机都用得完,实际上各个MCU厂商都只是用了一部分而已。STM32F1系列用到了:0x4000 0000 ~0x5003 FFFF。现在我们说的STM32的寄存器就是位于这个区域。 APB1、APB2、AHB 总线基地址 现在我们说的STM32的寄存器就是位于这个区域,这里面ST设计了三条总线:AHB、APB2和APB1,其中AHB和APB2是高速总线,APB1是低速总线。不同的外设根据速度不同分别挂载到这三条总线上。 从下往上依次是:APB1、APB2、AHB,每个总线对应的地址分别是:APB1:0x40000000,APB2:0x4001 0000,AHB:0x4001 8000。 这三条总线的基地址我们是从《STM32 中文参考手册》2.3小节—存储器映像得到的:APB1的基地址是TIM2定时器的起始地址,APB2的基地址是AFIO的起始地址,AHB的基地址是SDIO的起始地址。其中APB1地址又叫做外设基地址,是所有外设的基地址,叫做PERIPH_BASE。 现在我们把这三条总线地址用宏定义出来,以后我们在定义其他外设基地址的时候,只需要在这三条总线的基址上加上偏移地址即可,代码如下: #define PERIPH_BASE ((uint32_t)0x40000000)#define APB1PERIPH_BASE PERIPH_BASE#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)GPIO 端口基地址 因为GPIO挂载到APB2总线上,那么现在我们就可以根据APB2的基址算出各个GPIO端口的基地址,用宏定义实现代码如下: #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000) 第二层级:基地址宏定义+结构体封装完成用STM32控制一个LED的完整代码 #include <stdint.h>#define __IO volatile
typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR;} GPIO_TypeDef;
typedef struct { __IO uint32_t CR; __IO uint32_t CFGR; __IO uint32_t CIR; __IO uint32_t APB2RSTR; __IO uint32_t APB1RSTR; __IO uint32_t AHBENR; __IO uint32_t APB2ENR; __IO uint32_t APB1ENR; __IO uint32_t BDCR; __IO uint32_t CSR;} RCC_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)#define RCC ((RCC_TypeDef *) RCC_BASE)
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void){ // 开启端口B 的时钟 RCC->APB2ENR |= 1<<3;
// 配置PB0 为通用推挽输出模式,速率为2M GPIOB->CRL = (2<<0) | (0<<2);
// PB0 输出低电平,点亮LED GPIOB->ODR = 0<<0;}
void SystemInit(void){} 第二层级变化: ①、定义一个外设(GPIO)寄存器结构体,结构体的成员包含该外设的所有寄存器,成员的排列顺序跟寄存器偏移地址一样,成员的数据类型跟寄存器的一样。 ②外设内存映射,即把地址跟外设建立起一一对应的关系。 ③外设声明,即把外设的名字定义成一个外设寄存器结构体类型的指针。 ④通过结构体操作寄存器,实现点亮LED。 第三层级:基地址宏定义+结构体封装+“位封装”(每一位的对应字节封装) 上面我们在控制GPIO输出内容的时候控制的是ODR(Output data register)寄存器,ODR是一个16位的寄存器,必须以字的形式控制其实我们还可以控制BSRR和BRR这两个寄存器来控制IO的电平,下面我们简单介绍下BRR寄存器的功能,BSRR自行看手册研究。 位清除寄存器BRR只能实现位清0操作,是一个32位寄存器,低16位有效,写0没影响,写1清0。现在我们要使PB0输出低电平,点亮LED,则只要往BRR的BR0位写1即可,其他位为0,代码如下: GPIOB->BRR = 0X0001;这时PB0就输出了低电平,LED就被点亮了。 如果要PB2输出低电平,则是: GPIOB->BRR = 0X0004; 如果要PB3/4/5/6。。。。。。这些IO输出低电平呢? 且从赋值的16进制数字我们很难清楚的知道控制的是哪个IO。 这时,我们是否可以把BRR的每个位置1都用宏定义来实现,如GPIO_Pin_0就表示0X0001,GPIO_Pin_2就表示0X0004。只要我们定义一次,以后都可以使用,而且还见名知意。“位封装”(每一位的对应字节封装) 代码如下: #define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */ 这时PB0就输出了低电平的代码就变成了: GPIOB->BRR = GPIO_Pin_0; 如果同时让PB0/PB15输出低电平,用或运算,代码: - GPIOB->BRR = GPIO_Pin_0|GPIO_Pin_15;
- 为了不使main函数看起来冗余,上述库封装 的代码不应该放在main里面,因为其是跟GPIO相关的,我们可以把这些宏放在一个单独的头文件里面。
- 在工程目录下新建stm32f10x_gpio.h,把封装代码放里面,然后把这个文件添加到工程里面。这时我们只需要在main.c里面包含这个头文件即可。
- 第四层级:基地址宏定义+结构体封装+“位封装”+函数封装
- 我们点亮LED的时候,控制的是PB0这个IO,如果LED接到的是其他IO,我们就需要把GPIOB修改成其他的端口,其实这样修改起来也很快很方便。
- 但是为了提高程序的可读性和可移植性,我们是否可以编写一个专门的函数用来复位GPIO的某个位,这个函数有两个形参,一个是GPIOX(X=A...G),另外一个是GPIO_Pin(0...15),函数的主体则是根据形参GPIOX 和GPIO_Pin来控制BRR寄存器,代码如下:
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){ GPIOx->BRR = GPIO_Pin;}[color=rgba(0, 0, 0, 0.9)] 这时,PB0输出低电平,点亮LED的代码就变成了: GPIO_ResetBits(GPIOB,GPIO_Pin_0);[color=rgba(0, 0, 0, 0.9)] 同理, 我们可以控制BSRR这个寄存器来实现关闭LED,代码如下: // GPIO 端口置位函数void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){ GPIOx->BSRR = GPIO_Pin;}[color=rgba(0, 0, 0, 0.9)] 这时,PB0输出高电平,关闭LED的代码就变成了: GPIO_SetBits(GPIOB,GPIO_Pin_0);[color=rgba(0, 0, 0, 0.9)] 同样,因为这个函数是控制GPIO的函数,我们可以新建一个专门的文件来放跟gpio有关的函数。相关文章:STM32中GPIO工作原理详解。 [color=rgba(0, 0, 0, 0.9)] 在工程目录下新建stm32f10x_gpio.c,把GPIO相关的函数放里面。这时我们是否发现刚刚新建了一个头文件stm32f10x_gpio.h,这两个文件存放的都是跟外设GPIO相关的。 [color=rgba(0, 0, 0, 0.9)] C文件里面的函数会用到h头文件里面的定义,这两个文件是相辅相成的,故我们在stm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h这个头文件。别忘了把stm32f10x.h这个头文件也包含进去,因为有关寄存器的所有定义都在这个头文件里面。 [color=rgba(0, 0, 0, 0.9)] 如果我们写其他外设的函数,我们也应该跟GPIO一样,新建两个文件专门来存函数,比如RCC这个外设我们可以新建stm32f10x_rcc.c和stm32f10x_rcc.h。其他外依葫芦画瓢即可。 实例编写[color=rgba(0, 0, 0, 0.9)] 以上,是对库封住过程的概述,下面我们正在地使用库函数编写LED程序。 ①管理库的头文件[color=rgba(0, 0, 0, 0.9)] 当我们开始调用库函数写代码的时候,有些库我们不需要,在编译的时候可以不编译,可以通过一个总的头文件stm32f10x_conf.h来控制,该头文件主要代码如下: [color=rgba(0, 0, 0, 0.9)]
[color=rgba(0, 0, 0, 0.9)] 这里面包含了全部外设的头文件,点亮一个LED我们只需要RCC和GPIO 这两个外设的库函数即可,其中RCC控制的是时钟,GPIO控制的具体的IO口。所以其他外设库函数的头文件我们注释掉,当我们需要的时候就把相应头文件的注释去掉即可。 [color=rgba(0, 0, 0, 0.9)] stm32f10x_conf.h这个头文件在stm32f10x.h这个头文件的最后面被包含,在第8296行: #ifdef USE_STDPERIPH_DRIVER#include "stm32f10x_conf.h"#endif[color=rgba(0, 0, 0, 0.9)] 代码的意思是,如果定义了USE_STDPERIPH_DRIVER这个宏的话,就包含stm32f10x_conf.h这个头文件。 [color=rgba(0, 0, 0, 0.9)] 我们在新建工程的时候,在魔术棒选项卡C/C++中,我们定义了USE_STDPERIPH_DRIVER 这个宏,所以stm32f10x_conf.h 这个头文件就被stm32f10x.h包含了,我们在写程序的时候只需要调用一个头文件:stm32f10x.h即可。 ②编写LED初始化函数[color=rgba(0, 0, 0, 0.9)] 经过寄存器点亮LED的操作,我们知道操作一个GPIO输出的编程要点大概如下: [color=rgba(0, 0, 0, 0.9)]1、开启GPIO的端口时钟 [color=rgba(0, 0, 0, 0.9)]2、选择要具体控制的IO口,即pin [color=rgba(0, 0, 0, 0.9)]3、选择IO口输出的速率,即speed [color=rgba(0, 0, 0, 0.9)]4、选择IO口输出的模式,即mode [color=rgba(0, 0, 0, 0.9)]5、输出高/低电平 [color=rgba(0, 0, 0, 0.9)] STM32的时钟功能非常丰富,配置灵活,为了降低功耗,每个外设的时钟都可以独自的关闭和开启。STM32中跟时钟有关的功能都由RCC这个外设控制,RCC中有三个寄存器控制着所以外设时钟的开启和关闭:RCC_APHENR、RCC_APB2ENR和RCC_APB1ENR,AHB、APB2和APB1代表着三条总线,所有的外设都是挂载到这三条总线上,GPIO属于高速的外设,挂载到APB2总线上,所以其时钟有RCC_APB2ENR控制。 GPIO 时钟控制[color=rgba(0, 0, 0, 0.9)] 固件库函数:RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函数的原型为: void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState){/* Check the parameters */ assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph)); assert_param(IS_FUNCTIONAL_STATE(NewState));if (NewState != DISABLE) { RCC->APB2ENR |= RCC_APB2Periph; } else { RCC->APB2ENR &= ~RCC_APB2Periph; }}[color=rgba(0, 0, 0, 0.9)] 当程序编译一次之后,把光标定位到函数/变量/宏定义处,按键盘的F12或鼠标右键的Go to definition of,就可以找到原型。固件库的底层操作的就是RCC外设的APB2ENR这个寄存器,宏RCC_APB2Periph_GPIOB的原型是:0x00000008,即(1<<3),还原成存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件库操作,寄存器操作的代码可读性就很差,只有才查阅寄存器配置才知道具体代码的功能,而固件库操作恰好相反,见名知意。 GPIO 端口配置[color=rgba(0, 0, 0, 0.9)] GPIO的pin,速度,模式,都由GPIO的端口配置寄存器来控制,其中IO0~IO7由端口配置低寄存器CRL控制,IO8~IO15由端口配置高寄存器CRH配置。固件库把端口配置的pin,速度和模式封装成一个结构体: typedef struct{uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode;} GPIO_InitTypeDef;[color=rgba(0, 0, 0, 0.9)] pin可以是GPIO_Pin_0~GPIO_Pin_15或者是GPIO_Pin_All,这些都是库预先定义好的宏。speed也被封装成一个结构体: typedef enum{ GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz} GPIOSpeed_TypeDef;[color=rgba(0, 0, 0, 0.9)] 速度可以是10M,2M或者50M,这个由端口配置寄存器的MODE位控制,速度是针对IO口输出的时候而言,在输入的时候可以不用设置。mode也被封装成一个结构体: typedef enum{ GPIO_Mode_AIN = 0x0, // 模拟输入 GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入(复位后的状态) GPIO_Mode_IPD = 0x28, // 下拉输入 GPIO_Mode_IPU = 0x48, // 上拉输入 GPIO_Mode_Out_OD = 0x14, // 通用开漏输出 GPIO_Mode_Out_PP = 0x10, // 通用推挽输出 GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 GPIO_Mode_AF_PP = 0x18 // 复用推挽输出} GPIOMode_TypeDef;[color=rgba(0, 0, 0, 0.9)] IO口的模式有8种,输入输出各4种,由端口配置寄存器的CNF配置。平时用的最多的就是通用推挽输出,可以输出高低电平,驱动能力大,一般用于接数字器件。 [color=rgba(0, 0, 0, 0.9)] 最终用固件库实现就变成这样: // 定义一个GPIO_InitTypeDef 类型的结构体GPIO_InitTypeDef GPIO_InitStructure;
// 选择要控制的IO 口GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 设置引脚为推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置引脚速率为50MHzGPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
/*调用库函数,初始化GPIOB0*/GPIO_Init(GPIOB, &GPIO_InitStructure);[color=rgba(0, 0, 0, 0.9)] 倘若同一端口下不同引脚有不同的模式配置,每次对每个引脚配置完成后都要调用GPIO初始化函数,代码如下: GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO 输出控制[color=rgba(0, 0, 0, 0.9)] GPIO输出控制,可以通过端口数据输出寄存器ODR、端口位设置/清除寄存器BSRR和端口位清除寄存器BRR这三个来控制。端口输出寄存器ODR是一个32位的寄存器,低16位有效,对应着IO0~IO15,只能以字的形式操作,一般使用寄存器操作。 // PB0 输出高电平,点亮LEDGPIOB->ODR = 1<<0;[color=rgba(0, 0, 0, 0.9)] 端口位清除寄存器BRR是一个32位的寄存器,低十六位有效,对应着IO0~IO15,只能以字的形式操作,可以单独对某一个位操作,写1清0。 // PB0 输出低电平,点亮LEDGPIO_ResetBits(GPIOB, GPIO_Pin_0);[color=rgba(0, 0, 0, 0.9)] BSRR是一个32位的寄存器,低16位用于置位,写1有效,高16位用于复位,写1有效,相当于BRR寄存器。高16位我们一般不用,而是操作BRR这个寄存器,所以BSRR这个寄存器一般用来置位操作 // PB0 输出高电平,熄灭LEDGPIO_SetBits(GPIOB, GPIO_Pin_0);[color=rgba(0, 0, 0, 0.9)] 综上:固件库LED GPIO初始化函数。 void LED_GPIO_Config(void){// 定义一个GPIO_InitTypeDef 类型的结构体 GPIO_InitTypeDef GPIO_InitStructure;
// 开启GPIOB 的时钟 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
// 选择要控制的IO 口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 设置引脚为推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置引脚速率为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIOB0*/ GPIO_Init(GPIOB, &GPIO_InitStructure);
// 关闭LED GPIO_SetBits(GPIOB, GPIO_Pin_0);
|