- - GPIO_InitTypeDef GPIO_InitStructure;
- - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
- - GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
- - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_15;
- - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- - GPIO_Init(GPIOA, &GPIO_InitStructure);
- - GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_15);
- + rcu_periph_clock_enable(RCU_GPIOA);
- + rcu_periph_clock_enable(RCU_GPIOB);
- + (GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_12);
- + (GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3|GPIO_PIN_4);
- OLED_RES_Clr();
- - delay_ms(200);
- + sys_us_delay(200000);
- OLED_RES_Set();
然后修改 oled.h:
- + #include "gd32vw55x.h"
- #include "sys.h"
- #include "stdlib.h"
- //-----------------OLED端口定义----------------
- - #define OLED_SCL_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_0)//SCL
- - #define OLED_SCL_Set() GPIO_SetBits(GPIOA,GPIO_Pin_0)
- -
- - #define OLED_SDA_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_1)//SDA
- - #define OLED_SDA_Set() GPIO_SetBits(GPIOA,GPIO_Pin_1)
- -
- - #define OLED_RES_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_2)//RES
- - #define OLED_RES_Set() GPIO_SetBits(GPIOA,GPIO_Pin_2)
- -
- - #define OLED_DC_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_3)//DC
- - #define OLED_DC_Set() GPIO_SetBits(GPIOA,GPIO_Pin_3)
- -
- - #define OLED_CS_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_4)//CS
- - #define OLED_CS_Set() GPIO_SetBits(GPIOA,GPIO_Pin_4)
- + #define OLED_SCL_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_10) //SCL
- + #define OLED_SCL_Set() gpio_bit_set(GPIOA,GPIO_PIN_10)
- +
- + #define OLED_SDA_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_9) //SDA
- + #define OLED_SDA_Set() gpio_bit_set(GPIOA,GPIO_PIN_9)
- +
- + #define OLED_RES_Clr() gpio_bit_reset(GPIOA,GPIO_PIN_12) //RES
- + #define OLED_RES_Set() gpio_bit_set(GPIOA,GPIO_PIN_12)
- +
- + #define OLED_DC_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_3) //DC
- + #define OLED_DC_Set() gpio_bit_set(GPIOB,GPIO_PIN_3)
- +
- + #define OLED_CS_Clr() gpio_bit_reset(GPIOB,GPIO_PIN_4) //CS
- + #define OLED_CS_Set() gpio_bit_set(GPIOB,GPIO_PIN_4)
- +
- + #define u8 uint8_t
- + #define u16 uint16_t
- + #define u32 uint32_t
MCU 端源码启动软 AP 并启动 http server 接收 POST 请求,将 POST 收到的数据显示在屏幕上 —— 程序运行后将启动一个软 AP(可以在屏幕上看到 SSID 和密码),可使用其他设备连接
这里规定传输协议为 协议头+二进制图片,协议头为四字节的图片大小和显示坐标 uint8_t[4] = {img_w, img_h, offset_x, offset_y}
- #ifdef IFNAMSIZ
- #undef IFNAMSIZ
- #endif
- #define IFNAMSIZ NETIF_NAMESIZE
- #include <stdint.h>
- #include <stdio.h>
- #include "app_cfg.h"
- #include "gd32vw55x_platform.h"
- #include "wifi_management.h"
- #include "wifi_init.h"
- #include "lwip/sockets.h"
- #include "lwip/sys.h"
- #include "lwip/netdb.h"
- #include "oled.h"
- #include "bmp.h"
- // Soft AP 相关
- #define AP_SSID "GD32_AP"
- #define AP_PASSWORD "GD32666666"
- #define HTTP_PORT 80
- // 解析收到的JSON,-1为错误
- int get_json_num(char *json, char *key) {
- // 构造查找的键字符串,例如 "cmd":"
- char search_key[256];
- snprintf(search_key, sizeof(search_key), ""%s":", key);
- // 在 JSON 字符串中查找键
- char *key_start = strstr(json, search_key);
- if (key_start == NULL) {
- printf("JSON data does not contain '%s' field.\n", key);
- return -1; // 返回错误码
- }
- // 跳过键名和冒号,例如跳过 "cmd":"
- key_start += strlen(search_key);
- // 跳过可能存在的空格
- while (*key_start == ' ') {
- key_start++;
- }
- // 找到键值的结束位置(假设键值是数字,且后面紧跟逗号或右大括号)
- char *key_end = strchr(key_start, ',');
- if (key_end == NULL) {
- key_end = strchr(key_start, '}');
- if (key_end == NULL) {
- printf("Invalid JSON format.\n");
- return -1; // 返回错误码
- }
- }
- // 提取键值
- int value = 0;
- if (sscanf(key_start, "%d", &value) != 1) {
- printf("Failed to parse '%s' value.\n", key);
- return -1; // 返回错误码
- }
- return value; // 返回解析的整数值
- }
- int get_param(const char* input, const char* key, char* value) {
- char search_key[64] = {0};
- snprintf(search_key, sizeof(search_key), "%s=", key);
- const char* start = strstr(input, search_key);
- if (start != NULL) {
- start += strlen(search_key); // 跳过 "key="
- const char* end = strchr(start, '&');
- if (end != NULL) {
- // 如果找到 & 分隔符,复制到分隔符位置
- size_t len = end - start;
- memcpy(value, start, len);
- value[len] = '\0';
- } else {
- // 如果没找到 &,说明是最后一个参数,直接复制到末尾
- strcpy(value, start);
- }
- return true;
- }
- return false;
- }
- static void http_server_netconn_serve(int conn_fd) {
- char buffer[1024];
- int len = read(conn_fd, buffer, sizeof(buffer) - 1);
- if (len > 0) {
- buffer[len] = '\0';
- // printf("Received request:\n%s\n", buffer);
- // 判断请求方法
- if (strncmp(buffer, "GET", 3) == 0) {
- // GET 请求
- const char *response =
- "HTTP/1.1 200 OK\r\n"
- "Content-Type: text/html\r\n"
- "Connection: close\r\n"
- "\r\n"
- "<html><head>"
- "<title>GD32Vw55x-IoT</title><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />"
- "<style>body{margin:0;padding:20px;background-color:#f0f0f0;}h1{text-align:center;}"
- "form{margin:0 auto;background-color:#fff;padding: 20px;border-radius:5px;}"
- "div{margin-bottom:15px;}p{color:gray;font-size:10px;text-align:center;}"
- "input[type='text']{width:100%;padding:8px;border:1px solid #ddd;}"
- "input[type='submit']{width:100%;padding:10px;background-color:#d3d3d3;color:#fff;border:none;}"
- "input[type='submit']:hover{background-color: #808080;}</style></head>"
- "<body><h1>GD32Vw55x-IoT</h1>"
- "<p>Hello, world!</p>"
- "</body></html>";
- write(conn_fd, response, strlen(response));
- printf("HTTP response sent.\n");
- } else if (strncmp(buffer, "POST", 4) == 0) {
- // POST 请求
- char *content_type = strstr(buffer, "Content-Type: ");
- if (content_type != NULL) {
- content_type += 13; // 跳过 "Content-Type: "
- if (strstr(content_type, "application/octet-stream") != NULL) {
- char *body = strstr(buffer, "\r\n\r\n");
- if (body != NULL) {
- body += 4;
- uint8_t img[1024];
- uint8_t w = body[0];
- uint8_t h = body[1];
- uint8_t x = body[2];
- uint8_t y = body[3];
- body += 4;
- memcpy(img, body, 1016);
- OLED_Clear();
- OLED_ShowPicture(x, y, w, h, img, 1);
- OLED_Refresh();
- const char *response =
- "HTTP/1.1 200 OK\r\n"
- "Content-Type: application/json\r\n"
- "Connection: close\r\n"
- "\r\n"
- "{"err": "0"}";
- write(conn_fd, response, strlen(response));
- }
- }
- }
- } else {
- // 其他请求方法,返回 405 Method Not Allowed
- const char *response =
- "HTTP/1.1 405 Method Not Allowed\r\n"
- "Content-Type: text/html\r\n"
- "Connection: close\r\n"
- "\r\n"
- "<html><head><title>405 Method Not Allowed</title></head>"
- "<body><h1>405 Method Not Allowed</h1></body></html>";
- write(conn_fd, response, strlen(response));
- printf("HTTP response sent.\n");
- }
- } else {
- printf("No data received.\n");
- }
- close(conn_fd);
- printf("Client connection closed.\n");
- }
- static void http_server_task(void *arg) {
- int listen_fd, conn_fd;
- struct sockaddr_in server_addr, client_addr;
- socklen_t client_addr_len = sizeof(client_addr);
- // 创建 socket
- listen_fd = socket(AF_INET, SOCK_STREAM, 0);
- if (listen_fd < 0) {
- printf("Failed to create socket.\n");
- return;
- }
- printf("Socket created successfully.\n");
- // 配置 socket 地址
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(HTTP_PORT);
- server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- // 绑定 socket
- if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
- printf("Failed to bind socket.\n");
- close(listen_fd);
- return;
- }
- printf("Socket bound to port %d.\n", HTTP_PORT);
- // 监听连接
- if (listen(listen_fd, 5) < 0) {
- printf("Failed to listen on socket.\n");
- close(listen_fd);
- return;
- }
- printf("HTTP server is listening on port %d\n", HTTP_PORT);
- while (1) {
- // 接受连接
- conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len);
- if (conn_fd < 0) {
- printf("Failed to accept connection.\n");
- continue;
- }
- printf("New client connected. IP: %s\n", inet_ntoa(client_addr.sin_addr));
- // 处理 HTTP 请求
- http_server_netconn_serve(conn_fd);
- }
- }
- static void wifi_ap_task(void *param) {
- // 启动软 AP
- printf("Starting soft AP...\n");
- if (wifi_management_ap_start((char *)AP_SSID, (char *)AP_PASSWORD, 1, AUTH_MODE_WPA, 0) != 0) {
- printf("Failed to start soft AP.\n");
- goto exit;
- }
- printf("Soft AP started. SSID: %s, Password: %s\n", AP_SSID, AP_PASSWORD);
- // 启动 HTTP 服务器
- printf("Starting HTTP server...\n");
- http_server_task(NULL);
- exit:
- printf("The ap task has ended.\n");
- sys_task_delete(NULL);
- }
- int main(void)
- {
- platform_init();
- // 初始化 OLED
- OLED_Init();
- OLED_ColorTurn(0);
- OLED_DisplayTurn(0);
- OLED_ShowString(6, 6, (uint8_t *)"SSID:", 8, 1);
- OLED_ShowString(45, 6, (uint8_t *)AP_SSID, 8, 1);
- OLED_ShowString(6, 26, (uint8_t *)"PASS:", 8, 1);
- OLED_ShowString(45, 26, (uint8_t *)AP_PASSWORD, 8, 1);
- // OLED_ShowPicture(0, 0, 128, 64, (int8_t *)BMPt, 1);
- OLED_Refresh();
- if (wifi_init()) {
- printf("wifi init failed.\r\n");
- }
- sys_task_create_dynamic((const uint8_t *)"WiFi AP", 4096, OS_TASK_PRIORITY(0), wifi_ap_task, NULL);
- sys_os_start();
- for ( ; ; );
- }
Python 上位机Python 上位机负责对图片进行处理,处理后 POST 发送
- import socket, time, sys, argparse, platform, subprocess
- from PIL import Image
- import numpy as np
- from skimage import filters, morphology
- HOST = "192.168.237.1"
- PORT = 80
- GIF_FILE = "7f7f601fd877889b.gif"
- POST_URL = "/"
- # 屏幕上限
- MAX_W = 127
- MAX_H = 64
- def scale_keep_ratio(im: Image.Image) -> Image.Image:
- ow, oh = im.size
- ratio = min(MAX_W / ow, MAX_H / oh)
- return im.resize((int(ow * ratio), int(oh * ratio)), Image.LANCZOS)
- def clean_binarize(im: Image.Image) -> Image.Image:
- im = im.convert('RGB')
- bg_samples = [
- np.array(im.crop((0, 0, 20, 20))),
- np.array(im.crop((im.width - 20, 0, im.width, 20))),
- np.array(im.crop((0, im.height - 20, 20, im.height))),
- np.array(im.crop((im.width - 20, im.height - 20, im.width, im.height)))
- ]
- bg_color = np.mean(bg_samples, axis=(0, 1, 2)) # RGB 背景均值
- img_arr = np.array(im)
- diff = np.linalg.norm(img_arr - bg_color, axis=2)
- fg_mask = diff > 15 # 阈值可微调
- fg_gray = np.array(im.convert('L')) * fg_mask
- otsu = filters.threshold_otsu(fg_gray[fg_mask > 0])
- binary = (fg_gray > otsu) & fg_mask
- binary = morphology.remove_small_objects(binary, min_size=64)
- return Image.fromarray(binary.astype(np.uint8) * 255).convert('1')
- def pack_real_size(bmp: Image.Image) -> tuple[bytes, int, int]:
- w, h = bmp.size
- cols = w
- rows = (h + 7) // 8 # 每8行一组,向上取整
- buf = bytearray(cols * rows) # 总字节数
- for c in range(cols): # 逐列
- for r8 in range(rows): # 每8行一组
- byte = 0
- for dy in range(8):
- y = r8 * 8 + dy
- px = bmp.getpixel((c, y)) if y < h else 0
- byte = (byte >> 1) | ((1 if px else 0) << 7)
- buf[r8 * cols + c] = byte
- return bytes(buf), w, h
- def process_one_frame(im: Image.Image) -> tuple[bytes, int, int, int, int]:
- im = im.convert('RGB')
- scaled = scale_keep_ratio(im)
- sc_w, sc_h = scaled.size
- # bmp = scaled.convert('L').point(lambda p: 1 if p < 128 else 0, mode='1')
- bmp = clean_binarize(scaled)
- packed, _, _ = pack_real_size(bmp)
- off_x = (MAX_W - sc_w) // 2
- off_y = (MAX_H - sc_h) // 2
- header = bytes([sc_w, sc_h, off_x, off_y])
- return header + packed, sc_w, sc_h, off_x, off_y
- def gif_frames_to_bytes(path: str):
- with Image.open(path) as im:
- for idx in range(im.n_frames):
- im.seek(idx)
- im.load()
- yield process_one_frame(im)[0]
- def send_frame(pkt: bytes) -> bytes:
- req = (f"POST {POST_URL} HTTP/1.1\r\n"
- f"Host: {HOST}\r\n"
- "Content-Type: application/octet-stream\r\n"
- f"Content-Length: {len(pkt)}\r\n"
- "Connection: close\r\n\r\n").encode() + pkt
- with socket.create_connection((HOST, PORT), timeout=5) as sock:
- sock.sendall(req)
- return sock.recv(4096)
- def main():
- ap = argparse.ArgumentParser(description="任意图→等比缩放→打包→4字节头+数据→POST")
- ap.add_argument("--dump", action="store_true", help="显示第一帧信息并保存预览图")
- args = ap.parse_args()
- with Image.open(GIF_FILE) as im:
- packed, sc_w, sc_h, off_x, off_y = process_one_frame(im)
- if args.dump:
- print("协议头(宽,高,x,y):", [sc_w, sc_h, off_x, off_y])
- print("数据长度:", len(packed) - 4)
- png = "dump_real_size.png"
- Image.open(GIF_FILE).convert('RGB').resize((sc_w, sc_h), Image.LANCZOS).save(png)
- print(f"预览图已保存:{png}")
- if platform.system() == "Windows":
- subprocess.run(["start", "", png], shell=True, check=False)
- elif platform.system() == "Darwin":
- subprocess.run(["open", png], check=False)
- else:
- subprocess.run(["xdg-open", png], check=False)
- return
- frames = list(gif_frames_to_bytes(GIF_FILE))
- print(f"共 {len(frames)} 帧,开始 POST …")
- for i, pkt in enumerate(frames, 1):
- print(f"[{i}/{len(frames)}] 发送 {len(pkt)} 字节", end="")
- try:
- resp = send_frame(pkt)
- print(f" 返回 {len(resp)} 字节")
- except Exception as e:
- print(f" 失败:{e}")
- if i != len(frames):
- time.sleep(0.1)
- print("完成!")
- if __name__ == "__main__":
- try:
- main()
- except KeyboardInterrupt:
- sys.exit("用户中断")