搜索
bottom↓
回复: 0

《STM32MP1 M4裸机HAL库开发指南》第十三章

[复制链接]

出0入234汤圆

发表于 2022-10-29 09:46:01 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2022-10-29 09:45 编辑

1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png


第十三章 结构体实现外设定义实验


        在上一章的使用C语言编写LED灯的驱动实验中,每个寄存器地址都有其对应的宏定义,如果需要操作的寄存器很多,就需要写出所有寄存器地址的宏定义,这样操作起来是比较繁琐的。前面我们分析STM32Cube固件包的时候,了解到HAL库中将寄存器封装成结构体指针,使用中我们可以通过指针的方式来操作结构体的成员(寄存器):GPIOB->ODR = 0XFF。本章节我们就采用这样的方式来实现上一章节的实验。
        本章将分为如下几个小节:
        13.1、结构体实现外设定义介绍;
        13.2、硬件设计;
        13.3、软件设计;
        13.4、编译和测试;

13.1 结构体实现外设定义介绍

        HAL库已经有现成的API接口给我们使用了,我们可以直接调用这些API来操作外设,所以就不需要重复寄存器定义的工作,而本章节实验的目的是为了让大家更好地理解HAL库。
        前面我们在分析stm32mp157dxx_cm4.h文件的时候知道,STM32MP157的M4内核使用的寄存器都已经通过结构体的形式封装好了,我们可以在该文件中看到很多外设寄存器结构体声明,而声明一个结构体类型的时候是没有为它分配任何存储空间的,只有在定义结构体变量的时候,才会为变量分配存储空间,而C语言中结构体成员的地址是递增的,我们就利用这一点来访问寄存器。
第十三章 结构体实现外设定义实验574.png
图13.1.1 HAL库中寄存器的封装

在C语言中,结构体可以有不同的数据类型成员,成员在定义时依次存储在内存连续的空间中,结构体变量的首地址就是第一个成员的地址,内存偏移量就是各个成员相对于第一个成员地址的差(即,把低位内存分配给最先定义的变量)。理论上,结构体所占用的存储空间是各个成员变量所占的存储空间之和,但是为了提高CPU的访问效率,采用了内存对齐方式:
结构体的每一个成员起始地址必须是自身类型大小的整数倍,若不足,则不足部分用数据填充至所占内存的整数倍。
结构体大小必须是结构体占用最大字节数成员的整数倍,这样在处理数组时可以保证每一项都边界对齐
根据上面的说明,我们举例子分析如下:
        struct test
        {
            char  a;
            int   b;
            float  c;
            double d;
        }mytest;
如果定义了该结构体,那么这个结构体所占用的内存怎么算呢?理论结果为17,实际上并不是17,而是24。为什么会这样呢?这个就是前面我们说的内存对齐。下面我们来分析该结构体内存怎么计算:
1)char型变量占1个字节,所以它的起始地址是0;
2)int类型占用4个字节,它的起始地址要求是4的整数倍数,那么内存地址1、2、3就需要被填充(被填充的内存不适于变量),b从4开始;
3)float类型也是占用4个字节,起始地址要求是4的倍数,所以c的起始地址就是8;
4)double类型变量占用8个字节,起始地址为16,12~15被填充。
这里,第一个成员a的地址为首地止,第二个成员b的偏移量为4,第三个成员c的偏移量是8,以此类推,是如下图所示:
第十三章 结构体实现外设定义实验1325.png
图13.1.2结构地地址内存分配

上面的结构体成员a、b、c、d的类型是各不相同的,但在HAL库中,有很多的结构体,结构体中的成员类型基本上是一样的,例如GPIO的结构体,都是定义为uint32_t,即32位,每个成员占用4个字节,以第一个成员MODER为首地止0,第二个成员OTYPER相对于MODER偏移为4个字节,依次类推:
    typedef struct
    {
      __IO uint32_t MODER;           
      __IO uint32_t OTYPER;         
      __IO uint32_t OSPEEDR;        
      __IO uint32_t PUPDR;   
          __IO uint32_t IDR;
          __IO uint32_t ODR;        
     /******此处省略部分代码 */      
      __IO uint32_t VERR;            
      __IO uint32_t IPIDR;           
      __IO uint32_t SIDR;            
    } GPIO_TypeDef;
虽然结构体成员地址是连续的,但是还不能确定每个外设的地址,我们在ST的stm32mp157dxx_cm4.h文件中可以找到这样的定义:
#define PERIPH_BASE                             ((uint32_t)0x40000000)
#define MCU_AHB4_PERIPH_BASE        (PERIPH_BASE + 0x10000000)
#define GPIOB_BASE                            (MCU_AHB4_PERIPH_BASE + 0x3000)
#define GPIOB                                       ((GPIO_TypeDef *) GPIOB_BASE)
        这样一来就可以知道GPIOB基地址了,为:
        GPIOB_BASE= MCU_AHB4_PERIPH_BASE + 0x3000
                                = PERIPH_BASE + 0x10000000+0x3000
                                =0x40000000+0x10000000+0x3000
                                =0x50003000
        这个结果和我们在参考手册上查询到的结果一致:
第十三章 结构体实现外设定义实验2368.png
图13.1.3参考手册部分截图

确定了GPIOB的基地址,再加上#define GPIOB  ((GPIO_TypeDef *) GPIOB_BASE)这句,那么GPIOB所有的寄存器的地址基本上就可以确定了。该语句的意思是,定义一个宏GPIOB,宏GPIOB是指向地址GPIOB_BASE 的结构体指针,这么一来,我们就可以通过以下方式将GPIO_ODR寄存器的值设置为0XFF了:
GPIOB->ODR=0XFF
这样一来,我们就不需要将GPIO外设所有用到的寄存器的地址都定义一遍了,我们只需要声明一个结构体,结构体中将GPIO的寄存器封装好,然后定义GPIO的基地址,再定义GPIO的宏为指向该基地址的指针,这样所有的寄存器地址也都确定下来了。不仅仅是GPIO,对于其它外设的寄存器也是一样的。
下面,我们就模仿HAL库的这种方法来控制LED0和LED1交替闪烁,效果和上一章节的实验一样。
13.2 硬件设计
        硬件原理图和第十一章节一样,本章节控制的是开发板的LED0和LED1,当引脚为低电平时LED亮,当引脚为高电平时,LED灭。
13.3 软件设计
        本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\ MP157-M4 HAL库V1.2\实验2 结构体实现外设定义实验。
13.3.1 新建工程
        按照前面的步骤新建一个新的工程,并在工程中新建文件:启动文件startup_stm32mp15xx.s、main.c文件、main.h文件,这里startup_stm32mp15xx.s文件的内容和在上一章节的一样,可以直接将上一章节的代码拷贝过来:
第十三章 结构体实现外设定义实验3087.png
图13.3.1.1启动文件要修改的地方

        新建好的工程目录如下所示:
第十三章 结构体实现外设定义实验3124.png
图13.3.1.2新建的工程

13.3.2 添加用户代码
1. 添加main.h文件代码
        main.h文件主要是一些宏定义以及寄存器结构体封装,如下所示,因为RCC外设的寄存器比较多,我们这里不全部列举出来,省略掉了一部分:
1   #ifndef __MAIN_H
2   #define __MAIN_H
3  
4   /*
5    * 数据类型
6    */
7   #define __IO volatile   /* volatile表示强制编译器减少优化 */
8   #define RESET   0
9   #define SET     1
10  typedef unsigned int        uint32_t;
11   
12  /*
13   * 各个外设基地址
14   */
15  #define PERIPH_BASE               (0x40000000)
16  #define MCU_AHB4_PERIPH_BASE    (PERIPH_BASE + 0x10000000)
17  #define RCC_BASE                   (MCU_AHB4_PERIPH_BASE + 0x0000)
18
19  #define GPIOF_BASE                 (MCU_AHB4_PERIPH_BASE + 0x7000)
20  #define GPIOI_BASE                 (MCU_AHB4_PERIPH_BASE + 0xA000)
21
22  /*
23   * RCC外设结构体
24   */
25  typedef struct
26  {
27      __IO uint32_t TZCR;                  /* 偏移地址: 0x00 */
28          uint32_t RESERVED0[2];          /* 偏移地址: 0x04 */
29      __IO uint32_t OCENSETR;             /* 偏移地址: 0x0C */
30      __IO uint32_t OCENCLRR;             /* 偏移地址: 0x10 */
31          uint32_t RESERVED1;              /* 偏移地址: 0x14 */
32      __IO uint32_t HSICFGR;              /* 偏移地址: 0x18 */
33      __IO uint32_t CSICFGR;              /* 偏移地址: 0x1C */
34  /*******由于寄存器太多,这里省略了部分寄存器,具体代码可看本实验工程 ******/
35      __IO uint32_t MC_AHB4ENSETR;        /* 偏移地址: 0xAA8 */   
36      __IO uint32_t VERR;                 /* 偏移地址: 0xFF4 */
37      __IO uint32_t IPIDR;                /* 偏移地址: 0xFF8 */
38      __IO uint32_t SIDR;                 /* 偏移地址: 0xFFC */
39  } RCC_TypeDef;
40
41  /*
42   *  GPIO外设结构体
43   */
44  typedef struct
45  {
46    __IO uint32_t MODER;              /* 偏移地址: 0x000 */
47    __IO uint32_t OTYPER;             /* 偏移地址: 0x004 */
48    __IO uint32_t OSPEEDR;            /* 偏移地址: 0x008 */
49    __IO uint32_t PUPDR;              /* 偏移地址: 0x00C */
50    __IO uint32_t IDR;                /* 偏移地址: 0x010 */
51    __IO uint32_t ODR;                /* 偏移地址: 0x014 */
52    __IO uint32_t BSRR;               /* 偏移地址: 0x018 */
53    __IO uint32_t LCKR;               /* 偏移地址: 0x01C */
54    __IO uint32_t AFR[2];             /* 偏移地址: 0x020-0x024 */
55    __IO uint32_t BRR;                /* 偏移地址: 0x028 */
56         uint32_t RESERVED0;          /* 偏移地址: 0x02C */
57    __IO uint32_t SECCFGR;            /* 偏移地址: 0x030 */
58         uint32_t RESERVED1[229];     /* 偏移地址: 0x034-0x3C4 */
59    __IO uint32_t HWCFGR10;           /* 偏移地址: 0x3C8 */
60    __IO uint32_t HWCFGR9;            /* 偏移地址: 0x3CC */
61    __IO uint32_t HWCFGR8;            /* 偏移地址: 0x3D0 */
62    __IO uint32_t HWCFGR7;            /* 偏移地址: 0x3D4 */
63    __IO uint32_t HWCFGR6;            /* 偏移地址: 0x3D8 */
64    __IO uint32_t HWCFGR5;            /* 偏移地址: 0x3DC */
65    __IO uint32_t HWCFGR4;            /* 偏移地址: 0x3E0 */
66    __IO uint32_t HWCFGR3;            /* 偏移地址: 0x3E4 */
67    __IO uint32_t HWCFGR2;            /* 偏移地址: 0x3E8 */
68    __IO uint32_t HWCFGR1;            /* 偏移地址: 0x3EC */
69    __IO uint32_t HWCFGR0;            /* 偏移地址: 0x3F0 */
70    __IO uint32_t VERR;               /* 偏移地址: 0x3F4 */
71    __IO uint32_t IPIDR;              /* 偏移地址: 0x3F8 */
72    __IO uint32_t SIDR;               /* 偏移地址: 0x3FC */
73  } GPIO_TypeDef;
74
75  /*
76   * RCC和GPIOI以及GPIOF相关宏定义
77   */
78  #define RCC                 ((RCC_TypeDef *) RCC_BASE)
79  #define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
80  #define GPIOI               ((GPIO_TypeDef *) GPIOI_BASE)
81
82  #endif
        第7行,宏__IO表示volatile,volatile表示强制编译器减少优化,告诉编译器必须每次去内存中取变量值,这样确保了数据的准确性;
        第15~20行,确定GPIOI、GPIOF和RCC的基地址分别为0x5000A000、0x50007000和0x50000000;
        第25~39行,声明RCC_TypeDef结构体,此结构体中对RCC外设的寄存器进行了封装;
        第44~73行,声明GPIO_TypeDef结构体,此结构体中对GPIO外设的寄存器进行了封装;
        第78~80行,分别定义了宏RCC是指向地址RCC_BASE的结构体指针,宏GPIOF是指向地址GPIOF_BASE的结构体指针,宏GPIOI是指向地址GPIOI_BASE 的结构体指针。
        参照以上外设地址的定义方法,我们还可以在此文件中添加更多外设的定义,本节实验只是控制PI0和PF3的电平变化,就不需要再定义其它外设了。
2. 添加main.c文件代码
        main.c文件代码如下,前面我们通过结构体的形式对寄存器进行了封装,那么这里我们就可以通过指针的方式来操作对应的寄存器了:
1   #include "main.h"
2  
3   /* LED灯引脚定义 */
4   #define LED0_PORT       GPIOI
5   #define LED0_PIN        0
6   #define LED1_PORT       GPIOF
7   #define LED1_PIN        3
8  
9   /**
10   * @brief     使能GPIOF和GPIOI时钟
11   * @param     无
12   * @retval    无
13   */
14  void clk_enable(void)
15  {
16      RCC->MC_AHB4ENSETR |= ((unsigned int)1 << 5);   /* 使能GPIOF时钟 */
17      RCC->MC_AHB4ENSETR |= ((unsigned int)1 << 8);   /* 使能GPIOI时钟 */
18  }
19
20  /**
21   * @brief     初始化GPIO为推挽输出、高速、上拉模式
22   * @param     无
23   * @retval    无
24   */
25  void gpio_init(GPIO_TypeDef  *GPIOx, unsigned char pin)
26  {
27      /* 设置为输出 */
28      GPIOx->MODER &= ~((unsigned int)3 << (2 * pin));
29      GPIOx->MODER |= ((unsigned int)1 << (2 * pin));
30      
31      /* 设置为推完模式 */
32      GPIOx->OTYPER &= ~(1 << 15);
33      
34      /* 设置为高速模式 */
35      GPIOx->OSPEEDR &= ~((unsigned int)3 << (2 * pin));
36      GPIOx->OSPEEDR |= ((unsigned int)2 << (2 * pin));
37      
38      /* 设置为上拉 */
39      GPIOx->PUPDR &= ((unsigned int)3 << (2 * pin));
40      GPIOx->PUPDR |= ((unsigned int)1 << (2 * pin));
41  }
42
43  /**
44   * @brief     LED0开关函数
45   * @param     无
46   * @retval    无
47   */
48  void pin_write(GPIO_TypeDef *GPIOx, unsigned char pin, unsigned char state)
49  {
50      if(state == SET)
51      {
52          GPIOx->BSRR |= ((unsigned int)1 << pin);            /* 输出高电平 */
53      } else if(state == RESET)
54      {
55       GPIOx->BSRR |= ((unsigned int)1 << (15 + pin + 1)); /* 输出低电平 */
56      }
57  }
58      
59  /**
60   * @brief       短时间延时函数
61   * @param - n   要延时循环次数(空操作循环次数,模式延时)
62   * @retval      无
63   */
64  void delay_short(volatile unsigned int n)
65  {
66      while(n--){}
67  }
68
69  /**
70   * @brief       长延时函数
71   * @param - n   要延时的时间循环数
72   * @retval      无
73   */
74  void delay(volatile unsigned int n)
75  {
76      while(n--)
77      {
78          delay_short(0x7fff);
79      }
80  }
81
82  /**
83   * @brief       main函数
84   * @param       无
85   * @retval      无
86   */
87  int main(void)
88  {
89      clk_enable();                   /* 使能时钟     */
90      gpio_init(LED0_PORT, LED0_PIN); /* 初始化LED0    */
91      gpio_init(LED1_PORT, LED1_PIN); /* 初始化LED1    */
92      
93      while(1)
94      {
95          pin_write(LED0_PORT, LED0_PIN, RESET);  /* LED0低电平打开 */
96          pin_write(LED1_PORT, LED1_PIN, SET);    /* LED1高电平关闭 */           
97          delay(100);
98          pin_write(LED0_PORT, LED0_PIN, SET);    /* LED0高电平关闭 */
99          pin_write(LED1_PORT, LED1_PIN, RESET);  /* LED1低电平打开 */
100         delay(100);
101     }   
102 }
        以上的函数和上一章节的函数类似,不同的是是通过指针的方式来操作寄存器的,要注意的是,外设时钟开启以后才可以使用,所以不要忘记了使能外设时钟。代码比较简单,我们接下来直接编译和验证。
13.3.3 编译和测试
        编译后无报错,进入仿真,实现现象和上一章节的一样,LED0和LED1在交替闪烁。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-24 17:15

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表