问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

嵌入式开发入门:STM32F4开发板跑马灯实验详解

创作时间:
作者:
@小白创作中心

嵌入式开发入门:STM32F4开发板跑马灯实验详解

引用
CSDN
1.
https://m.blog.csdn.net/weixin_47040031/article/details/138869616

STM32F4系列微控制器是ST公司推出的一款高性能32位ARM Cortex-M4内核处理器,广泛应用于各种嵌入式系统开发中。跑马灯实验作为嵌入式开发入门的经典实验,通过控制开发板上的LED灯实现闪烁效果,能够帮助初学者快速掌握STM32F4的IO口控制方法。本文将详细介绍如何在正点原子探索者STM32F4开发板上实现跑马灯实验,包括硬件连接、软件编程以及最终的下载验证过程。

一、实现的功能

实现STM32F407开发板上LED灯:LED0和LED1每过500ms一次交替闪烁,实现类似跑马灯的效果。

二、硬件设计

本次用到的硬件只有LED(DS0和DS1)。其电路在探索者STM32F4开发板上默认是已经连接好了的。DS0接PF9,DS1接PF10。所以在硬件上不需要动任何东西。其连接原理图如图所示。

相同网络标号表示它们是连接在一起的,因此DS0发光二极管阴极是连接在STM32的PF9管脚上,DS1指示灯阴极连接在PF10管脚上。如果要使DS0指示灯亮,只需要控制PF9管脚输出低电平,如果要使DS0指示灯灭,只需控制PF9输出高电平。同理,DS1也是如此。

三、程序设计

关于库函数版本的工程模板如何创建,我在之前的博客已经详细记录,本次工程直接使用,并同时讲解实际开发的多文件编程的整个过程。

3.1 创建工程模板

因为我们采用的是库函数开发,所以直接复制创建好的库函数模板,在此模板上进行程序开发。将复制过来的模板文件夹重新命名为“实验一:跑马灯实验”。打开此文件夹,在Project目录下新建一个文件夹,命名为:MyLed,用于存放LED的驱动程序,然后再新建一个文件夹,命名为:MyDelay,用来存放延时模块,这里我们使用单片机内部的系统节拍定时器实现延时,比起软件延时,更加精准,后面介绍系统节拍定时器,后期详细介绍,如下图所示。

3.2 工程中创建对应的文件

打开工程,在项目下创建与文件夹中同名的文件夹:MyLed和MyDelay文件夹,保存在对应的文件夹路径中,同时创建对应的源文件.c文件和头文件.h文件,这两个文件内容是我们自己需要编写的,不是库文件。通常xxx.c文件用于存放编写的驱动程序,xxx.h文件用于存放xxx.c内的stm32头文件、全局变量声明、函数声明等内容,这样做的好处是方便我们能够快速移植代码,并且工程目录也非常清晰,对后续维护带来方便。MyLed文件夹用于存放我们编写的led驱动程序,假如后面要操作开发板上的蜂鸣器,同样新建一个MyBeep文件夹用于存放蜂鸣器的驱动程序。创建好后如下图所示:

3.3 添加文件路径

要想工程在编译阶段能够找到创建的文件,必须要加入文件路径,按照下面的步骤操作即可,如下图所示:

3.4 添加相应的外设固件库和所需文件

至此,工程所需文件已经全部创建完毕,接下来就是梳理程序逻辑,编写代码。

3.5 程序流程图

程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。本实验的程序流程图如下:

3.5.1 编写LED灯初始化函数/驱动代码

GPIO输出配置步骤:

1)使能对应GPIO时钟

STM32在使用任何外设之前,我们都要先使能其时钟。本实验用到PF9和PF10两个IO口,因此需要先使能GPIOF的时钟。

2)设置对应GPIO工作模式

本实验GPIO使用推挽输出模式,控制LED亮灭,通过定义GPIO结构体,设置好其中的成员变量参数,再调用库函数实现即可。

3)控制GPIO引脚输出高低电平

在配置好GPIO工作模式后,我们就可以通过相应的库函数控制GPIO引脚输出高低电平,从而控制LED的亮灭了。

注意:在配置STM32外设的时候,任何时候都要先使能该外设的时钟!通过查看该外设挂载在哪个总线下,从而决定开启那个哪个时钟。所有的GPIO是挂载在AHB1总线上的外设,在固件库中对挂载在AHB1总线上的外设时钟使能是通过函数RCC_AHB1PeriphClockCmd()来实现的。

在myled.h文件夹下编写如下代码:

#ifndef __MYLED_H
#define __MYLED_H
void LED_Init(void);
#endif  

在头文件的开头,使用“#ifndef”关键字,判断标号“__MYLED_H”是否被定义,若没有被定义,则从“#ifndef”至“#endif”关键字之间的内容都有效,也就是说,这个头文件若被其它文件“#include”,它就会被包含到其该文件中了,且头文件中紧接着使用 “#define”关键字定义上面判断的标号“__MYLED_H”。当这个头文件被同一个文件第二次 “#include”包含的时候,由于有了第一次包含中的“#define __MYLED_H”定义,这时再判断“#ifndef __LED_H”,判断的结果就是假了,从“#ifndef”至“#endif”之间的内容都无效,从而防止了同一个头文件被包含多次,编译时就不会出现“redefine(重复定义)” 的错误了一般来说,我们不会直接在C的源文件写两个“#include”来包含同一个头文件,但可能因为头文件内部的包含导致重复,这种代码主要是避免这样的问题。如“bsp_led.h” 文件中使用了“#include ―stm32f4xx.h ”语句,按习惯,可能我们写主程序的时候会在main文件写“#include ―bsp_led.h‖ 及#include ―stm32f4xx.h”,这个时候“stm32f4xx.h”文件就被包含两次了,如果没有这种机制,就会出错。至于为什么要用两个下划线来定义“__LED_H”标号,其实这只是防止它与其它普通宏定义重复了。

在myled.c文件夹下编写如下代码:

#include "stm32f4xx.h"                  // Device header
#include "myled.h"
void LED_Init(void)
{
    //第一步:使能GPIOF两个口的时钟 
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能 GPIOF 时钟
    
   //第二步:GPIOF9,F10 初始化设置
   GPIO_InitTypeDef GPIO_InitStructure;
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0 和 LED1 对应 IO 口
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
   GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化 GPIO
   //第三步:设置灯的初始状态
   GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10 设置高电平,灯灭
}  

因为我们使用固件库中的一系列函数和寄存器结构体的封装,所以,我们必须要引入固件库的头文件,其次,多文件编程,要引入相应的的头文件。这段代码的作用是使能AHB1总线上的GPIOF时钟。在设置完时钟之后,LED_Init调用GPIO_Init函数完成对PF9和PF10的初始化配置,然 后调用函数GPIO_SetBits控制LED0和LED1输出1(LED灭)。至此,两个LED的初始化完毕。这样就完成了对这两个IO口的初始化。

3.5.2 编写延时函数

在mydelay.h文件夹下编写如下代码:

利用系统节拍定时器可以实现精准延时(后期博客详细总结),因此我们实现了三个延时任意时间的函数,后续项目使用直接引入头文件,然后直接调用相应的函数即可。

#ifndef __MYDELAY_H__
#define __MYDELAY_H__
void My_Delay_us(uint32_t num); //延时任意微秒
void My_Delay_ms(uint32_t num); //延时任意毫秒
void My_Delay_s(uint32_t num);  //延时任意秒
#endif  

在mydelay.c文件夹下编写如下代码:

#include "stm32f4xx.h"                  // Device header
#include "mydelay.h"
//延时num微秒
void My_Delay_us(uint32_t num)
{
    while(num--)
    {
        SysTick ->CTRL = (1 << 0);
    
        SysTick ->CTRL &= ~(1<<2);
    
        SysTick ->CTRL &= ~(1<<1);
    
        SysTick ->VAL = 0x0;
    
        SysTick ->LOAD = 21;   //1秒 21000000HZ,1毫秒 21000HZ  1微秒 21 HZ
    
        while(!(SysTick ->CTRL & (1<<16)));
        SysTick ->CTRL = ~(1<<0);
    }
}
//延时num毫秒,1毫秒等于1000微秒
void My_Delay_ms(uint32_t num)
{
    while(num--)
    {
        My_Delay_us(1000);
    }
}
//延时num秒,1秒等于1000毫秒
void My_Delay_s(uint32_t num)
{
    while(num--)
    {
        My_Delay_ms(1000);
    }
}  

3.5.3 程序主函数/代码主要逻辑

main()函数非常简单,先调用LED_Init()来初始化GPIOF.9和GPIOF.10为输出。最后在死循环里面实现LED0和LED1交替闪烁,间隔为500ms。编写代码如下:

#include "stm32f4xx.h"                  // Device header
#include "myled.h"
#include "mydelay.h"
int main(void)
{ 
    //1.初始化 LED 端口
     LED_Init();
    
    //2.通过直接操作库函数的方式实现 IO 控制
    while(1)
    {
      //LED0 对应引脚 GPIOF.9 拉低,点亮 ;
      GPIO_ResetBits(GPIOF,GPIO_Pin_9); 
      //LED1 对应引脚 GPIOF.10 拉高,熄灭 ;
      GPIO_SetBits(GPIOF,GPIO_Pin_10); 
      My_Delay_ms(500); //延时 500ms
        
      //LED0对应引脚GPIOF.0拉高,熄灭;
      GPIO_SetBits(GPIOF,GPIO_Pin_9); 
      //LED1 对应引脚 GPIOF.10 拉低,点亮 
      GPIO_ResetBits(GPIOF,GPIO_Pin_10); 
      My_Delay_ms(500); //延时 500ms
     }
}  

四、下载验证

我们先来看看编译结果,如图所示:

可以看到0错误,0警告,编译通过接下来,大家就可以下载验证了。这里我们使用DAP仿真器下载。下载完之后,运行结果如图所示,LED0和LED1循环闪烁:

至此,我们的跑马灯实验的学习就结束了,本章介绍了STM32F407的IO口的使用及注意事项,是后面学习的基础,希望大家好好理解。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号