野火霸天虎 STM32F407 学习笔记_3 尝试寄存器映射方式点亮 LED 灯

新建工程

寄存器方式

要命啊,一看名字我就不想试。寄存器新建不得麻烦死。

哎算了为了学习原理,干了。

我们尝试自己写一个寄存器的库函数来引用。

首先我们需要引用 st 官方启动文件 stmf4xx.s,具体用途后面章节再展开讲解。然后我们自己新建一个 stm32f4xx.h 文件来映射寄存器。不过只是把这个文件包含进项目,编译会报错:

.\Objects\led_reg.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f40xx.o).

进入启动文件后,可以看到这么一个函数:

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

import 的作用相当于 extern,所以没有找到这个函数的定义,需要我们自己去定义。这就是为什么简单引入了启动文件会报错。

而 __main 是当我们定义了 main() 函数后,编译器会自动链接一些c语言库定义好的函数,用于初始化堆栈并且调用我们的 main().

注意,如果想要生成 __main 函数,必须勾选下面这一项。

image-20231102200243099

野火你讲的是真好啊。我之前草草学了学 stm32 单片机用法,比赛的时候自己想移植代码,改了启动文件也不好使,就是报错。原来是这个原因。

那么我们只需要定义这么一个函数,哪怕内容是空都无所谓。

最终我们定义的初步项目框架如下:

1698926683862

stm32f4xx.h:内容为空,有这么个东西就行。

main.c:

#include "stm32f4xx.h"

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

void SystemInit(){
    
    
	
}

好了,这个程序可以烧录到板子上的。烧录成功之后没有任何反应(因为本来程序也没做什么哈哈),但是这就是一个大进步了。

点灯——51单片机版

51单片机版就是引用 reg51.h 头文件,在其中声明了各个引脚的地址。我们只需要直接给引脚赋值即可。

调用代码:

#include "reg51.h"

#ifdef 0 
void main(){
    
    
	PA0=0xFE;
	while(1){
    
    }
}
#endif

接下来我们需要定义 LED 灯的寄存器位置。阅读原理图如下:

1698939342199

大致可以看出,板子上的这个 RGB LED 通过三个引脚来控制 RGB 亮度。输出低电平则导通点亮。

具体输出方式是通过 ODR 进行输出。查找 stm32f4xx 中文参考手册可见:

1698940158585

1698940257044

那么我们就要给 0x4002 1400 +14 的地址赋值,让 1<<6 1<<7 1<<8 的位分别赋值为低电平.

int main(){
    
    
	 *(unsigned int *)(0x40021400+14)&=~(1<<6); 
	while(1){
    
    
		
	}
}

然而这样也不亮。亮就怪了,stm32 寄存器是需要先做初始化配置的。

点灯——stm32 版

首先我们要设置 GPIO 模式。

1698940961543

想点灯 输出高低电平,是 01 通用输出模式。

*(unsigned int *)(0x40021400+0)&=~(3<<(6*2)); 
*(unsigned int *)(0x40021400+0)|=(1<<(6*2)); 

意思是先把 PF6 模式位置为00,然后赋值为01通用输出。

配置完模式之后,还需要配置时钟,stm32 每个外设都需要配置时钟。

前面提到过 GPIO 是在 AHB1.

1699103516061

1699103724261

全部代码如下:

#include "stm32f4xx.h"

int main(){
    
    
    //RCC
    *(unsigned int *)(0x40023800+0x30)|=(1<<5); 
    
    //Mode
    *(unsigned int *)(0x40021400+0)&=~(3<<(6*2)); 
    *(unsigned int *)(0x40021400+0)|=(1<<(6*2)); 
    
    
    *(unsigned int *)(0x40021400+0x14)&=~(1<<6); 
    while(1){
    
    
		
	}
}

void SystemInit(){
    
    
	
}

接下来,我们把这几个地址值提取出来,宏定义映射寄存器。

//stm32f4xx.h
/* 用来存放寄存器映射相关的代码 */
#define RCC_AHB1_ENR    *(unsigned int *)(0x40023800+0x30)
#define GPIOF_MODER     *(unsigned int *)(0x40021400+0)
#define GPIOF_ODR       *(unsigned int *)(0x40021400+0x14)

//main.c
#include "stm32f4xx.h"

int main(){
    
    
    //RCC
    RCC_AHB1_ENR|=(1<<5); 
    
    //Mode
    GPIOF_MODER&=~(3<<(6*2)); 
    GPIOF_MODER|=(1<<(6*2)); 
    
    
    GPIOF_ODR&=~(1<<6); 
    while(1){
    
    
		
	}
}

void SystemInit(){
    
    
	
}
点灯——流水灯闪烁

利用软件延时实现 RGB 流水灯闪烁。很简单,前面已经看了3个 LED 通道 PF678 了。

#include "stm32f4xx.h"

void delay_ms(int time);

int main(){
    
    
    //RCC
    RCC_AHB1_ENR|=(1<<5); 
    
    //Mode
    GPIOF_MODER&=~(3<<(6*2)); 
    GPIOF_MODER|=(1<<(6*2)); 
    GPIOF_MODER&=~(3<<(7*2)); 
    GPIOF_MODER|=(1<<(7*2)); 
    GPIOF_MODER&=~(3<<(8*2));   
    GPIOF_MODER|=(1<<(8*2)); 
    
    while(1){
    
    
        GPIOF_ODR|=(7<<6);
        GPIOF_ODR&=~(1<<6);
        delay_ms(1000);
        GPIOF_ODR|=(7<<6);
        GPIOF_ODR&=~(1<<7); 
        delay_ms(1000);
        GPIOF_ODR|=(7<<6);
        GPIOF_ODR&=~(1<<8);
        delay_ms(1000);
	}
}

void SystemInit(){
    
    
	
}

//毫秒级的延时
void delay_ms(int time)
{
    
        
   int i=0;  
   while(time--)
   {
    
    
      i=4000;
      while(i--) ;    
   }
}
点灯——GPIO 具体功能框图对应

GPIO:通用输入输出引脚。我们可以通过编程来输出或者读取数据。大部分 GPIO 是已经连接、定义好了一些功能(比如上面尝试过的 PF6 LED),有的引脚有多个功能支持重新映射。

STM32 GPIO 除了 adc 是 3.3v,其他 GPIO 都是 5v 容忍。

GPIO 框图(重点)如下:

image-20231104221140325

先从输出开始看。最右侧的 IO 引脚是连接在芯片周围一圈的144个引脚之一。除了 IO 引脚,此图中其他所有部分都是封装在芯片内部我们看不到的。

往左有两个保护二极管。当电压大于 5V,电流会往上 VDD_FT 走。当电压为负电压,电流会由 VSS 往 IO 引脚走。

上下拉电阻:比武外接一个低电平工作的设备,但是我们不希望一上电外设就工作,可以设置上拉电阻,稳定一段时间。

GPIO 输出的数据来源:复位寄存器 BSRR,或者 ODR 设置(图中的3下路部分)。复位寄存器高16位复位(写1置0)低16位置位(写1置1),置位优先级更高。

配置 GPIO 模式(输入/输出,选择哪一路)通过前面用过的 MODER 配置。

输出模式(图中输出控制部分)配置端口输出类型寄存器 OTYPER,比如推挽输出,开漏输出。

推挽输出:有直接驱动能力,输出0就是低电平,输出1就输出可以工作的高电平。原理是采用了一个放大的电路?

1699356797818

输入(INT)为高电平时,反向后 PMOS 导通,输出高电平。输入为低电平时,反向后 NMOS 导通,输出低电平。我们可以用一个小电流去驱动出来一个大电流。

开漏输出:自己本身没有输出高电平的手段。低电平可以接地,高电平没有 PMOS 管,是浮空状态。需要外接一个电阻。

1699357078909

stm32 输出 5V 电压的方法就是开漏输出外接电阻。通过接两个三极管的方式反向。

1699357296750

框图中的模拟部分输入输出则不用配置这些模式信息,直接由外设接到保护二极管再接到输出引脚。

框图中的输入部分经过保护电压后,还需要施密特触发器调整一下。比如原来电压的数值并非精确的0或 3.3V,施密特触发器将高于 1.8V 的全部视作1,低于的全部视作0后输入芯片。模拟部分则不需要经过施密特触发器。

因此配置 GPIO 输出的步骤如下:

  1. GPIO 功能,通用输出、复用功能、模拟输入等 MODER;
  2. 输出推挽 or 开漏 OTYPER;
  3. 输出速度 OSPEEDR;
  4. 上下拉电阻是否需要开启 PUPDR;
  5. 具体输出内容 BSRR or ODR.

输入部分后面输入实验介绍~

按整个流程重新串一遍代码,如下:(其实和前面差不多,就是重新按照流程串了一遍)

/* 用来存放寄存器映射相关的代码 */
#define RCC_BASE    (unsigned int *)    0x40023800
#define GPIOF_BASE  (unsigned int *)    0x40021400
    
#define RCC_AHB1ENR         *(RCC_BASE+0x30)
    
#define GPIOF_MODER         *(GPIOF_BASE+0x00)
#define GPIOF_OSPEEDR       *(GPIOF_BASE+0x08)
#define GPIOF_PUPDR         *(GPIOF_BASE+0x0C)
#define GPIOF_ODR           *(GPIOF_BASE+0x14)
#define GPIOF_BSRR          *(GPIOF_BASE+0x18)

//main.c
#include "stm32f4xx.h"

int main()
{
    
    
    RCC_AHB1ENR |= (1<<5);
    GPIOF_MODER &= ~(3<<(6*2));
    GPIOF_MODER |= (1<<(6*2));
    while (1)
    {
    
    
    }
}

void SystemInit()
{
    
    
}

烧录前记得勾选:use MicroLib.

猜你喜欢

转载自blog.csdn.net/jtwqwq/article/details/134276530