前言

我由于做软件业务的需要,在这几年开发经历中,发现一个现象:各家芯片厂商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,如下图操作:


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


分区表

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

① 怎么创建

  1. 旧基线的分区表改法如下图,可以生效。但新的基线上这么改已经会编译报错了

    • 这个位置的subtype是ota,因为ota这个类别本身就有一定自定义空间大小的能力,而且也够用。

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

    • 这里有我在ESP32论坛提问的帖子,可以直达去看看官方的回复 => 新增分区后编译失败的解决方式

② 怎么使用

按照上述方法创建好自定义分区后,怎么在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资源。

  • 这里出现问题时我也有在ESP32论坛提问,可以直达去看看官方的回复 => Bootloader.ld文件的iram_seg范围是多大

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

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

【填坑】ESP32 bootloader初探(下)相关推荐

  1. 【填坑】ESP32 bootloader初探(上)

    前言 大名鼎鼎的乐鑫ESP8266 WIFI模组你应该不陌生,不用我多说了.在这之后乐鑫还更迭了更多高性能的芯片型号,比如这次我要记录的ESP32-C3,搭载近期很火的RISC-V指令集处理器,支持2 ...

  2. 微信云托管-填坑之旅

    微信云开发开始收费了,一个月20块钱,贼贵,用不起.而按用量收费.资源还能复用的兄弟产品微信云托管还像还行.所以就有了一场填坑旅行. windows10下安装docker之坑 在本地开发,需要使用do ...

  3. 【过程改进】 windows下jenkins常见问题填坑

    没有什么高深的东西,1 2天的时间大多数人都能自己摸索出来,这里将自己遇到过的问题分享出来避免其他同学再一次挖坑. 目录 1. 主从节点 2. Nuget自动包还原 3. powershell部署 4 ...

  4. windows下jenkins常见问题填坑

    没有什么高深的东西,1 2天的时间大多数人都能自己摸索出来,这里将自己遇到过的问题分享出来避免其他同学再一次挖坑. 目录 1. 主从节点 2. Nuget自动包还原 3. powershell部署 4 ...

  5. Windows环境下安装React Native开发环境----记一次填坑过程

    前言 集成React Native开发环境遇到各个坑,调试了很久出现过找不到设备,百度上个别人的资料有误被绕来绕去耽误了很多时间,下载慢等待时间过长等问题,最后多方查阅资料,电脑重启等操作终于部署好了 ...

  6. 一个机械研究生在计算机与机械之间的徘徊与思考-(下)之填坑

    现已研三(上 ),自问自答一下当初问机械研究生(智能制造方向)到底该学什么,主力该放在学术研究上还是系统开发上?先说现状,已签工作(华为IE工程师),也拿了国奖.研一上发一篇小论文,研一下一篇,研二上 ...

  7. 填坑笔记-linux下安装cadence

    在Linux下有很多spice的仿真软件, 我之前用过ngspice, 也还在折腾. cadence也是一个比较优秀的软件,主要是有Linux版本支持,我于是又来了. windows之所以那么好用,给 ...

  8. 8.二叉树的下一个节点(待填坑)

    题目描述 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回.注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针. 思路1:窒息了,对树进行了一波分析,思路是对的,但是代 ...

  9. 《Getting Started with D3》填坑之旅(六):第三章(下)

    Chapter 3. Scales, Axes and Lines(比例尺.坐标轴与线) (接上篇:<Getting Started with D3>填坑之旅(五):第三章(上)) 示例2 ...

最新文章

  1. 如何在windows7上安装启明星系统。
  2. jsp Request获取url信息的各种方法比较
  3. 约翰诺曼超级计算机研究中心,第433章 拉泽尔松教授的决定_学霸的黑科技系统_晨星LL作品_du00...
  4. Android笔记 get方式提交数据到服务器 避免乱码 demo
  5. DPDK在Linux用户级执行环境中执行EAL
  6. JavaScript(一)js简单介绍
  7. JQUERY1.9学习笔记 之内容过滤器(三) has选择器
  8. 从零开始,跟我一起做jblog项目(一)引言
  9. MarkDown 语法手册
  10. 如何使用MISRA改进嵌入式编程
  11. MariaDB 安装
  12. Android布局之表格布局
  13. 2022.02.19四座楼苹果园
  14. unity学习笔记(二)—— 制作第一个小游戏
  15. MySQL系列之日志汇总:redo log、undo log、binlog、errorlog、slow query log、general log、relay log
  16. 华为matebook13安装折腾Debian11全过程
  17. 关于Google地图路线偏移的问题
  18. Linux—教你两个方法轻松找回root密码
  19. 【转】名企HR教你如何过网申
  20. CSDN高校俱乐部“名师高校行”——贵州遵义站

热门文章

  1. python中raise和raise e区别_python raise和assert的区别
  2. MFC学习(24)线程锁的概念函数EnterCriticalSection和LeaveCriticalSection的用法
  3. dmp如何导入mysql_如何导入MySQL数据库
  4. 服务器操作系统的维护,服务器操作系统维护方案
  5. 5 个最佳的 Linux 桌面环境
  6. RT-thread4.1.0更新后文件系统fal组件修改
  7. C#.Net 设计模式学习笔记之结构型 (三)
  8. WAF nginx反向代理和透明代理
  9. 【Mitmproxy】Mac + Python + mitmproxy透明代理配置,拦截所有网络请求
  10. MySQL数据库行级锁之行锁