【正点原子探索者STM32F407开发板例程连载+教学】第6章 跑马灯实验-GPIO

2019-07-21 04:17发布

第六章 跑马灯实验


[mw_shl_code=c,true] 硬件平台:正点原子探索者STM32F407开发板 软件平台:MDK5.1 固件库版本:V1.4.0 [/mw_shl_code]

任何一个单片机,最简单的外设莫过于IO口的高低电平控制了,本章将通过一个经典的跑马灯程序,带大家开启STM32F4之旅,通过本章的学习,你将了解到STM32F4IO口作为输出使用的方法。在本章中,我们将通过代码控制ALIENTEK探索者STM32F4开发板上的两个LEDDS0DS1交替闪烁,实现类似跑马灯的效果。 本章分为如下四个小节:        6.1STM32F4 IO口简介        6.2, 硬件设计        6.3, 软件设计        6.4, 下载验证

6.1 STM32F4 IO简介

本章将要实现的是控制ALIENTEK探索者STM32F4开发板上的两个LED实现一个类似跑马灯的效果,该实验的关键在于如何控制STM32F4IO口输出。了解了STM32F4IO口如何输出的,就可以实现跑马灯了。通过这一章的学习,你将初步掌握STM32F4基本IO口的使用,而这是迈向STM32F4的第一步。 这一章节因为是第一个实验章节,所以我们在这一章将讲解一些知识为后面的实验做铺垫。为了小节标号与后面实验章节一样,这里我们不另起一节来讲。        在讲解STM32F4GPIO之前,首先打开我们光盘的第一个固件库版本实验工程跑马灯实验工程(光盘目录为:“4,程序源码标准例程-库函数版本实验1跑马灯/USER/ LED.uvproj”),可以看到我们的实验工程目录:                   6.1.1 跑马灯实验目录结构     接下来我们逐一讲解一下我们的工程目录下面的组以及重要文件。 ①       FWLib下面存放的是ST官方提供的固件库函数,每一个源文件stm32f4xx_ppp.c都对应一个头文件stm32f4xx_ppp.h。分组内的文件我们可以根据工程需要添加和删除,但是一定要注意如果你引入了某个源文件,一定要在头文件stm32f4xx_conf.h文件中确保对应的头文件也已经添加。比如我们跑马灯实验,我们只添加了5个源文件,那么对应的头文件我们必须确保在stm32f4xx_conf.h内也包含进来,否则工程会报错。 ②       CORE下面存放的是固件库必须的核心文件和启动文件。这里面的文件用户不需要修改。大家可以根据自己的芯片型号选择对应的启动文件。 ③       SYSTEMALIENTEK提供的共用代码,这些代码的作用和讲解在第五章都有讲解,大家可以翻过去看下。 ④       HARDWARE下面存放的是每个实验的外设驱动代码,他的实现是通过调用FWLib下面的固件库文件实现的,比如led.c里面调用stm32f4xx_gpio.c内定义的函数对led进行初始化,这里面的函数是讲解的重点。后面的实验中可以看到会引入多个源文件。 ⑤       USER下面存放的主要是用户代码。但是system_stm32f4xx.c文件用户不需要修改,同时stm32f4xx_it.c里面存放的是中断服务函数,这两个文件的作用在3.1节有讲解,大家可以翻过去看看。Main.c函数主要存放的是主函数了,这个大家应该很清楚。 工程分组情况我们就讲解到这里,接下来我们就要进入我们跑马灯实验的讲解部分了。这里需要说明一下,我们在讲解固件库之前会首先对重要寄存器进行一个讲解,这样是为了大家对寄存器有个初步的了解。大家学习固件库,并不需要记住每个寄存器的作用,而只是通过了解寄存器来对外设一些功能有个大致的了解,这样对以后的学习也很有帮助。 首先要提一下,在固件库中,GPIO端口操作对应的库函数函数以及相关定义在文件stm32f4xx_gpio.hstm32f4xx_gpio.c中。 相对于STM32F1来说,STM32F4GPIO设置显得更为复杂,也更加灵活,尤其是复用功能部分,比STM32F1改进了很多,使用起来更加方便。        STM32F4每组通用 I/O 端口包括 4 32 位配置寄存器(MODEROTYPEROSPEEDR PUPDR)、2 32 位数据寄存器(IDR ODR)、1 32 位置位/复位寄存器 (BSRR)1 32 位锁定寄存器 (LCKR) 2 32 位复用功能选择寄存器(AFRH AFRL)等。 这样,STM32F4每组IO1032位寄存器控制,其中常用的有4个配置寄存器+2个数据寄存器+2个复用功能选择寄存器,共8个,如果在使用的时候,每次都直接操作寄存器配置IO,代码会比较多,也不容易记住,所以我们在讲解寄存器的同时会讲解是用库函数配置IO的方法。STM32F1一样,STM32F4IO可以由软件配置成如下8种模式中的任何一种: 1、输入浮空 2、输入上拉 3、输入下拉 4、模拟输入 5、开漏输出 6、推挽输出 7、推挽式复用功能 8、开漏式复用功能 关于这些模式的介绍及应用场景,我们这里就不详细介绍了,感兴趣的朋友,可以看看这个帖子了解下:http://www.openedv.com/posts/list/32730.htm 。接下来我们详细介绍IO配置常用的8个寄存器: MODEROTYPEROSPEEDRPUPDRODRIDR AFRHAFRL。同时讲解对应的库函数配置方法。 首先看MODER寄存器,该寄存器是GPIO端口模式控制寄存器,用于控制GPIOxSTM32F4最多有9IO,分别用大写字母表示,即x=A/B/C/D/E/F/G/H/I,下同)的工作模式,该寄存器各位描述如表5.2.5.1所示:  5.2.5.1 GPIOx MODER寄存器各位描述        该寄存器各位在复位后,一般都是0(个别不是0,比如JTAG占用的几个IO口),也就是默认条件下一般是输入状态的。每组IO下有16IO口,该寄存器共32位,每2个位控制1IO,不同设置所对应的模式见表5.2.5.1描述。        然后看OTYPER寄存器,该寄存器用于控制GPIOx的输出类型,该寄存器各位描述见表5.2.5.2所示:  5.2.5.2 GPIOx OTYPER寄存器各位描述        该寄存器仅用于输出模式,在输入模式(MODER[1:0]=00/11时)下不起作用。该寄存器低16位有效,每一个位控制一个IO口,复位后,该寄存器值均为0        然后看OSPEEDR寄存器,该寄存器用于控制GPIOx的输出速度,该寄存器各位描述见表5.2.5.3所示:  5.2.5.3 GPIOx OSPEEDR寄存器各位描述        该寄存器也仅用于输出模式,在输入模式(MODER[1:0]=00/11时)下不起作用。该寄存器每2个位控制一个IO口,复位后,该寄存器值一般为0 然后看PUPDR寄存器,该寄存器用于控制GPIOx的上拉/下拉,该寄存器各位描述见表5.2.5.4所示:  5.2.5.4 GPIOx PUPDR寄存器各位描述 该寄存器每2个位控制一个IO口,用于设置上下拉,这里提醒大家,STM32F1是通过ODR寄存器控制上下拉的,而STM32F4则由单独的寄存器PUPDR控制上下拉,使用起来更加灵活。复位后,该寄存器值一般为0 前面,我们讲解了4个重要的配置寄存器。顾名思义,配置寄存器就是用来配置GPIO的相关模式和状态,接下来我们讲解怎么在库函数初始化GPIO的配置。 GPIO相关的函数和定义分布在固件库文件stm32f4xx_gpio.c和头文件stm32f4xx_gpio.h文件中。 在固件库开发中,操作四个配置寄存器初始化GPIO是通过GPIO初始化函数完成: void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) 这个函数有两个参数,第一个参数是用来指定需要初始化的GPIO对应的GPIO组,取值范围为GPIOA~GPIOK。第二个参数为初始化参数结构体指针,结构体类型为GPIO_InitTypeDef。下面我们看看这个结构体的定义。首先我们打开我们光盘的跑马灯实验,然后找到FWLib组下面的stm32f4xx_gpio.c文件,定位到GPIO_Init函数体处,双击入口参数类型GPIO_InitTypeDef后右键选择“Go to definition of …”可以查看结构体的定义: typedef struct {   uint32_t GPIO_Pin;       GPIOMode_TypeDef GPIO_Mode;       GPIOSpeed_TypeDef GPIO_Speed;    GPIOOType_TypeDef GPIO_OType;     GPIOPuPd_TypeDef GPIO_PuPd;    }GPIO_InitTypeDef; 下面我们通过一个GPIO初始化实例来讲解这个结构体的成员变量的含义。 通过初始化结构体初始化GPIO的常用格式是: GPIO_InitTypeDef  GPIO_InitStructure;   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9//GPIOF9   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉   GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO 上面代码的意思是设置GPIOF的第9个端口为推挽输出模式,同时速度为100M,上拉。 从上面初始化代码可以看出,结构体GPIO_InitStructure的第一个成员变量GPIO_Pin用来设置是要初始化哪个或者哪些IO口,这个很好理解;第二个成员变量GPIO_Mode是用来设置对应IO端口的输出输入端口模式,这个值实际就是配置我们前面讲解的GPIOxMODER寄存器的值。在MDK中是通过一个枚举类型定义的,我们只需要选择对应的值即可: typedef enum {   GPIO_Mode_IN   = 0x00, /*!< GPIO Input Mode */   GPIO_Mode_OUT  = 0x01, /*!< GPIO Output Mode */   GPIO_Mode_AF   = 0x02, /*!< GPIO Alternate function Mode */   GPIO_Mode_AN   = 0x03  /*!< GPIO Analog Mode */ }GPIOMode_TypeDef; GPIO_Mode_IN是用来设置为复位状态的输入,GPIO_Mode_OUT是通用输出模式,GPIO_Mode_AF是复用功能模式,GPIO_Mode_AN是模拟输入模式。 第三个参数GPIO_SpeedIO口输出速度设置,有四个可选值。实际上这就是配置的GPIO对应的OSPEEDR寄存器的值。在MDK中同样是通过枚举类型定义: typedef enum {   GPIO_Low_Speed     = 0x00, /*!< Low speed    */   GPIO_Medium_Speed  = 0x01, /*!< Medium speed */   GPIO_Fast_Speed    = 0x02, /*!< Fast speed   */   GPIO_High_Speed    = 0x03  /*!< High speed   */ }GPIOSpeed_TypeDef;   /* Add legacy definition */ #define  GPIO_Speed_2MHz    GPIO_Low_Speed    #define  GPIO_Speed_25MHz   GPIO_Medium_Speed #define  GPIO_Speed_50MHz   GPIO_Fast_Speed #define  GPIO_Speed_100MHz  GPIO_High_Speed  这里需要说明一下,实际我们的输入可以是GPIOSpeed_TypeDef枚举类型中GPIO_High_Speed枚举类型值,也可以是GPIO_Speed_100MHz这样的值,实际上GPIO_Speed_100MHz就是通过define宏定义标识符定义出来的,它跟GPIO_High_Speed是等同的。 第四个参数GPIO_OTypeGPIO的输出类型设置,实际上是配置的GPIOOTYPER寄存器的值。在MDK中同样是通过枚举类型定义: typedef enum {   GPIO_OType_PP = 0x00,   GPIO_OType_OD = 0x01 }GPIOOType_TypeDef; 如果需要设置为输出推挽模式,那么选择值GPIO_OType_PP,如果需要设置为输出开漏模式,那么设置值为GPIO_OType_OD        第五个参数GPIO_PuPd用来设置IO口的上下拉,实际上就是设置GPIOPUPDR寄存器的值。同样通过一个枚举类型列出: typedef enum {   GPIO_PuPd_NOPULL = 0x00,   GPIO_PuPd_UP     = 0x01,   GPIO_PuPd_DOWN   = 0x02 }GPIOPuPd_TypeDef; 这三个值的意思很好理解,GPIO_PuPd_NOPULL为不使用上下拉,GPIO_PuPd_UP为上拉,GPIO_PuPd_DOWN为下拉。我们根据我们 需要设置相应的值即可。 这些入口参数的取值范围怎么定位,怎么快速定位到这些入口参数取值范围的枚举类型,在我们上面章节4.7的“快速组织代码”章节有讲解,不明白的朋友可以翻回去看一下,这里我们就不重复讲解,在后面的实验中,我们也不再去重复讲解怎么定位每个参数的取值范围的方法。 看完了GPIO的参数配置寄存器,接下来我们看看GPIO输入输出电平控制相关的寄存器。 首先我们看ODR寄存器,该寄存器用于控制GPIOx的输出,该寄存器各位描述见表5.2.5.5所示:  5.2.5.5 GPIOx ODR寄存器各位描述 该寄存器用于设置某个IO输出低电平(ODRy=0)还是高电平(ODRy=1),该寄存器也仅在输出模式下有效,在输入模式(MODER[1:0]=00/11时)下不起作用。 在固件库中设置ODR寄存器的值来控制IO口的输出状态是通过函数GPIO_Write来实现的: void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);   该函数一般用来往一次性一个GPIO的多个端口设值。 使用实例如下: GPIO_Write(GPIOA,0x0000); 大部分情况下,设置IO口我们都不用这个函数,后面我们会讲解我们常用的设置IO口电平的函数。 同时读ODR寄存器还可以读出IO口的输出状态,库函数为: uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 这两个函数功能类似,只不过前面是用来一次读取一组IO口所有IO口输出状态,后面的函数用来一次读取一组IO口中一个或者几个IO口的输出状态。 接下来我们看看IDR寄存器,该寄存器用于读取GPIOx的输入,该寄存器各位描述见表5.2.5.6所示:  5.2.5.6 GPIOx IDR寄存器各位描述 该寄存器用于读取某个IO的电平,如果对应的位为0(IDRy=0),则说明该IO输入的是低电平,如果是1(IDRy=1),则表示输入的是高电平。库函数相关函数为: uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); 前面的函数是用来读取一组IO口的一个或者几个IO口输入电平,后面的函数用来一次读取一组IO口所有IO口的输入电平。比如我们要读取GPIOF.5的输入电平,方法为: GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_5);        接下来我们看看32 位置位/复位寄存器 (BSRR),顾名思义,这个寄存器是用来置位或者复位IO口,该寄存器和ODR寄存器具有类似的作用,都可以用来设置GPIO端口的输出位是1还是0。寄存器描述如下:  5.2.5.7 BSRR寄存器各位描述 对于低16位(0-15),我们往相应的位写1,那么对应的IO口会输出高电平,往相应的位写0,对IO口没有任何影响。高16位(16-31)作用刚好相反,对相应的位写1会输出低电平,写0没有任何影响。也就是说,对于BSRR寄存器,你写0的话,对IO口电平是没有任何影响的。我们要设置某个IO口电平,只需要为相关位设置为1即可。而ODR寄存器,我们要设置某个IO口电平,我们首先需要读出来ODR寄存器的值,然后对整个ODR寄存器重新赋值来打到设置某个或者某些IO口的目的,而BSRR寄存器,我们就不需要先读,而是直接设置。 BSRR寄存器使用方法如下: GPIOA->BSRR=1<<1; //设置GPIOA.1为高电平 GPIOA->BSRR=1<<16+1//设置GPIOA.1为低电平; 库函数操作BSRR寄存器来设置IO电平的函数为: void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 函数GPIO_SetBits用来设置一组IO口中的一个或者多个IO口为高电平。GPIO_ResetBits用来设置一组IO口中一个或者多个IO口为低电平。比如我们要设置GPIOB.5输出高,方法为:        GPIO_SetBits(GPIOB,GPIO_Pin_5);//GPIOB.5输出高    设置GPIOB.5输出低电平,方法为: GPIO_ResetBits(GPIOB,GPIO_Pin_5);//GPIOB.5输出低        最后我们来看看2 32 位复用功能选择寄存器(AFRH AFRL),这两个寄存器是用来设置IO口的复用功能的。关于这两个寄存器的配置以及相关库函数的使用,在我们前面章节4.4 IO引脚复用和映射有详细讲解,这里我们就不做过多的说明。 GPIO相关的函数我们先讲解到这里。虽然IO操作步骤很简单,这里我们还是做个概括性的总结,操作步骤为: 1)  使能IO口时钟。调用函数为RCC_AHB1PeriphClockCmd () 2)  初始化IO参数。调用函数GPIO_Init(); 3)  操作IO。操作IO的方法就是上面我们讲解的方法。        上面我们讲解了STM32F4 IO口的基本知识以及固件库操作GPIO的一些函数方法,下面我们来讲解我们的跑马灯实验的硬件和软件设计。  

6.2 硬件设计

本章用到的硬件只有LEDDS0DS1)。其电路在ALIENTEK探索者STM32F4开发板上默认是已经连接好了的。DS0PF9DS1PF10。所以在硬件上不需要动任何东西。其连接原理图如图6.2.1下:  6.2.1 LEDSTM32F4连接原理图

6.3 软件设计

这是我们的第一个实验,所以我教大家怎么从我们前面讲解的Template工程一步一步加入我们的固件库以及我们的led相关的驱动函数到我们工程,使之跟我们光盘的跑马灯实验工程一模一样。首先大家打开我们3.3.2小节新建的库函数版本工程模板。如果您还没有新建,也可以直接打开我们光盘已经新建好了的工程模板,路径为:“4,程序源码标准例程-库函数版本实验0 Template工程模板”。注意,是直接点击工程下面的USER目录下面的Template.uvproj 大家可以看到,我们模板里面的FWLIB分组下面,我们引入了所有的固件库源文件和对应的头文件,如下图6.3.1  6.3.1  Template模板工程结构 实际上,这些大家可以根据工程需要添加,比如我们跑马灯实验,我们并没有用到ADC,自然我们可以去掉stm32f4xx_adc.c,这样可以减少工程编译时间。 跑马灯实验我们主要用到的固件库文件是: stm32f4xx_gpio.c /stm32f4xx_gpio.h stm32f4xx_rcc.c/stm32f4xx_rcc.h misc.c/ misc.h stm32f4xx_usart .c/stm32f4xx_usart.h stm32f4xx_syscfg.c/stm32f4xx_syscfg.h 其中stm32f4xx_rcc.h头文件在每个实验中都要引入,因为系统时钟配置函数以及相关的外设时钟使能函数都在这个其源文件stm32f4xx_rcc.c中。stm32f4xx_usart.hmisc.h头文件和对应的源文件在我们SYSTEM文件夹中都需要使用到,所以每个实验都会引用。stm32f4xx_syscfg.h和对应的源文件虽然本实验也没有用到,但是后面很多实验都要使用到,所以我们不妨也添加进来。stm32f4xx_conf.h文件里面,这些头文件默认都是打开的,实际我们可以不用理。当然我们也可以注释掉其他不用的头文件,但是如果你引入了某个源文件,一定不能不包含对应的头文件: #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" #include "stm32f4xx_usart.h" #include stm32f4xx_syscfg.h #include "misc.h" 接下来,我们讲解怎样去掉多余的其他的源文件,方法如下图,右击Template,选择“Manage project Items”,进入这个选项卡:  6.3.2   我们选中“FWLIB”分组,然后选中不需要的源文件点击删除按钮删掉,留下下图中我们使用到的五个源文件,然后点击OK  6.3.3 这样我们的工程FWLIB下面只剩下五个源文件:  6.3.4 然后我们进入我们工程的目录,在工程根目录文件夹下面新建一个HARDWARE的文件夹,用来存储以后与硬件相关的代码。然后在HARDWARE文件夹下新建一个LED文件夹,用来存放与LED相关的代码。如图6.3.5所示:  6.3.5新建HARDWARE文件夹 接下来,我们回到我们的工程(如果是使用的上面新建的工程模板,那么就是Template.uvproj,大家可以将其重命名为LED.uvproj),按按钮新建一个文件,然后按保存在HARDWARE->LED文件夹下面,保存为led.c,操作步骤如下图:  6.3.6 新建文件  6.3.7 保存led.c 然后在lcd.c文件中输入如下代码(代码大家可以直接打开我们光盘的跑马灯实验,从相应的文件中间复制过来),输入后保存即可: #include "led.h" //初始化PF9PF10为输出口.并使能这两个口的时钟             //LED IO初始化 void LED_Init(void) {             GPIO_InitTypeDef  GPIO_InitStructure;   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟   //GPIOF9,F10初始化设置   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0LED1对应IO   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉   GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO        GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭   } 该代码里面就包含了一个函数void LED_Init(void),该函数的功能就是用来实现配置PF9PF10为推挽输出。这里需要注意的是:在配置STM32外设的时候,任何时候都要先使能该外设的时钟!GPIO是挂载在AHB1总线上的外设,在固件库中对挂载在AHB1总线上的外设时钟使能是通过函数RCC_AHB1PeriphClockCmd ()来实现的。对于这个入口参数设置,在我们前面的“4.7快速组织代码”章节已经讲解很清楚了。看看我们的代码:   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟 这行代码的作用是使能AHB1总线上的GPIOF时钟。 在设置完时钟之后,LED_Init调用GPIO_Init函数完成对PF9PF10的初始化配置,然后调用函数GPIO_SetBits控制LED0LED1输出1LED灭)。至此,两个LED的初始化完毕。这样就完成了对这两个IO口的初始化。这段代码的具体含义,大家可以看前面一小节,我们有详细的讲解。初始化函数代码如下:   //GPIOF9,F10初始化设置   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0LED1对应IO   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉   GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO   GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭 保存led.c代码,然后我们按同样的方法,新建一个led.h文件,也保存在LED文件夹下面。在led.h中输入如下代码: #ifndef __LED_H #define __LED_H #include "sys.h" //LED端口定义 #define LED0 PFout(9) // DS0 #define LED1 PFout(10)       // DS1       void LED_Init(void);//初始化                                            #endif 这段代码里面最关键就是2个宏定义: #define LED0 PFout(9) // DS0 PF9 #define LED1 PFout(10)       // DS1 PF10 这里使用的是位带操作来实现操作某个IO口的1个位的,关于位带操作前面第五章5.2.1已经有介绍,这里不再多说。需要说明的是,这里同样可以使用固件库操作来实现IO口操作。如下: GPIO_SetBits(GPIOF, GPIO_Pin_9);        //设置GPIOF.9输出1,等同LED0=1; GPIO_ResetBits (GPIOF, GPIO_Pin_9);      //设置GPIOF.9输出0,等同LED0=0; 有兴趣的朋友不妨修改我们的位带操作为库函数直接操作,这样也有利于学习。led.h也保存一下。接着,我们在Manage Components管理里面新建一个HARDWARE的组,并把led.c加入到这个组里面,如图6.3.8所示:  6.3.8 给工程新增HARDWARE 单击OK,回到工程,然后你会发现在Project Workspace里面多了一个HARDWARE的组,在该组下面有一个led.c的文件。如图6.3.9所示:  6.3.9新增HARDWARE 然后用之前介绍的方法(在3.3.2节介绍的)将led.h头文件的路径加入到工程里面,然后点击OK回到主界面。  6.3.10 添加LED目录到PATH 回到主界面后,在main函数里面编写如下代码: #include "sys.h" #include "delay.h" #include "usart.h" #include "led.h"   int main(void) { delay_init(168);              //初始化延时函数 LED_Init();                  //初始化LED端口         /**下面是通过直接操作库函数的方式实现IO控制**/  while(1) { GPIO_ResetBits(GPIOF,GPIO_Pin_9);  //LED0对应引脚GPIOF.9拉低,亮  等同LED0=0; GPIO_SetBits(GPIOF,GPIO_Pin_10);   //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1; delay_ms(500);               //延时500ms GPIO_SetBits(GPIOF,GPIO_Pin_9);      //LED0对应引脚GPIOF.0拉高,灭  等同LED0=1; GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0; delay_ms(500);                     //延时500ms } } 代码包含了#include "led.h"这句,使得LED0LED1LED_Init等能在main()函数里被调用。这里我们需要重申的是,在固件库中,系统在启动的时候会调用system_stm32f4xx.c中的函数SystemInit()对系统时钟进行初始化,在时钟初始化完毕之后会调用main()函数。 所以我们不需要再在main()函数中调用SystemInit()函数。当然如果有需要重新设置时钟系统,可以写自己的时钟设置代码,SystemInit()只是将时钟系统初始化为默认状态。 main()函数非常简单,先调用delay_init()初始化延时,接着就是调用LED_Init()来初始化GPIOF.9GPIOF.10为输出。最后在死循环里面实现LED0LED1交替闪烁,间隔为500ms 上面是通过库函数来实现的IO操作,我们也可以修改main()函数,直接通过位带操作达到同样的效果,大家不妨试试。位带操作的代码如下: int main(void) {   delay_init(168);           //初始化延时函数 LED_Init();                //初始化LED端口   while(1)        {          LED0=0;                      //LED0             LED1=1;                           //LED1                delay_ms(500);                LED0=1;                           //LED0                LED1=0;                           //LED1                delay_ms(500);         } } 当然我们也可以通过直接操作相关寄存器的方法来设置IO,我们只需要将主函数修改为如下内容: int main(void) {          delay_init(168);              //初始化延时函数        LED_Init();                   //初始化LED端口        while(1)        {      GPIOF->BSRRH=GPIO_Pin_9;//LED0         GPIOF->BSRRL=GPIO_Pin_10;//LED1         delay_ms(500);      GPIOF->BSRRL=GPIO_Pin_9;//LED0         GPIOF->BSRRH=GPIO_Pin_10;//LED1           delay_ms(500);         }  }    将主函数替换为上面代码,然后重新执行,可以看到,结果跟库函数操作和位带操作一样的效果。大家可以对比一下。这个代码在我们跑马灯实验的main.c文件中有注释掉,大家可以替换试试。 然后按,编译工程,得到结果如图6.3.11所示:                                     6.3.11编译结果 可以看到没有错误,也没有警告。从编译信息可以看出,我们的代码占用FLASH大小为:5678字节(5256+424),所用的SRAM大小为:1880个字节(1832+48)。 这里我们解释一下,编译结果里面的几个数据的意义:        Code:表示程序所占用FLASH的大小(FLASH)。        RO-data:即Read Only-data,表示程序定义的常量(FLASH)。        RW-data:即Read Write-data,表示已被初始化的变量(SRAM        ZI-data:即Zero Init-data,表示未被初始化的变量(SRAM) 有了这个就可以知道你当前使用的flashsram大小了,所以,一定要注意的是程序的大小不是.hex文件的大小,而是编译后的CodeRO-data之和。 接下来,大家就可以下载验证了。如果有JTAG,则可以用jtag进行在线调试(需要先下载代码),单步查看代码的运行,STM32F4的在线调试方法介绍,参见:3.4.2节。  

6.4 下载验证

这里我们使用flymcu下载(也可以通过JTAG等仿真器下载,具体方法请参考3.4.2小节),如图6.4.1所示:  6.4.1 利用flymcu下载代码 下载完之后,运行结果如图6.4.2所示,LED0LED1循环闪烁:                                   图6.4.2 程序运行结果 至此,我们的第一章的学习就结束了,本章作为STM32F4的入门第一个例子,介绍了STM32F4IO口的使用及注意事项,同时巩固了前面的学习,希望大家好好理解一下。
 
实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm 
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779


  

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
1条回答
smilebest
2019-07-21 08:19
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 

这两个函数功能类似,只不过前面是用来一次读取一组IO口所有IO口输出状态,后面的函数用来一次读取一组IO口中一个或者几个IO口的输出状态。
这个函数不能用来一次读取几个IO口输出状态吧。
库函数定义:
if (((GPIOx->ODR) & GPIO_Pin) != (uint32_t)Bit_RESET)
  {
    bitstatus = (uint8_t)Bit_SET;
  }
  else
  {
    bitstatus = (uint8_t)Bit_RESET;
  }
  return bitstatus;
如果几个PIN传进去,只要有一个PIN是高电平,返回就是1

一周热门 更多>