你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F407 + LAN8720A + LWIP 实现TCP服务器

[复制链接]
STMCU小助手 发布时间:2022-4-11 10:16
环境说明:
开发板:某宝买的,STM32F407IG
STM32CUBEMX5.6
HAL Lib Version 1.25

(一)配置时钟

20200521102314536.png

(二)配置调试串口

I6]UV8_AXHHT@95MQAJ(1YH.png

(三)配置以太网ETH

(1)基础配置

2020052015571192.png

顺序依次说明:
LAN8720A使用的是RMII接口进行配置寄存器
自动重连使能
MAC地址
LAN8720A的物理地址(类似IIC的从设备地址),0或者1,LAN8720A上电后会读取RXER/PHYAD0引脚状态以此来确定设备地址,这里需要根据你自己实际的原理图进行配置,我的原理图是该引脚是悬空的,所以默认就是0。

6T9{3DM_%]8`[7_TFFGM`YL.png

接收模式:可选轮询和中断,我选择了轮询模式。(注:在STM32CUBEMX中如果开启了LWIP那么只能选择轮询模式,实际上是可以使用中断方式的,不过需要自己移植修改lwip协议栈,课参考正点原子)

校验:可选软件和硬件,我选择了由硬件去校验

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

(2)高级配置

20200520161703441.png
      看下图,此处的配置就是根据实际的PHY芯片寄存器进行配置了,默认的是LAN8742A,而我们使用的是LAN8720A,所以需要更改为 user PHY,配置项默认即可。

20200520162350617.png

对于默认配置,我们将默认配置与LAN8720A的数据手册进行对比然后检查是否正确,以PHY Reset这一项为例,默认值是0x8000,去查看数据手册:

BL~P[AYE[9F0MU$]%5G]_96.png

由上图的基本控制寄存器表可知该寄存器偏移地址为0,大小是16位,第15位是软件复位控制,=1是复位,默认为0,那么如果想要复位LAN8720A就需要将该寄存器的第15位置一,也就是0x8000,对比STM32CUBE的默认配置发现一致,其他配置项同理也是这么检查,检查完毕后发现默认配置是OK的。

配置LWIP协议栈

_CUFRU6Z`A3ICCW4DR1SC%I.png

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

工程代码修改
文件ethernetif.c中找到函数low_level_init函数,添加复位LAN8720A代码:

20200521094048989.png

在main函数的主循环中调用函数MX_LWIP_Process。
修改MX_LWIP_Process函数,在其中加入:

20200521095027706.png

这个函数的作用是检测当前网线的连接状态,如果状态发生改变(例如网线被拔插了一下),那么就会调用回调函数ethernetif_update_config,看函数名就知道这是更新配置,而这个函数尾部又调用了函数ethernetif_notify_conn_changed,看函数名可知道函数作用是通知连接状态改变,所以我们就更改此函数来达到热拔插自动重连的目的。

20200521100726742.png

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

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

  1. #if 1

  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdint.h>
  5. #include "lwip/tcp.h"
  6. #include "lwip/err.h"
  7. #include "lwip/memp.h"
  8. #include "lwip/inet.h"


  9. / 回调函数控制宏
  10. #define USE_ERROR_CALLBACK 1
  11. #define USE_SENT_CALLBACK 0
  12. #define USE_POLL_CALLBACK 0



  13. / 调试信息输出 //
  14. #define DEBUG

  15. #ifdef DEBUG
  16. #define debug(fmt, ...) do{printf(fmt, ##__VA_ARGS__);}while(0)
  17. #else
  18. #define debug(fmt, ...) do{;}while(0)
  19. #endif



  20. / tcp接收发送缓存 ///
  21. #define TCP_RX_LEN  8192
  22. uint8_t TCP_RX_BUF[TCP_RX_LEN];
  23. volatile uint16_t TCP_RX_STA = 0;  /* bit15:有无数据标志位        bit14-0:数据量计数 */



  24. / TCP结构句柄 ///
  25. struct tcp_pcb* tcppcb = NULL;



  26. / 本地函数定义 ///
  27. static void tcp_server_disconnect(struct tcp_pcb *tpcb);
  28. static uint32_t tcp_server_send(struct tcp_pcb *tpcb, const void* buf, uint32_t len);



  29. / 私有函数实现  ///

  30. #if (USE_ERROR_CALLBACK == 1)
  31. static void error_callback(void *arg, err_t err)
  32. {
  33.         debug("\r\n error_callback:%d.", err);
  34. }
  35. #endif

  36. #if (USE_ERROR_CALLBACK == 1)
  37. static void error_callback(void *arg, err_t err)
  38. {
  39.         debug("\r\n error_callback:%d.", err);
  40.         switch(err)
  41.         {
  42.         /* PC上位机如果正常运行中闪退或者不良退出会出现这个错误,此时服务器需要释放掉连接  */
  43.         case ERR_RST:
  44.         tcp_server_disconnect(tcppcb);
  45.         break;
  46.         default:break;
  47.         }
  48. }

  49. #endif

  50. static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
  51. {
  52.         if (p == NULL) /* 接收到空包表示对方断开连接 */
  53.         {
  54.                 tcp_server_disconnect(tpcb);
  55.                 err = ERR_CLSD;
  56.         }
  57.         else if (err != ERR_OK) /* 收到非空包但是出现错误 */
  58.         {
  59.                 pbuf_free(p);
  60.         }
  61.         else /* 接收数据正常,遍历pbuf拷贝出接收到的数据 */
  62.         {
  63.                 struct pbuf* it = p;

  64.                 if ((TCP_RX_STA & 0x8000) == 0) /* 当前缓存为空  */
  65.                 {
  66.                         for (it = p; it != NULL; it = it->next)
  67.                         {
  68.                                 if (TCP_RX_STA + it->len > TCP_RX_LEN) /* 缓存满了 */
  69.                                         break;
  70.                                 memcpy(TCP_RX_BUF + TCP_RX_STA, it->payload, it->len);  /* 将接收到的数据拷贝到自己的缓存中  */
  71.                                 TCP_RX_STA += it->len;
  72.                         }
  73.                         TCP_RX_STA |= 0x8000;    /* 标记有数据收到 */
  74.                 }
  75.                 tcp_recved(tpcb, p->tot_len); /* 滑动TCP窗口 */
  76.                 pbuf_free(p); /* 释放pbuf */
  77.         }
  78.         return err;
  79. }


  80. static err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
  81. {
  82.         if (tcppcb == NULL)
  83.         {
  84.                 if (err == ERR_OK)
  85.                 {
  86.                         tcppcb = newpcb;
  87.                         tcp_arg(newpcb, NULL);
  88.                         tcp_recv(newpcb, recv_callback);

  89. #if (USE_ERROR_CALLBACK == 1)
  90.                         tcp_err(newpcb, error_callback);
  91. #endif

  92. #if (USE_SENT_CALLBACK == 1)
  93.                         tcp_sent(newpcb, sent_callback);
  94. #endif

  95. #if (USE_POLL_CALLBACK == 1)
  96.                         tcp_poll(newpcb, poll_callback, 1);
  97. #endif
  98.                         debug("\r\n %s:%d connect.", inet_ntoa(newpcb->remote_ip), newpcb->remote_port);
  99.                 }
  100.                 else
  101.                 {
  102.                         tcp_server_disconnect(newpcb);
  103.                 }
  104.         }
  105.         else
  106.         {
  107.                 tcp_abort(newpcb);
  108.                 debug("\r\n already connected. ");
  109.         }
  110.         return err;
  111. }

  112. static void tcp_server_disconnect(struct tcp_pcb *tpcb)
  113. {
  114.         tcp_arg(tpcb, NULL);
  115.         tcp_recv(tpcb, NULL);

  116. #if (USE_SENT_CALLBACK == 1)
  117.         tcp_sent(tpcb, NULL);
  118. #endif

  119. #if (USE_POLL_CALLBACK == 1)
  120.         tcp_poll(tpcb, NULL, 0);
  121. #endif

  122. #if (USE_ERROR_CALLBACK == 1)
  123.         tcp_err(tpcb, NULL);
  124. #endif

  125.         tcp_abort(tpcb); /* 关闭连接并释放tpcb控制块 */
  126.         tcppcb = NULL;
  127.         debug("\r\n disconnected.");
  128. }

  129. static uint32_t tcp_server_send(struct tcp_pcb *tpcb, const void* buf, uint32_t len)
  130. {
  131.         uint32_t nwrite = 0, total = 0;
  132.         const uint8_t* p = (const uint8_t *) buf;
  133.         err_t err = ERR_OK;
  134.         if (!tpcb)
  135.                 return 0;
  136.         while ((err == ERR_OK) && (len != 0) && (tcp_sndbuf(tpcb) > 0))
  137.         {
  138.                 nwrite = tcp_sndbuf(tpcb) >= len ? len : tcp_sndbuf(tpcb);
  139.                 err = tcp_write(tpcb, p, nwrite, 1);
  140.                 if (err == ERR_OK)
  141.                 {
  142.                         len -= nwrite;
  143.                         total += nwrite;
  144.                         p += nwrite;
  145.                 }
  146.                 tcp_output(tpcb);
  147.         }
  148.         return total;
  149. }

  150. / 导出以下函数供外部调用 ///

  151. //extern int tcp_server_start(uint16_t port);
  152. //extern int user_senddata(const void* buf,uint32_t len);
  153. //extern int transfer_data();


  154. /**
  155. * 启动TCP服务器
  156. * @param  port 本地端口号
  157. * @return      成功返回0
  158. */
  159. int tcp_server_start(uint16_t port)
  160. {
  161.         int ret = 0;
  162.         struct tcp_pcb* pcb = NULL;
  163.         err_t err = ERR_OK;

  164.         /* create new TCP PCB structure */
  165.         pcb = tcp_new();
  166.         if (!pcb)
  167.         {
  168.                 debug("Error creating PCB. Out of Memory\n\r");
  169.                 ret = -1;
  170.                 goto __exit;
  171.         }

  172.         /* bind to specified @port */
  173.         err = tcp_bind(pcb, IP_ADDR_ANY, port);
  174.         if (err != ERR_OK)
  175.         {
  176.                 debug("Unable to bind to port %d: err = %d\n\r", port, err);
  177.                 ret = -2;
  178.                 goto __exit;
  179.         }

  180.         /* listen for connections */
  181.         pcb = tcp_listen(pcb);
  182.         if (!pcb)
  183.         {
  184.                 debug("Out of memory while tcp_listen\n\r");
  185.                 ret = -3;
  186.         }

  187.         /* specify callback to use for incoming connections */
  188.         tcp_accept(pcb, accept_callback);

  189.         /* create success */
  190.         debug("TCP echo server started @ port %d\n\r", port);
  191.         return ret;

  192. __exit:
  193.         if (pcb)
  194.                 memp_free(MEMP_TCP_PCB, pcb);
  195.         return ret;
  196. }

  197. /**
  198. * TCP发送数据
  199. * @param  buf 待发送的数据
  200. * @param  len 数据长度
  201. * @return     返回实际发送的字节数
  202. */
  203. int user_senddata(const void* buf, uint32_t len)
  204. {
  205.         return tcp_server_send(tcppcb, buf, len);
  206. }

  207. /**
  208. * 轮询函数,放置于main函数的while死循环中
  209. * @return 无
  210. */
  211. int transfer_data()
  212. {
  213.         uint32_t nsend = 0;
  214.         if (tcppcb != NULL && tcppcb->state == ESTABLISHED) /* 连接有效 */
  215.         {
  216.                 if (TCP_RX_STA & 0x8000)   /* 有数据收到  */
  217.                 {
  218.                         nsend = user_senddata(TCP_RX_BUF, TCP_RX_STA & 0x7FFF);    /* 将接收到的数据发回去  */
  219.                         TCP_RX_STA = 0;
  220.                         debug("\r\n send %d bytes success.", nsend);
  221.                 }
  222.         }
  223.         return 0;
  224. }

  225. #endif
复制代码




收藏 评论0 发布时间:2022-4-11 10:16

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版