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


    新建工程

    寄存器方式

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

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

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

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

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

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

    ; Reset handler
    Reset_Handler    PROC
                     EXPORT  Reset_Handler             [WEAK]
            IMPORT  SystemInit
            IMPORT  __main
    
                     LDR     R0, =SystemInit
                     BLX     R0
                     LDR     R0, =__main
                     BX      R0
                     ENDP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

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

    image-20231102200243099

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

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

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

    1698926683862

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

    main.c:

    #include "stm32f4xx.h"
    
    int main(){
    	while(1){
    		
    	}
    }
    
    void SystemInit(){
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    点灯——51单片机版

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

    调用代码:

    #include "reg51.h"
    
    #ifdef 0 
    void main(){
    	PA0=0xFE;
    	while(1){}
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来我们需要定义 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){
    		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    点灯——stm32 版

    首先我们要设置 GPIO 模式。

    1698940961543

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

    *(unsigned int *)(0x40021400+0)&=~(3<<(6*2)); 
    *(unsigned int *)(0x40021400+0)|=(1<<(6*2)); 
    
    • 1
    • 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(){
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

    //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(){
    	
    }
    
    • 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
    点灯——流水灯闪烁

    利用软件延时实现 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--) ;    
       }
    }
    
    • 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
    点灯——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()
    {
    }
    
    • 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

    烧录前记得勾选:use MicroLib.

  • 相关阅读:
    Linux驱动开发——PCI设备驱动
    科目三:左转弯
    Vue3.0引入Echarts并使用
    瑞吉外卖实战项目全攻略——第六天
    【Express.js】软件构建
    网络安全(黑客)—自学
    CCF CSP认证 历年题目自练Day34
    8类放球问题
    【Linux练习生】线程安全
    使用动态标题提炼图表信息
  • 原文地址:https://blog.csdn.net/jtwqwq/article/details/134276530