搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)

引言

随着物联网(IoT)技术的快速发展,基于 STM32 的服务器(类似网关)在数据采集、设备控制等方面的应用越来越广泛。本文将介绍搭建一个基于 STM32 的服务器所需的技术栈,以及详细的搭建步骤和代码示例。

技术栈介绍

在搭建基于 STM32 的服务器时,我们需要用到以下技术栈和组件:

1. 硬件平台

  • STM32 微控制器:选择 STM32F4 或 STM32F7 系列,根据性能需求和外设支持。
  • 网络模块:可以选择 ESP8266(Wi-Fi)、ESP32(Wi-Fi + 蓝牙)或以太网模块(如 W5500)。
  • 电源管理:使用稳压器或电源管理芯片,确保系统供电稳定。
  • 2. 开发环境

  • IDE:使用 STM32CubeIDE 或 Keil MDK 进行开发。
  • 库和驱动
  • STM32 HAL 库:简化硬件访问。
  • LWIP(轻量级IP协议栈):实现 TCP/IP 协议栈。
  • FreeRTOS(可选):支持多任务处理。
  • 3. 网络协议

  • TCP/IP:实现基本的网络通信。
  • HTTP/HTTPS:用于支持 Web 服务。
  • MQTT:适合 IoT 设备间的轻量级通信。
  • 4. 开发语言

  • C/C++:主要用于 STM32 的底层开发。
  • HTML/CSS/JavaScript:用于开发 Web 界面。
  • 5. 数据存储

  • Flash 存储:存储配置和小型数据。
  • SD 卡(可选):用于大容量数据存储。
  • 6. 安全性

  • TLS/SSL:实现数据加密,确保通信安全。
  • 认证机制:如 JWT,确保设备和用户身份验证。
  • 搭建步骤

    下面将详细介绍如何搭建基于 STM32 的服务器,涵盖硬件连接、软件开发和测试等步骤。

    一、硬件搭建

    1.1 准备硬件
  • 开发板:选择 STM32F4 或 STM32F7 开发板。
  • 网络模块:选择 ESP8266 或 W5500 以实现网络连接。
  • 其他配件:稳压电源模块、面包板、杜邦线等。
  • 1.2 硬件连接
    1. 连接 STM32 和网络模块

    2. ESP8266/ESP32

    3. 将 ESP 模块的 VCC 连接到 STM32 的 3.3V,GND 连接到 GND。
    4. 将 ESP 的 TX 接口连接到 STM32 的 RX 引脚,RX 接口连接到 STM32 的 TX 引脚。
    5. W5500

    6. 将 W5500 的 SPI 接口连接到 STM32 的相应引脚:
    7. MOSI -> STM32 MOSI
    8. MISO -> STM32 MISO
    9. SCK -> STM32 SCK
    10. CS -> STM32 GPIO(选择任意 GPIO 作为片选引脚)
    11. 连接电源(VCC 和 GND)。
    12. 确保电源管理

    13. 使用适当的稳压器,确保 STM32 和其他模块的电压符合要求。
    1.3 硬件连接示意图

    二、软件开发

    2.1 开发环境设置

    2.2 引入必要的库文件

    1. 下载 STM32CubeIDE

    2. 访问 ST 官网 下载并安装 STM32CubeIDE。
    3. 创建新工程

    4. 打开 STM32CubeIDE,选择新建 STM32 项目。
    5. 选择所用的 STM32 微控制器型号。
    6. 配置外设

    7. 在 STM32CubeMX 中,配置 UART(用于串口通信)和 SPI(如果使用 W5500)。
    8. 在“Pinout & Configuration”选项卡中,设置 UART 和 SPI 引脚。
    9. 启用 LWIP 协议栈

    10. 在“Middleware”选项卡中,启用 LWIP。配置 LWIP 参数,包括 IP 地址、网络掩码和网关。
    11. 配置 LWIP 的使用模式(如 DHCP 或静态 IP)。
    12. 生成代码

    13. 点击“Project”菜单,选择“Generate Code”,生成项目代码。
    14. STM32 HAL 库

    15. 在项目中默认已经包含 STM32 HAL 库,无需额外引入。
    16. LWIP 协议栈

    17. LWIP 协议栈已经在 STM32CubeMX 中配置并生成,可以直接使用。
    18. FreeRTOS(可选)

    19. 如果需要多任务处理,选择 FreeRTOS,设置任务优先级及堆大小。

    2.3 编写代码

    2.3.1 初始化代码

    在 main.c 文件中,增加网络初始化和服务器启动代码。以下是基本实现步骤:

    #include "lwip/init.h"
    #include "lwip/netconn.h"
    
    // 网络配置函数
    void init_network() {
        // 初始化硬件
        HAL_Init();
        SystemClock_Config();
        MX_GPIO_Init();
        MX_SPI_Init(); // 如果使用W5500
        MX_LWIP_Init(); // 初始化LWIP
    }
    
    // TCP/IP 服务器实现
    void start_server() {
        struct netconn *conn, *newconn;
        err_t err;
    
        // 创建 TCP 连接
        conn = netconn_new(NETCONN_TCP);
        netconn_bind(conn, NULL, 80); // 绑定到端口 80
        netconn_listen(conn);
    
        while (1) {
            err = netconn_accept(conn, &newconn); // 接受新的连接
            if (err == ERR_OK) {
                // 处理请求
                // 这里可以添加处理 HTTP 请求的代码
                netconn_delete(newconn); // 处理完毕后关闭连接
            }
        }
    }
    
    2.3.2 主函数

    在 main() 函数中调用初始化和服务器启动代码:

    int main(void) {
        init_network(); // 初始化网络
        start_server(); // 启动TCP服务器
    
        while (1) {
            sys_check_timeouts(); // 处理 LWIP 超时
        }
    }
    

    2.4 处理 HTTP 请求

    为了让服务器能够响应 HTTP 请求,可以在 start_server() 函数中添加 HTTP 请求处理逻辑。以下是一个简单的 HTTP 响应示例:

    void handle_request(struct netconn *newconn) {
        struct netbuf *inbuf;
        char *buffer;
        u16_t len;
    
        // 等待接收数据
        netconn_recv(newconn, &inbuf);
        netbuf_data(inbuf, (void**)&buffer, &len);
    
        // 简单处理 HTTP GET 请求
        if (strncmp(buffer, "GET ", 4) == 0) {
            // 发送 HTTP 响应
            const char* response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
                                   "<html><body><h1>Hello, STM32!</h1></body></html>";
            netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
        }
    
        netbuf_delete(inbuf); // 释放内存
    }
    
    // 在 start_server() 中调用
    if (err == ERR_OK) {
        handle_request(newconn); // 处理 HTTP 请求
        netconn_delete(newconn);
    }
    

    2.5 编译和上传

    1. 编译代码

    2. 在 STM32CubeIDE 中,点击“Build”按钮编译项目,确保代码没有错误。
    3. 上传代码

    4. 连接开发板,选择正确的调试器(如 ST-Link),点击“Run”按钮将代码上传到 STM32。

    2.6 测试服务器

    1. 连接网络

    2. 确保 STM32 开发板通过 ESP8266/ESP32 或 W5500 网络模块正确连接到网络。
    3. 如果使用 ESP8266/ESP32,请确保模块已连接到 Wi-Fi 网络。如果使用 W5500,请确保以太网线连接到路由器。
    4. 获取 IP 地址

    5. 如果使用 DHCP,STM32 会自动获取 IP 地址。
    6. 可以在调试输出(例如使用 UART)中打印出分配的 IP 地址。可以在 lwip 初始化后加入如下代码:
      ip_addr_t ipaddr, netmask, gw;
      netif_default->ip_addr.addr = netif_default->ip_addr.addr;
      netif_default->netmask.addr = netif_default->netmask.addr;
      netif_default->gw.addr = netif_default->gw.addr;
      
      printf("IP Address: %s\n", ipaddr_ntoa(&netif_default->ip_addr));
      
    7. 使用浏览器访问服务器

    8. 在 PC 或手机的浏览器中输入 STM32 的 IP 地址,例如 http://192.168.1.100(请根据实际分配的 IP 地址修改)。
    9. 如果一切正常,您应该能看到服务器返回的 HTML 页面,显示内容为 "Hello, STM32!"。
    10. 调试

    11. 如果未能访问网页,请检查以下事项:
    12. 确保 STM32 开发板的电源正常。
    13. 检查网络连接是否正常。
    14. 使用串口监视器查看调试信息,确认 IP 地址是否正确。
    15. 使用 Wireshark 等工具监控网络流量,检查请求是否到达 STM32。

    3.1 增加更多的功能

    处理不同的 HTTP 请求

    可以根据不同的 URL 路径处理不同的请求,例如通过 GET /data 获取传感器数据。以下是如何实现的步骤:

    1. 修改请求处理函数
      在 handle_request 函数中,根据请求的 URL 处理不同的请求。

      void handle_request(struct netconn *newconn) {
          struct netbuf *inbuf;
          char *buffer;
          u16_t len;
      
          // 等待接收数据
          netconn_recv(newconn, &inbuf);
          netbuf_data(inbuf, (void**)&buffer, &len);
      
          // 简单处理 HTTP GET 请求
          if (strncmp(buffer, "GET /data", 9) == 0) {
              // 假设我们有一个函数获取传感器数据
              char sensor_data[100]; // 假设存储传感器数据的数组
              get_sensor_data(sensor_data); // 实现这个函数以获取传感器数据
      
              // 发送 HTTP 响应
              char response[150];
              snprintf(response, sizeof(response), "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n%s", sensor_data);
              netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
          } else {
              // 处理其他请求
              const char* response = "HTTP/1.1 404 Not Found\r\n\r\n";
              netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
          }
      
          netbuf_delete(inbuf); // 释放内存
      }
      
      void get_sensor_data(char *data) {
          // 模拟传感器数据
          sprintf(data, "Temperature: 25.5 C\nHumidity: 60%%");
      }
      
    实现 MQTT 支持

    如果需要实现 IoT 设备间的通信,可以集成 MQTT 协议。可以使用如 Paho MQTT 或 MQTT-C 等轻量级的 MQTT 客户端库。以下是实现步骤:

    1. 下载并集成 MQTT 库

    2. 根据选定的库,下载源代码并将其添加到 STM32 项目中。
    3. 初始化 MQTT 客户端

      #include "MQTTClient.h"  // 根据所用的 MQTT 库引入头文件
      
      MQTTClient client;
      char *mqtt_broker = "tcp://broker.hivemq.com:1883"; // MQTT Broker 地址
      
      void mqtt_init() {
          MQTTClient_create(&client, mqtt_broker, "stm32_client_id", MQTTCLIENT_PERSISTENCE_NONE, NULL);
          MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL);
          MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
          conn_opts.keepAliveInterval = 20;
          conn_opts.cleansession = 1;
      
          if (MQTTClient_connect(client, &conn_opts) != MQTTCLIENT_SUCCESS) {
              printf("Failed to connect to MQTT broker\n");
              return;
          }
          printf("Connected to MQTT broker\n");
      }
      
    4. 发布和订阅消息

      void publish_message(const char *topic, const char *payload) {
          MQTTClient_message pubmsg = MQTTClient_message_initializer;
          pubmsg.payload = (void*)payload;
          pubmsg.payloadlen = strlen(payload);
          pubmsg.qos = 1;
          pubmsg.retained = 0;
          MQTTClient_publishMessage(client, topic, &pubmsg, NULL);
      }
      
      void subscribe_to_topic(const char *topic) {
          MQTTClient_subscribe(client, topic, 1);
      }
      

    3.2 数据存储

    使用 Flash 存储

    可以将设备配置(如 Wi-Fi SSID 和密码)存储在 Flash 中,以便在重启时自动加载。

    1. Flash 存储函数
    #include "stm32f4xx_hal_flash.h"
    
    void write_flash(uint32_t address, uint8_t *data, uint16_t size) {
        HAL_FLASH_Unlock(); // 解锁 Flash 写入
    
        // 擦除页
        FLASH_Erase_Sector(FLASH_SECTOR_2, VOLTAGE_RANGE_3); // 擦除选择的区域
        // 写入数据
           for (uint16_t i = 0; i < size; i++) {
               if (HAL_FLASH_Program(TYPEPROGRAM_BYTE, address + i, data[i]) != HAL_OK) {
                   // 处理写入错误
                   return;
               }
           }
    
           HAL_FLASH_Lock(); // 锁定 Flash 写入
       }
    
       void read_flash(uint32_t address, uint8_t *data, uint16_t size) {
           for (uint16_t i = 0; i < size; i++) {
               data[i] = *(__IO uint8_t*)(address + i); // 读取数据
           }
       }
    使用示例
    1. 写入 Wi-Fi 配置

      void save_wifi_config(const char* ssid, const char* password) {
          uint8_t ssid_len = strlen(ssid);
          uint8_t password_len = strlen(password);
          uint32_t address = 0x080E0000; // 假设选择这个地址存储配置
      
          // 写入 SSID
          write_flash(address, (uint8_t *)ssid, ssid_len);
      
          // 写入密码
          write_flash(address + 0x40, (uint8_t *)password, password_len); // 假设密码紧跟在 SSID 后
      }
      
    2. 读取 Wi-Fi 配置

      void load_wifi_config(char* ssid, char* password) {
          uint32_t address = 0x080E0000; // 读取配置的地址
          uint8_t ssid_len = 32; // 假设 SSID 最大长度为 32
          uint8_t password_len = 32; // 假设密码最大长度为 32
      
          read_flash(address, (uint8_t *)ssid, ssid_len);
          read_flash(address + 0x40, (uint8_t *)password, password_len);
      }
      

    使用 SD 卡存储数据

    如果需要存储大量数据,可以添加 SD 卡模块,并使用 FATFS 文件系统进行数据读写。

    1. 添加 SD 卡模块
    1. 硬件连接
    2. 将 SD 卡模块连接到 STM32 的 SPI 接口(MOSI、MISO、SCK 和 CS)。
    3. 连接 VCC 和 GND。
    2. 配置 FATFS
    1. 在 STM32CubeMX 中启用 FATFS

    2. 在中间件部分选择 FATFS,并配置为 SPI 模式。
    3. 生成代码

    4. 生成代码后,您将在项目中看到 FATFS 的相关文件。
    3. SD 卡读写示例
    1. 初始化 SD 卡

      FATFS FatFs;  // FatFs工作区
      FIL fil;      // 文件对象
      FRESULT fr;  // FATFS 结果
      
      void init_sd_card() {
          fr = f_mount(&FatFs, "", 1); // 挂载文件系统
          if (fr != FR_OK) {
              // 处理错误
          }
      }
      
    2. 写入数据到 SD 卡

      void write_to_sd_card(const char* filename, const char* data) {
          fr = f_open(&fil, filename, FA_WRITE | FA_CREATE_ALWAYS); // 打开文件
          if (fr == FR_OK) {
              f_write(&fil, data, strlen(data), NULL); // 写入数据
              f_close(&fil); // 关闭文件
          } else {
              // 处理打开文件错误
          }
      }
      
    3. 从 SD 卡读取数据

      void read_from_sd_card(const char* filename, char* buffer, uint32_t buffer_size) {
          fr = f_open(&fil, filename, FA_READ); // 打开文件
          if (fr == FR_OK) {
              f_read(&fil, buffer, buffer_size, NULL); // 读取数据
              f_close(&fil); // 关闭文件
          } else {
              // 处理打开文件错误
          }
      }

    3.3 增强安全性

    启用 HTTPS

    使用 TLS/SSL 库(如 mbedTLS)实现 HTTPS,确保数据传输安全。

    1. 集成 mbedTLS
    1. 下载 mbedTLS

    2. 访问 mbedTLS GitHub 页面 下载最新版本的 mbedTLS。
    3. 将相关源代码和头文件添加到 STM32 项目中。
    4. 配置 mbedTLS

    5. 在 mbedtls/config.h 中,启用所需功能,例如:
      #define MBEDTLS_SSL_CLI_C
      #define MBEDTLS_SSL_SRV_C
      #define MBEDTLS_TLS_C
      #define MBEDTLS_X509_CRT_PARSE_C
      #define MBEDTLS_SHA256_C
      #define MBEDTLS_AES_C
      
    2. 初始化 mbedTLS
    #include "mbedtls/net_sockets.h"
    #include "mbedtls/ssl.h"
    #include "mbedtls/error.h"
    
    // 全局变量
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    
    // 初始化 mbedTLS
    void init_mbedtls() {
        mbedtls_ssl_init(&ssl);
        mbedtls_ssl_config_init(&conf);
        mbedtls_entropy_init(&entropy);
        mbedtls_ctr_drbg_init(&ctr_drbg);
    
        // 设置随机数生成器
        const char *pers = "ssl_client";
        mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers));
    
        // 配置 SSL
        mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
        mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
    }
    
    // 连接到 HTTPS 服务器
    int connect_https(const char *hostname, const char *port) {
        mbedtls_net_context server_fd;
        mbedtls_net_init(&server_fd);
    
        // 连接到服务器
        if (mbedtls_net_connect(&server_fd, hostname, port, MBEDTLS_NET_PROTO_TCP) != 0) {
            return -1;
        }
    
        // 设置 SSL
        mbedtls_ssl_setup(&ssl, &conf);
        mbedtls_ssl_set_hostname(&ssl, hostname);
        mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
    
        // 完成 SSL 握手
        if (mbedtls_ssl_handshake(&ssl) != 0) {
            mbedtls_net_free(&server_fd);
            return -1;
        }
    
        return 0; // 连接成功
    }
    
    3. 发送 HTTPS 请求
    void send_https_request(const char *hostname, const char *path) {
        char request[512];
        snprintf(request, sizeof(request),
                 "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", path, hostname);
    
        mbedtls_ssl_write(&ssl, (unsigned char *)request, strlen(request));
    
        // 读取响应
        unsigned char buf[1024];
        int ret;
        do {
            ret = mbedtls_ssl_read(&ssl, buf, sizeof(buf) - 1);
            if (ret > 0) {
                buf[ret] = '\0'; // 添加字符串结束符
                printf("%s", (char *)buf); // 打印响应
            }
        } while (ret > 0);
    
        // 清理
        mbedtls_ssl_close_notify(&ssl);
        mbedtls_net_free(&server_fd);
    }

    用户身份认证

    1. JWT 生成和验证
    1. 生成 JWT

    使用 JWT 库生成 JSON Web Token,以下是如何生成 JWT 的示例代码:

    #include "jwt.h"
    
    // 生成 JWT
    char* generate_jwt(const char *secret, const char *username) {
        jwt_t *jwt = NULL;
        char *token = NULL;
    
        // 创建新的 JWT
        if (jwt_new(&jwt) != 0) {
            return NULL; // 处理错误
        }
    
        // 设置 JWT 的声明
        jwt_add_grant(jwt, "sub", username); // 用户名
        jwt_add_grant_int(jwt, "exp", time(NULL) + 3600); // 设置过期时间为1小时
    
        // 签名 JWT
        if (jwt_set_alg(jwt, JWT_ALG_HS256, (unsigned char *)secret, strlen(secret)) != 0) {
            jwt_free(jwt);
            return NULL; // 处理错误
        }
    
        // 获取 JWT 字符串
        token = jwt_encode_str(jwt);
        jwt_free(jwt); // 释放 JWT 结构体
    
        return token; // 返回生成的 JWT
    }
    
    2. 验证 JWT

    在服务器端验证 JWT,确保请求的用户身份合法。

    int verify_jwt(const char *token, const char *secret) {
        jwt_t *jwt = NULL;
        const char *username;
    
        // 解析 JWT
        if (jwt_decode(&jwt, token, (unsigned char *)secret, strlen(secret)) != 0) {
            return 0; // 验证失败
        }
    
        // 检查过期时间
        if (jwt_get_grant_int(jwt, "exp") < time(NULL)) {
            jwt_free(jwt);
            return 0; // JWT 已过期
        }
    
        username = jwt_get_grant(jwt, "sub"); // 获取用户名
        printf("Authenticated user: %s\n", username); // 打印用户信息
    
        jwt_free(jwt); // 释放 JWT 结构体
        return 1; // 验证成功
    }
    
    3. 结合 HTTP 请求与 JWT 验证

    在处理 HTTP 请求时,检查 Authorization 头中是否包含有效的 JWT。

    void handle_request(struct netconn *newconn) {
        struct netbuf *inbuf;
        char *buffer;
        u16_t len;
    
        netconn_recv(newconn, &inbuf);
        netbuf_data(inbuf, (void**)&buffer, &len);
    
        // 示例:处理 GET 请求,检查 JWT
        if (strncmp(buffer, "GET /data", 9) == 0) {
            // 检查 Authorization 头
            char *auth_header = strstr(buffer, "Authorization: ");
            if (auth_header) {
                char *token = strtok(auth_header + 15, "\r\n"); // 提取 JWT (Bearer token)
                if (verify_jwt(token, "your_secret_key")) { // 验证 JWT
                    // 返回传感器数据
                    char sensor_data[100];
                    get_sensor_data(sensor_data); // 获取传感器数据
                    char response[150];
                    snprintf(response, sizeof(response),
                             "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n%s", sensor_data);
                    netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
                } else {
                    // JWT 验证失败
                    const char* response = "HTTP/1.1 401 Unauthorized\r\n\r\n";
                    netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
                }
            } else {
                // 未提供 JWT
                const char* response = "HTTP/1.1 401 Unauthorized\r\n\r\n";
                netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
            }
        } else {
            // 处理其他请求
            const char* response = "HTTP/1.1 404 Not Found\r\n\r\n";
            netconn_write(newconn, response, strlen(response), NETCONN_NOCOPY);
        }
    
        netbuf_delete(inbuf); // 释放内存
    }

    总结

    通过本文,我们详细介绍了如何搭建一个基于 STM32 的服务器(类似网关),并实现了多种功能,具体包括:

    1. 硬件搭建

    2. 选择合适的 STM32 微控制器(如 STM32F4 或 STM32F7)和网络模块(如 ESP8266、ESP32 或 W5500)。
    3. 确保电源管理稳定,为系统提供稳定的电压。
    4. 软件开发

    5. 设置开发环境,使用 STM32CubeIDE 创建项目。
    6. 配置并初始化网络模块,使用 LWIP 协议栈实现 TCP/IP 通信。
    7. 编写处理 HTTP 请求的代码,支持根据 URL 路径返回不同的响应。
    8. 增加更多功能

    9. 处理不同的 HTTP 请求,获取传感器数据并返回。
    10. 集成 MQTT 协议,实现 IoT 设备间的通信。
    11. 提供 HTTP 服务和 MQTT 服务,增加系统的灵活性。
    12. 数据存储

    13. 使用 Flash 存储设备配置(如 Wi-Fi SSID 和密码)。
    14. 使用 SD 卡模块存储大量数据,并通过 FATFS 文件系统读写数据。
    15. 增强安全性

    16. 使用 mbedTLS 库启用 HTTPS,确保数据传输的安全性。
    17. 实现 JWT(JSON Web Token)身份认证,确保只有授权用户能够访问服务器。

    非常感谢您阅读到这里!您的关注和支持是我不断前进的动力。跟随着我探索嵌入式领域,希望因为兴趣而成为嵌入式领域的专家。

    在这个快速发展的技术时代,嵌入式系统无处不在,从智能家居到医疗设备,从自动驾驶汽车到工业控制,每一个领域都离不开嵌入式技术的支持。对我来说,嵌入式不仅仅是一门技术,更是一种激情和追求。通过不断学习和实践,我深深爱上了这个充满挑战和机遇的领域。每一次调试成功,每一个创新的实现,都是我继续前行的动力。

    ——by 极客小张

    作者:极客小张

    物联沃分享整理
    物联沃-IOTWORD物联网 » 搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(含代码示例)

    发表回复