
简介ESP32S3接入deepseek大模型的项目源码包面向嵌入式开发者与物联网爱好者特别适合希望在低成本硬件上体验云端大模型能力的入门及中级开发者旨在解决在资源受限的ESP32S3开发板上调用云端大模型实现串口问答的问题。代码基于Arduino IDE环境组织包含WiFi网络初始化、deepseek API请求发送与JSON响应解析等核心闭环用户通过串口输入问题即可获得模型返回的回答。压缩包共3个文件以inscode配置、HTML辅助页面、gitignore版本管理文件为主整体仅7KB结构紧凑适合快速查看与移植。目前已有194人学习下载。通过这份源码读者能获得完整的接入思路和可运行的示例框架考虑到现有实现存在响应较慢、缺少记忆功能等局限也可作为后续优化网络传输、加入本地缓存的实践基础。相关思路可延伸至智能家居、边缘设备等需要云端AI能力的应用场景。1. 方案设计与整体架构1.1 为什么是ESP32S3 DeepSeek API先回答一个所有人都会问的问题ESP32S3这种MCU上怎么跑大模型答案是——跑不动也没必要跑。以DeepSeek-R1这类模型为例动辄几十亿参数光权重文件就要好几个GB别说ESP32S3那8MB PSRAM就算树莓派都吃得够呛。那这个项目到底在做什么说白了就是让ESP32S3当一个聪明终端它负责联网、采集输入按键、语音、传感器数据、把问题发给DeepSeek的云端API再把返回结果展示出来屏幕、喇叭、串口。大模型的计算发生在云端ESP32S3只做I/O和网络通信。这套思路在物联网圈其实很常见类比一下就是智能音箱的模式——本地只做拾音和播放大脑在云端。选ESP32S3而不是ESP32、ESP8266主要理由有几点双核240MHz Xtensa LX7处理器跑HTTP/HTTPS、JSON解析这类任务余量充足自带512KB SRAM加上板载8MB PSRAMHMI开发板标配可以缓存较长的模型回复内置向量指令加速虽然跑不了大模型但未来接语音唤醒模型、关键词识别时优势明显USB-OTG原生支持接USB麦克风或者调试都方便不需要额外转接芯片1.2 系统架构与交互流程我搭的这个项目架构分三层用户输入层按键/串口/语音 → ESP32S3主控层联网、组包、解析 → DeepSeek API大模型推理实际交互流程是这样的设备上电连接Wi-Fi用户通过板载按键或串口输入触发提问ESP32S3把问题封装成DeepSeek Chat API要求的JSON格式通过HTTPS POST到https://api.deepseek.com/chat/completions接收返回的JSON解析出回答文本显示在OLED/LCD屏幕上同时通过串口打印这里有个关键设计决策整个链路里最耗时的是网络请求和模型生成本地代码要保证在等待期间不卡死其他任务。所以我用了FreeRTOS跑两个任务——主任务负责UI和输入网络任务负责请求和接收二者通过队列通信。一开始我也写过单线程阻塞式代码结果屏幕在等待响应时完全黑屏体验非常差这个后面细说。1.3 功能边界做什么不做什么这个项目的定位是AI助手硬件终端底座而不是一个完整的产品。我刻意砍掉了这些功能不做流式输出SSE第一版就老老实实用一次性返回等模型生成完了再一次性显示。流式输出能大幅改善体验但ESP32S3这边要处理chunked transfer编码复杂度高不少放在后续优化不做连续多轮对话的记忆管理每次请求都把完整历史记录发过去太费流量ESP32S3的RAM也不够存太多上下文不做本地离线模型上面已经说过了不现实做得好的核心功能就三个Wi-Fi稳定连接、API请求封装、响应解析与显示。这三个是后面所有扩展的地基。2. 硬件准备与开发环境2.1 开发板选型与硬件清单我用的是ESP32-S3-DevKitC-18MB PSRAM版本。如果手头没有买任何带PSRAM的ESP32S3开发板都行比如合宙ESP32S3、微雪ESP32-S3 LCD Kit。注意一定要选带PSRAM的版本因为后面跑HTTP请求时TLS握手和JSON解析对内存的要求远超想象。完整硬件清单硬件型号/规格用途主控板ESP32-S3-DevKitC-18MB PSRAM主控屏幕0.96寸OLEDI2CSSD1306显示问题和回答按键轻触按键 x 2触发提问/切换问题USB线USB Type-C数据线供电和烧录可选麦克风INMP441I2S接口语音输入进阶可选功放喇叭MAX98357A 3W喇叭语音播报进阶在没有屏幕的情况下也能跑串口监视器就是你的交互窗口。不过实际用下来有了屏幕才真正有AI终端的感觉OLED虽然只是单色显示但能滚动显示出完整回答已经很酷了。2.2 开发环境配置基础环境我用的Arduino IDE 2.x不是ESP-IDF。原因很直白Arduino下能用的库WiFiClientSecure、ArduinoJson足够完成任务代码量少调试方便。如果追求极致的内存优化和更精细的task调度再考虑迁移到ESP-IDF。安装步骤安装Arduino IDE 2.x在开发板管理器中添加ESP32支持文件 → 首选项 → 附加开发板管理器URL填入https://espressif.github.io/arduino-esp32/package_esp32_index.json在开发板管理器中搜索esp32安装esp32 by Espressif Systems版本建议3.x开发板选择ESP32S3 Dev Module需要安装的库库名用途安装方式ArduinoJsonJSON解析/构造库管理器搜索安装我用7.x版本U8g2OLED屏幕驱动库管理器安装支持SSD1306WiFiClientSecureHTTPS请求ESP32自带无需安装注意ArduinoJson 7.x和6.x的API有区别很多老教程用的是6.x语法。本项目代码基于7.x如果你用的是6.xJsonDocument的声明方式会不一样需要对应调整。2.3 获取DeepSeek API Key这一步属于必做的前置工作。登录DeepSeek开放平台后在控制台创建API Key。注意几件事API Key只显示一次关掉页面就看不到了务必先复制保存新用户通常有免费额度但过了赠送额度后是按token计费个人测试跑不了多少钱请求地址是https://api.deepseek.com/chat/completions不是https://api.deepseek.com/v1/chat/completions这一点很多教程搞混过模型名称填deepseek-chat对应的就是DeepSeek-V3。如果未来出了新模型以官方文档为准。3. 核心代码实现与解析3.1 API请求的核心要点DeepSeek提供的API是OpenAI兼容格式所以代码结构跟调用ChatGPT API完全相同。请求体的核心结构{ model: deepseek-chat, messages: [ {role: system, content: 你是一个嵌入式AI助手回答要简洁。}, {role: user, content: ESP32S3有多少个GPIO} ], stream: false }这里有个细节容易被忽略system提示词。别看这个字段简单它对回答质量影响非常大。实测下来加一句回答控制在100字以内返回内容会精简很多对ESP32S3有限的显示空间非常友好。不加system提示词模型经常给你长篇大论屏幕上要滚好几屏。3.2 完整代码实现全局定义和Wi-Fi连接#include WiFi.h #include WiFiClientSecure.h #include ArduinoJson.h #include U8g2lib.h // Wi-Fi 配置 const char* WIFI_SSID 你的WiFi名; const char* WIFI_PASS 你的WiFi密码; // DeepSeek API 配置 const char* API_KEY sk-你的APIKey; const char* API_HOST api.deepseek.com; const char* API_PATH /chat/completions; // 屏幕初始化I2C地址0x3C U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8C8_PIN_NONE, 21, 22); // 按键引脚 #define BUTTON_ASK 0 #define BUTTON_NEXT 14 String lastQuestion 你好介绍一下你自己; String lastAnswer ; void connectWiFi() { Serial.print(连接WiFi); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); int retry 0; while (WiFi.status() ! WL_CONNECTED retry 30) { delay(500); Serial.print(.); retry; } if (WiFi.status() WL_CONNECTED) { Serial.println(\n连接成功IP地址: WiFi.localIP().toString()); } else { Serial.println(\nWiFi连接失败请检查配置); } }HTTPS请求封装这里是最关键的部分。ESP32-S3发送HTTPS请求需要TLS握手用WiFiClientSecure的安全模式时要么安装证书、要么用setInsecure()跳过证书验证。出于内存和开发效率考虑第一版我用的是setInsecure()但这里必须说明生产环境绝对不能这么干有中间人攻击风险。个人项目玩一玩没问题做产品必须加载服务器证书。String callDeepSeek(String userMessage) { WiFiClientSecure client; client.setInsecure(); // 个人项目跳过证书验证生产环境请替换为正式证书 if (!client.connect(API_HOST, 443)) { return 连接API服务器失败; } // 构造请求体 StaticJsonDocument1024 requestBody; requestBody[model] deepseek-chat; requestBody[stream] false; JsonArray messages requestBody.createNestedArray(messages); JsonObject systemMsg messages.createNestedObject(); systemMsg[role] system; systemMsg[content] 你是一个嵌入式AI助手回答要简洁控制在100字以内。; JsonObject userMsg messages.createNestedObject(); userMsg[role] user; userMsg[content] userMessage; String payload; serializeJson(requestBody, payload); // 构造HTTP请求 client.println(POST String(API_PATH) HTTP/1.1); client.println(Host: String(API_HOST)); client.println(Content-Type: application/json); client.println(Authorization: Bearer String(API_KEY)); client.println(Content-Length: String(payload.length())); client.println(Connection: close); client.println(); client.println(payload); // 等待响应设置超时 unsigned long timeout millis() 30000; while (!client.available() millis() timeout) { delay(10); } if (millis() timeout) { return 请求超时; } // 读取响应 String response; while (client.available()) { response client.readString(); } client.stop(); return extractContent(response); }响应解析HTTP响应里包含了状态行、响应头和JSON正文。我们需要的是JSON里的choices[0].message.content字段。用ArduinoJson来解析注意先要跳过HTTP头String extractContent(String httpResponse) { // 找到空行跳过HTTP响应头 int headerEnd httpResponse.indexOf(\r\n\r\n); if (headerEnd -1) headerEnd httpResponse.indexOf(\n\n); String jsonBody httpResponse.substring(headerEnd 4); // 解析JSON JsonDocument doc; DeserializationError error deserializeJson(doc, jsonBody); if (error) { Serial.print(JSON解析失败: ); Serial.println(error.c_str()); return JSON解析失败: String(error.c_str()); } const char* content doc[choices][0][message][content]; if (content nullptr) { return 未找到回答内容; } return String(content); }主循环与按键交互void setup() { Serial.begin(115200); pinMode(BUTTON_ASK, INPUT_PULLUP); pinMode(BUTTON_NEXT, INPUT_PULLUP); u8g2.begin(); displayText(正在初始化...); connectWiFi(); displayText(WiFi已连接按左键提问); } void loop() { if (digitalRead(BUTTON_ASK) LOW) { delay(50); // 简单消抖 if (digitalRead(BUTTON_ASK) LOW) { askQuestion(); while (digitalRead(BUTTON_ASK) LOW) delay(10); // 等待释放 } } } void askQuestion() { displayText(正在思考...); Serial.println(问题: lastQuestion); String answer callDeepSeek(lastQuestion); lastAnswer answer; Serial.println(回答: answer); displayAnswer(lastQuestion, answer); }3.3 代码里容易踩的坑这段代码我调试的时候被三个问题卡过很久值得单独拿出来说。第一个坑StaticJsonDocument的容量。7.x版本用JsonDocument统一了动态和静态的实现但如果你用的是6.xStaticJsonDocument1024这里的1024字节约等于1024字节。问题描述system提示词整体可能超过这个大小结果就是serializeJson返回false请求体为空。我的经验是请求体缓冲区至少给2048因为URL编码后的中文字符会膨胀。第二个坑client.available()的判断。DeepSeek的响应速度受模型负载影响快的时候1秒慢的时候可能20秒。用client.available()判断数据到达在等待期间不能阻塞太久。我上面用了30秒超时实测足够。但注意如果你用client.readString()去读全部响应它会把响应头也读进去所以解析前必须先跳过头否则deserializeJson会报错。第三个坑Wi-Fi重连逻辑。ESP32S3在长时间运行后Wi-Fi可能断开这时候直接发API请求会失败。我后来加了自动重连每次请求前检查WiFi.status()如果不等于WL_CONNECTED就重新执行connectWiFi()。这个习惯从那时起保留到了我所有ESP32项目里。3.4 语音交互扩展思路如果想进阶做语音对话这也是我下一步的方向流程会变成按键 → INMP441录音 → 语音识别云端ASR→ 得到文字 → 调用DeepSeek → 得到回答 → TTS语音合成 → MAX98357A播放注意语音识别和TTS也都在云端做ESP32S3只负责采集和播放。这个链路里最大的风险是延迟叠加实测完整一轮下来可能超过10秒体验很糟糕。优化思路是引入本地唤醒词比如用ESP-SR库的WakeNet本地识别到小助手再启动云端链路。这套方案是ESP32S3 AI应用里最经典的落地组合等我把音频链路调通再单独写一篇。4. 常见问题与排查技巧实录4.1 问题速查表现象可能原因解决方法烧录失败/串口无输出没有按住BOOT键或驱动没装按住BOOT键再点烧录安装CP210x/CH340驱动WiFi连不上SSID中文名、5G频段不支持改用2.4G频段SSID临时改为英文数字返回连接API服务器失败网络问题/TLS握手失败检查网络确认client.connect返回值为true返回JSON解析失败响应头没跳过或响应被截断检查headerEnd定位逻辑增大超时时间屏幕显示乱码OLED驱动初始化参数不对确认I2C地址0x3C还是0x3D用I2C扫描确认提问后长时间无响应API Key无效或额度用完检查串口打印的HTTP状态码401就是Key问题回答被截断显示不全PSRAM未启用或显示缓冲不足开发板配置里确认PSRAM为OPI PSRAMU8g2使用全缓冲模式4.2 排查思路分享遇到问题不要瞎试用串口打印的状态码定位问题最靠谱。我加了一行调试代码client.println(User-Agent: ESP32S3-Client/1.0);虽然不解决实际问题但有些API服务会校验User-Agent提前加上能减少一个变量。再说说HTTP状态码的定位思路。收到401说明API Key不对收到429说明触发了限流收到400基本是JSON格式有误。串口监视器里务必打印出完整的HTTP响应头和状态行格式问题一看便知。注意如果响应体特别长超过1-2KBESP32S3的串口缓冲和String对象拼接会非常吃内存。建议在代码里做保护如果响应超过4000字符用reserve()预分配String容量避免频繁realloc导致堆碎片。4.3 内存优化实战这块算是我反复调整最多的部分。ESP32S3虽然有512KB SRAM 8MB PSRAM但Arduino环境默认把PSRAM当扩展RAM用不会自动把大对象放进去。实测跑一轮完整请求的内存峰值WiFiClientSecure TLS握手的RAM开销约70KBJSON请求体 响应体 String拼接约15-30KBU8g2全缓冲模式8KB整体峰值在100KB上下SRAM余量还算够。但如果你的代码里还有其他任务传感器读取、MQTT等就需要注意用ps_malloc()分配PSRAM内存来存大String。关于PSRAM的分配有个简单办法String没提供底层分配接口我会用char* buf (char*)ps_malloc(4096);配合snprintf和sprintf来组装数据把SRAM留给系统调用。4.4 API调用策略建议调用DeepSeek API时我建议在请求头里加上Accept-Encoding: identity显式禁用gzip压缩。虽然服务器支持的gzip能让响应体小一些但ESP32S3的HTTP客户端对gzip解码支持不完善解压失败反而得不偿失。另外就是提问频率控制。DeepSeek免费额度有限如果loop循环里忘记加while(digitalRead(BUTTON_ASK)LOW)等待释放按键抖动可能导致一次按下触发多次请求白白消耗额度。消抖逻辑不能省。5. 项目扩展与后续优化5.1 让回答更好用的提示词工程这个项目的灵魂其实在system提示词。我试了几组不同配置效果差异很大提示词回答风格体验评价空详细的教科书式回答太长屏幕显示体验差回答简洁中等长度偶尔啰嗦可用但还不够稳定你是桌面助手回答不超过50字带序号精炼务实最优适合屏幕展示建议在system里把角色、回答长度、格式、语言这四个维度都约束清楚。实测一句回答控制在100字以内使用中文分点列出之后回答质量稳定很多也省token。5.2 显示优化OLED屏幕只有128x64直接显示长文本需要滚动。我实现了一个简单的分页显示逻辑把回答按每屏四行切块按BUTTON_NEXT翻页。关键技巧是按UTF-8字符串做切分时要看字符边界不能截在半个中文字符中间。判断方法是字符的UTF-8编码首字节如果大于0x7F说明是中文多字节字符需要完整复制3个字节再切。5.3 更远的可能性这个项目本质上打通了MCU 云端大模型API的任督二脉玩法可以很多接上DHT22温湿度传感器让模型帮你分析数据含义接入本地MQTT做成家庭智能中枢把ESP32S3放到机器人底盘上模型变成机器人的大脑来规划路径。另外很多朋友也提到了SDIO驱动TF卡我可以做个离线提示词库和日志系统把每次问答都存到TF卡里跑几天后就能做数据分析。最后再分享一个我个人很受用的调试习惯每次改动代码之前先给当前版本拍个照Git提交出问题随时回退。嵌入式开发里改一处其他功能挂掉的情况太常见了特别是Arduino这种以宏定义为主的环境变量名冲突、库版本不对都能让你莫名其妙调一晚上。项目虽小版本管理习惯一定要跟上。本文还有配套的精品资源点击获取