小智AI机器人 - 代码框架梳理2


前言

本文主要是第2篇关于小智AI的文章,我想在这里和大家一起梳理下小智AI的核心应用部分的代码。

核心应用是只main下面直属的那些文件,通过了解核心应用部分的代码能够帮助我们理解小智AI是怎么进行启动的,以及其整体的工作机制大概是怎么样的。

前面的文章:

小智AI机器人 - 代码框架梳理1

小智AI gitcode地址(这个访问起来比较快):https://gitcode.com/gh_mirrors/xia/xiaozhi-esp32

1.简介

我们这里说的核心应用文件是指main目录下不在其它子文件夹的文件。
请添加图片描述

一般情况下我们是对某个子模块单独创建一个文件夹,所以在子文件夹中的大都是某个模块的内容,而直接在main目录下的为核心内容。

但是这里有个例外是把ota也放到了main目录下,没有单独存放到一个文件夹下方,实际上再创建个ota文件夹,把ota的内容放进去更合适。这里不放进去的原因我觉得有一部分是,作者在制定软件规范时,规定模块与模块之间不能相互调用,但是ota过程中可能会用到一些其它模块的内容,例如联网检查版本,拉取升级文件等等,所以如果把ota放到一个单独的ota模块中的话,因为其会调用其它组件,所以就不符合组件之间的解耦设计规范了。

我们能够看到ota是只有application中会去调用的,但是ota内部的话则是会去调用很多其他组件的东西。
在这里插入图片描述

2. main.cc: 应用程序的入口

请添加图片描述
上面我们可以看到这个文件中,只有一个app_main(),看到这里很多人可能会有疑问,为啥不是main呢?难道代码不是从这里开始运行的吗?

确实不是从这里开始运行的,真正的mainesp_idf的框架中。乐鑫应该是为了方便大家后续的开发,所以专门设计了这样的一个框架,该框架内有个main()函数,它会负责初始化各种所需的硬件和组件。

然后创建一个线程,该线程的回调函数中最终负责调用app_main()

简单来说大家可以把我们的应用也当作一个esp_idf框架中的组件,这个组件叫做应用组件。框架会负责调用到我们的应用组件,调用到之后怎么处理就是我们自己说的算了。

需要擦除
正常
芯片上电
ESP-IDF框架初始化
硬件底层初始化
时钟系统初始化
内存控制器初始化
FreeRTOS初始化
创建主任务
框架主循环
调用app_main
用户初始化
创建事件循环
NVS闪存初始化
检测NVS状态
执行NVS擦除
完成初始化
启动应用主程序
用户业务逻辑

本项目中app_main的作用
请添加图片描述
在这里app_main也只是整个应用的一个过渡,小智的代码整体上采用C++结合面向对象的方式进行开发。

我们可以看到,最终是创建了一个 应用的application对象,并启动这个对象。大家可以理解app_main启动了一个叫做application的小机器人,然后点一下这个小机器人的start,它就开始慢慢的工作了

3. application.h/cc:主应用逻辑实现

application 中放置的是如何创建一个application对象,以及使用该对象所具备的能力。

对于该项目来说,其它的代码都是组件,如何将这些组件配合使用起来,连接起来就是由application对象所负责的。

看到这里估计大家就对application有了一个大概的认识。

接下来我们看一下代码层面的内容

Application 类

public部分
请添加图片描述
public是一个对象所提供的外部引用该对象时,能够调用的接口。
一般情况下我们可以理解为该对象告知到外部自己有哪些能力以及允许外部所使用的能力。

本来在我的设想中,Application 应该属于整个框架的最上层,除了app_main能够调用它,其它组件内的东西应该是不允许调用到它的,所以它对外提供的能力应该只有start就够了。

但是实际上我们看对外提供的能力还是很多的。

我们可以看下除了main到底哪里在调用applicaiton
请添加图片描述
看来不光是application调用了这些组件,这些组件也在使用application提供的能力啊。

private部分
请添加图片描述
这些就是只有对象所实现的函数内部可以调用的,相当于咱们用C写的,在.c文件里面标明static的部分。

Application.c中的功能

application具体有哪些功能在其接口处已经体现出来了。
然后就是它作为应用所承担的,例如初始化组件、进行基础的配置、让整个应用跑起来啊之类的。

我们把Application:Start的注释提取出来能够更加直观的看到其具备哪些功能

Application:Start()
{

		/* Setup the display */
		/* Setup the audio codec */
		/* Wait for the network to be ready */
		// Check for new firmware version or get the MQTT broker address
		// Initialize the protocol
		// Wait for the new version check to finish
		// Enter the main event loop	
		
}

最后的这个*Enter the main event loop还是比较重要的
请添加图片描述
我们可以看到start后最终进入到了MainEventLoop,这里是一个专门等待接收SCHEDULE_EVENT事件的死循环,在有SCHEDULE_EVENT事件触发时会执行tasks列表中的所有的任务,当没有SCHEDULE_EVENT事件触发时,则会阻塞等待。

请添加图片描述
xiaozhi AI的业务,一部分是靠esp_idf的定时器去运行的,这里的定时器实际上是创建了一个专门的线程去执行定时器的调度。一部分是借助application的schdule和background_task的schdule进行一些业务的后台运行

只不过这个线程的创建这里使用的是一个特殊的任务创建函数xTaskCreatePinnedToCore ,它是 ESP-IDF 中用于创建 FreeRTOS 任务并绑定到指定 CPU 核心的核心 API,通过这种任务绑定机制,开发者可充分利用 ESP32 双核特性实现硬实时系统设计。(如果有两个CPU核,这种做法可以提供CPU的利用率和整体运行效率)

请添加图片描述

4. background_task.h/cc:后台任务处理

background + task顾名思义就是后台任务处理。

请添加图片描述
它和application中所实现的schedule实际上有类似的功能,都是后台任务处理或者说异步处理。
不过两者之间仍然有一些差异。


1. 功能定位差异

模块核心功能技术特性
background_task管理低优先级后台任务(如日志写入、传感器数据缓存)异步批量处理,允许任务延迟执行
application处理高实时性核心业务(如语音交互响应、设备控制指令)同步/高优先级响应,硬实时要求

2. 实现机制对比

任务队列与资源管理
  • background_task

    • 队列结构:使用std::list存储任务,通过std::mutexstd::condition_variable实现线程安全。
    • 资源检查:任务添加前检查活跃任务数(默认上限30)和剩余内存(低于10KB触发警告),防止资源耗尽。
    • 代码片段
      void BackgroundTask::Schedule(std::function<void()> callback) {
          std::lock_guard<std::mutex> lock(mutex_);
          if (active_tasks_ >= 30 && heap_caps_get_free_size(MALLOC_CAP_INTERNAL) < 10000) {
              ESP_LOGW(TAG, "资源不足,拒绝新任务");
          }
          main_tasks_.emplace_back([this, cb = std::move(callback)]() { /*...*/ });
      }
      
  • application

    • 事件驱动:通过FreeRTOS的xEventGroupSetBits触发任务执行,结合xEventGroupWaitBits实现非阻塞等待。
    • 实时性保障:任务立即执行,无批量处理延迟,适合毫秒级响应场景(如语音唤醒中断)。
执行模式
维度background_taskapplication
任务触发条件变量唤醒(condition_variable_事件组标志位触发(SCHEDULE_EVENT
执行频率批量处理(每次循环处理所有排队任务)单任务即时执行
优先级低(通常绑定到Core 0,优先级1-3)高(绑定到Core 1,优先级5-24)

3 应用场景示例

background_task 典型用例
  • 日志记录:异步写入SD卡或发送至服务器,避免阻塞主线程。
  • 传感器数据聚合:周期性读取温湿度传感器,缓存数据后批量处理。
  • 网络心跳包维护:定时发送MQTT心跳,防止连接超时。
application 典型用例
  • 语音交互:实时处理麦克风输入,调用ASR(语音识别)和LLM(大模型)生成响应。
  • 设备控制:立即执行“开灯”、“调节温度”等指令,通过GPIO操作硬件。
  • 中断响应:处理物理按钮的单击/长按事件,触发配网或状态切换。

5. settings.h/cc:配置参数管理

请添加图片描述
system_info中提供的接口,主要是围绕管理保存到flash中的各种配置信息所设计的。

有各种获取和擦除配置项信息的功能。例如保存和修改或者删除wifi信息。

6. system_info.h/cc:系统信息管理

请添加图片描述
上面提供的各种接口都是用于获取系统信息的,例如我们可以知道通过调用GetFreeHeapSize()当前还剩下多少堆内存,
通过调用GetMacAddress()获取mac地址,进而生成密钥或者用于发送消息时填充mac地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值