Bootstrap

STM32 实现 TCP 服务器与多个设备通信

目录

一、引言

二、硬件准备

三、软件准备

四、LWIP 协议栈的配置与初始化

五、创建 TCP 服务器

1.创建 TCP 控制块

2.绑定端口 

3. 进入监听状态

4.设置接收回调函数 

六、处理多个客户端连接 

七、数据处理与通信管理 

八、错误处理与资源管理 

九、总结 

 


一、引言

        在嵌入式系统开发中,常常需要实现设备之间的网络通信。STM32 作为一款广泛应用的微控制器,结合网络通信功能可以实现与多个设备的交互。本文将介绍如何在 STM32 上实现 TCP 服务器端,以便与多个设备进行通讯。

二、硬件准备

  1. STM32 开发板:选择一款带有以太网接口的 STM32 开发板,如 STM32F429 系列等。
  2. 以太网模块(可选):如果开发板没有内置以太网接口,可以选择一个外接的以太网模块,如 W5500 等。
  3. 网络连接:将开发板连接到同一局域网内,确保其他设备可以通过网络访问到 STM32 开发板。

三、软件准备

  1. 开发环境:如 Keil MDK、IAR Embedded Workbench 等。
  2. LWIP(Lightweight IP)协议栈:LWIP 是一个轻量级的 TCP/IP 协议栈,适用于嵌入式系统。可以从 LWIP 官方网站下载并集成到开发环境中。
  3. STM32 库:使用 STM32 的官方库或者其他第三方库来进行硬件驱动和开发。

四、LWIP 协议栈的配置与初始化

  1. 将 LWIP 协议栈的源文件添加到 STM32 项目中,并设置正确的编译选项。
  2. 在项目的初始化代码中,调用 LWIP 的初始化函数lwip_init(),完成协议栈的初始化。
  3. 根据实际需求,配置 LWIP 的参数,如 IP 地址、子网掩码、默认网关等。可以通过修改lwipopts.h文件来实现。

五、创建 TCP 服务器

1.创建 TCP 控制块

使用 LWIP 的 API 函数tcp_new()创建一个新的 TCP 控制块。

  struct tcp_pcb *tcp_server_pcb;
   tcp_server_pcb = tcp_new();

2.绑定端口 

使用tcp_bind()函数将 TCP 控制块绑定到一个特定的端口号。通常选择一个未被其他应用程序占用的端口。

   err_t err = tcp_bind(tcp_server_pcb, IP_ADDR_ANY, SERVER_PORT);
   if (err!= ERR_OK) {
       // 处理绑定失败的情况
   }

这里的SERVER_PORT是服务器监听的端口号。

3. 进入监听状态

使用tcp_listen()函数使 TCP 控制块进入监听状态,等待客户端的连接请求。

 tcp_server_pcb = tcp_listen(tcp_server_pcb);

4.设置接收回调函数 

使用tcp_accept()函数设置一个接收回调函数,当有客户端连接请求时,该回调函数将被调用。

tcp_accept(tcp_server_pcb, tcp_accept_callback);

tcp_accept_callback是自定义的回调函数,用于处理客户端的连接请求。

六、处理多个客户端连接 

1.在接收回调函数中,接受客户端的连接请求,并为每个连接创建一个新的 TCP 控制块来处理与该客户端的通信。

void tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
   {
       if (err == ERR_OK) {
           // 处理新的连接
           // 可以为每个连接分配一些资源,如缓冲区等
           tcp_recv(newpcb, tcp_receive_callback, NULL);
       }
   }

 

2.为每个连接设置接收回调函数,以便在有数据到达时进行处理。

void tcp_receive_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
   {
       if (err == ERR_OK && p!= NULL) {
           // 处理接收到的数据
           // 可以根据需要将数据转发给其他连接或者进行其他处理
           tcp_write(tpcb, p->payload, p->len, TCP_WRITE_FLAG_COPY);
           // 释放接收到的数据包缓冲区
           pbuf_free(p);
       }
   }

3.使用数据结构管理多个连接 

可以使用链表、数组等数据结构来管理多个客户端连接。例如,可以创建一个链表,每个节点存储一个客户端连接的信息,包括 TCP 控制块、客户端地址等。

typedef struct _client_connection
   {
       struct tcp_pcb *pcb;
       ip_addr_t client_ip;
       u16_t client_port;
       struct _client_connection *next;
   } client_connection_t;

   client_connection_t *client_list = NULL;

在连接建立时,将新的连接添加到链表中;在连接关闭时,从链表中删除相应的节点。

void add_client_connection(struct tcp_pcb *pcb, const ip_addr_t *client_ip, u16_t client_port)
   {
       client_connection_t *new_connection = (client_connection_t *)malloc(sizeof(client_connection_t));
       new_connection->pcb = pcb;
       new_connection->client_ip = *client_ip;
       new_connection->client_port = client_port;
       new_connection->next = client_list;
       client_list = new_connection;
   }

   void remove_client_connection(struct tcp_pcb *pcb)
   {
       client_connection_t *prev = NULL;
       client_connection_t *current = client_list;
       while (current!= NULL) {
           if (current->pcb == pcb) {
               if (prev == NULL) {
                   client_list = current->next;
               } else {
                   prev->next = current->next;
               }
               free(current);
               break;
           }
           prev = current;
           current = current->next;
       }
   }

七、数据处理与通信管理 

1.在接收回调函数中,可以根据接收到的数据进行相应的处理。例如,可以解析数据、执行特定的操作或者将数据转发给其他连接。

void tcp_receive_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
   {
       if (err == ERR_OK && p!= NULL) {
           // 处理接收到的数据
           char *data = (char *)p->payload;
           // 根据数据内容进行处理
           // 可以将数据转发给其他连接
           client_connection_t *current = client_list;
           while (current!= NULL) {
               if (current->pcb!= tpcb) {
                   tcp_write(current->pcb, data, strlen(data), TCP_WRITE_FLAG_COPY);
               }
               current = current->next;
           }
           // 释放接收到的数据包缓冲区
           pbuf_free(p);
       }
   }

2.如果需要向特定的客户端发送数据,可以遍历链表找到相应的客户端连接,并使用tcp_write函数进行数据发送。

void send_data_to_client(const ip_addr_t *client_ip, u16_t client_port, char *data)
   {
       client_connection_t *current = client_list;
       while (current!= NULL) {
           if (ip_addr_cmp(&current->client_ip, client_ip) && current->client_port == client_port) {
               tcp_write(current->pcb, data, strlen(data), TCP_WRITE_FLAG_COPY);
               break;
           }
           current = current->next;
       }
   }

八、错误处理与资源管理 

1.在整个通信过程中,需要进行适当的错误处理。例如,当连接失败、数据发送失败或接收超时等情况发生时,需要采取相应的措施,如重新连接、报告错误或释放资源等。

void tcp_error_callback(void *arg, err_t err)
   {
       // 处理错误情况
       struct tcp_pcb *tpcb = (struct tcp_pcb *)arg;
       remove_client_connection(tpcb);
   }

2.当连接关闭时,需要及时释放相关的资源,如缓冲区、TCP 控制块等,以避免内存泄漏。

 void tcp_close_callback(void *arg, err_t err)
   {
       // 处理连接关闭情况
       struct tcp_pcb *tpcb = (struct tcp_pcb *)arg;
       remove_client_connection(tpcb);
   }

九、总结 

通过以上步骤,我们可以在 STM32 上实现一个 TCP 服务器,与多个设备进行通信。在实际应用中,可以根据具体的需求进行进一步的优化和扩展,例如添加安全认证、数据加密、流量控制等功能。同时,还需要注意网络稳定性和可靠性,以确保通信的正常进行。

希望本文对大家在 STM32 上实现 TCP 服务器与多个设备通信有所帮助。如果有任何问题或建议,欢迎在评论区留言交流。

 

 

 

 

;