使用适用于微控制器和 SparkFun Edge 的 TensorFlow Lite 进行 AI 语音识别

1. 简介

构建内容

在此 Codelab 中,我们将学习如何使用 TensorFlow Lite For MicrocontrollersSparkFun Edge 开发板上运行深度学习模型。我们将使用开发板的内置语音检测模型,该模型使用卷积神经网络来检测通过开发板的两个麦克风说出的“是”和“否”这两个字词。

bf256d403a1821af.gif

微控制器上的机器学习

机器学习可用于创建智能工具,让用户的生活更轻松,例如 Google 助理。但通常,这些体验需要大量计算或资源,包括强大的云服务器或桌面设备。不过,现在可以在微控制器等小型低功耗硬件上运行机器学习推断。

微控制器非常常见、便宜、耗能极低且非常可靠。它们是各种家用设备(例如家用电器、汽车和玩具)的一部分。事实上,每年生产的由微控制器供电的设备大约有 300 亿台。

1360b61fbfa33657.jpeg

通过将机器学习技术引入微型微控制器,我们可以提升数十亿日常使用设备的智能化程度,而无需依赖昂贵的硬件或可靠的网络连接。不妨想象下这样一个世界:智能设备能根据您的日常安排自动调整,智能工业传感器能理解故障与正常操作之间的差异,还有神奇的玩具帮助孩子们通过有趣而愉悦的方式进行学习。

适用于微控制器的 TensorFlow Lite(软件)

358ffdb9eb758b90.png

TensorFlow 是 Google 的开源机器学习框架,用于训练和运行模型。TensorFlow Lite 是一种软件框架,是 TensorFlow 的优化版本,旨在在手机等小型、相对低功耗的设备上运行 TensorFlow 模型。

适用于微控制器的 TensorFlow Lite 是一种软件框架,是 TensorFlow 的优化版本,旨在在微控制器等小型低功耗硬件上运行 TensorFlow 模型。它遵循这些嵌入式环境中所需的限制,即二进制文件大小较小,不需要操作系统支持、任何标准 C/C++ 库或动态内存分配等。

SparkFun Edge(硬件)

SparkFun Edge 是一个基于微控制器的平台:一块电路板上的微型计算机。它具有处理器、内存和 I/O 硬件,可用于向其他设备发送和接收数字信号。它有四个可通过软件控制的 LED,采用您喜爱的 Google 颜色。

aa4493835a2338c6.png

与计算机不同,微控制器不运行操作系统。相反,您编写的程序会直接在硬件上运行。您可以在计算机上编写代码,然后通过一种称为编程器的设备将其下载到微控制器。

微控制器不是功能强大的计算机。它们配备的处理器较小,内存也不大。但由于微控制器的设计尽可能简单,因此耗电量非常低。根据程序的用途,SparkFun Edge 可以使用单个纽扣电池运行数周!

学习内容

  • 在计算机上编译 SparkFun Edge 的示例程序
  • 将程序部署到设备
  • 对程序进行更改并重新部署

所需条件

您需要以下硬件:

您需要以下软件:

  • Git(通过在命令行中运行 git 来检查是否已安装)
  • Python 3(在命令行中运行 python3python --version,检查是否已安装)
  • 适用于 Python 3 的 Pip(有用的 StackOverflow 回答
  • Make 4.2.1 或更高版本(通过在命令行中运行 make --version 来检查是否已安装)
  • SparkFun Serial Basic 驱动程序

2. 设置硬件

SparkFun Edge 微控制器随附一个预安装的二进制文件,可运行语音模型。在用我们自己的版本覆盖此模型之前,我们先运行此模型。

通过以下方式为开发板供电:

  1. 将纽扣电池插入板背面的电池连接器中(电池的“+”一侧朝上)。如果您的开发板已插入电池,请拔出塑料绝缘片,然后推动电池以确保其已完全插入)

25a6cc6b208e8a4e.png

  1. 如果您没有纽扣电池,可以使用 SparkFun USB-C Serial Basic 编程器设备为电路板供电。如需将此设备连接到您的主板,请执行以下步骤:
  • 找到 SparkFun Edge 侧面的六针接头。
  • 将 SparkFun USB-C Serial Basic 插入这些引脚,确保每个设备上标记为“BLK”和“GRN”的引脚正确对齐。
  • 使用 USB-C 数据线将 SparkFun USB-C Serial Basic 与计算机相连。

b140822f0019f92a.png

插入电池或连接 USB 编程器为开发板供电后,开发板将唤醒并开始通过麦克风监听。蓝色指示灯应开始闪烁。

板上的机器学习模型经过训练,能够识别“是”和“否”这两个字词,并检测是否存在语音。它通过点亮彩色 LED 来传达结果。下表显示了每种 LED 颜色所代表的含义:

检测结果

LED 颜色

“是”

黄色

“否”

红色

未知语音

绿色

未检测到语音

没有 LED 亮起

来试试吧!

将板子举到嘴边,然后说几次“是”。您会看到黄色 LED 指示灯闪烁。如果您说“是”后没有任何反应,请尝试以下操作:

  • 将板子放在距离嘴巴约 10 英寸的位置
  • 避免背景噪声过大
  • 快速连续重复说“是”几次(尝试说“是是是”)

3. 设置软件

现在,我们将自行下载、安装并在微控制器上运行语音模型。为此,我们首先下载该计划的源代码以及构建该计划所需的依赖项。该程序是用 C++ 编写的,必须先编译成 二进制文件,然后才能下载到板上。二进制文件是一种包含程序的特殊文件,可由 SparkFun Edge 硬件直接运行。

以下说明适用于 Linux 或 macOS。

下载 TensorFlow 代码库

您可以在 GitHub 上的 TensorFlow 代码库中找到相应代码,具体位置如下:

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro

在计算机上打开一个终端,切换到您通常用来存储编码项目的目录,下载 TensorFlow 代码库并进入创建的目录,如下所示:

cd ~  # change into your home (or any other) directory
git clone --depth 1 https://github.com/tensorflow/tensorflow.git
cd tensorflow

下载 Python 依赖项

我们将使用 Python 3 准备二进制文件并将其刷写到设备。Python 脚本依赖于某些可用的库。运行以下命令以安装这些依赖项:

pip3 install pycrypto pyserial --user

4. 构建并准备二进制文件

我们将构建二进制文件并运行命令,以准备将其下载到设备。

构建二进制文件

如需下载所有必需的依赖项并创建二进制文件,请运行以下命令:

make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge micro_speech_bin

如果 build 成功运行,则输出的最后一行应如下所示:

arm-none-eabi-objcopy tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin -O binary

如需确认二进制文件是否已成功创建,请运行以下命令:

test -f \
tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin && \
 echo "Binary was successfully created" || echo "Binary is missing"

您应该会在控制台中看到 Binary was successfully created!如果您看到 Binary is missing,则表示构建流程存在问题,需要进行调试。

准备二进制文件

二进制文件必须使用加密密钥进行签名,才能部署到设备。现在,我们将运行一些命令来对二进制文件进行签名,以便将其下载到 SparkFun Edge。

输入以下命令,设置一些可用于开发的虚拟加密密钥:

cp tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.2.0/tools/apollo3_scripts/keys_info0.py tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.2.0/tools/apollo3_scripts/keys_info.py

现在,运行以下命令以创建已签名的二进制文件:

python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.2.0/tools/apollo3_scripts/create_cust_image_blob.py \
--bin tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin \
--load-address 0xC000 \
--magic-num 0xCB \
-o main_nonsecure_ota \
--version 0x0

这会创建文件 main_nonsecure_ota.bin。现在,我们将运行另一条命令来创建文件的最终版本,该版本可用于通过我们在下一步中将使用的引导加载程序脚本来刷写设备:

python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.2.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \
--load-address 0x20000 \
--bin main_nonsecure_ota.bin \
-i 6 \
-o main_nonsecure_wire \
--options 0x1

现在,您应该在运行命令的目录中看到一个名为 main_nonsecure_wire.bin 的文件。这是我们将刷入设备的文件。

5. 准备刷写二进制文件

什么是刷机?

SparkFun Edge 将当前正在运行的程序存储在其 512 KB 的闪存中。如果我们想让开发板运行新程序,就必须将新程序发送到开发板,开发板会将其存储在闪存中,覆盖之前保存的所有程序。

此过程称为“刷写”,我们将使用它将程序发送到开发板。

将编程器连接到电路板

为了将新程序下载到开发板,我们将使用 SparkFun USB-C Serial Basic 串行编程器。此设备可让计算机通过 USB 与微控制器通信。

如需将此设备连接到您的主板,请执行以下步骤:

  1. 找到 SparkFun Edge 侧面的六针接头。
  2. 将 SparkFun USB-C Serial Basic 插入这些引脚,确保每个设备上标记为“BLK”和“GRN”的引脚正确对齐。

b140822f0019f92a.png

将编程器连接到计算机

我们将通过 USB 将开发板连接到计算机。如需对开发板进行编程,我们需要知道计算机为该设备指定的名称。最好的方法是列出连接设备之前和之后的所有计算机设备,然后查看哪个设备是新设备。

在通过 USB 连接设备之前,请运行以下命令:

If you are using Linux: ls /dev/tty*
If you are using MacOS: ls /dev/cu*

此命令应会输出一个附加设备的列表,如下所示:

/dev/cu.Bluetooth-Incoming-Port
/dev/cu.MALS
/dev/cu.SOC

现在,将编程器连接到计算机的 USB 端口。再次输入以下命令:

If you are using Linux: ls /dev/tty*
If you are using MacOS: ls /dev/cu*

您应该会在输出中看到一个额外的项目,如下例所示。新商品可能具有不同的名称。此新项是设备的名称。

/dev/cu.Bluetooth-Incoming-Port
/dev/cu.MALS
/dev/cu.SOC
/dev/cu.wchusbserial-1450

首先,我们将创建一个环境变量来标识设备名称:

export DEVICENAME=put your device name here

接下来,我们将创建一个环境变量来指定波特率,即向设备发送数据的速度:

export BAUD_RATE=921600

6. 刷写二进制文件

运行脚本以刷写开发板

为了刷写主板,我们必须将其置于特殊的“引导加载程序”状态,以便准备接收新的二进制文件。然后,我们将运行一个脚本,将二进制文件发送到开发板。

我们来熟悉一下开发板上的以下按钮:

64c620570b9d2f83.png

执行以下步骤来重置并刷写开发板:

  1. 确保开发板已连接到编程器,并且整个设置已通过 USB 连接到计算机。
  2. 开始:按住电路板上标记为 14 的按钮。继续按住,直到第 6 步。
  3. 在仍按住标记为 14 的按钮的情况下,为了将开发板重置为引导加载程序状态,请点击标记为 RST 的按钮以重置开发板。
  4. 在仍按住标记为 14 的按钮的情况下,将以下命令粘贴到终端中,然后按 Enter 键运行该命令(为方便起见,您可以在开始按住该按钮之前将此命令粘贴到终端中,但在到达此步骤之前不要按 Enter 键)
python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.2.0/tools/apollo3_scripts/uart_wired_update.py -b ${BAUD_RATE} ${DEVICENAME} -r 1 -f main_nonsecure_wire.bin -i 6
  1. 在仍按住标记为 14 的按钮的情况下,您现在应该会在屏幕上看到类似以下内容:
Connecting with Corvette over serial port /dev/cu.usbserial-1440...
Sending Hello.
Received response for Hello
Received Status
length =  0x58
version =  0x3
Max Storage =  0x4ffa0
Status =  0x2
State =  0x7
AMInfo =
0x1
0xff2da3ff
0x55fff
0x1
0x49f40003
0xffffffff
[...lots more 0xffffffff...]
Sending OTA Descriptor =  0xfe000
Sending Update Command.
number of updates needed =  1
Sending block of size  0x158b0  from  0x0  to  0x158b0
Sending Data Packet of length  8180
Sending Data Packet of length  8180
[...lots more Sending Data Packet of length  8180...]
  1. 看到 Sending Data Packet of length 8180 后,停止按住板上标记为 14 的按钮(即使继续按住也没关系)。程序将继续在终端上输出行。最终效果如下所示:
[...lots more Sending Data Packet of length  8180...]
Sending Data Packet of length  8180
Sending Data Packet of length  6440
Sending Reset Command.
Done.

如果您看到 Done,则表示刷写成功。如果程序输出以错误结束,请检查是否输出了 Sending Reset Command。如果是,则表示刷写可能已成功完成,尽管出现了错误。

在 Linux 机器上,您可能会遇到 NoResponse Error。这是因为 ch34x 串行驱动程序已与现有串行驱动程序一起安装,可以通过以下方式解决:

第 1 步:重新安装正确版本的 ch34x 库。请确保在安装过程中,设备已从电脑上拔下。

git clone https://github.com/juliagoda/CH341SER.git
cd CH341SER/
make
sudo insmod ch34x.ko
sudo rmmod ch341

第 2 步:插入开发板 USB 并运行以下命令:

dmesg | grep "ch34x"

您应该会看到类似如下的消息:

[ 1299.444724]  ch34x_attach+0x1af/0x280 [ch34x]
[ 1299.445386] usb 2-13.1: ch34x converter now attached to ttyUSB0

如果所用驱动程序不是“ch34x”(例如:ch341),请尝试运行以下命令来停用其他驱动程序:

rmmod <non-ch34x driver name>

拔下并重新插上设备,确保所用驱动程序为“ch34x”。

7. 演示

试用该计划

成功刷写主板后,点击标记为 的按钮

RST 以重启开发板并启动程序。如果蓝色 LED 开始闪烁,则表示刷写成功。如果不是,请向下滚动到下方的“如果此方法不起作用,该怎么办?”部分。

bf256d403a1821af.gif

板上的机器学习模型经过训练,能够识别“是”和“否”这两个字词,并检测是否存在语音。它通过点亮彩色 LED 来传达结果。下表显示了每种 LED 颜色所代表的含义:

检测结果

LED 颜色

“是”

黄色

“否”

红色

未知语音

绿色

未检测到语音

没有 LED 亮起

来试试吧!

将板子举到嘴边,然后说几次“是”。您会看到黄色 LED 指示灯闪烁。如果您说“是”后没有任何反应,请尝试以下操作:

  • 将板子放在距离嘴巴约 10 英寸的位置
  • 避免背景噪声过大
  • 快速连续重复说“是”几次(尝试说“是是是”)

如果不起作用,该怎么办?

以下是一些可能的问题以及如何调试这些问题:

问题:刷写后,所有 LED 均未亮起。

解决方法:尝试按 RST 按钮,或断开并重新连接开发板与编程器的连接。如果上述方法均无效,请尝试再次刷写主板。

问题:蓝色 LED 指示灯亮起,但亮度很低。

解决方案:更换电池,因为电量不足。或者,也可以使用编程器和线缆通过电脑为开发板供电。

8. 读取调试输出(可选)

如果您遇到问题并需要详细调试代码,请查看本部分。为了了解微控制器在代码运行时的情况,您可以通过开发板的串行连接打印调试信息。您可以使用计算机连接到开发板,并显示开发板正在发送的数据。

打开串行连接

默认情况下,我们的 SparkFun Edge 示例代码会记录所有语音指令及其置信度。如需查看开发板的输出,您可以运行以下命令:

screen ${DEVICENAME} 115200

您最初可能会看到类似如下所示的输出:(仅当板在连接后重置时才会显示此输出,否则您可能会开始看到调试信息)

Apollo3 Burst Mode is Available

                               Apollo3 operating in Burst Mode (96MHz)

尝试通过说“是”或“否”来发出一些指令。您应该会看到开发板针对每个命令打印的调试信息:

 Heard yes (202) @65536ms

在上述日志中,yes 是指命令。数字 202 表示系统听到命令的置信度(200 为最小值)。最后,65536ms 是指自微控制器上次重置以来经过的时间。

如需停止查看调试输出,请依次按 Ctrl+AKY 键。

写入调试日志

您可以在刚刚使用的 command_responder.cc 文件中看到记录此信息的代码:

tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/command_responder.cc

如需记录数据,您可以调用 error_reporter->Report() 方法。它支持用于字符串插值的标准 printf 令牌,您可以使用这些令牌在日志中包含重要信息:

error_reporter->Report("Heard %s (%d) @%dms", found_command, score, current_time);

在下一部分中自行更改代码时,此方法应该会派上用场。

9. 扩展代码(可选)

现在,您已了解如何构建和刷写 SparkFun Edge,可以开始使用代码并将其部署到设备,以查看结果。

读取代码

建议您从以下文件开始阅读代码:command_responder.cc.

tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/command_responder.cc

您可以点击此处在 GitHub 上查看该文件。

当检测到语音指令时,系统会调用此文件中的方法 RespondToCommand。现有代码会根据听到的是“是”“否”还是未知命令,开启不同的 LED。以下代码段展示了其工作原理:

if (found_command[0] == 'y') {
  am_hal_gpio_output_set(AM_BSP_GPIO_LED_YELLOW);
}
if (found_command[0] == 'n') {
  am_hal_gpio_output_set(AM_BSP_GPIO_LED_RED);
}
if (found_command[0] == 'u') {
  am_hal_gpio_output_set(AM_BSP_GPIO_LED_GREEN);
}

found_command 实参包含检测到的命令的名称。通过检查第一个字符,这组 if 语句可确定要点亮哪个 LED。

系统会使用多个实参调用 RespondToCommand 方法:

void RespondToCommand(tflite::ErrorReporter* error_reporter,
    int32_t current_time, const char* found_command,
    uint8_t score, bool is_new_command) {
  • error_reporter 用于记录调试信息(稍后会详细介绍)。
  • current_time 表示检测到命令的时间。
  • found_command 会告诉我们检测到了哪个命令。
  • score 用于表明我们检测到命令的信心程度。
  • is_new_command 用于告知我们用户是否是首次听到该指令。

score 是一个介于 0 到 255 之间的整数,表示检测到命令的概率。示例代码仅在得分大于 200 时才将命令视为有效。根据我们的测试,大多数有效命令都位于 200-210 的范围内。

修改代码

SparkFun Edge 板有四个 LED。目前,我们通过闪烁蓝色 LED 指示灯来表明正在进行识别。您可以在 command_responder.cc 文件中看到这一点:

static int count = 0;

// Toggle the blue LED every time an inference is performed.
++count;
if (count & 1) {
  am_hal_gpio_output_set(AM_BSP_GPIO_LED_BLUE);
} else {
  am_hal_gpio_output_clear(AM_BSP_GPIO_LED_BLUE);
}

由于我们有一组四个 LED,因此让我们修改程序,将它们用作给定命令的 score 的直观指示器。得分较低时,只会亮起一个 LED,得分较高时,会亮起多个 LED。

为确保我们能够知道程序正在运行,我们将使红色 LED 持续闪烁,而不是蓝色 LED。相邻的蓝色、绿色和黄色 LED 将用于显示最新 score 的强度。为简单起见,我们只会在说出“yes”这个字词时点亮这些 LED。如果检测到其他字词,LED 会熄灭。

如需进行此项更改,请将 command_responder.cc 文件中的所有代码替换为以下代码段:

#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h"

#include "am_bsp.h"

// This implementation will light up the LEDs on the board in response to different commands.
void RespondToCommand(tflite::ErrorReporter* error_reporter,
                      int32_t current_time, const char* found_command,
                      uint8_t score, bool is_new_command) {
  static bool is_initialized = false;
  if (!is_initialized) {
    // Setup LEDs as outputs
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_RED, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_BLUE, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_GREEN, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_YELLOW, g_AM_HAL_GPIO_OUTPUT_12);
    // Ensure all pins are cleared
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_RED);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_BLUE);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_GREEN);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_YELLOW);
    is_initialized = true;
  }
  static int count = 0;

   // Toggle the red LED every time an inference is performed.
   ++count;
   if (count & 1) {
     am_hal_gpio_output_set(AM_BSP_GPIO_LED_RED);
   } else {
     am_hal_gpio_output_clear(AM_BSP_GPIO_LED_RED);
   }

  if (is_new_command) {
    // Clear the last three LEDs
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_BLUE);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_GREEN);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_YELLOW);
    error_reporter->Report("Heard %s (%d) @%dms", found_command, score,
                           current_time);
    // Only indicate a 'yes'
    if (found_command[0] == 'y') {
      // Always light the blue LED
      am_hal_gpio_output_set(AM_BSP_GPIO_LED_BLUE);
      // Light the other LEDs depending on score
      if (score >= 205) {
        am_hal_gpio_output_set(AM_BSP_GPIO_LED_GREEN);
      }
      if(score >= 210) {
        am_hal_gpio_output_set(AM_BSP_GPIO_LED_YELLOW);
      }
    }
  }
}

如果检测到新命令,is_new_command 将为 true。我们将清除蓝色、绿色和黄色 LED,然后根据 found_commandscore 的值再次点亮它们。

重新构建并刷写

进行代码更改后,请运行构建并准备二进制文件中的所有步骤,以测试代码。

10. 后续步骤

恭喜,您已成功在微控制器上构建了第一个语音检测器!

希望您喜欢这篇简短的介绍,了解如何使用适用于微控制器的 TensorFlow Lite 进行开发。在微控制器上进行深度学习的想法新颖而令人兴奋,我们鼓励您大胆尝试!

参考文档

26699b18f2b199f.png

感谢您的支持,祝您构建愉快!