LED闪烁灯

交流QQ: 1048272975             QQ交流群: 636564526

控制LED灯的亮灭是MCU开发中一个最简单的应用功能,实现这个应用功能包含了MCU开发中工程的构建、编译的过程、下载烧录的方式、开机运行的流程等等内容。

1. 开发工具链

针对ESP32开发,乐鑫官方提供了ESP-IDF框架以及对应的开发工具链,开发环境的搭建可以参考上一章节的内容。作为一个LED灯控制的简单程序,只需要基于Xtensa架构的GNU交叉编译工具的支持。

2. LED闪烁灯程序

以通用的main函数作为程序的入口,通过控制ESP32 GPIO输出高低电平来控制LED灯的亮灭。

2.1. 关闭看门狗

参考ESP-IDF目录components/bootloader裸机引导程序的实现流程以及ESP32芯片数据手册,可以知道Flash启动时使能了主系统看门狗定时器MWDT和RTC看门狗定时器RWDT,程序应加入关闭看门狗的代码。

设置TIMGn_Tx_WDTCONFIG0_REG寄存器的TIMGn_Tx_WDT_FLASHBOOT_MOD_EN(位14)为0即可关闭主系统看门狗定时器MWDT。

主系统看门狗MWDT配置寄存器

设置RTC_CNTL_WDTCONFIG0_REG寄存器的RTC_CNTL_WDT_FLASHBOOT_MOD_EN (位10)为0即可关闭RTC看门狗定时器RWDT。

RTC看门狗RWDT配置寄存器

2.2. 初始化GPIO

通常情况下,芯片上电以后GPIO口处于输入状态,控制LED灯需设置成输出。本文使用ESP32-LyraT开发板,LED灯控制引脚为GPIO22,需设置该引脚为GPIO输出功能,并使能输出。

通过设置GPIO交换矩阵配置寄存器GPIO_FUNCn_OUT_SEL_CFG_REG的输出选择为256,使对应的GPIO n引脚为GPIO输出功能。通过设置GPIO输出使能置位寄存器PIO_ENABLE_W1TS_REG对应n位为1,使能GPIO n的输出。通过设置GPIO输出置位寄存器GPIO_OUT_W1TS_REG对应n位为1, GPIO n输出高电平,通过设置GPIO输出清零寄存器GPIO_OUT_W1TC_REG对应n位为1, GPIO n输出低电平。

GPIO交换矩阵输出信号

2.3. 循环闪烁

在while循环里面控制GPIO输出低电平,然后软件延时,再输出高电平,软件延时,如此循环,实现LED灯的闪烁。

2.4. c代码

LED闪烁灯项目路径为D:\esp32\led,完整的LED闪烁灯代码led.c如下:

#define GPIO_LED         22#define REG_WRITE(_r, _v) (*(volatile unsigned int *)(_r)) = (_v)
#define REG_READ(_r)        (*((volatile unsigned int *)(_r)))#define GPIO_OUT_W1TS_REG             0x3FF44008
#define GPIO_OUT_W1TC_REG               0x3FF4400C
#define GPIO_ENABLE_W1TS_REG            0x3FF44024
#define GPIO_FUNC0_OUT_SEL_CFG_REG      0x3FF44530#define RTC_CNTL_WDTWPROTECT_REG      0x3FF480A4
#define RTC_CNTL_WDTCONFIG0_REG         0x3FF4808C
#define TIMG_WDTWPROTECT_REG            0x3FF5F064
#define TIMG_WDTCONFIG0_REG             0x3FF5F048#define RTC_CNTL_WDT_WKEY_VALUE           0x50D83AA1
#define RTC_CNTL_WDT_FLASHBOOT_MOD_EN   10
#define TIMG_WDT_WKEY_VALUE             0x50D83AA1
#define TIMG_WDT_FLASHBOOT_MOD_EN       14void Gpio_SetLevel(unsigned char GpioNum, unsigned char Level)
{if (Level) {REG_WRITE(GPIO_OUT_W1TS_REG, 1 << GpioNum);} else {REG_WRITE(GPIO_OUT_W1TC_REG, 1 << GpioNum);}
}void Gpio_Init(unsigned char GpioNum)
{REG_WRITE(GPIO_ENABLE_W1TS_REG, 1 << GpioNum);// GPIO_FUNCn_OUT_SEL值256选择GPIO_OUT_REG的bit n作为输出值REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG+GpioNum*4, 256);
}void Delay(void)
{volatile int i;for (i=0; i<1000000; i++) {}
}void Wdt_Disable(void)
{unsigned int Value;REG_WRITE(RTC_CNTL_WDTWPROTECT_REG, RTC_CNTL_WDT_WKEY_VALUE);Value = REG_READ(RTC_CNTL_WDTCONFIG0_REG);Value &= ~(1 << RTC_CNTL_WDT_FLASHBOOT_MOD_EN);REG_WRITE(RTC_CNTL_WDTCONFIG0_REG, Value);REG_WRITE(RTC_CNTL_WDTWPROTECT_REG, 0);REG_WRITE(TIMG_WDTWPROTECT_REG, TIMG_WDT_WKEY_VALUE);Value = REG_READ(TIMG_WDTCONFIG0_REG);Value &= ~(1 << TIMG_WDT_FLASHBOOT_MOD_EN);REG_WRITE(TIMG_WDTCONFIG0_REG, Value);REG_WRITE(TIMG_WDTWPROTECT_REG, 0);
}void main(void)
{Wdt_Disable();Gpio_Init(GPIO_LED);while (1) {Gpio_SetLevel(GPIO_LED, 0);Delay();Gpio_SetLevel(GPIO_LED, 1);Delay();}
}

3. 编译运行

ESP32使用Xtensa架构的GNU交叉编译工具,可以使用通用的GUN工具命令编译代码,链接生成可执行代码,也可以生成相应的输出文件,如map文件、反汇编等等用于分析调试。

3.1. 编译

用gcc命令编译led.c,输出led.o文件。

xtensa-esp32-elf-gcc.exe -Os -c led.c -o led.o

代码编译

3.2. 链接

编译生成目标文件后,需要通过链接器链接为一个可执行文件,可以通过链接脚本控制链接过程。链接脚本可以指定程序的入口、各个输入段的内存布局等等,其有特定的语法格式。可以参考ESP-IDF目录\components\bootloader\subproject\main\ld\esp32下bootloader.ld文件,实现ESP32裸机程序的链接脚本。

LED闪烁灯项目的链接脚本led.ld实现如下:

MEMORY
{iram_seg (RWX) :           org = 0x40078000, len = 0x8000  /* 32KB, APP CPU cache */
}/*  Default entry point:  */
ENTRY(main);SECTIONS
{.iram.text :{_stext = .;_text_start = ABSOLUTE(.);*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)*(.fini.literal)*(.fini)*(.gnu.version)/** CPU will try to prefetch up to 16 bytes of* of instructions. This means that any configuration (e.g. MMU, PMS) must allow* safe access to up to 16 bytes after the last real instruction, add* dummy bytes to ensure this*/. += 16;_text_end = ABSOLUTE(.);_etext = .;} > iram_seg
}

链接脚本指定了程序的入口为main,代码段.text加载到0x40078000地址的内存区域执行。由于LED闪烁灯程序比较简单,只有代码段.text以及字面量段.literal,没有全局变量静态变量.bss段和.data段、常数.rodata段,可以不指定未使用段的内存布局。

用ld命令链接目标文件,生成可执行文件led.elf。用-T指定led.ld链接脚本,用-Map指定输出map文件,可以了解程序、数据、IO空间等等的映射关系。

xtensa-esp32-elf-ld.exe -Map led.map -T led.ld led.o -o led.elf

led.map文件

3.3. 反汇编

链接生成可执行elf文件后,可以提取可执行段并生成反汇编代码,用于代码优化、调试等分析。

用objdump命令反汇编led.elf,输出led.dis反汇编文件。

xtensa-esp32-elf-objdump.exe -d led.elf > led.dis

led.dis反汇编代码

ESP32为Xtensa架构,该架构具有很强的可重构性和可拓展性,允许自定义全新指令,用于硬件加速,可提升处理器的运算性能同时又便于软件实现控制。详细的汇编指令可参考Xtensa指令集体系结构(ISA)数据手册。

从反汇编代码可以看出,函数总是以entry指令开头,该指令主要实现两个功能:

1、为函数分配堆栈帧,计算并设置函数的栈指针。

2、根据函数调用指令calln/callxn,将寄存器窗口移动/旋转n个物理寄存器。

例如entry a1, 32,表示为函数分配32个字长的堆栈帧,并计算设置函数栈指针a1的值。当使用call8调用该函数时,寄存器窗口移动/旋转8个物理寄存器。

针对函数的调用,Xtensa架构设计了一种窗口旋转方式的寄存器管理机制,将逻辑寄存器和物理寄存器分开。对于ESP32,有16个逻辑寄存器(指令中的a0~a15),有64个环形物理寄存器,通过WindowBase寄存器,使逻辑寄存器可滑动对应实际的物理寄存器。从而避免寄存器覆盖,减少入栈和出栈的操作。

这一设计机制可让嵌入式软件性能得到明显提高。这是因为使用函数时,往往需要入栈,保存函数使用前的寄存器现场,函数结束时,需要出栈,恢复之前的寄存器现场后返回。入栈和出栈将对外部内存进行读写,而外部内存对于高性能CPU来说是慢速设备,需打断流水线等待访问完成,因此频繁访问外部内存将极大地影响性能。通常高性能的CPU,如Cortex-A系列ARM核,通过增加一级或二级Cache来降低CPU读写等待内存的几率。而Xtensa架构直接减少了函数调用时的入栈和出栈,降低了内存的访问,更大限度地提升了性能。

环形物理寄存器

3.4. 烧录文件

链接生成的可执行文件为elf格式,该文件保存了二进制可执行代码,适用于Unix类系统环境。ESP32无法直接运行elf代码,需要用官方提供的esptool.py工具把elf文件转换成ESP32可烧录的bin文件。该工具在ESP-IDF下 \components\esptool_py\esptool目录,实现从elf文件提取可执行段(如.text、.rodata、.data段),并记录段加载地址及长度,同时加入镜像头,生成bin文件。

用esptool.py命令转换led.elf,输出led.bin文件

esptool.py --chip esp32 elf2image --flash_mode="dio" --flash_freq "40m" --flash_size "4MB" -o led.bin led.elf

用WinHex打开led.bin,并对照ESP-IDF下bootloader工程,可以分析ESP32烧录bin的格式。

ESP32烧录bin格式定义

led.bin二进制数据

ESP32 bin文件总是以0xE9作为起始,镜像头共有24字节,第0x1字节为内存段总数,0x4~0x7字节为入口地址,0x18~0x1B为第一个内存段加载地址,0x1C~0x1F为第一个内存段长度,0x20开始为第一个内存段数据,直到该段最后一个数据。如果有第二段、第三段等更多的内存段,则按第一段相同的格式往下排列。

从led.bin二进制数据可以看出,led.bin只有1个内存段(代码段),入口地址为0x400780F0(main函数入口),第一个内存段(代码段)加载地址为0x40078000,段数据长度为0x0000013C字节。

上电后,ESP32最先运行内部厂商启动代码,该启动代码会从SPI Flash的0x1000偏移处获取镜像头信息,把相应段加载到各自段内存区域,最后根据入口地址跳转执行用户程序。

以led.bin为例, 上电后内部ROM从Flash中加载代码段共0x0000013C字节到0x40078000内存区域,最后跳转到入口地址0x400780F0执行用户程序。

转换生成的led.bin可用esptool.py工具通过串口的方式下载烧录到SPI Flash中。

esptool.py --chip esp32 --baud 115200 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 led.bin

ESP32串口烧录

3.5. 启动运行

内部ROM上电启动,打印信息load: 0x40078000,len:316,即表示从SPI Flash加载代码段316字节到0x40078000内存区域。打印信息entry 0x400780f0,即表示入口地址为0x400780f0,并跳转执行,此时板载的LED灯正常闪烁。

启动信息

4. 总结

ESP-IDF框架使用CMake工具自动化配置构建项目,生成makefile,通过python等脚本给开发者提供一个友好的前端,避免了各种工具命令的直接调用,极大地方便和简化项目的开发,通过本文可以对这些自动化管理工具、脚本等运行机制有一个基本的概念。

5. 附录

本文pdf格式文档

https://pan.baidu.com/s/1uH3kqaucIb251UBbU67EFw?pwd=hgqj

ESP32开发二_LED闪烁灯相关推荐

  1. 物联网开发笔记(31)- 使用Micropython开发ESP32开发板之手机扫二维码远程控制开关灯(1)

    一.目的 我们分3节讲述远程控制.这一节在我们的240x240的oled屏幕上显示二维码,然后用手机扫二维码,从开发板的TCP服务器上返回字符串. 二.环境 ESP32 + 240x240的oled彩 ...

  2. 物联网开发笔记(32)- 使用Micropython开发ESP32开发板之手机扫二维码远程控制开关灯(2)

    一.目的 上一节我们测试了远程控制的环境是好的,这一节在我们的240x240的oled屏幕上显示二维码,然后用手机扫二维码,远程控制LED灯的状态. 二.环境 ESP32 + 240x240的oled ...

  3. 【正点原子FPGA连载】第十二章 呼吸灯实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  4. 【正点原子MP157连载】 第十二章 呼吸灯实验-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  5. 【正点原子FPGA连载】第十二章呼吸灯实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  6. ESP32开发学习 LVGL Littlevgl 创建显示二维码

    生成效果: LVGL除了一些常用UI控件外,还提供了很多第三组件可以集成,例如jpg,bmp,gif,png,二维码. LVGL提供的生成器使用的是 nayuki 的二维码生成器,本文我们就来移植LV ...

  7. ESP32 ESP-IDF开发环境搭建,Windows下基于ESP-IDF | Cmake | VScode插件的 ESP32 开发环境搭建

      之前的一篇博客 Windows上基于ESP-IDF搭建ESP32开发环境 发布后,深受好评.几个月过去了,乐鑫的esp-idf-tools安装工具发生了较大的变化,VsCode插件的功能也愈加完善 ...

  8. 乐鑫Esp32学习之旅② 巧用eclipes编辑器,官方教程在Windows下搭建esp32开发环境,打印 “Hello World”。

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. 爬坑学习新旅程,虚拟机搭建esp32开发环境,打印 " ...

  9. 物联网开发笔记(53)- 使用Micropython开发ESP32开发板之蓝牙BLE通信

    一.目的 这一节我们学习如何使用我们的ESP32开发板通过蓝牙和手机进行通信. 二.环境 ESP32 + 手机(笔者用的小米10) + Thonny IDE 三.蓝牙介绍 这个知识大家自行百度吧,这里 ...

最新文章

  1. ASP.NET 文件操作实例
  2. oracle开发方案,oracle報表開發方案
  3. mongodb集群 java_MongoDB集群JavaAPI插入数据
  4. 判断线段相交 + vector. erase迭代指针 的使用 poj 2653 Pick-up sticks
  5. 错误代码1500什么意思_啊早安打工人是什么梗???
  6. 免费使用3天!52CV GPU云大促,疫情期间做深度学习的首选!
  7. C#中的for,while和do-while循环语句
  8. 穿山甲插屏广告居中_穿山甲跻身广告联盟头部阵营 如何实现增量创新?
  9. IOS Animation-KeyPath值
  10. 《 剑指offer》 目录索引
  11. 计算机应用基础讨论,计算机应用基础讨论
  12. 基于ERP与移动通信平台的商务系统设计
  13. 函数调用中的堆栈平衡
  14. CentOS SSH安装和配置
  15. python制作ppt动画_卧槽,还能这么玩!用Python生成动态PPT
  16. 最新机器视觉研究团队汇总
  17. CDR类CAD制作室内装修平面图
  18. 第6.1.3 vue动态路由初探
  19. 视频教程-系统集成项目管理工程师考试-技术部分-软考
  20. 骑行健身,对这四种慢性病效果立杆见影。

热门文章

  1. matlab rho是什么意思,什么是Rho值
  2. 大数据分析师工资待遇
  3. Arduino Leonardo教程:如何回车,特殊按键定义,DIY超便宜的键盘主控
  4. android仿最新版本微信相册--附源码
  5. 鸿蒙造化塔之秘,鸿蒙之始,天地之秘;生生不息,亘古不易;天为之天,地为之地;生为之续,死为之继;玲珑九转,造化之意;的作者、出处以及相似句子__句子大全...
  6. 1919 Problem A 二叉排序树
  7. Android MediaPlayer+SurfaceView播放视频 (异常处理)
  8. mac node repl_如何使用Node.js REPL
  9. 加密货币为什么有价值?
  10. unity 模型加点击事件