IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【填坑】ESP32 bootloader初探(下) -> 正文阅读

[嵌入式]【填坑】ESP32 bootloader初探(下)

前言

我由于做软件业务的需要,在这几年开发经历中,发现一个现象:各家芯片厂商boot开放的资料较少,不支持或少量支持定制化功能。可能也是需求少吧,毕竟对基线的改动需要的工作量也不小。但这也导致各家芯片的boot开发体验都不是太顺畅,开发者要自己摸索boot的一些定制化实现方案。

这篇内容接着上一篇 => 【填坑】ESP32 bootloader初探(上),看看bootloader里我是怎么搞定外设使用的。


开发工作

bootloader二看

在上篇中,已经初步了解过bootloader的文件结构,一些需要注意的特点,以及究竟如何修改bootloader文件,让自己的功能得到实现。

到这里,来看下具体到boot功能开发中,外设开发有哪些要解决的问题。

我在boot里主要就用到了串口和定时器两种外设。在官方说明里,boot支持的外设操作并不多,不包括我这次要用的这两种。需要自己编写函数,再放入驱动接口在内,给自己调用。

驱动接口上哪找

HAL层 —— 目录\component\hal

  • 在app应用层可以直接调用driver层的接口,比如uart_driver_installuart_param_config来初始化串口;uart_wait_tx_doneuart_tx_chars来发送;timer_init来初始化定时器等等。
  • 在boot中,driver层 (目录\component\driver) 的接口不能使用,即使拿源文件过来放在目录下,配置好CMake,也是会编译报错的。其实进到driver层接口里面去看,driver层还会使用到freertos的各种功能,包括进入退出临界区、heap内存空间申请等等,这些依赖操作系统的调度是无法使用的。
    • 一开始我也并不知道无法使用heap接口,一度尝试着把heap相关的源文件全部移入boot去编译,几番操作下来,错误越来越多。之后在论坛咨询才知道原来这些都是根本无法使用的,其实boot的ROM限制——64K,也不允许移入这么多复杂的外部组件功能。

LL层 —— 目录\components\hal\esp32c3\include\hal

  • 观察hal层的接口,又可以发现内部调用了更加底层的ll层。从目录也可以看出,ll层已经跟具体的型号密切相关了,这也是可以在boot中移植目录下的源文件就编译使用的。

还是建议使用hal层的接口,这一层改完,未来适配不同型号的芯片,更加通用灵活,改动也不必太大。

我的做法是依照了driver的流程,把临界区保护和动态申请空间的接口都去除了,只保留对硬件驱动的流程,这已经对大部分driver接口都适用了。

  • 值得一提的是中断的配置接口esp_intr_alloc_intrstatus不需要像app那样动态申请空间给空闲的中断号,boot里直接固定一个中断号就行了,可以根据该接口调用的位置,看看传入参数是怎么样的,再到接口内部实现去看有哪些流程可以直接干掉的
    • 比如flag参数涉及的流程,默认就都是0在生效;固定cpu核运行的流程也一样,可以干掉那些限制条件不会运行到的内容。

驱动移植遇到的问题

头、源文件找不到

  • 在hal层的源码里,经常看到头文件的包含使用了绝对路径比如:#include “hal/timer.h”。当你直接把这样的源文件加入到boot工程中去编译,它就告诉你hal的目录找不到。这时,你就需要把源文件或头文件直接拿到bootloader_support的目录下面去,修改好CMake的路径编译就没有问题了。

外设功能开发

串口和定时器开发中,都有遇到一些或大或小的问题,记录下来,看到的小伙伴也可以避坑了。

串口

  • 只有一路能自定义使用。ESP32-C3的串口总共有两路,UART0和UART1。UART0在内部已经作为了系统的打印输出口,无法取消掉。毕竟你一取消,那设备运行状态就一无所知了,不利于开发的推进。所以只有一路UART1给客户自己用的。
  • 中断的配置可以挪用单片机MCU的开发经验。回想在MCU上配置中断的步骤,基本就是要清楚外设所在的中断号,配置优先级,编写中断回调函数这些步骤。在这里也可以依照这个思路。
    • 中断号由自己确定一个固定给该外设就可以。优先级其实在这里没有特点的体现,我关注到的就是需要固定一个cpu的核给中断号使用,应该理解为该中断号与cpu挂靠好,cpu会自动去调度了吧。最后就是配置好中断回调的东西。
    • 这些步骤在上面提到的esp_intr_alloc_intrstatus接口里面都能找到,只不过要自己删减不支持和不会运行到的步骤,也不算太复杂。

定时器

  • 注意修改后函数参数的使用。在这里我其实已经根据上述过程移植好了,但运行定时器发现时间到了该定时器运行,设备就死机的情况,我一直很困惑怎么回事。仔细检查几遍自己修改的接口才发现问题。原来是回调函数的参数传入,应该给一个定时器结构体的指针,但我给了NULL,这就导致回调函数使用到原来指针的内容就访问错误了。
  • 看门狗问题。boot流程中其实开启了看门狗功能,而且这个初始化的调用层级比较深,很难注意到该功能已经被启动了。 我是在调式定时器中,总是发现还没到我设定的定时器回调启动时间,设备就自动复位,且时间总是规律的10s左右。我以为是定时器不准确,问了原厂才发现是没有做喂狗导致的。
    • 两种解决方式
      1. 根据esp_flash_encrypt_region的流程,修改自己花费时间长的函数流程,添加喂狗过程
      2. 修改Bootloader config -> Timeout for RTC watchdog (ms) ,改大时间比如 30000
      • 第2种方式适合boot中花费时间比较固定的流程,像我这次的串口交互功能时间不固定,就只能采用第1种方式来解决。
  • 基线要选择好。这个就是我在上篇中提到的情况。旧基线修改完毕定时器所有接口之后,怎么调试定时器功能都运行不起来,换了新基线IDF v4.4.2之后,立马就好了。

退出boot

boot流程完成后,因为我们自己开启了外设的各种配置包括中断号等等的,切记要调用关闭这两个外设的初始化和中断的接口。否则跑到app你再去使用这些外设可能会有异常,别造成更多开发问题。


bootloader三看

终于啊,bootloader外设都单独调试好了,我们把更多的逻辑流程放入进去,跟外设功能结合起来。可算要完工了,编译一下… 出错了。boot编译出来太大,超过了默认分配给boot的32K大小。那没办法,只能再扩充到64K了。

  • 这部分可以回顾上篇 => bootloader初探(上) 的bootloader初看 - 特点,固件大小这一节,也有怎么调整boot大小的内容。

menuconfig

  • 这是改大boot比较关键的步骤,运行make menuconfig,如下图操作:

找到partition table修改的地方
修改offset值

  • menuconfig修改完千万不要忘了烧录工具的partition-table.bin的偏移也要改,如下图。没有改这里,boot烧录进去,运行时会出现boot跳转到app固件出错的log。

烧录工具需要修改


分区表

在这次的boot开发中,我还涉及到存储数据到分区中,app再去用的功能。这就必须使用一个自定义的分区来放数据。接下来记录,新建一个自定义分区,在boot和app中怎么用。

① 怎么创建

  1. 旧基线的分区表改法如下图,可以生效。但新的基线上这么改已经会编译报错了
    • 这个位置的subtype是ota,因为ota这个类别本身就有一定自定义空间大小的能力,而且也够用。

旧基线上的分区表改法

  1. 新基线的分区表改法如下图,这样的改法能在IDF v4.4.2成功生效并在程序中使用起来。

新基线上的分区表改法

② 怎么使用

按照上述方法创建好自定义分区后,怎么在boot和app程序中使用起来?

在app中

  • 读写擦除的接口在partition.c中能找到。初始化我的写法如下:
esp_partition_t *p_partition = NULL;
//初始化接口
bool my_flash_init(void)
{
    p_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, (const char *)"my_partition");
    if (p_partition == NULL || p_partition->size == 0)
    {
        my_DebugPrint("my partition error!");
        return false;
    }
    p_partition->encrypted = 0;
    my_DebugPrint("my partition: type=%d, subtype=0x%02x, address=0x%x, size=0x%x, label=\"%s\", encrypted=%d",
                    p_partition->type,
                    p_partition->subtype,
                    p_partition->address,
                    p_partition->size,
                    p_partition->label,
                    p_partition->encrypted);
    
	return true;
}
//读接口
esp_err_t esp_partition_read(const esp_partition_t* partition,
                             size_t src_offset, void* dst, size_t size);
//写接口
esp_err_t esp_partition_write(const esp_partition_t* partition,
                              size_t dst_offset, const void* src, size_t size);
//擦除接口
esp_err_t esp_partition_erase_range(const esp_partition_t* partition,
                                    size_t offset, size_t size);

在boot中

  • 读写擦除的接口在bootloader_flash.c中能找到。初始化调用方式和我的写法如下:
/*********** bootloader_start.c begin ************/
...
void __attribute__((noreturn)) call_start_cpu0(void)
{
	...
	bootloader_state_t bs = {0};
    int boot_index = select_partition_number(&bs);
	if (boot_index == INVALID_INDEX) {
        bootloader_reset();
    }
	
+++	my_bl_init_flash(bs);
	...
}
...
/*********** bootloader_start.c end ************/

bootloader_state_t my_ps = {0};
//初始化接口
void my_bl_init_flash(bootloader_state_t ps)
{
	ESP_LOGI(LOG_TAG, "my_bl_init_flash: app_offset = 0x%x, app_size = 0x%x\n", ps.factory.offset, ps.factory.size);
	ESP_LOGI(LOG_TAG, "my_bl_init_flash: ota[0] offset = 0x%x, ota[0] size = 0x%x\n", ps.ota[0].offset, ps.ota[0].size);
	memcpy(&my_ps, &ps, sizeof(bootloader_state_t));
}

//读接口
esp_err_t bootloader_flash_read(size_t src_addr, void *dest, size_t size, bool allow_decrypt);
//写接口
esp_err_t bootloader_flash_write(size_t dest_addr, void *src, size_t size, bool write_encrypted);
//擦除接口
esp_err_t bootloader_flash_erase_sector(size_t sector);
  • 我相信看代码就能直观了解如何使用自己开辟的flash分区了吧。

链接文件

关于链接文件.ld的修改,主要决定了boot中能够使用的RAM资源有多少。

我自己在摸索RAM的大小分配上浪费了很多精力,建议先看官方文档 《ESP32-C3 技术参考手册》 的第三章“系统和存储器”章节 => 去看看,里面详细描述了各个存储区的地址范围。

  • 在这里主要就是SRAM的范围, 如下图可以看到,实际能用的大小是384K+400K,对boot开发来说绰绰有余了。只要修改ld文件中的部分内容就可以使用到这些范围的RAM资源。

内部存储区地址映射表格


记录到这,总算接近尾声了。多唠叨几句,开发中碰到问题,需要求助外部时,优先在ESP的论坛请教,官方的回复挺及时的,而且也可靠。

虽然如此,在提问前还是要多思考,多假设验证;求助时要会提有效的问题,而不是一股脑抛出心里想到的各种假设又不去做基本的验证,大家时间都很宝贵的。
最后,善用技术文档、各种手册资料,希望你在开发路上能披荆斩棘,不惧困难。共勉…
(*^▽^*)

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 02:09:57  更:2022-09-15 02:12:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:49:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码