STM32F103C8T6 作为一款经典的入门级微控制器,本身不具备运行大型 AI 模型(如 LLM 或 Stable Diffusion)的计算能力。但在物联网应用中,我们常将其作为"边缘控制端",通过连接 Wi-Fi 模块(如 ESP8266)或以太网模块,调用云端 AI API(如文本生成、图像识别、文本转视频等)来实现智能化功能。
以下将以"通过 ESP8266 模块调用云端 AI 接口"为例,手把手教你实现 STM32 与 AI 的交互。
一、 硬件准备与连接方案
在开始代码编写前,需要确保硬件连接正确。我们通常使用 USART(串口) 进行 STM32 与 ESP8266 的通信。
| 模块 | 引脚 | STM32F103C8T6 引脚 | 功能说明 |
|---|---|---|---|
| ESP8266 | TX | PA10 (RX1) | ESP8266 发送,STM32 接收 |
| ESP8266 | RX | PA9 (TX1) | STM32 发送,ESP8266 接收 |
| ESP8266 | VCC | 3.3V | 供电(注意电流需求,建议独立供电或 robust 的 3.3V 源) |
| ESP8266 | GND | GND | 共地 |
| STM32 | - | USB | 程序下载与调试串口 |
二、 整体交互逻辑架构
STM32 无法直接处理复杂的 HTTPS 请求(由于 TLS 握手消耗资源极大),因此通常采用透传模式:
- 指令下发:STM32 通过 AT 指令配置 ESP8266 连接 Wi-Fi。
- 建立连接:ESP8266 与 AI 服务器建立 TCP/SSL 连接。
- 数据传输:STM32 将格式化的 JSON 数据通过串口发给 ESP8266,ESP8266 转发至服务器。
- 结果接收:ESP8266 接收服务器响应,通过串口回传给 STM32。
三、 详细代码实现
本示例使用标准库,演示如何构造一个简单的 HTTP POST 请求来调用 AI 接口(例如调用 Wan2.2-T2V-5B 这类文本生成视频的 API )。
- 串口初始化配置
我们需要初始化两个串口:USART1 用于调试打印,USART2 用于连接 ESP8266。
c
#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
// 定义 ESP8266 使用的串口
#define ESP_USART USART2
#define ESP_USART_RX_BUF_SIZE 256
volatile char esp_rx_buf[ESP_USART_RX_BUF_SIZE];
volatile u16 esp_rx_ptr = 0;
// 简单的延时函数
void Delay_ms(uint32_t ms) {
uint32_t i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 9000; j++);
}
// 串口2初始化 (连接 ESP8266)
void USART2_Init(u32 baudrate) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
// TX (PA9) 配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX (PA10) 配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART2 配置
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART2, &USART_InitStructure);
// 中断配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
USART_Cmd(USART2, ENABLE);
}
// 串口1初始化 (调试打印)
void USART1_Init(u32 baudrate) {
// 类似 USART2 初始化代码,此处省略,假设 PA9(TX) PA10(RX) 已配置好用于 printf 重定向
// 实际项目中请确保实现了 fputc 函数用于 printf
// ...
}
// 发送一个字节到 ESP8266
void ESP_SendByte(u8 data) {
while(USART_GetFlagStatus(ESP_USART, USART_FLAG_TC) == RESET);
USART_SendData(ESP_USART, data);
}
// 发送字符串到 ESP8266
void ESP_SendStr(char *str) {
while(*str) {
ESP_SendByte(*str++);
}
}
// USART2 中断接收服务函数
void USART2_IRQHandler(void) {
if(USART_GetITStatus(ESP_USART, USART_IT_RXNE) != RESET) {
u8 res = USART_ReceiveData(ESP_USART);
// 将接收到的数据存入缓冲区,实际应用需处理缓冲区溢出
if(esp_rx_ptr < ESP_USART_RX_BUF_SIZE) {
esp_rx_buf[esp_rx_ptr++] = res;
}
USART_ClearITPendingBit(ESP_USART, USART_IT_RXNE);
}
}
- 调用 AI API 的核心逻辑
这里的关键在于构造符合 HTTP 协议的数据包。假设我们要调用一个 API,其 URL 为 http://api.example.com/ai/generate,需要发送 JSON 数据。
c
// 调用 AI 接口的核心函数
void Call_AI_API(char* api_key, char* prompt_text) {
char http_header[512];
// 1. 发送 AT 指令建立连接 (假设已配置好 Wi-Fi)
// 格式: AT+CIPSTART="TCP","域名",端口
ESP_SendStr("AT+CIPSTART=\"TCP\",\"api.example.com\",80\r
");
Delay_ms(1000); // 等待连接建立
// 2. 构造 HTTP POST 请求包
// 注意:这里需要根据具体的 API 文档调整 JSON 内容和 Content-Length
sprintf(http_header,
"POST /ai/generate HTTP/1.1\r
"
"Host: api.example.com\r
"
"Content-Type: application/json\r
"
"Authorization: Bearer %s\r
" // 如果需要鉴权
"Content-Length: %d\r
"
"\r
"
"{\"prompt\": \"%s\"}",
api_key,
strlen(prompt_text) + 13, // {"prompt": "} + prompt_text + "} 的长度估算,实际需精确计算
prompt_text);
// 3. 发送 AT 指令准备发送数据
char cmd_len[20];
sprintf(cmd_len, "AT+CIPSEND=%d\r
", strlen(http_header));
ESP_SendStr(cmd_len);
Delay_ms(500); // 等待收到 >
// 4. 发送实际的 HTTP 数据
ESP_SendStr(http_header);
// 5. 等待响应 (实际应用中应在中断或主循环中解析 esp_rx_buf)
printf("Request sent. Waiting for response...\r
");
}
- 主函数流程
c
int main(void) {
// 系统初始化
USART1_Init(115200); // 调试串口
USART2_Init(115200); // ESP8266 串口,默认波特率通常为 115200 或 9600
printf("System Start.\r
");
// ESP8266 初始化流程 (复位、检查波特率、连接 Wi-Fi)
// ESP_SendStr("AT+RST\r
");
// ... (此处省略具体的 AT 指令交互流程,请参考 ESP8266 指令集)
while(1) {
// 模拟触发条件:例如按键按下或定时器到期
// 假设我们要生成一段关于"未来城市"的视频描述
Call_AI_API("MY_SECRET_API_KEY", "A futuristic city with flying cars");
Delay_ms(5000); // 简单的防抖,避免频繁请求
}
}
四、 关键步骤详解与避坑指南
- 数据包长度的计算
在 AT+CIPSEND 指令中,必须准确告知 ESP8266 即将发送的数据字节数。
- 错误做法:随便写一个数字。
- 正确做法 :在代码中先使用
strlen()计算出完整的 HTTP Header 和 Body 的总长度,再发送 CIPSEND 指令。如果长度不对,服务器可能会拒绝请求或导致下一次通信错乱。
- JSON 格式化
STM32 的 sprintf 处理复杂的 JSON 字符串时容易出错。
- 转义字符 :JSON 中的双引号
"在 C 语言字符串中需要写成\"。 - 浮点数:如果需要发送传感器数值(如温度 25.5),注意浮点数的格式化精度。
- 接收解析(难点)
服务器返回的数据通常是分多次到达串口的。
- 不要在中断里直接处理复杂逻辑。
- 应该 在中断里将字节填入环形缓冲区,在主循环中检测缓冲区里是否包含完整的 HTTP 响应包(例如查找
\r \r分隔符或特定的结束符)。
- HTTPS 支持
如果 AI API 强制使用 HTTPS(端口 443),标准的 ESP8266 固件处理起来非常吃力,甚至需要外挂 SSL 芯片。
- 建议:初学者先寻找支持 HTTP (端口 80) 的 API,或者使用 ESP8266 的透传云服务作为中间层,由中间层负责 HTTPS 转发。
五、 进阶:MQTT 协议调用 AI
除了直接 HTTP POST,在物联网场景下,使用 MQTT 协议与 AI 云平台交互是更稳定的选择 。
- 原理:STM32 通过 AT 指令配置 ESP8266 连接到 MQTT Broker(如 EMQX)。
- 流程 :STM32 发布一条消息到特定 Topic(例如
/request/ai),云端服务订阅该 Topic,处理完后将结果发布到/response/ai,STM32 订阅该 Topic 即可获取结果。这种方式连接更稳定,适合长期在线的设备 。
通过以上步骤,即使是嵌入式小白,也能通过 STM32 搭建起通往 AI 世界的桥梁,实现诸如"语音控制生成视频"、"环境数据智能分析"等酷炫应用。