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

news2024/12/12 6:32:22

目录

一、引言

二、硬件准备

三、软件准备

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

五、创建 TCP 服务器

1.创建任务以及全局变量

2.创建 TCP 控制块

3.绑定端口 

4. 进入监听状态

5.设置接收回调函数 

六、处理多个客户端连接 

七、总结 



一、引言

        在嵌入式系统开发中,常常需要实现设备之间的网络通信。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.创建任务以及全局变量

我用的是ucosIII的实时操作系统,用freertos也差不多,把调度的代码替换一下就行了,第五个大标题2 3 4 5的代码都是在 tcp_server_task 这个任务函数里面的,然后第六个标题是每个客户端的回调函数,整理在一起就是完整的代码。

#include "sys.h"
#include "lwip_comm.h"
#include "includes.h"
#include "lwip/api.h"
#include "lwip/err.h"
#include "lwip/tcp.h"
#include <stdio.h>

#define TCP_SERVER_PORT 8088  // 定义TCP服务器监听的端口号,可按需修改
#define MAX_CONNECTIONS 2      // 最大允许同时连接的客户端数量
#define RX_BUFFER_SIZE 1024    // 接收缓冲区大小

// 用于存储客户端连接的结构体数组
struct tcp_pcb *tcp_server_pcbs[MAX_CONNECTIONS];

// 用于保护对tcp_server_pcbs数组操作的互斥信号量
OS_SEM tcp_server_pcbs_sem;

// 处理客户端连接的函数
err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err);
// 处理客户端接收数据的函数
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
// 处理客户端发送数据的函数(示例中简单回显数据给客户端)
err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 处理客户端断开连接的函数
void client_err_handler(void *arg, err_t err);
// TCP服务器任务函数
void tcp_server_task(void *p_arg);


//TCP客户端任务
#define TCP_PRIO		7
//任务堆栈大小
#define TCP_STK_SIZE	300
//任务控制块
OS_TCB	TcpTaskTCB;
//任务堆栈
CPU_STK TCP_TASK_STK[TCP_STK_SIZE];



//创建TCP线程
//返回值:0 TCP创建成功
//		其他 TCP创建失败
u8 tcp_demo_init(void)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	
	OS_CRITICAL_ENTER();//进入临界区
	//创建TCP任务
	OSTaskCreate((OS_TCB 	* )&TcpTaskTCB,		
				 (CPU_CHAR	* )"tcp task", 		
                 (OS_TASK_PTR )tcp_server_task, 			
                 (void		* )0,					
                 (OS_PRIO	  )TCP_PRIO,     
                 (CPU_STK   * )&TCP_TASK_STK[0],	
                 (CPU_STK_SIZE)TCP_STK_SIZE/10,	
                 (CPU_STK_SIZE)TCP_STK_SIZE,		
                 (OS_MSG_QTY  )0,					
                 (OS_TICK	  )0,					
                 (void   	* )0,					
                 (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
                 (OS_ERR 	* )&err);
	OS_CRITICAL_EXIT();	//退出临界区
	return err;
}

2.创建 TCP 控制块

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

 struct tcp_pcb *server_pcb;
    err_t err;
		OS_ERR os_err;
    // 创建一个TCP协议控制块(PCB)用于服务器端
    server_pcb = tcp_new();
    if (server_pcb == NULL)
    {
        printf("Error creating TCP PCB\n");
        return;
    }

3.绑定端口 

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

// 绑定服务器的IP地址和端口号
    err = tcp_bind(server_pcb, IP_ADDR_ANY, TCP_SERVER_PORT);
    if (err!= ERR_OK)
    {
        printf("Error binding TCP socket: %d\n", err);
        tcp_close(server_pcb);
        return;
    }

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

4. 进入监听状态

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

// 将服务器PCB设置为监听状态,等待客户端连接
    server_pcb = tcp_listen(server_pcb);
    if (server_pcb == NULL)
    {
        printf("Error listening for connections\n");
        tcp_close(server_pcb);
        return;
    }

5.设置接收回调函数 

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

// 设置接受客户端连接的回调函数
    tcp_accept(server_pcb, client_connection_handler);

    while (1)
    {
        // 任务可以在这里进行适当的阻塞等待,避免过度占用CPU资源
       OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&os_err); //延时100ms
    }

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

六、处理多个客户端连接 

1.在接收回调函数中,接受客户端的连接请求,并为每个连接创建一个新的 TCP 控制块来处理与该客户端的通信,由于单片机资源有限和需求,我设置的最多被两个客户端连接,再来了新的客户端连接会把之前最旧的关闭,这也是为了网线拔掉或者不主动关闭的无效连接占用资源

err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    OS_ERR os_err;
    int client_index;

    if (err!= ERR_OK)
    {
        return err;
    }

    // 获取互斥信号量,保护对tcp_server_pcbs数组的访问
		OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量
		if(tcp_server_pcbs[close_tcp]!=NULL){
				tcp_close(tcp_server_pcbs[close_tcp]);
		}
		tcp_server_pcbs[close_tcp]=newpcb;
		if(close_tcp==0){
			close_tcp=1;
		}else{
			close_tcp=0;
		}

    // 设置接收、发送和错误处理的回调函数
    tcp_recv(newpcb, client_recv_handler);
    tcp_sent(newpcb, client_send_handler);
    tcp_err(newpcb, client_err_handler);

    OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);
    return ERR_OK;
}

2.为每个连接设置接收回调函数,以便在有数据到达时进行处理,如果tcp客户端主动断开连接会走 else if (err == ERR_OK && p == NULL)  这里。

err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
		 OS_ERR os_err;

    if (err == ERR_OK && p!= NULL)
    {
        // 将接收到的pbuf数据复制到本地缓冲区(这里简单示例,可优化)
        char rx_buffer[RX_BUFFER_SIZE];
        int copied_bytes = 0;
        struct pbuf *q = p;
        while (q!= NULL)
        {
            int bytes_to_copy = (q->len < (RX_BUFFER_SIZE - copied_bytes))? q->len : (RX_BUFFER_SIZE - copied_bytes);
            memcpy(&rx_buffer[copied_bytes], q->payload, bytes_to_copy);
            copied_bytes += bytes_to_copy;
            q = q->next;
        }

        // 这里可以对接收到的数据进行处理,比如根据协议解析等,现在简单回显数据给客户端
        tcp_write(tpcb, rx_buffer, copied_bytes, TCP_WRITE_FLAG_COPY);
        tcp_output(tpcb);

        // 释放接收到的pbuf内存
        pbuf_free(p);
    }
    else if (err == ERR_OK && p == NULL)
    {
					OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量
        // 客户端关闭了连接,正常处理
					for (int i = 0; i < MAX_CONNECTIONS; i++)
					{
							if (tcp_server_pcbs[i] == tpcb)
							{
									tcp_server_pcbs[i] = NULL;
									break;
							}
					}
					 OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);
					 tcp_close(tpcb);
						
    }
    else
    {
        // 出现错误情况,关闭连接
        tcp_close(tpcb);
    }

    return ERR_OK;
}

3. 发送函数,这个我没用到,我只是被动返回消息。

err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
    // 这里可以根据实际发送情况做一些后续处理,当前示例只是简单回显,无需额外操作
    return ERR_OK;
}

4.错误处理,如果有超时的那种tcp连接,应该是会走这里释放TCP连接。

void client_err_handler(void *arg, err_t err)
{
    struct tcp_pcb *tpcb = (struct tcp_pcb *)arg;
    OS_ERR os_err;

    // 获取互斥信号量,保护对tcp_server_pcbs数组的访问
   OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); 	//请求信号量

    for (int i = 0; i < MAX_CONNECTIONS; i++)
    {
        if (tcp_server_pcbs[i] == tpcb)
        {
            tcp_server_pcbs[i] = NULL;
            break;
        }
    }

    OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);
    tcp_close(tpcb);
}

七、总结 

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2256724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LobeChat-46.6k星!顶级AI工具集,一键部署,界面美观易用,ApiSmart 是你肉身体验学习LLM 最好IDEA 工具

LobeChat LobeChat的开源&#xff0c;把AI功能集合到一起&#xff0c;真的太爽了。 我第一次发现LobeChat的时候&#xff0c;就是看到那炫酷的页面&#xff0c;这么强的前端真的是在秀肌肉啊&#xff01; 看下它的官网&#xff0c;整个网站的动效简直闪瞎我&#xff01; GitH…

计算机键盘的演变 | 键盘键名称及其功能 | 键盘指法

注&#xff1a;本篇为 “键盘的演变及其功能” 相关几篇文章合辑。 英文部分机翻未校。 The Evolution of Keyboards: From Typewriters to Tech Marvels 键盘的演变&#xff1a;从打字机到技术奇迹 Introduction 介绍 The keyboard has journeyed from a humble mechanical…

第三部分:进阶概念 7.数组与对象 --[JavaScript 新手村:开启编程之旅的第一步]

第三部分&#xff1a;进阶概念 7.数组与对象 --[JavaScript 新手村&#xff1a;开启编程之旅的第一步] 在 JavaScript 中&#xff0c;数组和对象是两种非常重要的数据结构&#xff0c;它们用于存储和组织数据。尽管它们都属于引用类型&#xff08;即它们存储的是对数据的引用而…

面试中遇到的一些有关进程的问题(有争议版)

一个进程最多可以创建多少个线程&#xff1f; 这个面经很有问题&#xff0c;没有说明是什么操作系统&#xff0c;以及是多少位操作系统。 因为不同的操作系统和不同位数的操作系统&#xff0c;虚拟内存可能是不一样多。 Windows 系统我不了解&#xff0c;我就说说 Linux 系统…

Excel技巧:如何批量调整excel表格中的图片?

插入到excel表格中的图片大小不一&#xff0c;如何做到每张图片都完美的与单元格大小相同&#xff1f;并且能够根据单元格来改变大小&#xff1f;今天分享&#xff0c;excel表格里的图片如何批量调整大小。 方法如下&#xff1a; 点击表格中的一个图片&#xff0c;然后按住Ct…

Stable Audio Open模型部署教程:用AI打造独家节拍,让声音焕发新活力!

Stable Audio Open 是一个开源的文本到音频模型&#xff0c;允许用户从简单的文本提示中生成长达 47 秒的高质量音频数据。该模型非常适合创建鼓点、乐器即兴演奏、环境声音、拟音录音和其他用于音乐制作和声音设计的音频样本。用户还可以根据他们的自定义音频数据微调模型&…

Linux上传代码的步骤与注意事项

最近因为工作需要&#xff0c;要上传代码到 DPDK 上&#xff0c;代码已经上传成功&#xff0c;记录一下过程&#xff0c;给大家提供一个参考。我这次需要上传的是pmd&#xff0c;即poll mode driver。 1 Coding Style 要上传代码&#xff0c;第一件事就是需要知道Coding Styl…

运费微服务和redis存热点数据

目录 运费模板微服务 接收前端发送的模板实体类 插入数据时使用的entity类对象 BaseEntity类 查询运费模板服务 新增和修改运费模块 整体流程 代码实现 运费计算 整体流程 总的代码 查找运费模板方法 计算重量方法 Redis存入热点数据 1.从nacos导入共享redis配置…

如何在windows10上部署WebDAV服务并通过内网穿透实现公网分享内部公共文件

WebDAV&#xff08;Web-based Distributed Authoring and Versioning&#xff09;是一种基于HTTP协议的应用层网络协议&#xff0c;它允许用户通过互联网进行文件的编辑和管理。这意味着&#xff0c;无论员工身处何地&#xff0c;只要连接到互联网&#xff0c;就能访问、编辑和…

gRPC 快速入门 — SpringBoot 实现(1)

目录 一、什么是 RPC 框架 &#xff1f; 二、什么是 gRPC 框架 &#xff1f; 三、传统 RPC 与 gRPC 对比 四、gRPC 的优势和适用场景 五、gRPC 在分布式系统中应用场景 六、什么是 Protocol Buffers&#xff08;ProtoBuf&#xff09;&#xff1f; 特点 使用场景 简单的…

深入浅出:SOME/IP-SD的工作原理与应用

目录 往期推荐 相关缩略语 SOME/IP 协议概述 协议介绍 SOME/IP TP 模块概述和 BSW 模块依赖性 原始 SOME/IP 消息的Header格式 SOME/IP-SD 模块概述 模块介绍 BSW modules依赖 客户端-服务器通信示例 Message 结构 用于SD服务的BSWM状态处理 往期推荐 ETAS工具…

字节高频算法面试题:小于 n 的最大数

问题描述&#xff08;感觉n的位数需要大于等于2&#xff0c;因为n的位数1的话会有点问题&#xff0c;“且无重复”是指nums中存在重复&#xff0c;但是最后返回的小于n最大数是可以重复使用nums中的元素的&#xff09;&#xff1a; 思路&#xff1a; 先对nums倒序排序 暴力回…

相机动态/在线标定

图1 图2 基本原理 【原理1】平行线在射影变换后会交于一点。如图所示,A为相机光心,蓝色矩形框为归一化平面,O为平面中心。地面四条黄色直线为平行且等距的车道线。HI交其中两条车道线于H、I, 过G作HI的平行线GM交车道线于M。HI、GM在归一化平面上的投影分别为JK、PN,二者会…

在 Windows 11 WSL (Ubuntu 24.04.1 LTS) | Python 3.12.x 下部署密码学库 charm

1. 在 Windows 11 上部署 Ubuntu (WSL) 由于作者没有高性能的 Ubuntu 服务器或个人电脑&#xff0c;且公司或学校提供的 Ubuntu 服务器虽然提供高性能 GPU 等硬件配置但通常不会提供 root 权限&#xff0c;因而作者通过在搭载了 Windows 11 的个人电脑上启动 Ubuntu (WSL) 来进…

【中间件开发】Redis基础命令详解及概念介绍

文章目录 前言一、Redis相关命令详解及原理1.1 string、set、zset、list、hash1.1.1 string1.1.2 list1.1.3 hash1.1.4 set1.1.5 zset 1.2 分布式锁的实现1.3 lua脚本解决ACID原子性1.4 Redis事务的ACID性质分析 二、Redis协议与异步方式2.1 Redis协议解析2.1.1 redis pipeline…

设计模式的艺术读书笔记

设计模式的艺术 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 创建的艺术创建型模式单例模式饿汉式单例与懒汉式单例的讨论通过静态内部类实现的更好办法 简单工厂模式工厂方法模式重载的工厂方法工厂方法的隐藏工厂方…

计算机毕设-基于springboot的甜品店管理系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Mac 录制电脑系统内的声音的具体方法?

1.第一步&#xff1a;下载BlackHole 软件 方式1&#xff1a;BlackHole官方下载地址 方式2&#xff1a; 百度云下载 提取码: n5dp 2.第二步&#xff1a;安装BlackHole 双击下载好的BlackHole安装包&#xff0c;安装默认提示安装。 3.第三步&#xff1a;在应用程序中找到音频…

【开源免费】基于Vue和SpringBoot的课程答疑系统(附论文)

博主说明&#xff1a;本文项目编号 T 070 &#xff0c;文末自助获取源码 \color{red}{T070&#xff0c;文末自助获取源码} T070&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

ACM latex模板中的CCSXML (即:CCS Concept)怎么填?

CCS Concept 感谢CCS Concept 怎么填 的珠玉在前. 问题描述 如下&#xff0c;ACM模板&#xff08;比如ACM computing surveys&#xff09;有一段是需要填写 ccsxml&#xff1a; %% %% The code below is generated by the tool at [http://dl.acm.org/ccs.cfm.](http://dl.…