当前位置:   article > 正文

STM32F407入门篇1 ·寄存器点灯_stm32f407的0x1fff7a22寄存器

stm32f407的0x1fff7a22寄存器

前言

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

开发环境

  使用开发板: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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  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)
{
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

  进行编译前要记得将微库勾选上,否则将会无法创建出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__ */

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  其次,改写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)
{
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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

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)
{
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

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

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

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

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

typedef unsigned int	uint32_t;
typedef unsigned short	uint16_t;
  • 1
  • 2

  第二步:根据数据手册上的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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

// 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  结合上述内容,整个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__ */

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

  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)
{
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

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

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))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  第二步:新创建一个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; 
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  第三步:改写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__ */

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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; 
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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)
{
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/56913
推荐阅读
相关标签
  

闽ICP备14008679号