【ESP32-Korvo测评】(3)ESP-Skainet工程的编译
<p> 既然 ESP-Korvo 具有语音处理的硬件,自然少不了官方的软件支持了。这次评测的重点是乐鑫提供的 ESP-Skainet 这个开放软件包,它提供了若干音频和语音识别的API支持,也包含了这个开发板的硬件驱动库。<br />我是从 github 上把 esp-skainet 克隆到本地电脑上的。(也许还有其它更便捷的下载方式)因为 ESP-Skainet 自带了某一个版本的 ESP-IDF, 所以就不用单独下载 IDF 了。什么是 ESP-IDF? 这是 ESP32 的软件开发框架,包括了很多部件,并不是一个单纯的硬件支持库。比如说,IDF 提供了 bootloader, 做了比 STM32 MCU 启动代码多很多的工作;比如说,IDF 底层有 FreeRTOS, 编写一个应用的入口函数是 app_main() 而不是 main(). IDF给我的感觉是更接近操作系统的一个东西,把ESP32启动过程隐藏了,能想得到的BLE、网络支持、文件系统支持它都有。如果不用 IDF 的话对 ESP32 开发就很难下手(乐鑫说:我们把框架都给你搭好了,你就用吧……)<br />
有了 ESP-IDF 就可以开发 ESP32 了,那么用什么编译呢?自然需要 Xtensa 处理器的 gcc 编译工具,可以从乐鑫的网站上下载到。想折腾,自己用 GCC 的源代码编译也是可以的。但只有编译器还不够,因为 IDF 每个工程也需要配置,就绕不开使用使用乐鑫的 Python 脚本和辅助程序。这跟开发 STM32/LPC/ATSAM...都很不一样是不是?也难怪,因为 IDF 实在复杂太多了。如果是 Linux 系统,软件工具的问题就看系统上装的够不够使,如果是 Windows 系统呢?需要一个类似 Linux 下的操作环境,比如 cygwin, msys 然后使用经过移植的软件。乐鑫又给了金点子:不好装吗?我给你准备了一套,下载去就能用咯!<br />
我这次使用的是直接下过来的 esp32_win32_msys2_environment_and_esp2020r2_toolchain-20200601.zip 这个压缩包,里面有 msys2 环境下的软件,和 Xtensa 编译工具。直接解压缩就可以使用了,缺点嘛,就是对 windows xp 不友好,这是 msys2 版本的问题。</p>
<p> 启动 msys2 终端程序(mingw32.exe),试着编译一下 esp-skainet 里面 examples/get_started 这个工程。因为已存在 Makefile, 就直接 make 好啦。</p>
<pre>
<code class="language-makefile">#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := get_started
EXTRA_COMPONENT_DIRS += ../../components/
include $(IDF_PATH)/make/project.mk</code></pre>
<p> 这个 Makefile 包含了 IDF 里面实际用的编译指令,所以要设置一下 IDF 的路径:<br />
<span style="font-family:Courier;"><strong>export IDF_PATH=../../esp-idf</strong></span><br />
然后再 make 就一路编译下去了……居然编译了很久,可不像一个MCU的工程。结束后在生成的 build 目录下可以发现有超过百兆字节的文件。我还没有配置 IDF 组件, 用的 esp-skainet 原先默认的了。如果想配置,那么执行 <strong><span style="font-family:Courier;">make menuconfig</span></strong>, 出现跟配置 linux 内核相仿的字符界面:<br />
</p>
<p> 在编译完成后有如下的提示,说明怎么下载到板子:<br />
<span style="color:#8e44ad;"><em>To flash all build output, run 'make flash' or:<br />
python /d/esp-skainet/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0x1000 /d/esp-skainet/examples/get_started/build/bootloader/bootloader.bin 0x10000 /d/esp-skainet/examples/get_started/build/get_started.bin 0x8000 /d/esp-skainet/examples/get_started/build/partitions.bin</em></span><br />
要烧写的文件有三个:bootloader.bin, get_started.bin, partitions.bin<br />
我使用连着板子的另一台机器来进行下载:<br />
</p>
<p> 复位之后运行正常,串口打印的内容和以前不同了。看来get_started例子启动了语音检测,那么就唤醒试试——可以工作。<br />
</p>
<p> 看看 main.c 里面写了什么。主函数是 app_main()</p>
<pre>
<code class="language-cpp">void app_main()
{
codec_init();
rec_rb = rb_init(BUFFER_PROCESS, 8 * 1024, 1, NULL);
#ifdef CONFIG_ESP32_KORVO_V1_1_BOARD
mase_rb = rb_init(BUFFER_PROCESS, 8 * 1024, 1, NULL);
#else
ns_rb = rb_init(BUFFER_PROCESS, 8 * 1024, 1, NULL);
#endif
agc_rb = rb_init(BUFFER_PROCESS, 8 * 1024, 1, NULL);
model_iface_data_t *model_data = wakenet->create(model_coeff_getter, DET_MODE_90);
model_data_mn = multinet->create(&MULTINET_COEFF, 4000);
xTaskCreatePinnedToCore(&recsrcTask, "rec", 2 * 1024, NULL, 8, NULL, 1);
#ifdef CONFIG_ESP32_KORVO_V1_1_BOARD
xTaskCreatePinnedToCore(&maseTask, "mase", 2 * 1024, NULL, 8, NULL, 1);
#else
xTaskCreatePinnedToCore(&nsTask, "ns", 2 * 1024, NULL, 8, NULL, 1);
#endif
xTaskCreatePinnedToCore(&agcTask, "agc", 2 * 1024, NULL, 8, NULL, 1);
xTaskCreatePinnedToCore(&wakenetTask, "wakenet", 2 * 1024, (void*)model_data, 5, NULL, 0);
printf("-----------awaits to be waken up-----------\n");
}</code></pre>
<p> app_main() 做的是初始化和创建语音处理、识别有关的几个任务,然后就结束了。 在 wakenetTask 任务当中,接收语音并处理:</p>
<pre>
<code class="language-cpp">void wakenetTask(void *arg)
{
model_iface_data_t *model_data = arg;
int frequency = wakenet->get_samp_rate(model_data);
int audio_chunksize = wakenet->get_samp_chunksize(model_data);
int chunk_num = multinet->get_samp_chunknum(model_data_mn);
printf("chunk_num = %d\n", chunk_num);
int16_t *buffer = malloc(audio_chunksize * sizeof(int16_t));
assert(buffer);
int chunks = 0;
int mn_chunks = 0;
bool detect_flag = 0;
while (1) {
rb_read(agc_rb, (uint8_t *)buffer, audio_chunksize * sizeof(int16_t), portMAX_DELAY);
if (detect_flag == 0) {
int r = wakenet->detect(model_data, buffer);
if (r) {
float ms = (chunks * audio_chunksize * 1000.0) / frequency;
printf("%.2f: %s DETECTED.\n", (float)ms / 1000.0, wakenet->get_word_name(model_data, r));
detect_flag = 1;
printf("-----------------LISTENING-----------------\n\n");
rb_reset(rec_rb);
rb_reset(ns_rb);
rb_reset(agc_rb);
}
} else {
int command_id = multinet->detect(model_data_mn, buffer);
mn_chunks++;
if (mn_chunks == chunk_num || command_id > -1) {
mn_chunks = 0;
detect_flag = 0;
if (command_id > -1) {
speech_commands_action(command_id);
} else {
printf("can not recognize any speech commands\n");
}
printf("\n-----------awaits to be waken up-----------\n");
rb_reset(rec_rb);
rb_reset(ns_rb);
rb_reset(agc_rb);
}
}
chunks++;
}
vTaskDelete(NULL);
}</code></pre>
<p><br />
代码和串口打印的内容的确能对应上。后面就可以尝试改代码了。</p>
<p>谢谢分享!</p>
<p>测试工具软件都可以从乐鑫的网站上下载,乐鑫的生态系统好</p>
页:
[1]