Wiznet makers

chen

Published November 20, 2025 ©

65 UCC

1 WCC

25 VAR

0 Contests

0 Followers

0 Following

Original Link

The W5100Sio-M module accesses Doubao AI via the HTTP protocol

This article introduces a solution for implementing Doubao AI access based on the W5100Sio-M module and STM32F103VCT6.

COMPONENTS Hardware components

WIZnet - W5500-io

x 1


PROJECT DESCRIPTION

1. Introduction
HTTP (Hypertext Transfer Protocol) is an application-layer protocol used for distributed, collaborative, hypermedia information systems. It transmits data based on the TCP/IP communication protocol and is the foundation of data communication on the World Wide Web (WWW). HTTP was initially designed to provide a method for publishing and receiving HTML pages. Resources requested via HTTP or HTTPS are identified by Uniform Resource Identifiers (URIs). This is a brief introduction to the HTTP protocol. For a more in-depth understanding of this protocol, please refer to the introduction on the Mozilla website: HTTP Overview - HTTP | MDN

The W5100Sio-M is a high-performance SPI-to-Ethernet module launched by WISHI, with the following features:

Minimalist design: integrates MAC, PHY, 16KB cache and RJ45 network port, directly connects to the main controller via 4-wire SPI interface, 3.3V power supply, compact size suitable for embedded scenarios .
Simple and easy to use: Users no longer need to port the complex TCP/IP protocol stack to the MCU, and can directly develop based on application layer data.
Abundant resources: Provides a wealth of MCU application examples and hardware reference designs that can be directly referenced and used, greatly shortening the development time. Hardware is compatible with the W5500io-M module, facilitating solution development and iteration.
Wide range of applications: It is widely used in industrial control, smart grid, charging piles, security and fire protection, new energy, energy storage and other fields.
Product Link: Product Details

2. Project Environment
2.1 Hardware Environment
W5100Sio-M
STM32F103VCT6 EVB
Network cable
DuPont wires
Switch or router
2.2 Software Environment
Example program link : https://www.w5100S.com
Serial Port Assistant
Bean bag A P I
Alibaba Cloud NTP server address​
Keil 5​
3 Hardware Connection and Solution
3.1 W5100S Hardware Connection
1. //W5100S_SCS    --->    STM32_GPIOD7    /*W5100S的片选引脚*/
2. //W5100S_SCLK    --->    STM32_GPIOB13    /*W5100S的时钟引脚*/
3. //W5100S_MISO    --->    STM32_GPIOB14    /*W5100S的MISO引脚*/ 
4. //W5100S_MOSI    --->    STM32_GPIOB15    /*W5100S的MOSI引脚*/ 
5. //W5100S_RESET--->    STM32_GPIOD8    /*W5100S的RESET引脚*/ 
6. //W5100S_INT    --->    STM32_GPIOD9    /*W5100S的INT引脚*/
3.2 Scheme Diagram


4. Obtaining AI parameters for Doubao
4.1 Activate Service
Go to Account Login - Volcano Engine , and follow the prompts to register or log in to an account.

Clicking the "Activate Service" button on the far right of Doubao-pro-4k will bring up a pop-up window.

Select the 6 models of Doubao in the pop-up window.

(Doubao-pro-4k, Doubao-pro-8k, Doubao-pro-32k, Doubao-lite-4k, Doubao-lite-8k, Doubao-lite-32k), then click [Activate Now].

After activating the service, each model can enjoy 500,000 free tokens per day (1 token is approximately 4 characters or 0.75 words).

4.2 Creating an API Key
Go to API Key Management , click 【Create API Key】, fill in the name, and create the API Key for later use.

4.3 Creating an Access Point
The Doubao model cannot be used directly; an access point must be created within the platform before it can be used.

Taking Doubao-pro-32k as an example: Go to Create Inference Access Point and click 【Create Inference Access Point】.
Clicking the "Add Model" button will bring up a pop-up window.
In the model plaza, select "Doubao-pro-32k", and the model version will appear on the right.
There is usually only one model version, and the name is a date consisting of 6 digits (e.g., 240515), but there may also be versions with prefixes (e.g., functioncall-240515, character-240528).
Choose a model version without a prefix, such as 240515, which only has 6 digits.
After selecting the model version, click "Add" in the bottom right corner of the page.
The name should be the text shown in the "Access Model" field, such as "Doubao-pro-32k-240515" (replace the forward slash / with a hyphen -).
Click the "Access Model" button on the right side of the page.
       Then, you will return to the model inference page. At this time, you will see the access point you just created named "Doubao-pro-32k-240515" in the table. Below the name, there is a string of text starting with ep- and in the format ep-xxxxxxxxxx-xxxxx. This is the access point ID we need. Copy it for later use.

You can correctly call the Doubao API by obtaining the following parameters :

Enter https://ark.cn-beijing.volces.com/api/v3 in the API address (OPENAI_BASE_URL) .
Enter the API Key you created in the OPENAI_API_KEY field.
Enter the access point ID you created in the OPENAI_MODEL field.


5 routine modifications
5.1 Modification of the main function file
The main.c file has been modified as follows :

Main functions include

Main functions include:

Hardware initialization: Configure SPI interface, USART debug serial port, timers, and watchdog timer.
Network initialization: Configure W5100 network parameters (MAC, IP, gateway, etc.)
NTP Time Synchronization: Obtain network time from Alibaba Cloud NTP server
Domain name resolution: Resolving the API domain name into an IP address via DNS.
User interaction: Receive user questions via serial port and send them to AI.

 


#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
#include "wiz_platform.h"
#include "wizchip_conf.h"
#include "wiz_interface.h"
#include "do_dns.h"
#include "httpclient.h"
#include "stm32f10x_iwdg.h"
#include "ntp_client.h"

#define SOCKET_ID 0
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)

/* 豆包API参数 */
#define DOUBAO_API_KEY "dc2972ae-fc48-4ecf-b113-9dff3668e70c"
#define MODEL_ID "ep-20241211221157-z2rfx"
#define API_DOMAIN "ark.cn-beijing.volces.com"
#define API_PATH "/api/v3/chat/completions"
#define API_PORT 80

/* NTP参数 */
#define NTP_SERVER "ntp.aliyun.com"
#define NTP_PORT 123

/* network information */
wiz_NetInfo default_net_info = {
   .mac = {0x00, 0x08, 0xdc, 0x12, 0x22, 0x12},
   .ip = {192, 168, 1, 30},
   .gw = {192, 168, 1, 1},
   .sn = {255, 255, 255, 0},
   .dns = {8, 8, 8, 8},
   .dhcp = NETINFO_DHCP};

uint8_t ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {0};
uint8_t api_server_ip[4] = {0}; /* API服务器IP地址 */
uint8_t ntp_server_ip[4] = {0}; /* NTP服务器IP地址 */

// 用户输入缓冲区
char user_input[256] = {0};

// 系统时间
volatile uint32_t system_time = 0;
volatile uint32_t last_ntp_update = 0;

// 看门狗初始化
void IWDG_Init(void) {
   IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
   IWDG_SetPrescaler(IWDG_Prescaler_256); // 40KHz / 256 = 156Hz (6.4ms)
   IWDG_SetReload(156 * 10); // 10秒超时
   IWDG_ReloadCounter();
   IWDG_Enable();
}

/**
* @brief   自定义fgets函数,支持回显和退格
* @param   str: 输入缓冲区
* @param   size: 缓冲区大小
* @return  输入字符串指针
*/
char *custom_fgets(char *str, int size) {
   int count = 0;
   int c;  // 改为int类型接收所有字节值
   
   // 清空串口输入缓冲区
   while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) {
       USART_ReceiveData(USART1);
   }
   
   while (count < size - 1) {
       c = fgetc(stdin); // 获取字节(int类型)
       
       // 回车结束输入
       if (c == '\r' || c == '\n') {
           if (count > 0) {
               str[count] = '\0';
               printf("\r\n");
               return str;
           }
           continue;
       }
       
       // 退格处理
       if (c == '\b') {
           if (count > 0) {
               count--;
               printf("\b \b");
           }
           continue;
       }
       
       // 接受所有非控制字符 (包括UTF-8字节)
       if (c != 0) {  // 关键修改:允许所有非空字符
           str[count++] = (char)c;
           putchar(c);
       }
   }
   
   str[count] = '\0';
   printf("\r\n");
   return str;
}

int main(void) {
   uint16_t len;
   delay_init();
   debug_usart_init();
   wiz_timer_init();
   wiz_spi_init();
   wiz_rst_int_init();
   IWDG_Init(); // 初始化看门狗
   
   printf("System Initialized\r\n");
   printf("W5500 HTTP Client Example with Doubao AI\r\n");

   /* wizchip init */
   wizchip_initialize();
   network_init(ethernet_buf, &default_net_info);

   // 解析NTP域名
   if (do_dns(ethernet_buf, (uint8_t *)NTP_SERVER, ntp_server_ip)) {
       printf("DNS resolution failed for %s\r\n", NTP_SERVER);
   } else {
       printf("%s resolved to: %d.%d.%d.%d\r\n", 
              NTP_SERVER, 
              ntp_server_ip[0], 
              ntp_server_ip[1], 
              ntp_server_ip[2], 
              ntp_server_ip[3]);
       
       // 获取NTP时间
       if (get_ntp_time(ntp_server_ip, NTP_PORT, &system_time)) {
           last_ntp_update = system_time;
           printf("NTP time updated: %lu\r\n", system_time);
       }
   }

   // 解析API域名
   if (do_dns(ethernet_buf, (uint8_t *)API_DOMAIN, api_server_ip)) {
       printf("DNS resolution failed for %s\r\n", API_DOMAIN);
       while (1) {
           IWDG_ReloadCounter(); // 喂狗
           delay_ms(100);
       }
   }
   printf("%s resolved to: %d.%d.%d.%d\r\n", 
          API_DOMAIN, 
          api_server_ip[0], 
          api_server_ip[1], 
          api_server_ip[2], 
          api_server_ip[3]);
   
   printf("Network initialized. Type your question and press Enter:\r\n");
   
   uint32_t last_time_check = system_time;
   
   while(1) {
       // 喂狗操作
       IWDG_ReloadCounter();
       
       // 更新时间(每秒更新)
       if (system_time - last_time_check >= 1) {
           system_time++;
           last_time_check = system_time;
           
           // 每小时同步一次NTP
           if ((system_time - last_ntp_update) >= 3600) {
               if (get_ntp_time(ntp_server_ip, NTP_PORT, &system_time)) {
                   last_ntp_update = system_time;
                   printf("NTP time re-synced: %lu\r\n", system_time);
               }
           }
       }
       
       printf("User's question: ");
       custom_fgets(user_input, sizeof(user_input));
       
       // 移除所有换行符和回车符
       char *src = user_input, *dst = user_input;
       while (*src) {
           if (*src != '\r' && *src != '\n') {
               *dst++ = *src;
           }
           src++;
       }
       *dst = '\0';
       
       if(strlen(user_input) > 0) {
           printf("Sending to AI...\r\n");
           
           // 构建并发送POST请求
           len = http_post_for_doubao(ethernet_buf, user_input);
           do_http_request(SOCKET_ID, ethernet_buf, len, api_server_ip, API_PORT);
           
           // 清空输入缓存
           memset(user_input, 0, sizeof(user_input));
       }
       
       delay_ms(100);
   }
}

5.2 Modify HttpClient​
The httpcl ient.c file has been modified as follows :

The `http_post_for_doubao` function is responsible for constructing HTTP POST requests that conform to the Doubao AI API specification. Its core functionalities include:

JSON request body construction:
Use snprintf to generate JSON requests conforming to OpenAI format.
It includes a `model` parameter and a `messages` array, where the `messages` array contains user roles and content.
Add the current time (obtained via get_current_time_str) to the system role message.
Example of generated JSON:
{"model":"ep-20241211221157-z2rfx","messages":[{"role":"user","content":"User Issues"}]}

HTTP request construction:
Request line: Specifies the POST method and API path
Request headers: contain key fields such as Host, Authorization, and Content-Type.
The Content-Length field ensures that the server correctly parses the request body length.
Use `Connection: close` to tell the server to close the connection after the request is complete.
The `do_http_request` function is responsible for handling the sending of HTTP requests and the parsing of responses, and is a core part of network communication.

1. Communication Flow Analysis

Socket Management:

Close and reopen the socket before each request to ensure connection freshness.

Establish a reliable connection using the TCP protocol (Sn_MR_TCP)

The local port is fixed at 50000 and can be adjusted as needed.

State machine processing:

The socket state is obtained through getSn_SR, and different situations are handled based on a state machine.

SOCK_INIT: Connect to the server

SOCK_ESTABLISHED: Sends a request and processes the response.

SOCK_CLOSE_WAIT: Handles remaining data before the server closes the connection.

SOCK_CLOSED: Reopen the socket.

2. Response parsing logicJSON response handling:

Detect the start tag '{' in JSON.

Find the starting position of the "content" field (by character matching).

Handling escape characters (such as ", \, etc.)

The parsing stops when a closing quotation mark is encountered.

Timeout mechanism:

Receive timeout counter recv_timeout

If the request takes more than 10 seconds (10 loops), it is considered a timeout.

The connection will be closed and an error will be returned after the timeout.

 

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "socket.h"
#include "wizchip_conf.h"
#include "httpclient.h"
#include "wiz_platform.h"
#include "stm32f10x_iwdg.h"
#include "ntp_client.h"

// 豆包API参数声明
#define DOUBAO_API_KEY "dc2972ae-fc48-4ecf-b113-9dff3668e70c"
#define MODEL_ID "ep-20241211221157-z2rfx"
#define API_DOMAIN "ark.cn-beijing.volces.com"
#define API_PATH "/api/v3/chat/completions"

extern volatile uint32_t system_time; // 来自main.c的全局时间

/**
* @brief   构建豆包API的POST请求
* @param   pkt: 数据缓冲区
* @param   user_message: 用户消息
* @return  数据包长度
*/
uint32_t http_post_for_doubao(uint8_t *pkt, const char *user_message) {
   char json_body[1024]; // 增大缓冲区
   char time_str[32];
   
   // 获取当前时间字符串
   get_current_time_str(time_str, sizeof(time_str));
   
   // 构建JSON请求体
   snprintf(json_body, sizeof(json_body),
       "{\"model\":\"%s\",\"messages\":["
       "{\"role\":\"system\",\"content\":\"Current time is %s\"},"
       "{\"role\":\"user\",\"content\":\"%s\"}"
       "]}",
       MODEL_ID, time_str, user_message);
   
   int body_len = strlen(json_body);
   
   // 清空缓冲区
   memset(pkt, 0, ETHERNET_BUF_MAX_SIZE);
   
   // 构建HTTP请求
   char *buf = (char *)pkt;
   int pos = 0;
   
   // 请求行
   pos += sprintf(buf + pos, "POST %s HTTP/1.1\r\n", API_PATH);
   
   // 请求头
   pos += sprintf(buf + pos, "Host: %s\r\n", API_DOMAIN);
   pos += sprintf(buf + pos, "Authorization: Bearer %s\r\n", DOUBAO_API_KEY);
   pos += sprintf(buf + pos, "Content-Type: application/json\r\n");
   pos += sprintf(buf + pos, "Content-Length: %d\r\n", body_len);
   pos += sprintf(buf + pos, "Connection: close\r\n");
   pos += sprintf(buf + pos, "\r\n");  // 空行结束头部
   
   // 请求体
   pos += sprintf(buf + pos, "%s", json_body);
   
   return pos;  // 返回数据包长度
}

/**
* @brief   HTTP响应处理
* @param   data: HTTP响应数据
* @param   len: 数据长度
*/
void process_http_response(uint8_t *data, uint16_t len) {
   // 查找JSON正文开始位置
   char *json_start = strstr((char *)data, "\r\n\r\n");
   if (!json_start) {
       json_start = strstr((char *)data, "\n\n");
   }
   
   if (json_start) {
       json_start += 4; // 跳过空行
       
       // 简化版JSON解析
       char *content_start = strstr(json_start, "\"content\":\"");
       if (content_start) {
           content_start += 11; // 跳过"content":"
           char *content_end = strstr(content_start, "\"");
           
           if (content_end) {
               printf("\r\nAI: ");
               char *p = content_start;
               while (p < content_end) {
                   // 处理转义字符
                   if (*p == '\\') {
                       p++;
                       if (*p == 'n') {
                           printf("\n");
                       } else if (*p == 't') {
                           printf("\t");
                       } else {
                           putchar(*p);
                       }
                   } else {
                       putchar(*p);
                   }
                   p++;
               }
               printf("\r\n");
               return;
           }
       }
       
       // 错误信息处理
       char *error_start = strstr(json_start, "\"message\":\"");
       if (error_start) {
           error_start += 11; // 跳过"message":"
           char *error_end = strstr(error_start, "\"");
           
           if (error_end) {
               printf("\r\nAI Error: ");
               for (char *p = error_start; p < error_end; p++) {
                   putchar(*p);
               }
               printf("\r\n");
               return;
           }
       }
   }
   
   printf("\r\nInvalid response format\r\n");
}

/**
* @brief   HTTP Client get data stream test.
* @param   sn:         socket number
* @param   buf:        request message content
* @param   len:        request message length
* @param   destip:     destion ip
* @param   destport:   destion port
* @return  0:timeout,1:Received response..
*/
uint8_t do_http_request(uint8_t sn, uint8_t *buf, uint16_t len, uint8_t *destip, uint16_t destport) {
   uint16_t local_port   = 50000;
   uint16_t recv_timeout = 0;
   uint8_t  send_flag    = 0;
   uint16_t total_len    = 0;
   uint8_t  response_buf[ETHERNET_BUF_MAX_SIZE] = {0};
   
   // 先关闭socket(如果已打开),然后重新打开
   close(sn);
   socket(sn, Sn_MR_TCP, local_port, 0x00);
   
   while (1) {
       // 喂狗操作
       IWDG_ReloadCounter();
       
       switch (getSn_SR(sn)) {
       case SOCK_INIT:
           // Connect to http server.
           connect(sn, destip, destport);
           break;
       case SOCK_ESTABLISHED:
           if (send_flag == 0) {
               // send request
               send(sn, buf, len);
               send_flag = 1;
           }
           // Response content processing
           len = getSn_RX_RSR(sn);
           if (len > 0) {
               // 读取数据
               uint16_t read_len = recv(sn, response_buf + total_len, 
                                     ETHERNET_BUF_MAX_SIZE - total_len - 1);
               total_len += read_len;
               
               // 检查是否接收完成(根据Content-Length或连接关闭)
               if (getSn_SR(sn) == SOCK_CLOSE_WAIT || total_len >= ETHERNET_BUF_MAX_SIZE - 1) {
                   // 处理完整响应
                   response_buf[total_len] = '\0';
                   process_http_response(response_buf, total_len);
                   disconnect(sn);
                   close(sn);
                   return 1;
               }
           } else {
               recv_timeout++;
               delay_ms(100);
           }
           // timeout handling
           if (recv_timeout > 100) { // 10秒超时
               printf("Request failed: Timeout!\r\n");
               disconnect(sn);
               close(sn);
               return 0;
           }
           break;
       case SOCK_CLOSE_WAIT:
           // 读取剩余数据
           len = getSn_RX_RSR(sn);
           if (len > 0) {
               uint16_t read_len = recv(sn, response_buf + total_len, 
                                     ETHERNET_BUF_MAX_SIZE - total_len - 1);
               total_len += read_len;
           }
           // 处理完整响应
           response_buf[total_len] = '\0';
           process_http_response(response_buf, total_len);
           disconnect(sn);
           close(sn);
           return 1;
       case SOCK_CLOSED:
           // close socket
           close(sn);
           // open socket
           socket(sn, Sn_MR_TCP, local_port, 0x00);
           break;
       default:
           break;
       }
   }
}

5.3 Accessing NTP Service
The following method uses Doubao AI to obtain the time by connecting to Alibaba Cloud 's NTP server .

The ntp_client.c file needs to be included .

1. The get_ntp_time function

The main function of this function is to obtain a timestamp from an NTP server and convert it to a Unix time format.

NTP request packet construction: Create a standard NTP request packet, set the version to 4, and the mode to client (Mode=3).
Network communication processing: Communicates with the NTP server using the UDP protocol, implemented through the socket API of the WIZnet chip.
Timeout handling mechanism: A 5-second timeout limit is set to prevent the program from being blocked for a long time.
Timestamp Extraction and Conversion: Extract time information from a specific location (bytes 40-43) in the NTP response packet and convert it to a Unix timestamp (the number of seconds since January 1, 1970).


2. The `get_current_time_str` function

This function converts the system time into a human-readable time string (format: HH:MM:SS).

Time zone handling: Adjust the time zone using the GMT_OFFSET_SEC parameter.
Time calculation: Converts the total number of seconds to hours, minutes, and seconds.
String formatting: Use snprintf to ensure that the output string does not overflow the buffer.
The ntp_client.c file is as follows :

#include "ntp_client.h"
#include "socket.h"
#include "wizchip_conf.h"
#include <string.h>
#include <stdio.h>  // 添加snprintf声明

// NTP时间戳起始点 (1900-01-01 到 1970-01-01 的秒数)
#define NTP_TIMESTAMP_DELTA 2208988800UL

/**
* @brief   从NTP服务器获取时间
* @param   ntp_server: NTP服务器IP
* @param   port: NTP端口
* @param   timestamp: 返回的时间戳
* @return  成功返回1,失败返回0
*/
uint8_t get_ntp_time(uint8_t *ntp_server, uint16_t port, volatile uint32_t *timestamp) {
   uint8_t sock = NTP_SOCKET_ID;
   uint8_t ntp_packet[48] = {0};
   uint8_t response[48] = {0};
   uint16_t len;
   uint32_t timeout = 0;
   
   // 创建NTP请求包
   memset(ntp_packet, 0, sizeof(ntp_packet));
   ntp_packet[0] = 0x1B; // LI=0, Version=4, Mode=3 (Client)
   
   // 关闭socket(如果已打开)
   close(sock);
   
   // 创建UDP socket
   if (socket(sock, Sn_MR_UDP, 0, 0) != sock) {
       return 0;
   }
   
   // 发送NTP请求
   sendto(sock, ntp_packet, sizeof(ntp_packet), ntp_server, port);
   
   // 等待响应
   while (timeout++ < 1000) { // 5秒超时
       if ((len = getSn_RX_RSR(sock)) > 0) {
           if (len > sizeof(response)) len = sizeof(response);
           len = recvfrom(sock, response, len, ntp_server, &port);
           
           if (len >= 48) {
               // 提取时间戳 (第40-43字节)
               uint32_t ntp_time = (uint32_t)response[40] << 24;
               ntp_time |= (uint32_t)response[41] << 16;
               ntp_time |= (uint32_t)response[42] << 8;
               ntp_time |= (uint32_t)response[43];
               
               // 转换为Unix时间戳
               *timestamp = ntp_time - NTP_TIMESTAMP_DELTA;
               close(sock);
               return 1;
           }
       }
       delay_ms(5);
   }
   
   close(sock);
   return 0;
}

/**
* @brief   获取当前时间字符串
* @param   buffer: 输出缓冲区
* @param   size: 缓冲区大小
*/
void get_current_time_str(char *buffer, size_t size) {
   uint32_t current = system_time + GMT_OFFSET_SEC;
   uint32_t seconds = current % 86400;  // 删除未使用的days变量
   
   uint8_t hour = seconds / 3600;
   uint8_t minute = (seconds % 3600) / 60;
   uint8_t second = seconds % 60;
   

   snprintf(buffer, size, "%02d:%02d:%02d", hour, minute, second);
}

6. Dialogue Test
       After the hardware connection is complete, the following information will be printed after powering on the program:

Documents
Comments Write