STM32F407入门篇1 ·寄存器点灯

前言

  打算往单片机方向发展,故编写此系列的博文作为笔记使用。

开发环境

  使用开发板:STM32F407ZGT6开发板
  MDK版本信息如下图所示:
在这里插入图片描述
  固件包版本信息:Keil.STM32F4xx_DFP.2.15.0

开发板原理图

  本文所涉及到的开发板原理图:

  MCU上相关引脚:
在这里插入图片描述
  LED灯引脚:
在这里插入图片描述

0x010 新建工程

  1. 创建一个项目工程文件,并新建两个文件,一个为main.c,另一个为stm32f4xx.h,别忘记放一个启动文件在项目工程的文件夹中:
    在这里插入图片描述
    在这里插入图片描述
  2. main.c中编写下列代码:
#include "stm32f4xx.h"

int main(void)
{
	while(1);
}

void SystemInit(void)
{
}
// 注意,这里有一行空行,否则编译会警告。事实上不是强迫症可以不管。
  1. 更改编译器版本
    在这里插入图片描述
  2. 编译
    在这里插入图片描述

0x020 使用寄存器简单点亮LED灯

  在上一个项目工程的基础上,添加一些新玩意儿,使用操控寄存器的方法点亮一个LED灯。可以按照0x010 新建工程中再建一个工程,但如果对这方面完全熟悉的话,建议直接copy一份刚刚的工程进行下面的操作比较快捷。

  我们需要点亮LED 0这个LED灯,根据文章头部的介绍的开发板原理图,可以看出LED 0接到了MCU的PF9引脚,那么根据《STM32F4xx中文参考手册》:
在这里插入图片描述
查看到第53页,里面可以看到GPIOF和所挂载到的总线:
在这里插入图片描述
可以看出出GPIOF挂载在AHB1总线上,起始地址为0x40021400。下一步需要找出相关寄存器所在的位置及对应的状态位。
  由于个引脚的时钟都为关闭状态,所以需要找出PF9对应的的时钟寄存器,并进行使能(ENABLE,启用、打开的意思),从上面可知GPIOF是挂载到AHB1总线上的,所以我们要配置RCC AHB1 外设时钟使能寄存器,该寄存器各位如下图所示:
在这里插入图片描述
  虽然说现在已经知道了AHB1的外设时钟使能寄存器的各位信息及偏移位置,那么RCC复位和时钟的基地址呢?让我们回到手册的第53页,可以看到RCC的基地址为0x40023800,也就意味着,RCC AHB1 外设时钟使能寄存器的绝对地址为基地址+偏移地址,则:0x40023800 + 0x30 = 0x40023830。这个地址需要稍微注意一下,后面编写代码的操控寄存器编程的时候需要使用到。
在这里插入图片描述
在本章节中,仅需要使用最简单的方式去点亮一颗LED灯,现在还需要知道三个寄存器的信息,它们分别是GPIO端口模式寄存器 (GPIOx_MODER)和GPIO端口输出数据寄存器 (GPIOx_ODR),这两个寄存器的信息如下图所示:
在这里插入图片描述
在这里插入图片描述

  结合上述所知,GPIOF的基地址为0x40021400,那么对应的GPIO端口模式寄存器的地址为0x40021400+0x00 = 0x40021400,输出数据寄存器的地址为0x40021400 + 0x14 = 0x40021414。
  整理一下上述信息:
  1. RCC AHB1 外设时钟使能寄存器 (RCC_AHB1ENR)的地址为:0x40023830
  2. GPIO 端口模式寄存器 (GPIOx_MODER)的地址为:0x40021400
  3. GPIO 端口输出数据寄存器 (GPIOx_ODR)的地址为:0x40021414

  有了上述信息,可以进行编程了。此时main.c文件的代码为:

#include "stm32f4xx.h"

int main(void)
{
	/**
	  * 第一步:开时钟
	  */
	*(unsigned int *)(0x40023800 + 0x30) |= (0x01 << 5);

	/**
	  * 第二步:清除GPIOF_MODER上的第18-19位数据,再置为0x01
	  */
	*(unsigned int *)(0x40021400 + 0x00) &= ~(0x03 << (2 * 9));
	*(unsigned int *)(0x40021400 + 0x00) |= (0x01 << (2 * 9));
	
	/**
	  * 第三步:将GPIOF_ODR上的第9位置0
	  */	
	*(unsigned int *)(0x40021400 + 0x14) &= ~(0x01 << 9);
	while(1);
}

void SystemInit(void)
{
}

  进行编译前要记得将微库勾选上,否则将会无法创建出main环境,其中的代码也会无法执行,造成点灯失败(经验之谈):
在这里插入图片描述

  编译结果和烧写结果:
在这里插入图片描述
  开发板现象:
在这里插入图片描述

0x030 使用寄存器简单点亮LED灯(代码优化)

  直接使用寄存器编写代码的缺点是可读性不高,如果将其寄存器相关操作做成宏定义将会提高可读性,所以将上述代码进行重新编写。将0x020的代码重新copy一份,然后进行下列操作:
  首先,先在stm32f4xx.h头文件中使用宏定义封装寄存器地址:

  stm32f4xx.h

#ifndef __STM32F407xx_H__
#define __STM32F407xx_H__

#define	RCC_AHB1ENR	*(unsigned int *)(0x40023800 + 0x30)
#define	GPIOF_MODER	*(unsigned int *)(0x40021400 + 0x00)
#define	GPIOF_ODR	*(unsigned int *)(0x40021400 + 0x14)

#endif /* __STM32F407xx_H__ */

  其次,改写main.c文件:

  main.c

#include "stm32f4xx.h"

int main(void)
{
	/**
	  * 第一步:开时钟
	  */
	RCC_AHB1ENR|= (0x01 << 5);

	/**
	  * 第二步:将输出模式设置为推挽输出模式
	  */
	GPIOF_MODER &= ~(0x03 << (2 * 9));
	GPIOF_MODER |= (0x01 << (2 * 9));
	
	/**
	  * 第三步:输出低电平
	  */	
	GPIOF_ODR &= ~(0x01 << 9);
	while(1);
}

void SystemInit(void)
{
}

  进行编译和烧写:
在这里插入图片描述
  开发板中的现象:
在这里插入图片描述

0x040 两LED灯间隔亮起

  完成上述实验后,可以试着让两个LED灯间隔亮起,结合“开发板原理图”中的信息可知,另一颗LED灯接在了PF10上,所以在0x030的代码基础上进行一些更改。下列是main.c文件的详细内容,而原本的stm32f4xx.h保持不变即可。

main.c

#include "stm32f4xx.h"

// 十分简单的延时函数
void Delay(unsigned int number)
{
	while(--number);
}

int main(void)
{
	/**
	  * 第一步:开时钟
	  */
	RCC_AHB1ENR|= (0x01 << 5);

	/**
	  * 第二步:将PF9和PF10输出模式设置为推挽输出模式
	  */
	GPIOF_MODER &= ~(0x03 << (2 * 9));
	GPIOF_MODER |= (0x01 << (2 * 9));
	
	GPIOF_MODER &= ~(0x03 << (2 * 10));
	GPIOF_MODER |= (0x01 << (2 * 10));
	
	/**
	  * 第三步:先让两颗LED灯熄灭,简而言之就是PF9和PF10都输出高电平
	  */	
	GPIOF_ODR |= (0x1 << 9);
	GPIOF_ODR |= (0x1 << 10);
	
	while(1)
	{
		// 点亮LED0,延时一段时间后熄灭
		GPIOF_ODR &= ~(0x1 << 9);
		Delay(0x0FFFFF);
		GPIOF_ODR |= (0x1 << 9);
		
		// 点亮LED1,延时一段时间后熄灭
		GPIOF_ODR &= ~(0x1 << 10);
		Delay(0x0FFFFF);
		GPIOF_ODR |= (0x1 << 10);
	}
}

void SystemInit(void)
{
}

  上述代码进行编译和烧写:
在这里插入图片描述

  开发板中的现象(此图为GIF动图,大小为1.75MB):
在这里插入图片描述

0x050 使用结构体的方式封装寄存器地址

  在上方的所有操作中,对寄存器的操控都是使用绝对地址去操控,STM32F407具有上千个地址,如果每个地址都计算和定义出来,那是一个十分庞大的工程,。因此,在知道基地址和GPIOF的偏移地址之后,可以使用结构体的方式来操控寄存器,由于寄存器占用空间都是连续的,这跟结构体相似,使用这种方法就很好的解决需要重复定义寄存器地址的问题。
  那么如何编写代码呢?
  第一步:先给一些数据类型重起一个别名,以后使用这个别名就能看出相关变量占多少位。

typedef unsigned int	uint32_t;
typedef unsigned short	uint16_t;

  第二步:根据数据手册上的GPIO寄存器排列顺序,定义一个结构体。

typedef struct
{
	uint32_t MODER;		// GPIO 端口模式寄存器
	uint32_t OTYPER;	// GPIO 端口输出类型寄存器
	uint32_t OSPEEDR;	// GPIO 端口输出速度寄存器
	uint32_t PUPDR;		// GPIO 端口上拉/下拉寄存器
	uint32_t IDR;		// GPIO 端口输入数据寄存器
	uint32_t ODR;		// GPIO 端口输出数据寄存器
	uint16_t BSRRL;		// GPIO 端口置位寄存器
	uint16_t BSRRH;		// GPIO 端口复位寄存器
	uint32_t LCKR;		// GPIO 端口配置锁定寄存器
	uint32_t AFRL;		// GPIO 复用功能低位寄存器
	uint32_t AFRH;		// GPIO 复用功能高位寄存器
}GPIO_TypeDef;

  第三步:定义相关基地址,及结合上方所定义的结构体,对完善对应的寄存器的定义

// GPIOF的基地址
#define GPIOF_BASE    ((unsigned int)0x40021400)
// RCC时钟的基地址
#define RCC_BASE      ((unsigned int)0x40023800)
// 以指针形式进行定义的GPIOF基地址
#define GPIOF           ((GPIO_TypeDef *)GPIOF_BASE)
// AHB1寄存器的地址,写成解引用方式方便后续操作
#define RCC_AHB1ENR   *(unsigned int *)(RCC_BASE+0X30)

  结合上述内容,整个stm32f4xx.h文件就变成了:

  stm32f4xx.h

#ifndef __STM32F407xx_H__
#define __STM32F407xx_H__

typedef unsigned int	uint32_t;
typedef unsigned short	uint16_t;

typedef struct
{
	uint32_t MODER;		// GPIO 端口模式寄存器
	uint32_t OTYPER;	// GPIO 端口输出类型寄存器
	uint32_t OSPEEDR;	// GPIO 端口输出速度寄存器
	uint32_t PUPDR;		// GPIO 端口上拉/下拉寄存器
	uint32_t IDR;		// GPIO 端口输入数据寄存器
	uint32_t ODR;		// GPIO 端口输出数据寄存器
	uint16_t BSRRL;		// GPIO 端口置位寄存器
	uint16_t BSRRH;		// GPIO 端口复位寄存器
	uint32_t LCKR;		// GPIO 端口配置锁定寄存器
	uint32_t AFRL;		// GPIO 复用功能低位寄存器
	uint32_t AFRH;		// GPIO 复用功能高位寄存器
}GPIO_TypeDef;

// GPIOF的基地址
#define GPIOF_BASE    ((unsigned int)0x40021400)
// RCC时钟的基地址
#define RCC_BASE      ((unsigned int)0x40023800)
// 以指针形式进行定义的GPIOF基地址
#define GPIOF           ((GPIO_TypeDef *)GPIOF_BASE)
// AHB1寄存器的地址,写成解引用方式方便后续操作
#define RCC_AHB1ENR   *(unsigned int *)(RCC_BASE+0X30)


#endif /* __STM32F407xx_H__ */

  main.c文件的代码为:

  main.c

#include "stm32f4xx.h"

// 简单的延时函数
void delay(uint32_t number)
{
	while(--number);
}

int main(void)
{
	// 第一步:开时钟
	RCC_AHB1ENR |= (1 << 5);
	
    // 第二步:配置GPIO输出模式
	GPIOF->MODER &= ~(0x03 << (2 * 9));
	GPIOF->MODER |= (0x01 << (2 * 9));
	
	// 第三步:先关闭LED0灯
	GPIOF->ODR |= (1 << 9);
	
	while(1)
	{
		// 第四步:开启LED0灯
		GPIOF->ODR &=  ~(1 << 9);
		// 延时
		delay(0x0FFFFF);
		
		// 第五步:关闭LED0灯
		GPIOF->ODR |= (1 << 9);
		delay(0x0FFFFF);
	}
	
}

void SystemInit(void)
{
}

  编译及烧写情况:
在这里插入图片描述
  在开发板中的运行现象:
在这里插入图片描述

0x060 封装复位/置位寄存器操作成为函数

  虽然封装了相关寄存器成为结构体,但是对其进行操作时还是不够直观,比如上述程序代码中有一行是GPIOF->ODR &= ~(1 << 9);,虽然开发的时候知道的是将输出数据寄存器的第9位置为0,输出低电平点亮LED灯,但是时间久了,不一定能够想起来这是什么操作,为什么这个结构体里面的ODR成员要进行一个位操作,这是什么意思?有什么用?所以为了让程序具有更高的可读性,代码可以再进一步封装成函数。
  那么应该如何去封装呢?此例以点亮绿色的LED1灯为主。
  第一步:创建一个stm32f4xx_gpio.h文件,将复位/置位操作所需要到的引脚宏定义都编写在这个文件中。

// 各引脚定义
#define	GPIO_Pin_0		((uint16_t)(1<<0))
#define	GPIO_Pin_1		((uint16_t)(1<<1))
#define GPIO_Pin_2		((uint16_t)(1<<2))
#define GPIO_Pin_3		((uint16_t)(1<<3))
#define GPIO_Pin_4		((uint16_t)(1<<4))
#define GPIO_Pin_5		((uint16_t)(1<<5))
#define GPIO_Pin_6		((uint16_t)(1<<6))
#define GPIO_Pin_7		((uint16_t)(1<<7))
#define GPIO_Pin_8		((uint16_t)(1<<8))
#define GPIO_Pin_9		((uint16_t)(1<<9))
#define GPIO_Pin_10		((uint16_t)(1<<10))
#define GPIO_Pin_11		((uint16_t)(1<<11))
#define GPIO_Pin_12		((uint16_t)(1<<12))
#define GPIO_Pin_13		((uint16_t)(1<<13))
#define GPIO_Pin_14		((uint16_t)(1<<14))
#define GPIO_Pin_15		((uint16_t)(1<<15))
#define GPIO_Pin_All	((uint16_t)(0xFFFF))

  第二步:新创建一个stm32f4xx_gpio .c文件,编写置位/复位函数

// 置位函数
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	GPIOx->BSRRL = GPIO_Pin; 
}

// 复位函数
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	GPIOx->BSRRH = GPIO_Pin; 
}

  第三步:改写main.c文件
  综合上述,相关文件的代码为:

stm32f4xx_gpio.h

#ifndef __STM32F4XX_GPIO_H__
#define __STM32F4XX_GPIO_H__

#include "stm32f4xx.h"

// 各引脚定义
#define	GPIO_Pin_0		((uint16_t)(1 << 0))
#define	GPIO_Pin_1		((uint16_t)(1 << 1))
#define GPIO_Pin_2		((uint16_t)(1 << 2))
#define GPIO_Pin_3		((uint16_t)(1 << 3))
#define GPIO_Pin_4		((uint16_t)(1 << 4))
#define GPIO_Pin_5		((uint16_t)(1 << 5))
#define GPIO_Pin_6		((uint16_t)(1 << 6))
#define GPIO_Pin_7		((uint16_t)(1 << 7))
#define GPIO_Pin_8		((uint16_t)(1 << 8))
#define GPIO_Pin_9		((uint16_t)(1 << 9))
#define GPIO_Pin_10		((uint16_t)(1 << 10))
#define GPIO_Pin_11		((uint16_t)(1 << 11))
#define GPIO_Pin_12		((uint16_t)(1 << 12))
#define GPIO_Pin_13		((uint16_t)(1 << 13))
#define GPIO_Pin_14		((uint16_t)(1 << 14))
#define GPIO_Pin_15		((uint16_t)(1 << 15))
#define GPIO_Pin_All	((uint16_t)(0xFFFF))

// 声明复位/置位函数
void GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

#endif /* __STM32F4XX_GPIO_H__ */

stm32f4xx_gpio.c

#include "stm32f4xx_gpio.h"

// 置位函数
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	GPIOx->BSRRL = GPIO_Pin; 
}

// 复位函数
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	GPIOx->BSRRH = GPIO_Pin; 
}

main.c

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"

// 简单的延时函数
void delay(uint32_t number)
{
	while(--number);
}

int main(void)
{
	// 第一步:开时钟
	RCC_AHB1ENR |= (1 << 5);
	
    // 第二步:配置GPIO输出模式
	GPIOF->MODER &= ~(0x03 << (2 * 10));
	GPIOF->MODER |= (0x01 << (2 * 10));
	
	// 第三步:先关闭LED0灯
	GPIOF->ODR |= (1 << 10);
	
	while(1)
	{
		// 第四步:开启LED0灯
		GPIO_ResetBits(GPIOF, GPIO_Pin_10);
		// 延时
		delay(0x0FFFFF);
		
		// 第五步:关闭LED0灯
		GPIO_SetBits(GPIOF, GPIO_Pin_10);
		delay(0x0FFFFF);
	}
	
}

void SystemInit(void)
{
}

  编译及烧写结果:
在这里插入图片描述
  在开发板中的运行现象:
在这里插入图片描述
  至此,入门结束。如果想要进一步提高,可以试着仿照标准库写一个库,但这个太麻烦了,而且此博文太长了,后边就不写了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Version: 2.15.0 (2020-09-28) Keil.STM32F4xx_DFP.2.15.0.pack Download Updated Pack to STM32Cube_FW_F4 Firmware Package version V1.25.1 using HAL Drivers V1.7.9. STM32CubeMX integration (Version 6.0.1): Added support for Timebase Source TIMx (FrameworkCubeMX_gpdsc.ftl). Removed non-existent include path. CMSIS Flash Algorithm: Corrected STM32F42xxx_43xxx_OPT Algorithm. CMSIS SVD: Updated STM32F42*.svd, STM32F43*.svd files. CMSIS-Driver: I2C: Corrected 2 byte reception in master mode. MCI: Replaced empty delay loops with _NOP(). SPI: Corrected PowerControl function (to return error if Initialize was not called, to abort active transfer if power off was requested). Updated GetDataCount function to give accurate count in DMA mode. Corrected Control function (abort in DMA mode, software controlled slave select in slave mode, TI Frame Format selection, ignore bus speed for slave mode). Corrected Uninitialize function (to power off the peripheral if it is powered). Corrected SPI3_SCK pin configuration. Corrected DMA MemDataAlignment configuration. USART: Corrected DMA MemDataAlignment configuration. USBD_HS/USBH_HS: OTG_HS ULPI clock disabled in low power if internal PHY is used to enable proper operation of OTG_HS port in FS mode during CPU sleep. CAN/EMAC/USBD/USBH: Removed macros already provided by cmsis_compiler.h. Updated Boards Examples: Migrated CubeMX projects to V6.0.1 and updated config files. Changed variant selection to "MDK-Plus" where possible. Updated all USB Host/Device examples with user templates from MDK-Middleware v7.11.1. Terminating app_main thread with osThreadExit() to avoid endless loop Updated MS Windows UBS driver files.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值