您的位置:首页 > 游戏 > 游戏 > 【RTT-Studio】详细使用教程十一:ETH网口设备--LAN8720A

【RTT-Studio】详细使用教程十一:ETH网口设备--LAN8720A

2024/11/16 16:01:23 来源:https://blog.csdn.net/Hei_se_meng_yan/article/details/140999906  浏览:    关键词:【RTT-Studio】详细使用教程十一:ETH网口设备--LAN8720A

文章目录

    • 一、简介
    • 二、ETH基础配置
    • 三、初始化配置
    • 四、完整代码
    • 五、测试验证

一、简介

1.TCP协议
  传输控制协议在LWIP协议栈中占据了大半的代码,它是最常见的传输层协议,也是最稳定的传输层协议,很多上层应用都是依赖 TCP 协议进程传输数据,如 SMTP,FTP 等等。

  TCP 与 UDP 一样,都是传输层的协议,但是提供的服务却不相同,UDP 为上层应用提供的是一种不可靠的,无连接的服务,而 TCP 提供一种面向连接,可靠的字节传输服务,TCP 让两个主机建立连接的关系,应用数据以数据流的形式进行传输,先后发出的数据在网络中虽然也是互不干扰的传输,但是这些数据本身携带的信息确实紧密联系的,TCP 协议会给每个传输的字号进行编号,当然了,两个主机方向上的数据编号是彼此独立的,在传输的过程中,发送方把数据的起始编号与长度放在 TCP 报文中,接收方将所有数据按照编号组装起来,然后返回一个确认,当所有的数据接收完成后才将数据递交到应用层。

2.TCP 建立连接

  • 客户端发送含 SYN 标志的报文,表示要建立一个连接, 初始化连接的同步序号 seq;
  • 服务器端接收到含 SYN 的报文后, 也将一条含 SYN 标志报文, 作为 ACK 回复给客户端, 一则表示收到客户端创建连接的请求,二则表示服务端也同时创建与客户端的连接;
  • 客户端收到回复后再发送一条 ACK 报文, 表示收到服务端的创建连接请求。

  不论是客户端还是服务端, 创建连接发送数据的时候数据包中都包含一个初始化序列号 seq, 对方收到数据并回复时会将收到的 seq 加 1 作为 ACK 的数据。 这样发送端就可以判断是不是对本次数据的回复。 以后每次发数据都会在前一次 seq 的基础上 +1 作为本次的 seq (注意, 建立连接和断开连接时 seq 是加 1, 传输应用数据时 seq 加的是上次发送的数据长度 )。
在这里插入图片描述
tcp_connect实现的,大概流程如下:
在这里插入图片描述
服务端连接流程:
在这里插入图片描述
3.TCP断开连接
TCP断开连接时需要 4 个步骤来完成断开, 俗称四次挥手:

  • 已经建立连接的其中一段想要断开连接,称为主动关闭方。其发送一个含 FIN 标志、seq 值(假设为 Q)和 ACK 值(假设为 K, 来自上次收到的 seq)的报文到连接对方,称为被关闭方;
  • 被关闭方收到含 FIN 标志的报文后,将 K 作为 seq 值,Q 加 1 后作为 ACK 值回复给主动关闭方。
  • 被关闭方同时检查自身是否还有处理完的数据包,若没有则开始启动关闭操作,身份转变为主动关闭方,同样发送一个含 FIN、seq(值仍为 K)和 ACK 值(值仍为 Q+1)初始化的报文到连接对方。
  • 原主动关闭方转变为被关闭方,收到报文后,回复一条 seq 为 Q+1,ACK 为 K+1 的报文。至此,完成 TCP 连接的断开。
    在这里插入图片描述
    正常的TCP断开连接一般是从ESTABLISHED状态开始, 就是说两个网络端已经建立了连接, 断开流程如下:
    主动关闭方流程:
    在这里插入图片描述
    被动关闭方流程:
    在这里插入图片描述
    4.TCP 超时重传
      TCP 在发起一次传输时会开启一个定时器,如果在定时器超时时未收到应答就会进行重传。一个 TCP 连接的往返时间为 RTT(Round-Trip Time),然后根据 RTT 来设置重传时间就是 RTO(Retransmission TimeOut)。 如果 RTO 较大,由于等待时间过长会影响 TCP 的效率;较小则可能使系统来不及响应而认为数据传输失败而重传。所以 RTO 是随着网络的状态动态调整的,而网络状态可以通过测量报文段的往返时间来反应。

二、ETH基础配置

1.cubemx配置
配置时钟,一般都使用最大时钟频率即可。
在这里插入图片描述
配置网口引脚设置,具体如下:
在这里插入图片描述
2.board.h配置

/** if you want to use eth you can use the following instructions.** STEP 1, define macro related to the eth*                 such as    BSP_USING_ETH** STEP 2, copy your eth init function from stm32xxxx_hal_msp.c generated by stm32cubemx to the end if board.c file*                 such as     void HAL_ETH_MspInit(ETH_HandleTypeDef* heth)** STEP 3, modify your stm32xxxx_hal_config.h file to support eth peripherals. define macro related to the peripherals*                 such as     #define HAL_ETH_MODULE_ENABLED** STEP 4, config your phy type*                 such as     #define PHY_USING_LAN8720A*                             #define PHY_USING_DM9161CEP*                             #define PHY_USING_DP83848C* STEP 5, implement your phy reset function in the end of board.c file*                 void phy_reset(void)** STEP 6, config your lwip or other network stack**/#define BSP_USING_ETH
#ifdef BSP_USING_ETH
#define PHY_USING_LAN8720A
/*#define PHY_USING_DM9161CEP*/
/*#define PHY_USING_DP83848C*/
#endif

3.rtthread settings配置
(1)勾选以太网驱动配置
在这里插入图片描述

(2)勾选网络层驱动
在这里插入图片描述
(3)设置控制板IP信息
在这里插入图片描述
4.以太网复位引脚设置

/*** @brief ETH初始化* @param ethHandle句柄*/
void HAL_ETH_MspInit(ETH_HandleTypeDef* heth)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(heth->Instance==ETH){/* USER CODE BEGIN ETH_MspInit 0 *//* USER CODE END ETH_MspInit 0 *//* Peripheral clock enable */__HAL_RCC_ETH_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**ETH GPIO ConfigurationPC1     ------> ETH_MDCPA1     ------> ETH_REF_CLKPA2     ------> ETH_MDIOPA7     ------> ETH_CRS_DVPC4     ------> ETH_RXD0PC5     ------> ETH_RXD1PB11     ------> ETH_TX_ENPB12     ------> ETH_TXD0PB13     ------> ETH_TXD1*/GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF11_ETH;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF11_ETH;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF11_ETH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* USER CODE BEGIN ETH_MspInit 1 *//* USER CODE END ETH_MspInit 1 */}}//定义的复位引脚
#define RESET_IO GET_PIN(C, 6)/*** @brief phy复位引脚初始化*/
void phy_reset(void)
{rt_pin_mode(RESET_IO, PIN_MODE_OUTPUT);rt_pin_write(RESET_IO, PIN_HIGH);rt_thread_mdelay(50);rt_pin_write(RESET_IO, PIN_LOW);rt_thread_mdelay(50);rt_pin_write(RESET_IO, PIN_HIGH);
}

5.修改rtthread源码
修改 eth_demo\rt-thread\components\drivers\include\drivers\phy.h文件,注释掉32行。图中 drivers 文件夹下的 drv_eth.c 报错是因为这个变量重定义了。
在这里插入图片描述

6.修改 cubemx 生成的 main 函数
  此时编译,应该是没有报错能通过的。cubemx中的main函数会用到我们删掉的那两个函数,所有会有警告,不想看可以在main函数中也注释或者删掉。
在这里插入图片描述


三、初始化配置

在控制台执行函数中,将广播函数进行调用,从而实现将数据通过网口进行通信。
在这里插入图片描述


四、完整代码

1.tcp_server.c

#include <rtthread.h>
#include "board.h"
#ifdef BSP_USING_ETH
#include "tcp_server.h"int server_state_flag = 0;/*** @brief 广播数据* @param buffer 发送的数据* @param len    数据长度*/
void broadcast(char *buffer, int len)
{for(int i=0; i < MAXCLIENT; i++){if(g_client[i] < 0)continue;send(g_client[i], buffer, len, 0);}
}/*** @brief 设置服务器参数*/
static void set_server_parameter(void)
{struct netdev *netdev = RT_NULL;ip_addr_t addr;netdev = netdev_get_by_name("e0");if (netdev == RT_NULL){rt_kprintf("netdev is null\n");return;}// 设置IP地址inet_aton(serverInfo.serverIpAddr, &addr);netdev_set_ipaddr(netdev, &addr);// 设置网关inet_aton(serverInfo.serverGateway, &addr);netdev_set_gw(netdev, &addr);// 设置子网掩码inet_aton(serverInfo.serverMask, &addr);netdev_set_netmask(netdev, &addr);// 重启控制板rt_thread_delay(RT_TICK_PER_SECOND / 100);rt_hw_cpu_reset();
}/*** @brief 创建TCP服务器连接的线程* @param parameter 传入的参数*/
static void server_thread_entry(void* parameter)
{int ret, maxFd, i=0;int optval = 1;char buff[] = {0};int j = 0;int z = 0;struct sockaddr_in clientAddr;       // 存储连接上来的客户端的IP地址和端口号struct sockaddr_in serverAddr;int newClientFd = -1;                // 新的套接字uint32_t len = sizeof(struct sockaddr_in);param_fac_s *param = (param_fac_s *)parameter;rt_kprintf("Server IP:%s:%d \n", param->server.serverIpAddr,param->server.serverPort);// 建立套接字int socketFd = socket(AF_INET,SOCK_STREAM, 0);if(socketFd == -1){rt_kprintf("socket error\n");}// 端口复用setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));// 绑定自己的IP地址和端口号serverAddr.sin_family = AF_INET;if (param->server.serverPort == 0){serverAddr.sin_port = htons(SERVER_PORT);}else{serverAddr.sin_port = htons(param->server.serverPort);}char buf[16] = {0};if (rt_strcmp(param->server.serverIpAddr, buf) == 0){inet_pton(AF_INET, RT_LWIP_IPADDR, &(serverAddr.sin_addr.s_addr));}else{inet_pton(AF_INET, param->server.serverIpAddr, &(serverAddr.sin_addr.s_addr));}ret = bind(socketFd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_in));if(ret == -1){rt_kprintf("bind error\n");}// 设置监听listen(socketFd, MAXCLIENT);// 定义文件描述符集合fd_set rset,allset;FD_ZERO(&allset);// 把监听套接字 加入到集合中FD_SET(socketFd, &allset);// 最大的文件描述符maxFd = socketFd;// 初始化为 -1for(int k=0; k < MAXCLIENT; k++){g_client[k] = -1;}while(1){rset = allset;// 多路复用 ,同时监听 多个套接字文件描述符的状态  --阻塞监听int nready = select(maxFd + 1, &rset, NULL, NULL, NULL);if(nready == -1){rt_kprintf("select error\n");break;}// 如果是监听套接字文件描述符发生变化了,说明一定是有新客户端连接上来了if(FD_ISSET(socketFd, &rset)){// 接收新的客户端newClientFd = lwip_accept(socketFd, (struct sockaddr *)&clientAddr, &len);rt_kprintf("new client select success: IP:%s Port:%hu newClientFd:%d\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),newClientFd);// 把新的客户端文件描述符加入到 集合中FD_SET(newClientFd, &allset);// 更新文件描述符最大值if(newClientFd > maxFd)maxFd = newClientFd;// 将连接上来的客户端文件描述符 加入到 数组中for(i = 0; i < MAXCLIENT; i++){if(g_client[i] < 0){g_client[i] = newClientFd;break;}}// 说明 该集合中 只有监听套接字 文件描述符发生变化if(--nready == 0)continue;}// 程序走到这里,说明 有客户端 发送数据过来for(i=0; i < MAXCLIENT; i++){if(g_client[i] < 0)continue;if(FD_ISSET(g_client[i], &rset)){ret = recv(g_client[i], g_buf, sizeof(g_buf), 0);// 该客户端断开了if(ret == 0 || ret == -1){rt_kprintf("IP:%s Port:%d client close.....\n", \inet_ntoa(clientAddr.sin_addr), \ntohs(clientAddr.sin_port));closesocket(g_client[i]);     // 关闭文件描述符FD_CLR(g_client[i],&allset);  // 将该客户端从 文件集合中删除g_client[i] = -1;             // 该文件描述符对应的数组 位置 置 -1// 客户端断开连接直接关闭出光char *argv[] = {"OFFKEY"};int argc = sizeof(argv) / sizeof(argv[0]);startCtrl_cmd(argc, argv);break;}//                rt_kprintf("recv data:%s", g_buf);
//                send(g_client[i], g_buf, ret, 0);while(ret > 0){if(g_buf[z] == '\r' || g_buf[z] == '\n'){if(j > 0){buff[j] = 0;msh_exec(buff, j);memset(buff, 0, j+1);}j = 0;z++;}else{buff[j] = g_buf[z];j++;z++;}
//                    g_buf[ret-1] = 0;ret--;}z = 0;
//                msh_exec(g_buf, ret);if (server_state_flag == 1){server_state_flag = 0;goto exit;}// 说明所有发生变化的文件描述符if(--nready == 0)break;}}}exit:// 关闭服务器rt_kprintf("server ip already changed to %s:%d, restarting...",param->server.serverIpAddr,param->server.serverPort);closesocket(socketFd);for(int k = 0; k < MAXCLIENT; k++){if (g_client[k] >= 0){closesocket(g_client[k]);FD_CLR(g_client[k],&allset);g_client[k] = -1;}}// 设置服务器参数并重启set_server_parameter();
}/*** @brief 服务器初始化*/
void server_init(param_fac_s *param)
{rt_thread_t thread = rt_thread_create("server", server_thread_entry, param, 4096, 16, 20);if (thread != RT_NULL){rt_thread_startup(thread);}
}
//INIT_APP_EXPORT(server_init);
//MSH_CMD_EXPORT_ALIAS(server_init, serverInit, Server init);/*** @brief 判断输入的IP地址是否合理* @param ip 输入的IP地址 0.0.0.0 --- 255.255.255.255* @return 1:输入IP地址合理   0:不合理*/
static int is_valid_ip(char* ip)
{// 判断是否为空int len = strlen(ip);int count = 0, num = 0, size = 0;if (ip == NULL || len < 7 || len > 15){return 0;}// 判断是否包含三个.for (int i = 0; i < len; i++){if (ip[i] == '.'){count++;}}if (count != 3){return 0;}// 字符串切割成四段判断char *token = strtok(ip, ".");while (token != NULL){num = atoi(token);if (num < 0 || num > 255){return 0;}else{size++;}token = strtok(NULL, ".");}// 四段才是符合要求的if (size == 4){return 1;}return 0;
}/*** @brief 手动设置以太网的IP地址* @note  指令 IpAddr + 192.168.16.100*/
int set_server_ip_addr(int argc, char **argv)
{if (argc != 2){rt_kprintf("input parameter error\n");return -1;}// 对输入的IP判断char str[20] = {0};rt_strcpy(str, argv[1]);int ret = is_valid_ip(str);if (ret != 1){rt_kprintf("input ip addr is invalid\n");return -1;}rt_strcpy(serverInfo.serverIpAddr, argv[1]);serverInfo.serverInfoSetFlag |= 0x01;rt_kprintf("server ip addr save success\n");return 0;
}
MSH_CMD_EXPORT_ALIAS(set_server_ip_addr, IpAddr, Set server ip addr);/*** @brief 手动设置以太网的网关* @note  指令 GwAddr + 192.168.16.1*/
int set_server_gw_addr(int argc, char **argv)
{if (argc != 2){rt_kprintf("input parameter error\n");return -1;}// 对输入的网关格式判断char str[20] = {0};rt_strcpy(str, argv[1]);int ret = is_valid_ip(str);if (ret != 1){rt_kprintf("input ip addr is invalid\n");return -1;}rt_memcpy(serverInfo.serverGateway, argv[1], sizeof(serverInfo.serverGateway));serverInfo.serverInfoSetFlag |= 0x02;rt_kprintf("server gw addr save success\n");return 0;
}
MSH_CMD_EXPORT_ALIAS(set_server_gw_addr, GwAddr, Set server gw addr);/*** @brief 手动设置以太网的子网掩码* @note  指令 NetmaskAddr + 255.255.255.0*/
int set_server_netmask_addr(int argc, char **argv)
{if (argc != 2){rt_kprintf("input parameter error\n");return -1;}// 对输入的子网掩码格式判断char str[20] = {0};rt_strcpy(str, argv[1]);int ret = is_valid_ip(str);if (ret != 1){rt_kprintf("input ip addr is invalid\n");return -1;}rt_memcpy(serverInfo.serverMask, argv[1], sizeof(serverInfo.serverMask));serverInfo.serverInfoSetFlag |= 0x04;rt_kprintf("server netmask addr save success\n");return 0;
}
MSH_CMD_EXPORT_ALIAS(set_server_netmask_addr, NetmaskAddr, Set server netmask addr);/*** @brief 手动设置服务器的端口号* @note  指令 Port + 5000*/
int set_server_port(int argc, char **argv)
{if (argc != 2){rt_kprintf("input parameter error\n");return -1;}// 对输入的子网掩码格式判断int port = atoi(argv[1]);if (port <= 0 || port > 10000){rt_kprintf("input parameter error\n");return -1;}serverInfo.serverPort = port;serverInfo.serverInfoSetFlag |= 0x08;rt_kprintf("server port save success\n");return 0;
}
MSH_CMD_EXPORT_ALIAS(set_server_port, Port, Set server port);/*** @brief  确认保存服务参数,并重启控制板* @note   设置指令:saveParam*/
int save_server_param(int argc, char **argv)
{if (argc != 1){rt_kprintf("input parameter error\n");return -1;}if ((serverInfo.serverInfoSetFlag & 0x0F) != 0x0F){rt_kprintf("set parameter incomplete\n");return -1;}// 存储服务器参数到flashfac_device_write_deal(FAC_PARAM_DEV_NAME, ST_OFT(param_fac_s, server),&serverInfo,ST_SIZE(param_fac_s, server),RT_FALSE);server_state_flag = 1;int count = 0;for (int i = 0; i < MAXCLIENT; i++){if (g_client[i] == -1){count++;}}if (count == MAXCLIENT){// 设置服务器参数并重启set_server_parameter();}return 0;
}
MSH_CMD_EXPORT_ALIAS(save_server_param, saveParam, Save server param);#endif

2.tcp_server.h

#include <board.h>
#ifdef BSP_USING_ETH
#ifndef APPLICATIONS_INC_TCP_SERVER_H_
#define APPLICATIONS_INC_TCP_SERVER_H_#include <finsh.h>
#include "msh.h"
#include <lwip/sockets.h>
#include "param_def.h"
#include "common_def.h"
#include "fac_param_cmd.h"
#include <lwip/netif.h>
#include <stdlib.h>
#include <string.h>
#include <netdev.h>#define SERVER_PORT 5000
#define DATA_SIZE   1024
#define MAXCLIENT   5char g_buf[DATA_SIZE];             // 接收数据缓冲区
int  g_client[MAXCLIENT];          // 存储所有客户端的文件描述符server_info serverInfo;
extern int startCtrl_cmd(int argc, char **argv);
extern void broadcast(char *buf, int len);
extern void server_init(param_fac_s *param);#endif /* APPLICATIONS_INC_TCP_SERVER_H_ */
#endif

五、测试验证

通过连接网口进行数据的交互,并且发送ifconfig指令到控制台,查询网口是否可以正常通信。
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com