当前位置: 首页 > news >正文

【esp32s3】GPIO 寄存器 开发解析

在这里插入图片描述

从类51 / 类stm32 单片机过渡到 esp,总会有点惯性思维,寄存器在哪?我要怎么操作寄存器,手册怎么没有配置过程的指导。数据手册的寄存器排版还写得那么难看!!!


一、库函数测试

1.1. 代码

  • 创建组件,生成简单的库函数调用例子:
  • 运行后用电表测试,GPIO_NUM_4 有正常的电平转换
#include <stdio.h>
#include "gpio_reg_test.h"#include "esp_log.h"
#include "driver/gpio.h" // 需要添加依赖 PRIV_REQUIRES driver
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"static const char *TAG = "gpio_reg_test.c";// 配置参数
#define BLINK_GPIO GPIO_NUM_4 // 使用GPIO4作为示例(可根据需要修改)
#define BLINK_DELAY_MS (5*1000)    // 闪烁间隔(毫秒)// 测试 gpio 寄存器 函数
void gpio_reg_test_fun(void)
{// GPIO配置结构体gpio_config_t io_conf = {.pin_bit_mask = (1ULL << BLINK_GPIO),  // 选择GPIO.mode = GPIO_MODE_OUTPUT,              // 输出模式.pull_up_en = GPIO_PULLUP_DISABLE,     // 不上拉.pull_down_en = GPIO_PULLDOWN_DISABLE, // 不下拉.intr_type = GPIO_INTR_DISABLE         // 禁用中断};// 初始化GPIOESP_ERROR_CHECK(gpio_config(&io_conf));// 主循环while (1){// 调用库函数 设置高电平ESP_ERROR_CHECK(gpio_set_level(BLINK_GPIO, 1));ESP_LOGI(TAG, "GPIO%d set HIGH", BLINK_GPIO);vTaskDelay(pdMS_TO_TICKS(BLINK_DELAY_MS));// 调用库函数 设置低电平ESP_ERROR_CHECK(gpio_set_level(BLINK_GPIO, 0));ESP_LOGI(TAG, "GPIO%d set LOW", BLINK_GPIO);vTaskDelay(pdMS_TO_TICKS(BLINK_DELAY_MS));}
}
  • 教训:函数名字不可以和组件名字相同,会导致 main.c 导入头文件失败,一直找不到文件!!!
// * 确保头文件内容在单个编译单元内仅被展开一次(类似 #ifndef + #define + #endif 组合)
//#pragma one
#ifndef _GPIO_REG_TEST_H_
#define _GPIO_REG_TEST_H_// 测试 gpio 寄存器 函数
extern void gpio_reg_test_fun(void); // ! 教训:函数名字不可以和组件名字相同,会导致 main.c 导入头文件失败,一直找不到文件!!!#endif /* _GPIO_REG_TEST_H_ */

1.2. 解析

1.2.1. esp_driver_gpio

  • gpio的初始化过程大体和stm32的类似,使用结构体配置,然后传入结构体参数,然后调用一个设置高低电平的函数。
    // 初始化GPIOESP_ERROR_CHECK(gpio_config(&io_conf));
  • 按住 Ctrl 后鼠标左键点击函数名,跳转到原型查看:可以看到 gpio_config 函数是属于 esp_driver_gpio 组件的内容。
    • 可以看到里面是逐一判断结构体的取值,选择调用进一步不同的函数:
      • 如果是 类stm32 的芯片,这时一般就已经是寄存器操作了,gpio这种简单配置不会嵌套太多层。

在这里插入图片描述

  • 进一步到每一个结构体配置项查看,比如 gpio_input_enable / gpio_input_disable 配置输入开关:
    • 可以看到这些配置的api还是属于 esp_driver_gpio 组件的内容:
    • 它们内部调用了 hal 库的内容,hal 在开发 类stm32 时经常看到。我把它理解为是一个二次打包的库,有点类似逐飞为不同开发板写同一套api的做法,方便调用移植。

在这里插入图片描述

1.2.2. HAL

  • 再进一步,点击进入查看,会看到,跳转到组件 hal 中:
    • 然后这个函数其实是宏定义,链接到了 ll 库,ll 在开发 类stm32 时也经常看到,就是官方给的出厂驱动库,好像叫 dll

在这里插入图片描述

1.2.3. LL

  • 再进一步,点进去查看,看到跳转到了 hal 库的 esp32s3 类别中的 头文件 中:
    • 注意到函数前面加了 __attribute__((always_inline))static inline
    • 这些函数是被定义在头文件中的。

在这里插入图片描述

  • 鼠标悬空看到扩展内容:这条宏定义最后会被解析成寄存器的地址,然后直接赋值。
  • 这就是我们熟悉的寄存器操作了!!!

在这里插入图片描述

1.2.4. SOC

  • 再进一步查看,这个宏定义是位于组件 socesp32s3 的类别下。
  • 这里面定义了对于单片机类型的寄存器起始地址和偏移地址。

在这里插入图片描述

1.2.5. REG 数据手册

  • 打开数据手册查看基础地址,IO MUX0x6000_9000

在这里插入图片描述

  • GPIO 配置寄存器 的偏移地址是 0x0004 , 且这个寄存器的占位大小是 32位 = 4字节 = 0x4 倍数偏移

在这里插入图片描述

  • 其中输入开关控制位 IO_MUX_FUN_IE 处于第9位 1 << 9

在这里插入图片描述

  • 再回过头看看宏定义的扩展内容:完全符合
    • 外设基地址 -> 寄存器偏移地址 -> 赋值 -> 先取值再求或运算 -> 对应位数置1

在这里插入图片描述

  • 在线调试能看到实时结果,卡好断点单步运行,看外设寄存器值变化:
    • 自行查看不同外设不同变化,下面只是一个示意:

在这里插入图片描述

二、寄存器测试

  • 上面已经使用库函数完成了gpio的电平切换,使用上面的方法逐一查看,实际调用的寄存器地址和位数,然后使用寄存器操作实现切换:
static uint32_t * const GPIO_OUT_W1TS_REG = (uint32_t *)(0x60004000 + 0x0008); // GPIO0 ~ 31 输出置位寄存器
static uint32_t * const GPIO_OUT_W1TC_REG = (uint32_t *)(0x60004000 + 0x000C); // GPIO0 ~ 31 输出清零寄存器/* 省略不需要更改的地方 */// 调用寄存器 设置高电平
GPIO_OUT_W1TS_REG[0] |= (1 << BLINK_GPIO);
ESP_LOGI(TAG, "GPIO%d set HIGH", BLINK_GPIO);
vTaskDelay(pdMS_TO_TICKS(BLINK_DELAY_MS));// 调用寄存器 设置低电平
GPIO_OUT_W1TC_REG[0] |= (1 << BLINK_GPIO);
ESP_LOGI(TAG, "GPIO%d set LOW", BLINK_GPIO);
vTaskDelay(pdMS_TO_TICKS(BLINK_DELAY_MS));
  • 实测和库函数调用是一样的效果, 这样操作就没有移植性,不过可以用来测试模拟spi/iic,方便验证模块功能。也是一种思路。

三、API 指南 - 硬件抽象

官方介绍:硬件抽象
以下原封不动截取部分内容:

  • ESP-IDF 提供了一组用于硬件抽象的 API,支持以不同抽象级别控制外设,相比仅使用 ESP-IDF 驱动程序与硬件进行交互,使用更加灵活。ESP-IDF 硬件抽象适用于编写高性能裸机驱动程序,或尝试将 ESP 芯片移植到另一个平台。

3.1. 架构

  • ESP-IDF 的硬件抽象由以下层级各组成,从接近硬件的低层级抽象,到远离硬件的高层级抽象。

    • 低级层 (LL)
    • 硬件抽象层 (HAL)
    • 驱动层 (esp_driver_gpio)
  • LL 层和 HAL 完全包含在 hal 组件中,每一层都依赖于其下方的层级,即驱动层依赖于 HAL 层,HAL 层依赖于 LL 层,LL 层依赖于寄存器头文件。

3.2. LL 层(低级层)

  • LL 层主要目的是将寄存器字段访问抽象为更容易理解的函数。LL 函数本质是将各种输入/输出参数转换为外设寄存器的寄存器字段,并以获取/设置函数的形式呈现。所有必要的位移、掩码、偏移和寄存器字段的字节顺序都应由 LL 函数处理。
  • 所有 LL 函数均定义为 static inline,因此,由于编译器优化而调用这些函数时,开销最小。这些函数不保证由编译器内联,因此在禁用缓存时(例如从 IRAM ISR 上下文调用)调用的任何 LL 函数都应标记为 __attribute__((always_inline))

3.3. HAL(硬件抽象层)

  • HAL 将外设的操作过程建模成一组通用步骤,其中每个步骤都有一个相关联的函数。对于每个步骤,HAL 隐藏(抽象)了外设寄存器的实现细节(即需要设置/读取的寄存器)。通过将外设操作过程建模为一组功能步骤,HAL 可以抽象化(即透明处理)不同目标或芯片版本间的微小硬件实现差异。换句话说,特定外设的 HAL API 在多个目标/芯片版本之间基本保持相同。

  • HAL 函数不应包含任何操作系统原语,如队列、信号量、互斥锁等。所有同步/并发操作应在更高层次(如驱动程序)处理。

3.4. 总结

  • 官方手册里明确规范了不同层的函数名与参数名的习惯定义,方便一眼知道作用,增加可读性。
  • 层层嵌套是为了移植性,编译时自动根据选择芯片切换不同的LL层,HAL及其以上组件是共用的。
  • api的调用需要自行确保不冲突,使用rtos的互斥或信号量等保护。
寄存器读写
原子操作
位域控制
跨芯片兼容接口
安全API
硬件层
ESP32寄存器
LL层
Low-Level
HAL层
Hardware Abstraction
驱动层
esp_driver_xxx
应用层
用户代码
http://www.lryc.cn/news/597296.html

相关文章:

  • MACOS安装配置Gradle
  • 垃圾回收介绍
  • static 关键字的 特殊性
  • 双流join 、 Paimon Partial Update 和 动态schema
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-2,(电路分析/MOS管)
  • OpenLayers 快速入门(四)View 对象
  • PyTorch中nn.Module详解和综合代码示例
  • 大模型提示词漏洞攻防实战:从注入攻击到智能免疫系统的进化之路
  • mac电脑搭载c、c++环境(基于vs code)
  • 在mac 上zsh 如何安装最新的 go 工具
  • GRE实验
  • 微软Fabric重塑数据管理:Forrester报告揭示高ROI
  • 「iOS」——KVC
  • linxu CentOS 配置nginx
  • 【音视频学习】四、深入解析视频技术中的YUV数据存储方式:从原理到实践
  • 开源UI生态掘金:从Ant Design二次开发到行业专属组件的技术变现
  • 7月23日华为机考真题第二题-200分
  • 7月23日华为机考真题第一题100分
  • 关于原车一键启动升级手机控车的核心信息及注意事项
  • 将AI协作编程从“碰运气”的提示工程(Prompt Engineering)提升到“可预期”的上下文工程(Context Engineering)
  • 驯服AI的“魔法咒语”:Prompt提示词工程使用教程
  • [特殊字符] 从数据库无法访问到成功修复崩溃表:一次 MySQL 故障排查实录
  • 显微科研中的关键选择:不同显微镜相机技术特性与应用适配性全面解析
  • SpringBoot Stream实战指南
  • Django学习之旅--第13课:Django模型关系进阶与查询优化实战
  • 电科金仓推出AI融合数据库,开启国产数据库新时代
  • 深入理解 Java Builder 设计模式:解决构造函数爆炸问题
  • Java SE:类与对象的认识
  • 编程语言Java——核心技术篇(二)类的高级特性
  • Python 程序设计讲义(9):Python 的基本数据类型——复数