嵌入式开发十二:初入STM32世界—跑马灯实验

      任何一个单片机,最简单的外设莫过于 IO 口的高低电平控制了,本章将通过一个经典的跑马灯程序,带大家开启 STM32F4 之旅,通过本次的学习,你将了解到 STM32F4 的 IO 口作为输出使用的方法。我们将通过代码控制正点原子探索者 STM32F4 开发板上的两个 LED:DS0 和 DS1 交替闪烁,实现类似跑马灯的效果。

目录

一、实现的功能

二、硬件设计

三、程序设计

3.1 创建工程模板

3.2  工程中创建对应的文件

3.3 添加文件路径

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

3.5 程序流程图

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

3.5.2 编写延时函数

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

四、下载验证


一、实现的功能

          实现 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 口的使用及注意事项,是后面学习的基础,希望大家好好理解。

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来可期,静待花开~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值