如何用你的声音玩像飞翔的鸟一样的游戏!
Quote from Matoo robot on 2024年7月1日, pm4:26通过使用 AI 为您进行声音分类来赢得时间
项目介绍
欢迎阅读本教程,了解如何使用 Arduino R4 WIFI 板创建类似 Flappy Bird 的游戏。在本指南中,我们将介绍硬件设置以及与代码相关的所有内容,以便您可以重现它。
以下是该项目的主要部分:
- 使用 LED 矩阵
- 使用麦克风
- 自动对要播放的声音进行分类(使用 AI!!
- 处理多个循环()
- 处理上下移动的球员
- 处理一堵有洞的墙向玩家移动并发生碰撞
- 跟踪分数
- 随着游戏的进行增加难度
重现游戏需要步骤 1、7 和 8。此处的其他步骤将解释代码的每个部分。
第 1 步:设置
首先,我们需要将麦克风连接到Arduino板。
使用跳线连接:
- OUT(麦克风)到 A0(板)
- GND 到板上的一个 GND
- VCC 至 3.3v
确保您有一根 USB 数据线将主板连接到 PC。
在Arduino IDE中:
确保您选择了正确的 COM 端口:工具>端口,然后选择正确的端口。
选择正确的电路板:
- Arduino Renesas UNO R4 > 开发板的工具>板 > Arduino UNO R4 WIFI
- 如果没有找到它,请单击“Tools > Boards > Boards Manager...”,查找 UNO R4 并安装软件包
选择所需的库,Sketch > Include Library,我们需要包括以下库:
- Arduino图形
- Arduino_LED_Matrix
- 线
- 调度
您还可以下载您没有的库:草图>包括库>管理库...
在本教程的后面(步骤 8)中,我们还需要添加 NanoEdge AI 库来自行处理声音,而不是手动处理。
第 2 步:麦克风
要从麦克风获取声音,我们不需要太多:
我们需要定义我们使用的引脚并调用函数 analogRead()
int const AMP_PIN = A0; // Preamp output pin connected to A0 void setup() { ... } void loop() { ... /* Get a sound sample */ static uint16_t sample = 0; //stock values sample = analogRead(AMP_PIN); ... }
此示例演示如何从麦克风获取单个值,但在本项目中,我们将使用样本缓冲区。更多内容请见第 7 步。
第 3 步:LED 矩阵
为了玩游戏,我们使用板上的 LED 矩阵。
基本上,我们有一个表示矩阵的数组,我们在其中放置 0(LED 关闭)和 1(LED 打开)。
以下是使用它所需的简单示例:
/* Libraries needed */ #include "ArduinoGraphics.h" #include "Arduino_LED_Matrix.h" /* Defines */ #define HEIGHT 8 #define WIDTH 12 /* Object */ ArduinoLEDMatrix matrix; /* Declare matrix to display */ byte frame[8][12] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; void setup() { ... /* requiered to use the LED matrix */ matrix.begin(); ... } void loop() { ... /* Change value of the LED matrix */ frame[some x][some y] = 1 /* Update display when needed */ matrix.renderBitmap(frame, HEIGHT, WIDTH); ... }
根据我们想要显示的内容,有多种方法可以做到这一点。
在这种情况下,我们计算玩家和墙壁的位置,并在 while 循环中更新上面的矩阵。
这是文档: $ https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/ $
我们还使用矩阵在游戏开始时显示文本,以下是我们如何实现它(阅读文档了解更多信息):
void introduction_message() { matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); matrix.textScrollSpeed(50); // add the text const char text[] = " Flappy bird! "; matrix.textFont(Font_5x7); matrix.beginText(0, 1, 0xFFFFFF); matrix.println(text); matrix.endText(SCROLL_LEFT); matrix.endDraw(); }
第 4 步:循环
为了获得更好的体验,我们使用多个循环:
- 一个处理墙的生成、移动、与玩家的碰撞和分数
- 第二个处理玩家位置和声音检测以使其移动
两个循环允许我们几乎独立于墙壁移动玩家,我们可以多次移动它,而墙壁只能移动一次。
文档: $ https://www.arduino.cc/reference/en/libraries/scheduler/ $
要使用它,我们只需要导入调度程序库并在设置中创建第二个循环:
/* Libraries needed */ #include <Scheduler.h> void setup() { ... /* start a second loop */ Scheduler.startLoop(loop2); ... } void loop() { ... /* Code related to the wall */ ... } void loop2() { ... /* Code related the player */ ... }
第 5 步:墙壁循环
以下是 Wall 循环中的代码:
void loop() { if (game_ongoing) { /* Clean the last column of the matrix */ for (uint8_t y = 0; y < HEIGHT; y++) { frame[y][WIDTH - 1] = 0; frame[y][WIDTH - 2] = 0; } /* Set wall position on the matrix using random */ wall_start_pix = random(0, 5); /* Move the wall through matrix */ do { if (wall_move) { for (uint8_t y = 0; y < HEIGHT; y++) { frame[y][wall_pos_x] = (y >= wall_start_pix && y < wall_start_pix + wall_size) ? 0 : 1; if (wall_pos_x > 1) { frame[y][wall_pos_x - 2] = 0; } } wall_pos_x++; } wall_move = !wall_move; /* Update display */ matrix.renderBitmap(frame, HEIGHT, WIDTH); /* Adapt level */ adapt_game_level(); // function in the full code /* Check if player touch the wall */ if (frame[player_y][player_x] == frame[player_y][player_x - 1]) { game_ongoing = 0; //stop the game reset_global_variables(); //function in the full code return; } } while (wall_pos_x < WIDTH); /* Increment score counter */ score++; /* Reset wall position */ wall_pos_x = 0; } }
代码是不言自明的,但这是完成的:
- 墙从左向右移动。因为我们正在循环,所以我们需要在到达边缘时删除墙,然后再制作新的墙(可以在循环结束时完成,但我们选择在这里)
- 我们随机确定墙上孔的位置。墙的大小为 3,矩阵的高度为 8,因此我们有 5 个可能的起点。
- 我们将墙移到屏幕的右端。这意味着将 1 放在新位置,将 0 放在墙的最后一个位置
- 更新矩阵
- 根据分数增加难度
- 检查碰撞并结束游戏 + 根据需要显示分数
第 6 步:播放器循环
这是播放器的循环:
void loop2() { if (game_ongoing) { /* make a classification only if we detect a sound */ if (analogRead(AMP_PIN) > 400) { get_microphone_data(); //function in the full code neai_classification(neai_buffer, output_class_buffer, &id_class); /* Player next movement based on class detected */ if (id_class == 2) { player_move = -1; //up } else if (id_class == 3) { player_move = 1; //down } } else { //We don't want to repeat the same movement until we detect something else id_class = 0; player_move = 0; } //move the player move_player(); /* Clean neai buffer */ memset(neai_buffer, 0.0, AXIS * SENSOR_SAMPLES * sizeof(float)); } delay(10); }
我们在这里所做的也很简单:
- 如果检测到声音(400 是我在什么都没发生时得到的值),我们会做一些事情
- 我们从麦克风收集声音
- 我们将这种声音分类:要么意味着向上移动,要么意味着向下移动播放器(见下一步)
- 根据职业,我们相应地移动玩家
第 7 步:声音分类
为了玩游戏,我们发出两种声音,一种是向上,一种是向下。
为了对声音进行分类,我们可以手动完成。我不确定如何,但如果我们计算 FFT 并设置阈值,这应该是可能的。
取而代之的是,我们使用 NanoEdge AI Studio 自动完成,并在我们的代码中包含一个非常小的 AI 模型。
- 安装 NanoEdge AI Studio: https://stm32ai.st.com/download-nanoedgeai/ 美元
创建N类分类项目:
- 选择麦克风 1 轴作为传感器
- 选择Arduino UNO R4 WIFI作为目标
收集数据:
- 下面附有 Arduino IDE 的 Flash 代码 (sound_datalogger.ino)
- 在 NanoEdge 的 signals 选项卡中,单击 add Signal,然后单击 serial
- 首先收集多个声音示例以“向上”播放(大约 100 个示例)
- 然后是另一个声音的多个示例来播放“向下”
启动基准测试并获得良好的准确性
编译库
有关如何使用 NanoEdge AI Studio 和 Arduino 的更多详细信息,分步教程: $ https://wiki.st.com/stm32mcu/wiki/AI:How_to_create_Arduino_Rock-Paper-Scissors_game_using_NanoEdge_AI_Studio $
注意:
随附的代码仅包含一个功能,用于收集声音的下采样缓冲区并通过串行发送它们。
我们得到一个下采样缓冲区,因为采集的基本频率非常快,所以我们会得到一个代表几毫秒的缓冲区。通过对它进行缩减采样,它代表了现实生活中的更多时间。
为此,我们只需读取麦克风发送的所有值,但我们只保留 1/8 值。
第 8 步:添加分类
现在我们有了分类库,我们需要将其添加到我们的Arduino代码中:
- 打开得到的.zip,有一个Arduino文件夹,里面有另一个zip
- 在Arduino IDE中导入库:Sketch > Include library > Add .ZIP library...,然后选择Arduino文件夹中的.zip
然后在我们的代码中,要使用 NanoEdge 分类,我们只需要一些代码,如下例所示:
- 我们包括 NanoEdge 库
- 我们初始化库
- 我们从麦克风收集数据
- 我们进行分类
#include "NanoEdgeAI.h" #include "knowledge.h" /* NanoEdgeAI variables part */ uint8_t neai_code = 0; uint16_t id_class = 0; // Point to id class (see argument of neai_classification fct) float output_class_buffer[CLASS_NUMBER]; // Buffer of class probabilities const char *id2class[CLASS_NUMBER + 1] = { // Buffer for mapping class id to class name "unknown", "up", "down", }; static float neai_buffer[SENSOR_SAMPLES * AXIS] = {0.0}; void setup() { ... /* initialize NanoEdge Library */ neai_code = neai_classification_init(knowledge); if (neai_code != NEAI_OK) { Serial.print("Not supported board.n"); } ... } void loop() { ... /* make a classification */ get_microphone_data(); neai_classification(neai_buffer, output_class_buffer, &id_class); /* then depending on the class in id_class, we play up, down or wathever */ ... }
警告:
您需要检查 Arduino 代码中的 id2class 变量是否与我们之前导入的库中的 NanoEdgeAI.h 文件中的变量相同:
在 document/arduino/libraries/nanoedge/src/NanoEdgeAI.h 中(在文件末尾)
const char *id2class[CLASS_NUMBER + 1] = { "unknown", "up", "down", };
第 9 步:最终项目
该项目的完整代码如下 (arduino_demo_flappy_bird_sound.ino)
您需要按照步骤 1 和步骤 7 和 8 使其正常工作!
这个想法是展示如何使用人工智能更快地实现项目。无需手动进行声音分类,而是自动完成,使用 NanoEdge,只需几分钟即可找到模型并对其进行集成。
您可以以此示例为例,并使用相同的方法来争取时间并实现简单的 POC,如果手动完成,可能会花费更多时间。
感谢您阅读:)
代码:声音数据记录仪:
#include <Wire.h> #define SENSOR_SAMPLES 512 static float neai_buffer[SENSOR_SAMPLES] = {0.0}; static uint16_t neai_ptr = 0; int const AMP_PIN = A0; // Preamp output pin connected to A0 /* Prototypes ----------------------------------------------------------*/ void get_microphone_data(void); void setup() { // put your setup code here, to run once: Serial.begin(115200); } void loop() { // put your main code here, to run repeatedly: get_microphone_data(); } /* Functions declaration ----------------------------------------------------------*/ void get_microphone_data() { static uint16_t temp = 0; if (analogRead(AMP_PIN) > 400){ int sub = 0; while(neai_ptr < SENSOR_SAMPLES) { if (sub > 8){ /* Fill neai buffer with new accel data */ neai_buffer[neai_ptr] = analogRead(AMP_PIN); /* Increment neai pointer */ neai_ptr++; sub = 0; } else{ temp = analogRead(AMP_PIN); } sub ++; } for(uint16_t i = 0; i < SENSOR_SAMPLES; i++) { Serial.print(neai_buffer[i]); Serial.print(" "); } Serial.print("n"); /* Reset pointer */ neai_ptr = 0; } }
主代码:
/* Libraries ----------------------------------------------------------*/ #include "ArduinoGraphics.h" #include "Arduino_LED_Matrix.h" #include <Wire.h> #include "NanoEdgeAI.h" #include "knowledge.h" #include <Scheduler.h> /* Defines ----------------------------------------------------------*/ /* Matrix part */ #define HEIGHT 8 #define WIDTH 12 #define PLAYER_X 10 //initial player position #define PLAYER_Y 4 /* NEAI part */ #define SENSOR_SAMPLES 512 #define AXIS 1 /* Prototypes ----------------------------------------------------------*/ void introduction_message(void); void get_microphone_data(void); void adapt_game_level(void); void print_score(uint16_t game_score); void reset_global_variables(void); /* Objects ----------------------------------------------------------*/ ArduinoLEDMatrix matrix; /* Global variables ----------------------------------------------------------*/ int game_ongoing = 0; static byte wall_move = false; static int8_t player_move = 0; static uint8_t player_x = PLAYER_X, player_y = PLAYER_Y; static uint8_t wall_start_pix = 0, wall_pos_x = 0, wall_size = 3; static uint16_t score = 0, neai_ptr = 0, time_to_wait = 50; static float neai_buffer[SENSOR_SAMPLES * AXIS] = {0.0}; int const AMP_PIN = A0; // Preamp output pin connected to A0 /* NanoEdgeAI variables part */ uint8_t neai_code = 0; uint16_t id_class = 0; // Point to id class (see argument of neai_classification fct) float output_class_buffer[CLASS_NUMBER]; // Buffer of class probabilities const char *id2class[CLASS_NUMBER + 1] = { // Buffer for mapping class id to class name "unknown", "up", "down", }; /* Declare matrix to display */ byte frame[8][12] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; /* Setup function ----------------------------------------------------------*/ void setup() { Serial.begin(115200); Scheduler.startLoop(loop2); //first loop is wall movement, second is player matrix.begin(); introduction_message(); //display a message when starting /* Initialize NanoEdgeAI AI */ neai_code = neai_classification_init(knowledge); if (neai_code != NEAI_OK) { Serial.print("Not supported board.n"); } else { game_ongoing = 1; //launch game if nanoedge working correctly } } /* Infinite loop ----------------------------------------------------------*/ void loop() { if (game_ongoing) { /* Clean the last column of the matrix */ for (uint8_t y = 0; y < HEIGHT; y++) { frame[y][WIDTH - 1] = 0; frame[y][WIDTH - 2] = 0; } /* Set wall position on the matrix using random */ wall_start_pix = random(0, 5); /* Move the wall through matrix */ do { if (wall_move) { for (uint8_t y = 0; y < HEIGHT; y++) { frame[y][wall_pos_x] = (y >= wall_start_pix && y < wall_start_pix + wall_size) ? 0 : 1; if (wall_pos_x > 1) { frame[y][wall_pos_x - 2] = 0; } } wall_pos_x++; } wall_move = !wall_move; /* Update display */ matrix.renderBitmap(frame, HEIGHT, WIDTH); /* Adapt level */ adapt_game_level(); /* Check if player touch the wall */ if (frame[player_y][player_x] == frame[player_y][player_x - 1]) { game_ongoing = 0; //stop the game reset_global_variables(); return; } } while (wall_pos_x < WIDTH); /* Increment score counter */ score++; /* Reset wall position */ wall_pos_x = 0; } } void loop2() { if (game_ongoing) { /* make a classification only if we detect a sound */ if (analogRead(AMP_PIN) > 400) { get_microphone_data(); neai_classification(neai_buffer, output_class_buffer, &id_class); /* Player next movement based on class detected */ if (id_class == 1) { player_move = -1; //up } else if (id_class == 2) { player_move = 1; //down } } else { //We don't want to repeat the same movement until we detect something else id_class = 0; player_move = 0; } //move the player move_player(); /* Clean neai buffer */ memset(neai_buffer, 0.0, AXIS * SENSOR_SAMPLES * sizeof(float)); } delay(1); } /* Functions declaration ----------------------------------------------------------*/ void introduction_message() { matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); matrix.textScrollSpeed(50); // add the text const char text[] = " Flappy bird! "; matrix.textFont(Font_5x7); matrix.beginText(0, 1, 0xFFFFFF); matrix.println(text); matrix.endText(SCROLL_LEFT); matrix.endDraw(); } void move_player() { /* Move the player and check if it stays in the screen */ if (player_y + player_move >= 0 && player_y + player_move < HEIGHT) { /* Clear player last position */ frame[player_y][player_x] = 0; /* Change player y coordinate */ player_y += player_move; } /* Display player in matrix */ frame[player_y][player_x] = 1; /* Update display */ matrix.renderBitmap(frame, HEIGHT, WIDTH); } /* function to get a single downsampled sample from sensor We get every values but keep only one every eight values because the model was train with data like this. It permits to listen for a longer period of time and get a better accuracy */ void get_microphone_data() { static uint16_t temp = 0; //stock values int sub = 0; //increment to downsample //while the buffer is not full while (neai_ptr < SENSOR_SAMPLES) { //if it is the eighth value if (sub > 8) { /* Fill neai buffer with new accel data */ neai_buffer[neai_ptr] = analogRead(AMP_PIN); /* Increment neai pointer */ neai_ptr++; sub = 0; //reset increment } else { temp = analogRead(AMP_PIN); } sub ++; } for(uint16_t i = 0; i < SENSOR_SAMPLES; i++) { Serial.print(neai_buffer[i]); Serial.print(" "); } Serial.print("n"); neai_ptr = 0; } void adapt_game_level() { /* Adapt speed & hole size */ if (score < 5) { time_to_wait = 50; } else if (score >= 5 && score < 10) { time_to_wait = 40; } else if (score >= 10 && score < 15) { time_to_wait = 30; } else if (score >= 15 && score < 20) { wall_size = 2; } else { wall_size = 1; } delay(time_to_wait); } void print_score(uint16_t game_score) { uint8_t text_pos_x = (game_score < 10) ? 5 : 3; matrix.clear(); matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); char text[3]; itoa(game_score, text, 10); matrix.textFont(Font_4x6); matrix.beginText(text_pos_x, 1, 0xFFFFFF); matrix.println(text); matrix.endText(); matrix.endDraw(); } void reset_global_variables() { memset(frame, 0, WIDTH * HEIGHT * sizeof(byte)); player_x = PLAYER_X; player_y = PLAYER_Y; print_score(score); /* Reset score after loosing the party */ score = 0; /* Reset wall position */ wall_pos_x = 0; /* Reset wall size */ wall_size = 3; /* Reset delay */ time_to_wait = 50; delay(1000); game_ongoing = 1; }
通过使用 AI 为您进行声音分类来赢得时间
项目介绍
欢迎阅读本教程,了解如何使用 Arduino R4 WIFI 板创建类似 Flappy Bird 的游戏。在本指南中,我们将介绍硬件设置以及与代码相关的所有内容,以便您可以重现它。
以下是该项目的主要部分:
- 使用 LED 矩阵
- 使用麦克风
- 自动对要播放的声音进行分类(使用 AI!!
- 处理多个循环()
- 处理上下移动的球员
- 处理一堵有洞的墙向玩家移动并发生碰撞
- 跟踪分数
- 随着游戏的进行增加难度
重现游戏需要步骤 1、7 和 8。此处的其他步骤将解释代码的每个部分。
第 1 步:设置
首先,我们需要将麦克风连接到Arduino板。
使用跳线连接:
- OUT(麦克风)到 A0(板)
- GND 到板上的一个 GND
- VCC 至 3.3v
确保您有一根 USB 数据线将主板连接到 PC。
在Arduino IDE中:
确保您选择了正确的 COM 端口:工具>端口,然后选择正确的端口。
选择正确的电路板:
- Arduino Renesas UNO R4 > 开发板的工具>板 > Arduino UNO R4 WIFI
- 如果没有找到它,请单击“Tools > Boards > Boards Manager...”,查找 UNO R4 并安装软件包
选择所需的库,Sketch > Include Library,我们需要包括以下库:
- Arduino图形
- Arduino_LED_Matrix
- 线
- 调度
您还可以下载您没有的库:草图>包括库>管理库...
在本教程的后面(步骤 8)中,我们还需要添加 NanoEdge AI 库来自行处理声音,而不是手动处理。
第 2 步:麦克风
要从麦克风获取声音,我们不需要太多:
我们需要定义我们使用的引脚并调用函数 analogRead()
int const AMP_PIN = A0; // Preamp output pin connected to A0
void setup() {
...
}
void loop() {
...
/* Get a sound sample */
static uint16_t sample = 0; //stock values
sample = analogRead(AMP_PIN);
...
}
此示例演示如何从麦克风获取单个值,但在本项目中,我们将使用样本缓冲区。更多内容请见第 7 步。
第 3 步:LED 矩阵
为了玩游戏,我们使用板上的 LED 矩阵。
基本上,我们有一个表示矩阵的数组,我们在其中放置 0(LED 关闭)和 1(LED 打开)。
以下是使用它所需的简单示例:
/* Libraries needed */
#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"
/* Defines */
#define HEIGHT 8
#define WIDTH 12
/* Object */
ArduinoLEDMatrix matrix;
/* Declare matrix to display */
byte frame[8][12] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
void setup() {
...
/* requiered to use the LED matrix */
matrix.begin();
...
}
void loop() {
...
/* Change value of the LED matrix */
frame[some x][some y] = 1
/* Update display when needed */
matrix.renderBitmap(frame, HEIGHT, WIDTH);
...
}
根据我们想要显示的内容,有多种方法可以做到这一点。
在这种情况下,我们计算玩家和墙壁的位置,并在 while 循环中更新上面的矩阵。
这是文档: $ https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix/ $
我们还使用矩阵在游戏开始时显示文本,以下是我们如何实现它(阅读文档了解更多信息):
void introduction_message()
{
matrix.beginDraw();
matrix.stroke(0xFFFFFFFF);
matrix.textScrollSpeed(50);
// add the text
const char text[] = " Flappy bird! ";
matrix.textFont(Font_5x7);
matrix.beginText(0, 1, 0xFFFFFF);
matrix.println(text);
matrix.endText(SCROLL_LEFT);
matrix.endDraw();
}
第 4 步:循环
为了获得更好的体验,我们使用多个循环:
- 一个处理墙的生成、移动、与玩家的碰撞和分数
- 第二个处理玩家位置和声音检测以使其移动
两个循环允许我们几乎独立于墙壁移动玩家,我们可以多次移动它,而墙壁只能移动一次。
文档: $ https://www.arduino.cc/reference/en/libraries/scheduler/ $
要使用它,我们只需要导入调度程序库并在设置中创建第二个循环:
/* Libraries needed */
#include <Scheduler.h>
void setup() {
...
/* start a second loop */
Scheduler.startLoop(loop2);
...
}
void loop() {
...
/* Code related to the wall */
...
}
void loop2() {
...
/* Code related the player */
...
}
第 5 步:墙壁循环
以下是 Wall 循环中的代码:
void loop() {
if (game_ongoing) {
/* Clean the last column of the matrix */
for (uint8_t y = 0; y < HEIGHT; y++) {
frame[y][WIDTH - 1] = 0;
frame[y][WIDTH - 2] = 0;
}
/* Set wall position on the matrix using random */
wall_start_pix = random(0, 5);
/* Move the wall through matrix */
do {
if (wall_move) {
for (uint8_t y = 0; y < HEIGHT; y++) {
frame[y][wall_pos_x] = (y >= wall_start_pix && y < wall_start_pix + wall_size) ? 0 : 1;
if (wall_pos_x > 1) {
frame[y][wall_pos_x - 2] = 0;
}
}
wall_pos_x++;
}
wall_move = !wall_move;
/* Update display */
matrix.renderBitmap(frame, HEIGHT, WIDTH);
/* Adapt level */
adapt_game_level(); // function in the full code
/* Check if player touch the wall */
if (frame[player_y][player_x] == frame[player_y][player_x - 1]) {
game_ongoing = 0; //stop the game
reset_global_variables(); //function in the full code
return;
}
} while (wall_pos_x < WIDTH);
/* Increment score counter */
score++;
/* Reset wall position */
wall_pos_x = 0;
}
}
代码是不言自明的,但这是完成的:
- 墙从左向右移动。因为我们正在循环,所以我们需要在到达边缘时删除墙,然后再制作新的墙(可以在循环结束时完成,但我们选择在这里)
- 我们随机确定墙上孔的位置。墙的大小为 3,矩阵的高度为 8,因此我们有 5 个可能的起点。
- 我们将墙移到屏幕的右端。这意味着将 1 放在新位置,将 0 放在墙的最后一个位置
- 更新矩阵
- 根据分数增加难度
- 检查碰撞并结束游戏 + 根据需要显示分数
第 6 步:播放器循环
这是播放器的循环:
void loop2() {
if (game_ongoing) {
/* make a classification only if we detect a sound */
if (analogRead(AMP_PIN) > 400) {
get_microphone_data(); //function in the full code
neai_classification(neai_buffer, output_class_buffer, &id_class);
/* Player next movement based on class detected */
if (id_class == 2) {
player_move = -1; //up
}
else if (id_class == 3) {
player_move = 1; //down
}
}
else {
//We don't want to repeat the same movement until we detect something else
id_class = 0;
player_move = 0;
}
//move the player
move_player();
/* Clean neai buffer */
memset(neai_buffer, 0.0, AXIS * SENSOR_SAMPLES * sizeof(float));
}
delay(10);
}
我们在这里所做的也很简单:
- 如果检测到声音(400 是我在什么都没发生时得到的值),我们会做一些事情
- 我们从麦克风收集声音
- 我们将这种声音分类:要么意味着向上移动,要么意味着向下移动播放器(见下一步)
- 根据职业,我们相应地移动玩家
第 7 步:声音分类
为了玩游戏,我们发出两种声音,一种是向上,一种是向下。
为了对声音进行分类,我们可以手动完成。我不确定如何,但如果我们计算 FFT 并设置阈值,这应该是可能的。
取而代之的是,我们使用 NanoEdge AI Studio 自动完成,并在我们的代码中包含一个非常小的 AI 模型。
- 安装 NanoEdge AI Studio: https://stm32ai.st.com/download-nanoedgeai/ 美元
创建N类分类项目:
- 选择麦克风 1 轴作为传感器
- 选择Arduino UNO R4 WIFI作为目标
收集数据:
- 下面附有 Arduino IDE 的 Flash 代码 (sound_datalogger.ino)
- 在 NanoEdge 的 signals 选项卡中,单击 add Signal,然后单击 serial
- 首先收集多个声音示例以“向上”播放(大约 100 个示例)
- 然后是另一个声音的多个示例来播放“向下”
启动基准测试并获得良好的准确性
编译库
有关如何使用 NanoEdge AI Studio 和 Arduino 的更多详细信息,分步教程: $ https://wiki.st.com/stm32mcu/wiki/AI:How_to_create_Arduino_Rock-Paper-Scissors_game_using_NanoEdge_AI_Studio $
注意:
随附的代码仅包含一个功能,用于收集声音的下采样缓冲区并通过串行发送它们。
我们得到一个下采样缓冲区,因为采集的基本频率非常快,所以我们会得到一个代表几毫秒的缓冲区。通过对它进行缩减采样,它代表了现实生活中的更多时间。
为此,我们只需读取麦克风发送的所有值,但我们只保留 1/8 值。
第 8 步:添加分类
现在我们有了分类库,我们需要将其添加到我们的Arduino代码中:
- 打开得到的.zip,有一个Arduino文件夹,里面有另一个zip
- 在Arduino IDE中导入库:Sketch > Include library > Add .ZIP library...,然后选择Arduino文件夹中的.zip
然后在我们的代码中,要使用 NanoEdge 分类,我们只需要一些代码,如下例所示:
- 我们包括 NanoEdge 库
- 我们初始化库
- 我们从麦克风收集数据
- 我们进行分类
#include "NanoEdgeAI.h"
#include "knowledge.h"
/* NanoEdgeAI variables part */
uint8_t neai_code = 0;
uint16_t id_class = 0; // Point to id class (see argument of neai_classification fct)
float output_class_buffer[CLASS_NUMBER]; // Buffer of class probabilities
const char *id2class[CLASS_NUMBER + 1] = { // Buffer for mapping class id to class name
"unknown",
"up",
"down",
};
static float neai_buffer[SENSOR_SAMPLES * AXIS] = {0.0};
void setup() {
...
/* initialize NanoEdge Library */
neai_code = neai_classification_init(knowledge);
if (neai_code != NEAI_OK) {
Serial.print("Not supported board.n");
}
...
}
void loop() {
...
/* make a classification */
get_microphone_data();
neai_classification(neai_buffer, output_class_buffer, &id_class);
/* then depending on the class in id_class, we play up, down or wathever */
...
}
警告:
您需要检查 Arduino 代码中的 id2class 变量是否与我们之前导入的库中的 NanoEdgeAI.h 文件中的变量相同:
在 document/arduino/libraries/nanoedge/src/NanoEdgeAI.h 中(在文件末尾)
const char *id2class[CLASS_NUMBER + 1] = {
"unknown",
"up",
"down",
};
第 9 步:最终项目
该项目的完整代码如下 (arduino_demo_flappy_bird_sound.ino)
您需要按照步骤 1 和步骤 7 和 8 使其正常工作!
这个想法是展示如何使用人工智能更快地实现项目。无需手动进行声音分类,而是自动完成,使用 NanoEdge,只需几分钟即可找到模型并对其进行集成。
您可以以此示例为例,并使用相同的方法来争取时间并实现简单的 POC,如果手动完成,可能会花费更多时间。
感谢您阅读:)
代码:声音数据记录仪:
#include <Wire.h>
#define SENSOR_SAMPLES 512
static float neai_buffer[SENSOR_SAMPLES] = {0.0};
static uint16_t neai_ptr = 0;
int const AMP_PIN = A0; // Preamp output pin connected to A0
/* Prototypes ----------------------------------------------------------*/
void get_microphone_data(void);
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
}
void loop() {
// put your main code here, to run repeatedly:
get_microphone_data();
}
/* Functions declaration ----------------------------------------------------------*/
void get_microphone_data()
{
static uint16_t temp = 0;
if (analogRead(AMP_PIN) > 400){
int sub = 0;
while(neai_ptr < SENSOR_SAMPLES) {
if (sub > 8){
/* Fill neai buffer with new accel data */
neai_buffer[neai_ptr] = analogRead(AMP_PIN);
/* Increment neai pointer */
neai_ptr++;
sub = 0;
}
else{
temp = analogRead(AMP_PIN);
}
sub ++;
}
for(uint16_t i = 0; i < SENSOR_SAMPLES; i++) {
Serial.print(neai_buffer[i]);
Serial.print(" ");
}
Serial.print("n");
/* Reset pointer */
neai_ptr = 0;
}
}
主代码:
/* Libraries ----------------------------------------------------------*/
#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"
#include <Wire.h>
#include "NanoEdgeAI.h"
#include "knowledge.h"
#include <Scheduler.h>
/* Defines ----------------------------------------------------------*/
/* Matrix part */
#define HEIGHT 8
#define WIDTH 12
#define PLAYER_X 10 //initial player position
#define PLAYER_Y 4
/* NEAI part */
#define SENSOR_SAMPLES 512
#define AXIS 1
/* Prototypes ----------------------------------------------------------*/
void introduction_message(void);
void get_microphone_data(void);
void adapt_game_level(void);
void print_score(uint16_t game_score);
void reset_global_variables(void);
/* Objects ----------------------------------------------------------*/
ArduinoLEDMatrix matrix;
/* Global variables ----------------------------------------------------------*/
int game_ongoing = 0;
static byte wall_move = false;
static int8_t player_move = 0;
static uint8_t player_x = PLAYER_X, player_y = PLAYER_Y;
static uint8_t wall_start_pix = 0, wall_pos_x = 0, wall_size = 3;
static uint16_t score = 0, neai_ptr = 0, time_to_wait = 50;
static float neai_buffer[SENSOR_SAMPLES * AXIS] = {0.0};
int const AMP_PIN = A0; // Preamp output pin connected to A0
/* NanoEdgeAI variables part */
uint8_t neai_code = 0;
uint16_t id_class = 0; // Point to id class (see argument of neai_classification fct)
float output_class_buffer[CLASS_NUMBER]; // Buffer of class probabilities
const char *id2class[CLASS_NUMBER + 1] = { // Buffer for mapping class id to class name
"unknown",
"up",
"down",
};
/* Declare matrix to display */
byte frame[8][12] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
/* Setup function ----------------------------------------------------------*/
void setup() {
Serial.begin(115200);
Scheduler.startLoop(loop2); //first loop is wall movement, second is player
matrix.begin();
introduction_message(); //display a message when starting
/* Initialize NanoEdgeAI AI */
neai_code = neai_classification_init(knowledge);
if (neai_code != NEAI_OK) {
Serial.print("Not supported board.n");
}
else {
game_ongoing = 1; //launch game if nanoedge working correctly
}
}
/* Infinite loop ----------------------------------------------------------*/
void loop() {
if (game_ongoing) {
/* Clean the last column of the matrix */
for (uint8_t y = 0; y < HEIGHT; y++) {
frame[y][WIDTH - 1] = 0;
frame[y][WIDTH - 2] = 0;
}
/* Set wall position on the matrix using random */
wall_start_pix = random(0, 5);
/* Move the wall through matrix */
do {
if (wall_move) {
for (uint8_t y = 0; y < HEIGHT; y++) {
frame[y][wall_pos_x] = (y >= wall_start_pix && y < wall_start_pix + wall_size) ? 0 : 1;
if (wall_pos_x > 1) {
frame[y][wall_pos_x - 2] = 0;
}
}
wall_pos_x++;
}
wall_move = !wall_move;
/* Update display */
matrix.renderBitmap(frame, HEIGHT, WIDTH);
/* Adapt level */
adapt_game_level();
/* Check if player touch the wall */
if (frame[player_y][player_x] == frame[player_y][player_x - 1]) {
game_ongoing = 0; //stop the game
reset_global_variables();
return;
}
} while (wall_pos_x < WIDTH);
/* Increment score counter */
score++;
/* Reset wall position */
wall_pos_x = 0;
}
}
void loop2() {
if (game_ongoing) {
/* make a classification only if we detect a sound */
if (analogRead(AMP_PIN) > 400) {
get_microphone_data();
neai_classification(neai_buffer, output_class_buffer, &id_class);
/* Player next movement based on class detected */
if (id_class == 1) {
player_move = -1; //up
}
else if (id_class == 2) {
player_move = 1; //down
}
}
else {
//We don't want to repeat the same movement until we detect something else
id_class = 0;
player_move = 0;
}
//move the player
move_player();
/* Clean neai buffer */
memset(neai_buffer, 0.0, AXIS * SENSOR_SAMPLES * sizeof(float));
}
delay(1);
}
/* Functions declaration ----------------------------------------------------------*/
void introduction_message()
{
matrix.beginDraw();
matrix.stroke(0xFFFFFFFF);
matrix.textScrollSpeed(50);
// add the text
const char text[] = " Flappy bird! ";
matrix.textFont(Font_5x7);
matrix.beginText(0, 1, 0xFFFFFF);
matrix.println(text);
matrix.endText(SCROLL_LEFT);
matrix.endDraw();
}
void move_player() {
/* Move the player and check if it stays in the screen */
if (player_y + player_move >= 0 && player_y + player_move < HEIGHT) {
/* Clear player last position */
frame[player_y][player_x] = 0;
/* Change player y coordinate */
player_y += player_move;
}
/* Display player in matrix */
frame[player_y][player_x] = 1;
/* Update display */
matrix.renderBitmap(frame, HEIGHT, WIDTH);
}
/* function to get a single downsampled sample from sensor
We get every values but keep only one every eight values because the model was train with data like this.
It permits to listen for a longer period of time and get a better accuracy
*/
void get_microphone_data()
{
static uint16_t temp = 0; //stock values
int sub = 0; //increment to downsample
//while the buffer is not full
while (neai_ptr < SENSOR_SAMPLES) {
//if it is the eighth value
if (sub > 8) {
/* Fill neai buffer with new accel data */
neai_buffer[neai_ptr] = analogRead(AMP_PIN);
/* Increment neai pointer */
neai_ptr++;
sub = 0; //reset increment
}
else {
temp = analogRead(AMP_PIN);
}
sub ++;
}
for(uint16_t i = 0; i < SENSOR_SAMPLES; i++) {
Serial.print(neai_buffer[i]);
Serial.print(" ");
}
Serial.print("n");
neai_ptr = 0;
}
void adapt_game_level()
{
/* Adapt speed & hole size */
if (score < 5) {
time_to_wait = 50;
}
else if (score >= 5 && score < 10) {
time_to_wait = 40;
}
else if (score >= 10 && score < 15) {
time_to_wait = 30;
}
else if (score >= 15 && score < 20) {
wall_size = 2;
}
else {
wall_size = 1;
}
delay(time_to_wait);
}
void print_score(uint16_t game_score)
{
uint8_t text_pos_x = (game_score < 10) ? 5 : 3;
matrix.clear();
matrix.beginDraw();
matrix.stroke(0xFFFFFFFF);
char text[3];
itoa(game_score, text, 10);
matrix.textFont(Font_4x6);
matrix.beginText(text_pos_x, 1, 0xFFFFFF);
matrix.println(text);
matrix.endText();
matrix.endDraw();
}
void reset_global_variables()
{
memset(frame, 0, WIDTH * HEIGHT * sizeof(byte));
player_x = PLAYER_X;
player_y = PLAYER_Y;
print_score(score);
/* Reset score after loosing the party */
score = 0;
/* Reset wall position */
wall_pos_x = 0;
/* Reset wall size */
wall_size = 3;
/* Reset delay */
time_to_wait = 50;
delay(1000);
game_ongoing = 1;
}