STM32F407 + LAN8720A + LWIP 实现TCP服务器

news/2024/11/24 9:48:22/

STM32F407 + LAN8720A + LWIP 实现TCP客户端


环境说明:

  • 开发板:某宝买的,STM32F407IG
  • STM32CUBEMX5.6
  • HAL Lib Version 1.25

(一)配置时钟

在这里插入图片描述


(二)配置调试串口

在这里插入图片描述


(三)配置以太网ETH

(1)基础配置

在这里插入图片描述
顺序依次说明:

  • LAN8720A使用的是RMII接口进行配置寄存器
  • 自动重连使能
  • MAC地址
  • LAN8720A的物理地址(类似IIC的从设备地址),0或者1,LAN8720A上电后会读取RXER/PHYAD0引脚状态以此来确定设备地址,这里需要根据你自己实际的原理图进行配置,我的原理图是该引脚是悬空的,所以默认就是0。
    在这里插入图片描述
  • 接收模式:可选轮询和中断,我选择了轮询模式。(注:在STM32CUBEMX中如果开启了LWIP那么只能选择轮询模式,实际上是可以使用中断方式的,不过需要自己移植修改lwip协议栈,课参考正点原子)
  • 校验:可选软件和硬件,我选择了由硬件去校验

除此之外还有一个复位引脚ETH_RST,拉低是复位LAN8720A,根据你实际的原理图连线配置该IO为复用输出功能即可。

(2)高级配置

LAN8720A数据手册pdf下载:https://www.alldatasheet.com/datasheet-pdf/pdf/516623/SMSC/LAN8720A.html
在这里插入图片描述

~~~~~~~~         看下图,此处的配置就是根据实际的PHY芯片寄存器进行配置了,默认的是LAN8742A,而我们使用的是LAN8720A,所以需要更改为 user PHY,配置项默认即可。在这里插入图片描述
对于默认配置,我们将默认配置与LAN8720A的数据手册进行对比然后检查是否正确,以PHY Reset这一项为例,默认值是0x8000,去查看数据手册:
在这里插入图片描述
由上图的基本控制寄存器表可知该寄存器偏移地址为0,大小是16位,第15位是软件复位控制,=1是复位,默认为0,那么如果想要复位LAN8720A就需要将该寄存器的第15位置一,也就是0x8000,对比STM32CUBE的默认配置发现一致,其他配置项同理也是这么检查,检查完毕后发现默认配置是OK的。


配置LWIP协议栈

在这里插入图片描述
如上图,需要开启状态改变回调函数和连接状态改变回调函数,否则无法实现网线的热拔插。


工程代码修改

  • 文件ethernetif.c中找到函数low_level_init函数,添加复位LAN8720A代码:
    在这里插入图片描述
  • 在main函数的主循环中调用函数MX_LWIP_Process
  • 修改MX_LWIP_Process函数,在其中加入:
    在这里插入图片描述
    这个函数的作用是检测当前网线的连接状态,如果状态发生改变(例如网线被拔插了一下),那么就会调用回调函数ethernetif_update_config,看函数名就知道这是更新配置,而这个函数尾部又调用了函数ethernetif_notify_conn_changed,看函数名可知道函数作用是通知连接状态改变,所以我们就更改此函数来达到热拔插自动重连的目的。
    在这里插入图片描述

到此,连接上网线ping测试一下即可。


(四)TCP服务器代码

下面代码的流程是:接收来自客户端的数据->将数据从lwip中拷贝出来->发回去。

#if 1#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "lwip/tcp.h"
#include "lwip/err.h"
#include "lwip/memp.h"
#include "lwip/inet.h"/ 回调函数控制宏 
#define USE_ERROR_CALLBACK 1
#define USE_SENT_CALLBACK 0
#define USE_POLL_CALLBACK 0/ 调试信息输出 //
#define DEBUG#ifdef DEBUG
#define debug(fmt, ...) do{printf(fmt, ##__VA_ARGS__);}while(0)
#else
#define debug(fmt, ...) do{;}while(0)
#endif/ tcp接收发送缓存 ///
#define TCP_RX_LEN  8192
uint8_t TCP_RX_BUF[TCP_RX_LEN];
volatile uint16_t TCP_RX_STA = 0;  /* bit15:有无数据标志位        bit14-0:数据量计数 *// TCP结构句柄 ///
struct tcp_pcb* tcppcb = NULL;/ 本地函数定义 ///
static void tcp_server_disconnect(struct tcp_pcb *tpcb);
static uint32_t tcp_server_send(struct tcp_pcb *tpcb, const void* buf, uint32_t len);/ 私有函数实现  ///#if (USE_ERROR_CALLBACK == 1)
static void error_callback(void *arg, err_t err)
{debug("\r\n error_callback:%d.", err);
}
#endif#if (USE_ERROR_CALLBACK == 1)
static void error_callback(void *arg, err_t err)
{debug("\r\n error_callback:%d.", err);switch(err){/* PC上位机如果正常运行中闪退或者不良退出会出现这个错误,此时服务器需要释放掉连接  */case ERR_RST:tcp_server_disconnect(tcppcb);break;default:break;}
}#endifstatic err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{if (p == NULL) /* 接收到空包表示对方断开连接 */{tcp_server_disconnect(tpcb);err = ERR_CLSD;}else if (err != ERR_OK) /* 收到非空包但是出现错误 */{pbuf_free(p);}else /* 接收数据正常,遍历pbuf拷贝出接收到的数据 */{struct pbuf* it = p;if ((TCP_RX_STA & 0x8000) == 0) /* 当前缓存为空  */{for (it = p; it != NULL; it = it->next){if (TCP_RX_STA + it->len > TCP_RX_LEN) /* 缓存满了 */break;memcpy(TCP_RX_BUF + TCP_RX_STA, it->payload, it->len);  /* 将接收到的数据拷贝到自己的缓存中  */TCP_RX_STA += it->len;}TCP_RX_STA |= 0x8000;    /* 标记有数据收到 */}tcp_recved(tpcb, p->tot_len); /* 滑动TCP窗口 */pbuf_free(p); /* 释放pbuf */}return err;
}static err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
{if (tcppcb == NULL){if (err == ERR_OK){tcppcb = newpcb;tcp_arg(newpcb, NULL);tcp_recv(newpcb, recv_callback);#if (USE_ERROR_CALLBACK == 1)tcp_err(newpcb, error_callback);
#endif#if (USE_SENT_CALLBACK == 1)tcp_sent(newpcb, sent_callback);
#endif#if (USE_POLL_CALLBACK == 1)tcp_poll(newpcb, poll_callback, 1);
#endifdebug("\r\n %s:%d connect.", inet_ntoa(newpcb->remote_ip), newpcb->remote_port);}else{tcp_server_disconnect(newpcb);}}else{tcp_abort(newpcb);debug("\r\n already connected. ");}return err;
}static void tcp_server_disconnect(struct tcp_pcb *tpcb)
{tcp_arg(tpcb, NULL);tcp_recv(tpcb, NULL);#if (USE_SENT_CALLBACK == 1)tcp_sent(tpcb, NULL);
#endif#if (USE_POLL_CALLBACK == 1)tcp_poll(tpcb, NULL, 0);
#endif#if (USE_ERROR_CALLBACK == 1)tcp_err(tpcb, NULL);
#endiftcp_abort(tpcb); /* 关闭连接并释放tpcb控制块 */tcppcb = NULL;debug("\r\n disconnected.");
}static uint32_t tcp_server_send(struct tcp_pcb *tpcb, const void* buf, uint32_t len)
{uint32_t nwrite = 0, total = 0;const uint8_t* p = (const uint8_t *) buf;err_t err = ERR_OK;if (!tpcb)return 0;while ((err == ERR_OK) && (len != 0) && (tcp_sndbuf(tpcb) > 0)){nwrite = tcp_sndbuf(tpcb) >= len ? len : tcp_sndbuf(tpcb);err = tcp_write(tpcb, p, nwrite, 1);if (err == ERR_OK){len -= nwrite;total += nwrite;p += nwrite;}tcp_output(tpcb);}return total;
}/ 导出以下函数供外部调用 /////extern int tcp_server_start(uint16_t port);
//extern int user_senddata(const void* buf,uint32_t len);
//extern int transfer_data();/*** 启动TCP服务器* @param  port 本地端口号* @return      成功返回0*/
int tcp_server_start(uint16_t port)
{int ret = 0;struct tcp_pcb* pcb = NULL;err_t err = ERR_OK;/* create new TCP PCB structure */pcb = tcp_new();if (!pcb){debug("Error creating PCB. Out of Memory\n\r");ret = -1;goto __exit;}/* bind to specified @port */err = tcp_bind(pcb, IP_ADDR_ANY, port);if (err != ERR_OK){debug("Unable to bind to port %d: err = %d\n\r", port, err);ret = -2;goto __exit;}/* listen for connections */pcb = tcp_listen(pcb);if (!pcb){debug("Out of memory while tcp_listen\n\r");ret = -3;}/* specify callback to use for incoming connections */tcp_accept(pcb, accept_callback);/* create success */debug("TCP echo server started @ port %d\n\r", port);return ret;__exit:if (pcb)memp_free(MEMP_TCP_PCB, pcb);return ret;
}/*** TCP发送数据* @param  buf 待发送的数据* @param  len 数据长度* @return     返回实际发送的字节数*/
int user_senddata(const void* buf, uint32_t len)
{return tcp_server_send(tcppcb, buf, len);
}/*** 轮询函数,放置于main函数的while死循环中* @return 无*/
int transfer_data()
{uint32_t nsend = 0;if (tcppcb != NULL && tcppcb->state == ESTABLISHED) /* 连接有效 */{if (TCP_RX_STA & 0x8000)   /* 有数据收到  */{nsend = user_senddata(TCP_RX_BUF, TCP_RX_STA & 0x7FFF);    /* 将接收到的数据发回去  */TCP_RX_STA = 0;debug("\r\n send %d bytes success.", nsend);}}return 0;
}#endif

以上代码已测试!


ends。。。


http://www.ppmy.cn/news/145567.html

相关文章

STM32H7+LAN8720A之ETH与LWIP配置问题(End)

开篇介绍 由于项目中需要使用到STM32H7系列的芯片&#xff0c;且该系列无法移植ST的标准库&#xff0c;只能使用ST的HAL库&#xff0c;通过STM32Cube生成HAL库的基本代码。在项目开发中需要使用到STM32板载的ETH口&#xff0c;在简化的四层模型中充当着数据链路层的角色&#…

【上电即上华为云】华为云openCPU智联模组_wifi_8720_MQTT

原贴地址&#xff1a;https://bbs.huaweicloud.com/blogs/233458 【摘要】 华为云openCPU智联模组_wifi_8720_MQTT&#xff1a;上电即上华为云 华为云openCPU智联模组_wifi_8720_MQTT&#xff1a;上电即上华为云 一、wifi 8720基础SDK、patch 基础SDK 9351_00018082-sdk-ameb…

RTL8720WIFI扫描增加信道显示(arduino)

一、环境 添加rtl8720的库 https://github.com/ambiot/ambd_arduino/raw/master/Arduino_package/package_realtek.com_amebad_index.json板子选择 BW16 例子选择Wifi->ScanNetworksScanNetworks 二、修改 源码位置&#xff1a;C:\Users\Administrator\AppData\Local\Ar…

ESP32驱动LAN8720网卡

简介 ESP32 使用内置的 MAC 驱动外置的 PHY(LAN8720)&#xff0c;软件和版本为 ESP-IDF-V4.3 连接ESP32和LAN8720 接线示意图如下图所示&#xff0c;其中GPIO17可不接 接线线序 以下引脚不能更改线序&#xff1a; GPIORMII SignalESP32 EMAC Function0REF_CLKEMAC_TX_CLK2…

设计模式-访问者模式

访问者模式 问题背景解决方案&#xff1a;传统方案 访问者模式基本介绍原理UML类图 使用访问者模式解决问题UML类图示例代码运行结果 注意事项和细节 问题背景 我们来制作一台电脑&#xff0c;他的硬件有CPU和磁盘&#xff0c;CPU和磁盘类都有一个常量作为他们各自的数据&…

linux-静态库制作与使用

创建2个目录进行创建与使用的演示 创建静态库 准备源文件与头文件 查看所有源文件与头文件 将源文件编译.o文件&#xff0c;然后将.o文件打包为静态库 gcc -c mymath.c -o mymath.o -stdc99 gcc -c myprint.c -o myprint.o -stdc99 ar指令&#xff1a;打包多个.o文件为静态…

2024系统分析师论文模版-《论需求分析方法及应用》

论需求分析方法及应用 需求分析是提炼、分析和仔细审查已经获取到的需求的过程。需求分析的目的是确保所有的项目干系.人(利益相关者)都理解需求的含义并找出其中的错误、遗漏或其它不足的地方。需求分析的关键在于对问题域的研究与理解。为了便于理解问题域&#xff0c;现代软…

用好这三个技巧,谷歌广告投放就可以高效获取B2B客户!

大家都知道&#xff0c;B2B领域的企业在谷歌广告投放上具有很大的挑战性&#xff0c;但谷歌又是海外客户搜索产品和服务的主要平台之一&#xff0c;如果我要做B2B的谷歌投放&#xff0c;应该注意哪些点才能提高我的广告效果&#xff1f;东哥今天就给大家分享几个小技巧&#xf…