【前言】
目前在拥抱AI的时代,我们的单片机如何也能跟上时代的潮流,体验如何与国产大模型deepseek交谈。笔者就此做了探讨,成功的在STM32F769上对接了deepseek本地大模型,实现了无缝交流。
【程序框架】
根据本地大模型的搭建,以及STM32F769的编程,我设计了程序框图如下:

1、在STM32F769中使用nr_micro_sheel用来与用户做交流,用户可以使用指令来输入想要与模型交流的指令,同时也通过shell来展示,模型反馈的结果。
2、在shell获取用户交互指令后,由lwip来实现对数据的转发,把指令通过http_post到转发服务端,同时接收反馈的数据,反馈给shell用于展示。
3、在服务端,使用转发服务,转发用户的指令,重新包装好请示指领,同时在获取用户指令后,把用户关心的数据过滤出来,转发给STM32F769。
【程序实现】
1、 添加shell指令,在shell_uart.c中,编写用于shell用户输入的数据,使用 const char *data = &(argv[argv[1]]);
来提取用户指令。然后通过send_http_post_request(data);来把指令通过http post到服务器。
void shll_http_send_response(char argc, char *argv)
{
if (argc < 2)
{
shell_printf("Usage: http <data>\r\n");
return;
}
const char *data = &(argv[argv[1]]);
if (data == NULL || strlen(data) == 0)
{
shell_printf("Error: Data is empty or NULL\n");
return;
}
send_http_post_request(data);
}2、在指令集中添加代码:

3、
编写app_http.c/h
在程中,首先创建一个tcp连接,使用netconn_connect(conn, &server_ip, SERVER_PORT);连接到转发服务器。使用char post_data[256];
snprintf(post_data, sizeof(post_data), "{\"prompt\":\"%s\"}", data);
来拼接请求的数据。再通过组装post字符串,通过netconn_write(conn, request, strlen(request), NETCONN_COPY);
把请求数据发送给服务端
最后增加返回处理,其完成代码如下:
#include "lwip/api.h"
#include "lwip/err.h"
#include "lwip/sys.h" // 添加此头文件以使用 sys_timeout
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "shell_uart.h"
#define SERVER_IP "192.168.3.231" // 替换为实际服务器IP
#define SERVER_PORT 8000
#define POST_URL "/api/generate" // 替换为实际的POST URL
int count = 0;
// 处理接收到的数据
void handle_response(struct netconn *conn)
{
struct netbuf *inbuf;
err_t err;
char *buf;
u16_t buflen;
int is_chunked = false;
char *chunk_size_str;
int chunk_size = 0;
// 读取响应头
while (true)
{
err = netconn_recv(conn, &inbuf);
if (err != ERR_OK)
{
shell_printf("Failed to receive response header, error: %d\n", err);
return;
}
netbuf_data(inbuf, (void **)&buf, &buflen);
// 检查是否为分块传输编码
if (strstr(buf, "Transfer-Encoding: chunked") != NULL)
{
is_chunked = true;
}
// 打印响应头
for (int i = 0; i < buflen; i++)
{
// shell_printf("%c", buf[i]);
if (buf[i] == '\n')
{
break;
}
}
netbuf_delete(inbuf);
// 如果遇到空行,表示响应头结束
if (buflen > 0 && buf[buflen - 2] == '\r' && buf[buflen - 1] == '\n')
{
break;
}
}
// 处理分块传输编码的响应体
if (is_chunked)
{
while (true)
{
count++;
// 读取块大小
err = netconn_recv(conn, &inbuf);
if (err != ERR_OK)
{
// 修改为继续接收数据直到连接关闭
if (err == ERR_CLSD)
{
shell_printf("Connection closed by server\n");
break; // 连接关闭,结束接收
}
else
{
shell_printf("Failed to receive response body, error: %d\n", err);
return;
}
}
netbuf_data(inbuf, (void **)&buf, &buflen);
// 解析块大小
chunk_size_str = strtok(buf, "\r\n");
if (chunk_size_str == NULL)
{
shell_printf("Invalid chunk size format\n");
netbuf_delete(inbuf);
return;
}
chunk_size = (int)strtol(chunk_size_str, NULL, 16);
netbuf_delete(inbuf);
// 如果块大小为0,表示所有块已接收完毕
if (chunk_size == 0)
{
shell_printf("All chunks received\n");
break;
}
// 读取并打印块数据
char *chunk_buf = (char *)malloc(chunk_size + 1);
if (chunk_buf == NULL)
{
shell_printf("Memory allocation failed\n");
return;
}
err = netconn_recv(conn, &inbuf);
if (err != ERR_OK)
{
shell_printf("Failed to receive chunk data, error: %d\n", err);
free(chunk_buf);
return;
}
netbuf_data(inbuf, (void **)&buf, &buflen);
memcpy(chunk_buf, buf, chunk_size);
chunk_buf[chunk_size] = '\0';
shell_printf("%s\r\n", chunk_buf);
free(chunk_buf);
netbuf_delete(inbuf);
// 跳过块末尾的CRLF
err = netconn_recv(conn, &inbuf);
if (err != ERR_OK)
{
shell_printf("Failed to receive CRLF after chunk, error: %d\n", err);
return;
}
netbuf_delete(inbuf);
}
}
else
{
// 处理非分块传输编码的响应体
char *response_buf = NULL;
size_t response_size = 0;
size_t total_received = 0;
while (true)
{
err = netconn_recv(conn, &inbuf);
if (err != ERR_OK)
{
// 修改为继续接收数据直到连接关闭
if (err == ERR_CLSD)
{
break; // 连接关闭,结束接收
}
else
{
shell_printf("Failed to receive response body, error: %d\n", err);
free(response_buf);
return;
}
}
netbuf_data(inbuf, (void **)&buf, &buflen);
// 调整缓冲区大小
char *new_buf = (char *)realloc(response_buf, total_received + buflen + 1);
if (new_buf == NULL)
{
shell_printf("Memory allocation failed\n");
free(response_buf);
return;
}
response_buf = new_buf;
memcpy(response_buf + total_received, buf, buflen);
total_received += buflen;
response_buf[total_received] = '\0';
netbuf_delete(inbuf);
}
shell_printf("%s\r\n", response_buf);
free(response_buf);
}
// 关闭连接
if (conn != NULL)
{
// shell_printf("Closing connection\n");
// netconn_close(conn);
// shell_printf("Deleting connection\n");
// netconn_delete(conn);
}
}
// 发送HTTP POST请求
void send_http_post_request(const char *data)
{
struct netconn *conn;
err_t err;
ip_addr_t server_ip;
// 将字符串IP地址转换为ip_addr_t类型
IP4_ADDR(&server_ip, 192, 168, 3, 231); // 替换为实际服务器IP
// 创建一个TCP连接
conn = netconn_new(NETCONN_TCP);
if (conn != NULL)
{
// 连接到服务器
err = netconn_connect(conn, &server_ip, SERVER_PORT);
if (err == ERR_OK)
{
// 构建HTTP POST请求
char request[4096];
char post_data[256]; // 假设data不会超过256个字符
snprintf(post_data, sizeof(post_data), "{\"prompt\":\"%s\"}", data);
size_t data_length = strlen(post_data);
snprintf(request, sizeof(request),
"POST %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n"
"\r\n"
"%s",
POST_URL, SERVER_IP, SERVER_PORT, data_length, post_data);
// 打印构建的 HTTP 请求以进行调试
shell_printf("HTTP Request:\n%s\n", request);
// 发送HTTP POST请求
err = netconn_write(conn, request, strlen(request), NETCONN_COPY);
if (err == ERR_OK)
{
// 处理响应
handle_response(conn);
}
else
{
shell_printf("Failed to send HTTP POST request\n");
}
}
else
{
shell_printf("Failed to connect to server\n");
}
// 关闭连接
if (conn != NULL)
{
// shell_printf("Closing connection\n");
netconn_close(conn);
// shell_printf("Deleting connection\n");
netconn_delete(conn);
}
}
else
{
shell_printf("Failed to create TCP connection\n");
}
}最后通过shell把数据打印到串口终端之上,完成一次交互。
【实现效果】

由于本地模型是最小的,只能玩玩。如果需要更加准确的,还是得用api做在线的提问。
我要赚赏金
