【前言】
目前在拥抱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做在线的提问。