文章目录
- 前言
- 一、了解开发板电路
- 二、了解芯片的启动配置
- 三、了解芯片内部GPIO的部分电路
- 四、明确目的看数据手册
- 五、代码编写
- 1.main主函数编写
- 2.led.c模块的编写
- 3.led.h模块的编写
- 4.led模块CMakeLists.txt文件的编写
- 5.main主函数的CMakeLists.txt文件编写
- 六、我踩的一些坑
- 七、总结
前言
我之前学习过stm32的HAL库但是还是不会看数据手册,所以我想先从ESP32开始学习看数据手册配置寄存器,而后再去深入学习stm32,在这里我是想分享自己的学习过程来做到复盘的效果,我使用的是ESP32-WROOM-32开发板在vscode上开发,欢迎各位大佬指点😭。
我今天学习的是配置GPIO的寄存器点亮LED。
一、了解开发板电路
可以看到这里开发板上有LED连接的GPIO2且高电平亮,在这里使用GPIO2使LED闪烁
二、了解芯片的启动配置
在这里我们了解到了芯片自启动的流程,还有看到GPIO2还在启动上发挥着一些作用,可以看到当GPIO0为1,GPIO为任意值就会启动从flash下载到RAM然后就会运行程序,那我编写的代码会不会影响启动呢,是不会的因为代码是在启动后才运行的,在还未启动时内部会有上/下拉电阻来配置GPIO2的电平
三、了解芯片内部GPIO的部分电路
1)首先先看芯片内部的原理图
可以看出图中将GPIO输入和输出分为了三大路,上面那一路是高速外设路径高速信号需要最小的延迟和干扰,新开一路 GPIO matrix 可以获得更好的高频特性,中间那一路是GPIO_matrix是GPIO交换矩阵,就像一个交叉开关可以将外设信号接到任意GPIO引脚,这两路都共同连接着IO_MUX,IO_MUX是输入输出多路复用器它可以让一个引脚复用多个功能可以决定每个引脚连接到内部哪个模块,所以ESP32在外设引脚上在很大程度是不固定的,然后最后一路RTC路径是低功耗路径,因为在深度睡眠模式下CPU和大部分外设都关闭了,只有RTC模块保持工作,所以可以在低功耗模式下检测外部事件(检测按键),唤醒芯片。
这副图就讲了信号如何输出和输入的(这里Buf是输出缓冲器)不过多赘述
四、明确目的看数据手册
1)首先要先明确自己的目的点亮LED以及确定目标GPIO,及更改对应GPIO引脚的电平为目标电平,所以需要输出使能的寄存器和修改对应GPIO口电平的寄存器,此时翻看芯片手册查找输出使能寄存器和输出置位寄存器可以看到
注意到该小节括号中的地址是基于GPIO基地址0x3FF4_4000的地址偏移量,所以需要明确对应寄存器的值。首先是输出置位寄存器这里我使用的是GPIO2所以看到GPIO_OUT_REG寄存器是GPIO0-31的32位输出值寄存器,是可读可写的寄存器,下面又有输出置位寄存器GPIO_OUT_W1TS_REG和输出清零寄存器GPIO_OUT_W1TC_REG,这两个寄存器都是只写的寄存器且都是32位的寄存器,而后我们看到GPIO_OUT_W1TS_REG 寄 存器在每一位置 1,则 GPIO_OUT_REG 中的相应位也会置 1,GPIO_OUT_W1TC_REG寄存器也是类似清零后相应的GPIO_OUT_REG寄存器中对应的值也会清零,可以看到我们既可以编写GPIO_OUT_REG寄存器直接控制对应GPIO引脚的高低电平,也可以更改GPIO_OUT_W1TS_REG寄存器和GPIO_OUT_W1TC_REG寄存器来做到置1和清零。
这里我说一下我对这几个寄存器的理解,首先对于寄存器赋值这里赋值都是将数值覆盖整个寄存器,所以这就面临了几个问题如果我在使用GOIO_OUT_REG改变高低电平时,若不提前读取更改前对应的引脚电平,那么一旦赋值就可能会改变原来一些引脚的电平,而且还可能会遇到多任务或中断环境下,多个执行流同时访问共享资源时,由于执行顺序不确定而导致的不可预测行为,也就是竞态风险。而对比之下的GPIO_OUT_W1TS_REG寄存器在对应GPIO引脚位置写1同步到GPIO_OUT_REG寄存器上就只有对应GPIO引脚上才会变为高电平,GPIO_OUT_W1TC_REG寄存器也是一样,所以还是感觉下面两个比较安全。
2)接下来就是输出使能寄存器了
这里和上面的三个寄存器也是很类似就不过多赘述了。
五、代码编写
1.main主函数编写
#include<stdio.h>#include<stdint.h>#include"led.h"voidapp_main(void){led_init();while(1){led_flash();}}2.led.c模块的编写
#include"led.h"#include<stdint.h>#defineBASE_ADDRESS0x3FF44000#defineGPIO_OUT_W1TS_REG_ADDRESS(BASE_ADDRESS+0x0008)#defineGPIO_ENABLE_W1TS_REG_ADDRESS(BASE_ADDRESS+0x0024)#defineGPIO_OUT_W1TC_REG_ADDRESS(BASE_ADDRESS+0x000c)voiddelay(intcount){volatileinti;for(i=0;i<count;i++){__asm__("nop");//空操作}}voidled_init(void){*((volatileuint32_t*)GPIO_ENABLE_W1TS_REG_ADDRESS)=(1<<2);//启用GPIO2输出(1<<2)相当于将1向左移两位到GPIO2//因为是对地址操作所以要在前面加个*//volatile是告诉编译器这个地址的值可能会被更改让编译器不要优化而跳过或合并对寄存器的读写操作//(uint32_t*)是将地址转换为指向32位无符号整数的指针*((volatileuint32_t*)GPIO_OUT_W1TC_REG_ADDRESS)=(1<<2);//设置GPIO2初始为低电平}voidled_flash(void){*((volatileuint32_t*)GPIO_OUT_W1TS_REG_ADDRESS)=(1<<2);delay(500000);*((volatileuint32_t*)GPIO_OUT_W1TC_REG_ADDRESS)=(1<<2);delay(500000);}3.led.h模块的编写
#ifndef__LED_H__#define__LED_H__voidled_init(void);voidled_flash(void);#endif4.led模块CMakeLists.txt文件的编写
//此文件用于告诉构建系统如何编译这个LED 组件set(src_dirs.)//set用于定义变量//src_dirs变量名,代表"源文件目录"//‘.’代表源文件在当前目录.c文件set(include_dirs.)//include_dirs变量名,代表"头文件目录".h文件set(requires driver)//requires变量名,代表"依赖的组件"//driver是ESP-IDF 的驱动组件(包含 GPIO、UART、SPI 等驱动)idf_component_register(SRC_DIRS ${src_dirs}INCLUDE_DIRS ${include_dirs}REQUIRES ${requires})//idf_component_register:ESP-IDF 的核心命令,用于向构建系统注册组件//SRC_DIRS ${src_dirs}:展开为 SRC_DIRS .,指定源文件目录//INCLUDE_DIRS ${include_dirs}:展开为 INCLUDE_DIRS .,指定头文件目录//REQUIRES ${requires}:展开为 REQUIRES driver,声明依赖组件component_compile_options(-ffast-math-O3-Wno-error=format=-Wno-format)//component_compile_options是ESP-IDF命令,用于为当前组件设置编译选项//-ffast-math:启用快速数学运算;//-O3:最高级别的优化(优化代码大小和执行速度);//-Wno-error=format:关闭格式相关警告作为错误//-Wno-format:关闭格式检查警告5.main主函数的CMakeLists.txt文件编写
idf_component_register(SRCS"main.c"INCLUDE_DIRS"."REQUIRES LED)//SRCS是Sources 的缩写,指定组件的源文件//告诉编译器"这个组件的源代码在 main.c 文件里"//头文件目录在本路径下//组件是LED解释一下CMakeLists.txt文件的作用结合代码的运行流程(个人理解)
- 启动构建 读取根目录配置
- 扫描组件 发现并解析组件配置
- 编译组件 指定源文件和头文件目录
- 链接组件 自动处理依赖关系
- 生成固件 生成可烧录文件
- 运行 执行 main()
总结就是CMakeLists.txt就是告诉DSP-IDF去哪找文件依赖什么组件,如何优化编译
注:一个文件目录下可能有多个.c和.h文件所以编译器会先看你引用了哪些
再去找对应的头文件而源文件是全部编译。
六、我踩的一些坑
开始本来我想使用GPIO12外接LED但是不管怎么试都不亮我用万用表打GPIO12的引脚也一直是低电平,因为我之前用库函数写的时候GPIO12是可以改变电平的,而后我就开始查数据手册看GPIO12是否有其他的作用,看到MTDI应该保持低电平而对应电路图MTDI对应的就是io12
为什么要保持低电平呢,应该是因为低电平的GPIO12对应的是3.3V的Flash模式,但是为什么库函数编写时可以改变呢,我觉得应该是因为在库函数调用时自动处理了一些底层操作,禁用了GPIO12的一些功能。
七、总结
以上就是今天要讲的内容,学的不够深可能有些说法有问题,欢迎指正,也欢迎各位大佬们提出对文章建议和对我的建议,感谢大家的阅读😚。