打印
[应用相关]

STM32实战总结:HAL之GPIO

[复制链接]
手机看帖
扫描二维码
随时随地手机跟帖
21
结合国际经验|  楼主 | 2023-12-27 14:56 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
该宏定义替换后,是后面的内容,看起来像是把一个宏定义内容进行强制类型转换,那么,被转换的是什么呢?又是转换成了什么类型呢?最终的效果又是什么呢?

在该文件上面,找到了宏定义:

#define GPIOE_BASE            (APB2PERIPH_BASE + 0x00001800UL)

使用特权

评论回复
22
结合国际经验|  楼主 | 2023-12-27 14:56 | 只看该作者
可知,被转换的好像是个地址,一个基地址再加上一个数,继续查找基地址:

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000UL)

使用特权

评论回复
23
结合国际经验|  楼主 | 2023-12-27 14:56 | 只看该作者
继续查找PERIPH_BASE
#define PERIPH_BASE           0x40000000UL /*!< Peripheral base address in the alias region */

使用特权

评论回复
24
结合国际经验|  楼主 | 2023-12-27 14:57 | 只看该作者
涉及到基地址加上一个数,联想到基地址加偏移量,又提到了别名区,难道是位带操作?

具体查找数据手册,看内存映射图中外设起始地址是哪个。

或者查看参考手册中,存储器映像表,看起始地址。

使用特权

评论回复
25
结合国际经验|  楼主 | 2023-12-27 14:57 | 只看该作者
0x4000 0000 - 0x4000 03FF      TIM2定时器

可知,起始地址为:0x4000 0000

上面的地址经过相加,可得出为0x4001 1800,查阅手册得知,正好是端口E的地址。0x4001 1800 - 0x4001 1BFF      GPIO端口E

由此可知,GPIOE表示的就是端口E的地址所对应的值。

此时,只是一个值,被转成了一个指针。

那么,又转换成了什么类型的指针呢?

那就要看,GPIO_TypeDef,是怎么定义的?

使用特权

评论回复
26
结合国际经验|  楼主 | 2023-12-27 14:57 | 只看该作者
typedef struct
{
  __IO uint32_t CRL;
  __IO uint32_t CRH;
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;

使用特权

评论回复
27
结合国际经验|  楼主 | 2023-12-27 14:57 | 只看该作者
这是一个结构体,定义了GPIO每个端口的寄存器。

因为,结构体的指针,指向的是首元素的首地址,所以,上面的强制转换的含义就是,指明端口E每一个寄存器的地址。

搞了这么多,就是定义了端口E各个寄存器的地址。虽然略显复杂,但是实现了标准化。

所以,GPIOE啥意思?就是端口E对应寄存器结构体的地址。所以对端口E的操作都基于GPIOE,其他端口,甚至所有外设,同理。

使用特权

评论回复
28
结合国际经验|  楼主 | 2023-12-27 14:57 | 只看该作者
再回过头看看stm32f103xe.h的功能说明:
  * @file    stm32f103xe.h
  * @author  MCD Application Team
  * @brief   CMSIS Cortex-M3 Device Peripheral Access Layer Header File.
  *          This file contains all the peripheral register's definitions, bits
  *          definitions and memory mapping for STM32F1xx devices.            
  *            
  *          This file contains:
  *           - Data structures and the address mapping for all peripherals
  *           - Peripheral's registers declarations and bits definition
  *           - Macros to access peripheral抯 registers hardware

使用特权

评论回复
29
结合国际经验|  楼主 | 2023-12-27 14:57 | 只看该作者
HAL_Init();

看名称就知道是HAL库的初始化,那么HAL库的初始化要做哪些事情呢?

先看声明:

跳转打开了stm32f1xx_hal.h
/* Initialization and de-initialization functions  */
HAL_StatusTypeDef HAL_Init(void);
HAL_StatusTypeDef HAL_DeInit(void);
……

使用特权

评论回复
30
结合国际经验|  楼主 | 2023-12-27 14:58 | 只看该作者
可知,其返回一个什么,可查看到返回的是一个状态码,该状态码是个枚举类型,定义在头文件stm32f1xx_hal_def.h中,/**
  * @brief  HAL Status structures definition
  */
typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

使用特权

评论回复
31
结合国际经验|  楼主 | 2023-12-27 14:59 | 只看该作者
再跳转到对应c中去看定义:

HAL_StatusTypeDef HAL_Init(void)
{
  /* Configure Flash prefetch */
#if (PREFETCH_ENABLE != 0)
#if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \
    defined(STM32F102x6) || defined(STM32F102xB) || \
    defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \
    defined(STM32F105xC) || defined(STM32F107xC)

  /* Prefetch buffer is not available on value line devices */
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  HAL_InitTick(TICK_INT_PRIORITY);

  /* Init the low level hardware */
  HAL_MspInit();

  /* Return function status */
  return HAL_OK;
}

使用特权

评论回复
32
结合国际经验|  楼主 | 2023-12-27 14:59 | 只看该作者
根据英文注释不难看出来每个代码是干嘛的。

一直往下追踪,可以发现,其实底层都是对相关寄存器进行操作。

main.c中的后续初始化等代码类似,不再赘述。

使用特权

评论回复
33
结合国际经验|  楼主 | 2023-12-27 14:59 | 只看该作者
GPIO模块化编程
单片机的封装通常都是分层的。

怎么说呢?

首先,底层针对寄存器的读写时序是第一层;

再往上第二层,就是针对特定硬件的一些基本功能,比如读和写;

再往上就是第三层,可能是面向对象层,实现特定硬件的具体功能;

再往上,就是业务层,通过硬件的具体功能来实现不同的业务需求。

……

现在,有三个LED灯,对其进行模块化编程。

首先,HAL提供了写引脚、转换引脚电平、读引脚电平等功能。

我们进一步封装,实现该LED灯的具体功能,有打开、关闭以及转换开关状态(此时不用关注下一层的细节问题,面向的是具体的LED灯)

使用特权

评论回复
34
结合国际经验|  楼主 | 2023-12-27 14:59 | 只看该作者
思路如下:

为LED外设单独创建一个文件;

创建LED.c和LED.h,放到MyApplications中;

在myapplication.h中添加对应的头文件;

要实现哪些功能?对应的要提供什么函数,什么变量?将这些变量封装成一个结构体。

使用特权

评论回复
35
结合国际经验|  楼主 | 2023-12-27 14:59 | 只看该作者
#ifndef _LED_H_
#define _LED_H_

#include "stdint.h"

//确定要实现的led功能
typedef struct
{
    //点亮
    void (*led_light)(uint8_t);
    //熄灭
    void (*led_extinguish)(uint8_t);
    //转换亮灭
    void (*led_switch)(uint8_t);   
   
} led_funtcions;


//有三个LED灯,定义成枚举,并编号
typedef enum
{
    LED1 = 1u, LED2, LED3
   
} led_status;

//将结构体声明出去
extern led_funtcions led_operater;

#endif

使用特权

评论回复
36
结合国际经验|  楼主 | 2023-12-27 16:07 | 只看该作者
接着,在对应的c中,定义一个结构体全局变量(相应的在头文件中要声明出去),这个变量就作为一个对接人,也就是该文件的一个对象。之后,依次实现其中所定义的函数,并将函数赋值给结构体。同时注意,将所有函数设置成当前文件可见的,即加上static。我们不会对外暴露任何函数,要想访问函数,必须通过结构体变量去访问元素的形式。=

使用特权

评论回复
37
结合国际经验|  楼主 | 2023-12-27 16:07 | 只看该作者
#include "myapplication.h"

static void LedLight(uint8_t lednum);
static void LedExtinguish(uint8_t lednum);
static void LedSwitch(uint8_t lednum);
static void Led1Blink(void);

led_funtcions led_operater =
{
    LedLight,
    LedExtinguish,
    LedSwitch
};

static void LedLight(uint8_t lednum)
{
    switch(lednum)
    {
        case LED1 :
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
            break;
        case LED2 :
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
            break;
        case LED3 :
            HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
            break;
        default :
            Led1Blink();
    }
}

static void LedExtinguish(uint8_t lednum)
{
    switch(lednum)
    {
        case LED1 :
            HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
            break;
        case LED2 :
            HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
            break;
        case LED3 :
            HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);
            break;
        default :
            Led1Blink();
    }
}

static void LedSwitch(uint8_t lednum)
{
    switch(lednum)
    {
        case LED1 :
            HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
            break;
        case LED2 :
            HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
            break;
        case LED3 :
            HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
            break;
        default :
            Led1Blink();
    }
}

//如果输入的不是LED1/LED2/LED3则LED1闪烁
static void Led1Blink(void)
{
    HAL_Delay(100);
    HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}

使用特权

评论回复
38
结合国际经验|  楼主 | 2023-12-27 16:07 | 只看该作者
通过结构体变量去访问:static void Run(void)
{
    HAL_Delay(500);
    led_operater.led_light(LED1);
    led_operater.led_light(LED2);
    led_operater.led_light(LED3);
   
    HAL_Delay(500);
   
    led_operater.led_extinguish(LED1);
    led_operater.led_extinguish(LED2);
    led_operater.led_extinguish(LED3);
   
    HAL_Delay(500);
   
    led_operater.led_switch(LED1);
   
    HAL_Delay(500);
   
    led_operater.led_switch(LED2);
   
    HAL_Delay(500);
   
    led_operater.led_switch(LED3);
   
    HAL_Delay(500);
   
    led_operater.led_extinguish(LED3);
   
    HAL_Delay(500);
   
    led_operater.led_extinguish(LED2);
   
    HAL_Delay(500);
   
    led_operater.led_extinguish(LED1);
}   

使用特权

评论回复
39
结合国际经验|  楼主 | 2023-12-27 16:08 | 只看该作者
在已有的框架下,我们不用去动任何main函数里的内容。只用处理好这里的Run函数和相应外设即可,便于移植和维护。

使用特权

评论回复
40
结合国际经验|  楼主 | 2023-12-27 16:08 | 只看该作者
状态机
我们所说的状态机是有限状态机,是一种思想,把复杂的控制逻辑分解成有限个稳定状态,组成闭环系统,通过事件触发,让状态机按设定的顺序处理事务。

如果不用状态机思想,那么我们要想根据条件来做出不同的应对,最直接的就是在while里面疯狂使用if来判断。(不管什么时候,在程序里使用过多的if语句,都不是优先选项,会让程序易出错,且可读性很差)

单片机C语言的状态机编程,是利用条件选择语句(switch-case)切换状态,通过函数内部指令改变状态机状态,让程序按照设定的顺序执行。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则