我爱小智
由于小智聊天机器人太火了,火遍了大江南北,每天哪个群都在聊小智的话题,连初中的同学们也组团研究小智了,很快就把小智摸透了。小智机器人通过开源生态、硬件工程化及智能交互技术的创新,推动服务机器人从工业级向消费级普及。基于ESP32的开源架构提供完整硬件设计文件、驱动代码及服务端框架,结合GitHub社区的持续迭代优化,开发者可快速复用成熟模块并实现功能升级。离线语音交互库支持多语言识别与方言适配,抖音/B站标准化教程进一步降低技术门槛。硬件方面形成从ESP32-S3-WROOM-1核心板到外围组件的完整供应链,模块化套件将开发周期缩短至小时级,3D打印一体化外壳结合材料优化将成本压至百元级。技术上实现"语音+视觉+动作"多模态融合,双核处理能力支持实时视频流传输与边缘计算,集成ROS2系统及SLAM算法实现环境感知与导航,声纹识别和情绪分析技术提升个性化陪伴体验。该体系通过开源创新、硬件工程化、智能算法突破的三重驱动,使开发成本降低70%以上,开发周期缩短至传统方案的1/10,已在智能家居控制、教育陪伴、商业服务等场景落地应用,推动机器人技术普惠化发展。
初三的同学都开始学习了,学的内容比我还深入,我好害怕阿呀。
一、目标产品功能介绍
2025贸泽电子M-Design创意设计竞赛是由贸泽电子与硬禾科技联合主办,然后从Mouser官网采购开发板、元器件等完成订单,且至少用到订单上一款产品。
我的注意力也离不开小智了,整天也想着用小智来玩点什么新奇的东西,这次我就实现了用小智来驱动Midi播放器,已经不能满足于简单的播放了,我们让小智开动脑筋进行midi乐谱的创作。小智的音乐水平自然比我高多了,我认为它至少达到高中水平。下面我们来看看关键的实现原理。
我买的是M5stack的UNIT midi
M5Stack 的 Unit Synth 是一款为 MIDI 声音系统打造的音频处理单元。它内置 SAM2695 音频合成芯片,借助串口通信接收标准 MIDI 信号,可实现音频合成、混音、音效处理等多种功能。支持多通道乐器音源输出配置,能对四段 EQ、混响等参数进行调节。搭载的 NS4150B D 类功放芯片,可驱动 8Ω@1W 扬声器输出清晰音频。适用于即兴演奏、音乐制作、电子舞台、STEAM 教育等场景。
我使用的主控是 M5Stack CoreS3,CoreS3 是一款基于 ESP32-S3 的高性能控制器,搭载双核心 240MHz 处理器,集成 16MB Flash 和 8MB PSRAM,支持 WiFi、OTG/CDC 功能。配备 2.0 英寸电容触控 IPS 屏(320×240)、30 万像素摄像头、接近传感器、六轴 IMU(BMI270)、磁力计(BMM150)及 RTC 模块。电源管理采用 AXP2101 芯片,支持 9-24V DC 输入或 500mAh 锂电池,内置 1W 扬声器和双麦克风。开发支持 Arduino、UiFlow2、ESP-IDF 等平台,提供丰富接口(UART/I2C/GPIO)和扩展底座,适用于 IoT、智能家居、工业控制等场景。支持 SD 卡扩展(最大 16GB),具备低功耗设计和 DIN 导轨安装能力。结合AW88298和ES7210编解码芯片处理24kHz采样的数字音频信号,经由I2S接口输出至扬声器,完全符合小智智能聊天系统配置要求,还同时具有良好的视觉感知功能扩展。
二、项目核心原理框架图
该项目实现了一个基于ESP32-S3主控的多音轨MIDI音乐合成系统,能够解析结构化乐谱输入(如"乐器:音符-力度-时长"格式),通过动态分配16个MIDI通道实现多乐器并行播放,利用FreeRTOS为每个乐器创建独立任务,通过UART2以31250bps标准MIDI协议向合成器发送NoteOn/NoteOff指令控制音符触发与关闭,支持128种GM标准音色库调用及混响、合唱、均衡器等音效调节,形成从乐谱解析、多通道MIDI指令生成、音频合成到物理输出的完整音乐创作链路,同时提供音量、声像、弯音轮等实时控制接口,具备电子乐器核心功能与扩展能力。借助小智智能聊天系统的llm能力来理解midi的乐理,实现音乐的自主创造,剩下的只有大胆地提出你的需求。
三、Midi智能创作核心代码解析
本次仅聚焦于 midi_iot 部分代码的介绍。这部分代码在系统中承担着特定的功能,与外部设备的交互和数据传输密切相关。而小智固件代码内容庞大且复杂,涉及众多方面的逻辑和功能,若要详细阐述,需要撰写专门的书籍,因此不在此处展开。
#include "M5UnitSynth.h"
#include "audio_codec.h"
#include "board.h"
#include "esp_wifi.h"
#include "freertos/event_groups.h"
#include "iot/thing.h"
#include "mqtt_client.h"
#include <esp_log.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <unordered_map>
#include <map>
#define TAG "Midi"
constexpr int MIDI_CHANNEL = 0;
constexpr int MAX_MIDI_CHANNELS = 16;
std::string trim(const std::string &str)
{
const auto first = str.find_first_not_of(" \t\r\n");
if (first == std::string::npos)
return "";
const auto last = str.find_last_not_of(" \t\r\n");
return str.substr(first, last - first + 1);
}
// int noteNameToMidiNumber(const std::string ¬eName)
// {
// static const std::map<std::string, int> noteMap = {
// {"NOTE_B0", 23}, {"NOTE_C1", 24}, {"NOTE_CS1", 25}, {"NOTE_D1", 26}, {"NOTE_DS1", 27}, {"NOTE_E1", 28}, {"NOTE_F1", 29}, {"NOTE_FS1", 30}, {"NOTE_G1", 31}, {"NOTE_GS1", 32}, {"NOTE_A1", 33}, {"NOTE_AS1", 34}, {"NOTE_B1", 35}, {"NOTE_C2", 36}, {"NOTE_CS2", 37}, {"NOTE_D2", 38}, {"NOTE_DS2", 39}, {"NOTE_E2", 40}, {"NOTE_F2", 41}, {"NOTE_FS2", 42}, {"NOTE_G2", 43}, {"NOTE_GS2", 44}, {"NOTE_A2", 45}, {"NOTE_AS2", 46}, {"NOTE_B2", 47}, {"NOTE_C3", 48}, {"NOTE_CS3", 49}, {"NOTE_D3", 50}, {"NOTE_DS3", 51}, {"NOTE_E3", 52}, {"NOTE_F3", 53}, {"NOTE_FS3", 54}, {"NOTE_G3", 55}, {"NOTE_GS3", 56}, {"NOTE_A3", 57}, {"NOTE_AS3", 58}, {"NOTE_B3", 59}, {"NOTE_C4", 60}, {"NOTE_CS4", 61}, {"NOTE_D4", 62}, {"NOTE_DS4", 63}, {"NOTE_E4", 64}, {"NOTE_F4", 65}, {"NOTE_FS4", 66}, {"NOTE_G4", 67}, {"NOTE_GS4", 68}, {"NOTE_A4", 69}, {"NOTE_AS4", 70}, {"NOTE_B4", 71}, {"NOTE_C5", 72}, {"NOTE_CS5", 73}, {"NOTE_D5", 74}, {"NOTE_DS5", 75}, {"NOTE_E5", 76}, {"NOTE_F5", 77}, {"NOTE_FS5", 78}, {"NOTE_G5", 79}, {"NOTE_GS5", 80}, {"NOTE_A5", 81}, {"NOTE_AS5", 82}, {"NOTE_B5", 83}, {"NOTE_C6", 84}, {"NOTE_CS6", 85}, {"NOTE_D6", 86}, {"NOTE_DS6", 87}, {"NOTE_E6", 88}, {"NOTE_F6", 89}, {"NOTE_FS6", 90}, {"NOTE_G6", 91}, {"NOTE_GS6", 92}, {"NOTE_A6", 93}, {"NOTE_AS6", 94}, {"NOTE_B6", 95}, {"NOTE_C7", 96}, {"NOTE_CS7", 97}, {"NOTE_D7", 98}, {"NOTE_DS7", 99}, {"NOTE_E7", 100}, {"NOTE_F7", 101}, {"NOTE_FS7", 102}, {"NOTE_G7", 103}, {"NOTE_GS7", 104}, {"NOTE_A7", 105}, {"NOTE_AS7", 106}, {"NOTE_B7", 107}, {"NOTE_C8", 108}, {"NOTE_CS8", 109}, {"NOTE_D8", 110}, {"NOTE_DS8", 111}};
// const auto it = noteMap.find(noteName);
// return (it != noteMap.end()) ? it->second : -1;
// }
int noteNameToMidiNumber(const std::string& noteName) {
static const std::map<std::string, int> noteMap = {
{"NOTE_B0", 23}, {"NOTE_C1", 24}, {"NOTE_CS1", 25}, {"NOTE_D1", 26}, {"NOTE_DS1", 27},
{"NOTE_E1", 28}, {"NOTE_F1", 29}, {"NOTE_FS1", 30}, {"NOTE_G1", 31}, {"NOTE_GS1", 32},
{"NOTE_A1", 33}, {"NOTE_AS1", 34}, {"NOTE_B1", 35}, {"NOTE_C2", 36}, {"NOTE_CS2", 37},
{"NOTE_D2", 38}, {"NOTE_DS2", 39}, {"NOTE_E2", 40}, {"NOTE_F2", 41}, {"NOTE_FS2", 42},
{"NOTE_G2", 43}, {"NOTE_GS2", 44}, {"NOTE_A2", 45}, {"NOTE_AS2", 46}, {"NOTE_B2", 47},
{"NOTE_C3", 48}, {"NOTE_CS3", 49}, {"NOTE_D3", 50}, {"NOTE_DS3", 51}, {"NOTE_E3", 52},
{"NOTE_F3", 53}, {"NOTE_FS3", 54}, {"NOTE_G3", 55}, {"NOTE_GS3", 56}, {"NOTE_A3", 57},
{"NOTE_AS3", 58}, {"NOTE_B3", 59}, {"NOTE_C4", 60}, {"NOTE_CS4", 61}, {"NOTE_D4", 62},
{"NOTE_DS4", 63}, {"NOTE_E4", 64}, {"NOTE_F4", 65}, {"NOTE_FS4", 66}, {"NOTE_G4", 67},
{"NOTE_GS4", 68}, {"NOTE_A4", 69}, {"NOTE_AS4", 70}, {"NOTE_B4", 71}, {"NOTE_C5", 72},
{"NOTE_CS5", 73}, {"NOTE_D5", 74}, {"NOTE_DS5", 75}, {"NOTE_E5", 76}, {"NOTE_F5", 77},
{"NOTE_FS5", 78}, {"NOTE_G5", 79}, {"NOTE_GS5", 80}, {"NOTE_A5", 81}, {"NOTE_AS5", 82},
{"NOTE_B5", 83}, {"NOTE_C6", 84}, {"NOTE_CS6", 85}, {"NOTE_D6", 86}, {"NOTE_DS6", 87},
{"NOTE_E6", 88}, {"NOTE_F6", 89}, {"NOTE_FS6", 90}, {"NOTE_G6", 91}, {"NOTE_GS6", 92},
{"NOTE_A6", 93}, {"NOTE_AS6", 94}, {"NOTE_B6", 95}, {"NOTE_C7", 96}, {"NOTE_CS7", 97},
{"NOTE_D7", 98}, {"NOTE_DS7", 99}, {"NOTE_E7", 100}, {"NOTE_F7", 101}, {"NOTE_FS7", 102},
{"NOTE_G7", 103}, {"NOTE_GS7", 104}, {"NOTE_A7", 105}, {"NOTE_AS7", 106}, {"NOTE_B7", 107},
{"NOTE_C8", 108}, {"NOTE_CS8", 109}, {"NOTE_D8", 110}, {"NOTE_DS8", 111}
};
const auto it = noteMap.find(noteName);
return (it != noteMap.end()) ? it->second : -1;
}
std::vector<std::string> split(const std::string &s, char delimiter)
{
std::vector<std::string> tokens;
std::string token;
std::stringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
using InstrumentNotesMap = std::map<std::string, std::vector<std::string>>;
InstrumentNotesMap parseInstruments(const std::string &input)
{
InstrumentNotesMap instruments;
const auto lines = split(input, '\n');
for (const auto &line : lines)
{
const auto colonPos = line.find(": ");
if (colonPos == std::string::npos)
continue;
const auto instrument = trim(line.substr(0, colonPos));
const auto notesStr = trim(line.substr(colonPos + 2));
const auto notes = split(notesStr, ',');
instruments[instrument] = notes;
}
return instruments;
}
unit_synth_instrument_t getInstrumentFromString(const std::string &instrumentName)
{
const auto it = instrumentMap.find(instrumentName);
if (it != instrumentMap.end())
{
return it->second;
}
printf("Warning: Unknown instrument %s, using default GrandPiano_1\n", instrumentName.c_str());
return GrandPiano_1;
}
std::unordered_map<std::string, int> instrumentChannelMap;
int nextChannel = 0;
int getMidiChannel(const std::string &instrument)
{
const auto it = instrumentChannelMap.find(instrument);
if (it != instrumentChannelMap.end())
{
return it->second;
}
const int assignedChannel = nextChannel % MAX_MIDI_CHANNELS;
instrumentChannelMap[instrument] = assignedChannel;
nextChannel++;
return assignedChannel;
}
void playInstrumentTask(void *param)
{
// 将传入的void指针转换为包含合成器实例的三元组指针
// tuple结构:<合成器指针, 音符列表, MIDI通道号>
auto *data = static_cast<std::tuple<M5UnitSynth *, std::vector<std::string>, int> *>(param);
// 解包任务数据
M5UnitSynth *synth = std::get<0>(*data); // 获取合成器实例
auto ¬es = std::get<1>(*data); // 获取当前乐器的音符序列引用
const int midiChannel = std::get<2>(*data); // 获取分配的MIDI通道号
// 遍历处理每个音符
for (const auto ¬e : notes)
{
// 将音符字符串分割为名称-力度-时长三部分(例如"C4-100-500")
const auto note_info = split(note, '-');
if (note_info.size() != 3) // 有效性检查
continue;
// 解析音符要素(去除前后空格) 播放的时候是 采用节拍数和 全音符时长来控制的,而不是 毫秒,建议统一,节拍数统一安排,而不需要
// 每个音符都携带 时长信息,只要音符名和节拍数即可。
const auto note_name = trim(note_info[0]); // 音符名称(如"C4"或"REST")
const int velocity = std::stoi(trim(note_info[1])); // 力度 (0-127) 对于播放现成midi力度是统一的,因为没有那么精细的音频数据
// 但对于打击音乐的自由创作过程则可以设计 不同力度的打击效果的
const int duration = std::stoi(trim(note_info[2])); // 时长(单位:毫秒)
// 处理休止符(不发音)
if (note_name == "REST") {
vTaskDelay(pdMS_TO_TICKS(duration)); // 使用FreeRTOS延时函数
continue; // 跳过后续音符播放处理
}
// 将音符名称转换为MIDI编号(例如C4->60)
const int note_num = noteNameToMidiNumber(note_name);
if (note_num == -1) // 无效音符检查
continue;
synth->setNoteOn(midiChannel, note_num, velocity); // 触发音符开始
vTaskDelay(pdMS_TO_TICKS(duration)); // 维持音符时长
synth->setNoteOff(midiChannel, note_num, velocity); // 触发音符结束
// 注意:实际MIDI协议应通过定时器管理音符时长,此处简化实现
}
// 清理资源
delete data; // 删除动态分配的任务数据
vTaskDelete(nullptr); // 终止当前FreeRTOS任务(参数nullptr表示当前任务)
}
void playNotesMulti(M5UnitSynth &synth, const std::string &input)
{
const auto instruments = parseInstruments(input);
for (const auto &[instrument, notes] : instruments)
{
const int channel = getMidiChannel(instrument);
printf("Playing %s on MIDI channel %d...\n", instrument.c_str(), channel);
// 设置合成器的乐器音色:
// 参数说明 - 0: 音色库编号,channel: MIDI通道,getInstrumentFromString(): 音色枚举值
synth.setInstrument(0, channel, getInstrumentFromString(instrument));
auto *data = new std::tuple<M5UnitSynth *, std::vector<std::string>, int>(
&synth, notes, channel);
xTaskCreate(playInstrumentTask, instrument.c_str(), 4096, data, 1, nullptr);
}
}
std::string getInstrumentByChannel(int channel) const
{
// 遍历乐器通道映射表(instrumentChannelMap),该表存储了乐器名称和对应的通道号
for (const auto &pair : instrumentChannelMap)
{
// 检查当前遍历到的通道号是否与传入的通道号相等
if (pair.second == channel)
{
return pair.first;
}
}
return "Unknown";
}
namespace iot
{
class Midi : public Thing
{
private:
M5UnitSynth synth;
public:
Midi() : Thing("Midi", "midi播放器")
{
synth.begin(UART_NUM_2, UNIT_SYNTH_BAUD, 18, 17);
methods_.AddMethod("Play", "乐队演奏", ParameterList({Parameter("Notes", R"(使用以下音符和乐器信息,创作一段音乐。
音符范围:
NOTE_B0 (23) 到 NOTE_DS8 (111),以及 REST (0) 表示休止符。
乐器列表:
GrandPiano_1, BrightPiano_2, ElGrdPiano_3, HonkyTonkPiano, ElPiano1, ElPiano2, Harpsichord, Clavi, Celesta, Glockenspiel, MusicBox, Vibraphone, Marimba, Xylophone, TubularBells, Santur, DrawbarOrgan, PercussiveOrgan, RockOrgan, ChurchOrgan, ReedOrgan, AccordionFrench, Harmonica, TangoAccordion, AcGuitarNylon, AcGuitarSteel, AcGuitarJazz, AcGuitarClean, AcGuitarMuted, OverdrivenGuitar, DistortionGuitar, GuitarHarmonics, AcousticBass, FingerBass, PickedBass, FretlessBass, SlapBass1, SlapBass2, SynthBass1, SynthBass2, Violin, Viola, Cello, Contrabass, TremoloStrings, PizzicatoStrings, OrchestralHarp, Timpani, StringEnsemble1, StringEnsemble2, SynthStrings1, SynthStrings2, ChoirAahs, VoiceOohs, SynthVoice, OrchestraHit, Trumpet, Trombone, Tuba, MutedTrumpet, FrenchHorn, BrassSection, SynthBrass1, SynthBrass2, SopranoSax, AltoSax, TenorSax, BaritoneSax, Oboe, EnglishHorn, Bassoon, Clarinet, Piccolo, Flute, Recorder, PanFlute, BlownBottle, Shakuhachi, Whistle, Ocarina, Lead1Square, Lead2Sawtooth, Lead3Calliope, Lead4Chiff, Lead5Charang, Lead6Voice, Lead7Fifths, Lead8BassLead, Pad1Fantasia, Pad2Warm, Pad3PolySynth, Pad4Choir, Pad5Bowed, Pad6Metallic, Pad7Halo, Pad8Sweep, FX1Rain, FX2Soundtrack, FX3Crystal, FX4Atmosphere, FX5Brightness, FX6Goblins, FX7Echoes, FX8SciFi, Sitar, Banjo, Shamisen, Koto, Kalimba, BagPipe, Fiddle, Shanai, TinkleBell, Agogo, SteelDrums, Woodblock, TaikoDrum, MelodicTom, SynthDrum, ReverseCymbal, GtFretNoise, BreathNoise, Seashore, BirdTweet, TelephRing, Helicopter, Applause, Gunshot.
请按照以下格式输出你的创作:
[乐器1]: [音符1]-[节拍数1], [音符2]-[节拍数2], ...\r\n
[乐器2]: [音符3]-[节拍数3], [音符4]-[节拍数4], ...\r\n
其中:
- 音符使用提供的 NOTE_XXX 格式或 REST (0)。
-节拍数为4,8,16,-4等, 4 表示四分音符,8 表示八分音符,16 表示十六分音符,以此类推。 特别指出负数用于表示附点音符,例如 -4 表示附点四分音符,即一个四分音符加上一个八分音符。)",
kValueTypeString, true)}),
[this](const ParameterList ¶meters)
{
const std::string Notes = parameters["Notes"].string();
printf("Notes: %s\n", Notes.c_str());
playNotesMulti(synth, Notes);
});
}
};
} // namespace iot
DECLARE_THING(Midi);
在整个系统中,最为核心的代码便是下面要介绍的这段。它宛如一把钥匙,教会了小智如何创造 MIDI 音乐。本质上,它是在向大语言模型(LLM)传递 MIDI 乐谱的数据格式。MIDI 作为一种专门用于音乐信息表示的标准,其数据格式包含了音符、音高、时长、力度等诸多关键音乐元素的编码。通过这段代码,LLM 能够理解这些特定格式的数据,进而依据规则和算法生成出富有韵律和美感的 MIDI 音乐,为音乐创作带来了新的可能和便利。
namespace iot
{
class Midi : public Thing
{
private:
M5UnitSynth synth;
public:
Midi() : Thing("Midi", "midi播放器")
{
synth.begin(UART_NUM_2, UNIT_SYNTH_BAUD, 18, 17);
methods_.AddMethod("Play", "乐队演奏", ParameterList({Parameter("Notes", R"(使用以下音符和乐器信息,创作一段音乐。
音符范围:
NOTE_B0 (23) 到 NOTE_DS8 (111),以及 REST (0) 表示休止符。
乐器列表:
GrandPiano_1, BrightPiano_2, ElGrdPiano_3, HonkyTonkPiano, ElPiano1, ElPiano2, Harpsichord, Clavi, Celesta, Glockenspiel, MusicBox, Vibraphone, Marimba, Xylophone, TubularBells, Santur, DrawbarOrgan, PercussiveOrgan, RockOrgan, ChurchOrgan, ReedOrgan, AccordionFrench, Harmonica, TangoAccordion, AcGuitarNylon, AcGuitarSteel, AcGuitarJazz, AcGuitarClean, AcGuitarMuted, OverdrivenGuitar, DistortionGuitar, GuitarHarmonics, AcousticBass, FingerBass, PickedBass, FretlessBass, SlapBass1, SlapBass2, SynthBass1, SynthBass2, Violin, Viola, Cello, Contrabass, TremoloStrings, PizzicatoStrings, OrchestralHarp, Timpani, StringEnsemble1, StringEnsemble2, SynthStrings1, SynthStrings2, ChoirAahs, VoiceOohs, SynthVoice, OrchestraHit, Trumpet, Trombone, Tuba, MutedTrumpet, FrenchHorn, BrassSection, SynthBrass1, SynthBrass2, SopranoSax, AltoSax, TenorSax, BaritoneSax, Oboe, EnglishHorn, Bassoon, Clarinet, Piccolo, Flute, Recorder, PanFlute, BlownBottle, Shakuhachi, Whistle, Ocarina, Lead1Square, Lead2Sawtooth, Lead3Calliope, Lead4Chiff, Lead5Charang, Lead6Voice, Lead7Fifths, Lead8BassLead, Pad1Fantasia, Pad2Warm, Pad3PolySynth, Pad4Choir, Pad5Bowed, Pad6Metallic, Pad7Halo, Pad8Sweep, FX1Rain, FX2Soundtrack, FX3Crystal, FX4Atmosphere, FX5Brightness, FX6Goblins, FX7Echoes, FX8SciFi, Sitar, Banjo, Shamisen, Koto, Kalimba, BagPipe, Fiddle, Shanai, TinkleBell, Agogo, SteelDrums, Woodblock, TaikoDrum, MelodicTom, SynthDrum, ReverseCymbal, GtFretNoise, BreathNoise, Seashore, BirdTweet, TelephRing, Helicopter, Applause, Gunshot.
请按照以下格式输出你的创作:
[乐器1]: [音符1]-[节拍数1], [音符2]-[节拍数2], ...\r\n
[乐器2]: [音符3]-[节拍数3], [音符4]-[节拍数4], ...\r\n
其中:
- 音符使用提供的 NOTE_XXX 格式或 REST (0)。
-节拍数为4,8,16,-4等, 4 表示四分音符,8 表示八分音符,16 表示十六分音符,以此类推。 特别指出负数用于表示附点音符,例如 -4 表示附点四分音符,即一个四分音符加上一个八分音符。)",
kValueTypeString, true)}),
[this](const ParameterList ¶meters)
{
const std::string Notes = parameters["Notes"].string();
printf("Notes: %s\n", Notes.c_str());
playNotesMulti(synth, Notes);
});
}
};
} //
四、项目演示图片
总结:
小智聊天机器人是一款基于先进技术打造的智能交互工具。它依托强大的自然语言处理算法,能够精准理解用户的提问意图。无论是生活琐事、知识疑问,还是寻求建议,小智都能迅速给出清晰、准确的回应。它拥有丰富的知识储备,涵盖了众多领域,能为用户提供全面的信息支持。同时,小智具备良好的对话逻辑和流畅的表达能力,对话过程自然流畅,仿佛与一位贴心的伙伴在交流。而且,它还在不断学习和进化,通过持续优化算法和更新数据,致力于为用户带来更优质、高效的聊天体验,助力人们获取知识、解决问题。
ESP32小智机器人的发展正从技术驱动转向需求导向。其开源基因与硬件灵活性为创新提供了肥沃土壤。随着边缘计算能力的提升与服务端模型的轻量化。在消费升级与技术普惠的双重作用下,这类高性价比AI终端有望重塑人机交互的边界,成为智能家居、教育陪伴等场景的标配设备,嵌入式系统与人工智能的深度结合拉开了帷幕。
浸淫esp32多年,终于等到了一个好玩的!