一、ETH概述
STM32F4xx 系列控制器内部集成了一个以太网外设,实际是一个通过DMA控制器进行介质访问控制MAC,功能就是实现 MAC 层任务,可以通过 ETH 外设按照 IEEE 802.3-2002 标准发送和接收MAC数据包; ETH 内部自带专用的 DMA 控制器用于 MAC,ETH 支持两个工业标准接口介质独立接口 (MII) 和简化介质独立接口 (RMII) 用于与外部 PHY 芯片连接; MII 和 RMII 接口用于 MAC 数据包传输,ETH 还集成了站管理接口 (SMI) 接口专门用于与外部 PHY 通信,用于访问 PHY 芯片寄存器; 物理层定义了以太网使用的传输介质、传输速度、数据编码方式和冲突检测机制,PHY 芯片是物理层功能实现的实体,生活中常用水晶头网线+水晶头插座+PHY组合构成了物理层。 |
1、RMII接口
RMII 全称是Reduced Media Independent Interface,翻译过来就是精简的介质独立接口,也就是MII 接口的精简版本,RMII 接口只需要7 根数据线,相比MII 直接减少了9 根,极大的方便了板子布线,RMII 接口连接PHY 芯片的示意图如图所示:
|
2、MDIO接口
MDIO全称是Management Data Input/Output,直译过来就是管理数据输入输出接口,是一个简单的两线串行接口,一根MDIO数据线,一根MDC时钟线; 驱动程序可以通过MDIO和MDC这两根线访问PHY芯片的任意一个寄存器; MDIO接口支持多达32个PHY,同一时刻内只能对一个PHY进行操作,和IIC一样,使用器件地址即可,同一MDIO接口下的所有PHY芯片,其器件地址不能冲突,必须保证唯一,具体器件地址值要查阅相应的PHY数据手册,因此,MAC和外部PHY芯片进行连接的时候主要是MII/RMII和MDIO接口,另外可能还需要复位、中断等其他引脚。 |
3、RJ45接口
网络设备是通过网线连接起来的,插入网线的叫做RJ45座: RJ45座要与PHY芯片连接在一起,但是中间需要一个网络变压器(现在很多RJ45座子内部已经集成网络变压器),网络变压器用于隔离以及滤波等,网络变压器也是一个芯片,外形如图所示(所设计的硬件是需要内置网络变压器的RJ45座,肯定不能随便焊接一个不内置变压器的RJ45座,否则网络工作不正常!): RJ45座子上一般有两个灯,一个黄色(橙色),一个绿色,绿色亮的话表示网络连接正常,黄色闪烁的话说明当前正在进行网络通信,这两个灯由PHY 芯片控制,PHY 芯片会有两个引脚来连接RJ45 座上的这两个灯。 |
二、CubeMX驱动生成
(注意引脚分配) ETH_MDIO:PA2 ETH_MDC:PC1 RMII_TXD0:PG13 RMII_TXD1:PG14 RMII_TXEN:PG11 RMII_RXD0:PC4 RMII_RXD1:PC5 RMII_CRS_DV:PA7 RMII_REF_CLK:PA1 |
三、使用以太网物理层设备驱动/LWIP
|
确保HAL库hal_conf.h功能模块使能(CubeMX会自动解除注释): #define HAL_ETH_MODULE_ENABLED 添加eth.c硬件驱动到board.c: void HAL_ETH_MspInit(ETH_HandleTypeDef* ethHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(ethHandle->Instance==ETH) { /* USER CODE BEGIN ETH_MspInit 0 */
/* USER CODE END ETH_MspInit 0 */ /* ETH clock enable */ __HAL_RCC_ETH_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); /**ETH GPIO Configuration PC1 ------> ETH_MDC PA1 ------> ETH_REF_CLK PA2 ------> ETH_MDIO PA7 ------> ETH_CRS_DV PC4 ------> ETH_RXD0 PC5 ------> ETH_RXD1 PG11 ------> ETH_TX_EN PG13 ------> ETH_TXD0 PG14 ------> 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_13|GPIO_PIN_14; 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(GPIOG, &GPIO_InitStruct);
/* USER CODE BEGIN ETH_MspInit 1 */
/* USER CODE END ETH_MspInit 1 */ } } 修改board.h: #define BSP_USING_ETH #ifdef BSP_USING_ETH #define PHY_USING_LAN8720A /*#define PHY_USING_DM9161CEP*/ /*#define PHY_USING_DP83848C*/ #endif 配置PHY寄存器(cubemx/Inc/stm32f4xx_hal_conf.h): #define PHY_SR ((uint16_t)0x1F) /*!< PHY special control/ status register Offset */ #define PHY_SPEED_STATUS ((uint16_t)0x0004) /*!< PHY Speed mask */ #define PHY_DUPLEX_STATUS ((uint16_t)0x0010) /*!< PHY Duplex mask */ 屏蔽错误(drivers/drv_eth.c): enum { PHY_LINK = (1 << 0), PHY_100M = (1 << 1), // PHY_FULL_DUPLEX = (1 << 2), }; 实现phy_reset函数(main.c): #include <rtthread.h> #include <rtdevice.h> #include <drv_common.h> #define RESET_IO GET_PIN(D, 3) 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); } |
eth设备: ifconfig: ping局域网内ip: |
四、netdev网络接口设备
netdev(network interface device),即网络接口设备,又称网卡; 每一个用于网络连接的设备都可以注册成网卡,为了适配更多的种类的网卡,避免系统中对单一网卡的依赖,RT-Thread 系统提供了 netdev 组件用于网卡管理和控制; netdev 组件主要作用是解决设备多网卡连接时网络连接问题,用于统一管理各个网卡信息与网络连接状态,并且提供统一的网卡调试命令接口。 其主要功能特点如下所示:
|
头文件: #include <arpa/inet.h> /* 包含 ip_addr_t 等地址相关的头文件 */ #include <netdev.h> /* 包含全部的 netdev 相关操作接口函数 */ |
参阅文档:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev?id=%e9%85%8d%e7%bd%ae%e9%80%89%e9%a1%b9 |
五、示例代码
1、获取/设置网卡信息
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include <arpa/inet.h>
#include <netdev.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define RESET_IO GET_PIN(D, 3)
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);
}
#define NETDEV_NAME "e0"
int enable_netdev() {
struct netdev *netdev;
netdev = netdev_get_by_name(NETDEV_NAME);
if (netdev == RT_NULL) {
rt_kprintf("network interface device %s not found!\n", NETDEV_NAME);
return -1;
}
netdev_set_up(netdev);
return 0;
}
int disable_netdev() {
struct netdev *netdev;
netdev = netdev_get_by_name(NETDEV_NAME);
if (netdev == RT_NULL) {
rt_kprintf("network interface device %s not found!\n", NETDEV_NAME);
return -1;
}
netdev_set_down(netdev);
return 0;
}
int is_up() {
struct netdev *netdev;
netdev = netdev_get_by_name(NETDEV_NAME);
if (netdev == RT_NULL) {
rt_kprintf("network interface device %s not found!\n", NETDEV_NAME);
return -1;
}
rt_kprintf("netdev_is_up: %d\n", netdev_is_up(netdev));
return 0;
}
int is_link_up() {
struct netdev *netdev;
netdev = netdev_get_by_name(NETDEV_NAME);
if (netdev == RT_NULL) {
rt_kprintf("network interface device %s not found!\n", NETDEV_NAME);
return -1;
}
rt_kprintf("netdev_is_link_up: %d\n", netdev_is_link_up(netdev));
return 0;
}
MSH_CMD_EXPORT(enable_netdev, enable_netdev);
MSH_CMD_EXPORT(disable_netdev, disable_netdev);
MSH_CMD_EXPORT(is_up, is_up);
MSH_CMD_EXPORT(is_link_up, is_link_up);
int main(void)
{
while (1) {
//LOG_D("Hello RT-Thread!");
rt_thread_mdelay(1000);
}
return RT_EOK;
}
2、TCP服务端
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include <sys/socket.h>
#include <netdb.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define RESET_IO GET_PIN(D, 3)
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);
}
#define BUFSZ (1024)
static const char send_data[] = "This is TCP Server from RT-Thread.";
static void tcpserv(int argc, char **argv)
{
char *recv_data;
socklen_t sin_size;
int sock, connected, bytes_received;
struct sockaddr_in server_addr, client_addr;
rt_bool_t stop = RT_FALSE;
int ret;
// 分配接收缓冲区
recv_data = rt_malloc(BUFSZ + 1);
if (recv_data == RT_NULL) {
rt_kprintf("No memory\n");
return;
}
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
// 创建失败的错误处理
rt_kprintf("Socket error\n");
// 释放已分配的接收缓冲
rt_free(recv_data);
return;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5000);
server_addr.sin_addr.s_addr = INADDR_ANY;
rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) {
// 绑定失败
rt_kprintf("Unable to bind\n");
// 释放已分配的接收缓冲
rt_free(recv_data);
return;
}
if (listen(sock, 5) == -1) {
rt_kprintf("Listen error\n");
// 释放已分配的接收缓冲
rt_free(recv_data);
return;
}
rt_kprintf("\nTCPServer Waiting for client on port 5000...\n");
while (stop != RT_TRUE) {
sin_size = sizeof(struct sockaddr_in);
// 接受一个客户端连接socket的请求,这个函数调用是阻塞式的
connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);
// 返回的是连接成功的socket
if (connected < 0) {
rt_kprintf("accept connection failed! errno = %d\n", errno);
continue;
}
// 接受返回的client_addr指向了客户端的地址信息
rt_kprintf("I got a connection from (%s , %d)\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 客户端连接处理
while (1) {
// 发送数据到connected socket
ret = send(connected, send_data, strlen(send_data), 0);
if (ret < 0) {
// 发送失败,关闭这个连接
closesocket(connected);
rt_kprintf("\nsend error,close the socket.\r\n");
break;
} else if (ret == 0) {
// 打印send函数返回值为0的警告信息
rt_kprintf("\n Send warning,send function return 0.\r\n");
}
// 接收数据,接收buffer是1024大小,但并不一定能够收到1024大小的数据
bytes_received = recv(connected, recv_data, BUFSZ, 0);
if (bytes_received < 0) {
// 接收失败,关闭这个connected socket
closesocket(connected);
break;
} else if (bytes_received == 0) {
// 客户端断开连接
rt_kprintf("\nReceived warning,recv function return 0.\r\n");
closesocket(connected);
break;
}
// 接收到数据,把末端清零
recv_data[bytes_received] = '\0';
if (strcmp(recv_data, "q") == 0 || strcmp(recv_data, "Q") == 0) {
// 如果是首字母是q或Q,关闭这个连接
closesocket(connected);
break;
} else if (strcmp(recv_data, "exit") == 0) {
// 如果接收的是exit,则关闭整个服务端
closesocket(connected);
stop = RT_TRUE;
break;
} else {
// 在控制终端显示收到的数据
rt_kprintf("RECEIVED DATA = %s \n", recv_data);
}
}
}
// 退出服务
closesocket(sock);
// 释放接收缓冲
rt_free(recv_data);
return ;
}
MSH_CMD_EXPORT(tcpserv, a tcp server sample);
int main(void)
{
while (1) {
//LOG_D("Hello RT-Thread!");
rt_thread_mdelay(1000);
}
return RT_EOK;
}
3、TCP客户端
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include <sys/socket.h>
#include <netdb.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define RESET_IO GET_PIN(D, 3)
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);
}
#define BUFSZ 1024
static const char send_data[] = "This is TCP Client from RT-Thread.";
void tcpclient(int argc, char **argv)
{
int ret;
char *recv_data;
struct hostent *host;
int sock, bytes_received;
struct sockaddr_in server_addr;
const char *url;
int port;
if (argc < 3) {
rt_kprintf("Usage: tcpclient URL PORT\n");
rt_kprintf("Like: tcpclient 192.168.12.44 5000\n");
return ;
}
url = argv[1];
port = strtoul(argv[2], 0, 10);
// 通过函数入口参数url获得host地址(如果是域名,会做域名解析)
host = gethostbyname(url);
// 分配用于存放接收数据的缓冲
recv_data = rt_malloc(BUFSZ);
if (recv_data == RT_NULL) {
rt_kprintf("No memory\n");
return;
}
// 创建一个socket,类型是SOCKET_STREAM,TCP类型
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
/* 创建socket失败 */
rt_kprintf("Socket error\n");
/* 释放接收缓冲 */
rt_free(recv_data);
return;
}
// 初始化预连接的服务端地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
// 连接到服务端
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) {
/* 连接失败 */
rt_kprintf("Connect fail!\n");
closesocket(sock);
/*释放接收缓冲 */
rt_free(recv_data);
return;
}
while (1) {
// 从sock连接中接收最大BUFSZ - 1字节数据
bytes_received = recv(sock, recv_data, BUFSZ - 1, 0);
if (bytes_received < 0) {
// 接收失败,关闭这个连接
closesocket(sock);
rt_kprintf("\nreceived error,close the socket.\r\n");
// 释放接收缓冲
rt_free(recv_data);
break;
} else if (bytes_received == 0) {
// 默认 recv 为阻塞模式,此时收到0认为连接出错,关闭这个连接
closesocket(sock);
rt_kprintf("\nreceived error,close the socket.\r\n");
// 释放接收缓冲
rt_free(recv_data);
break;
}
// 有接收到数据,把末端清零
recv_data[bytes_received] = '\0';
if (strncmp(recv_data, "q", 1) == 0 || strncmp(recv_data, "Q", 1) == 0) {
// 如果是首字母是q或Q,关闭这个连接
closesocket(sock);
rt_kprintf("\n got a 'q' or 'Q',close the socket.\r\n");
// 释放接收缓冲
rt_free(recv_data);
break;
} else {
// 在控制终端显示收到的数据
rt_kprintf("\nReceived data = %s ", recv_data);
}
// 发送数据到sock连接
ret = send(sock, send_data, strlen(send_data), 0);
if (ret < 0) {
// 接收失败,关闭这个连接
closesocket(sock);
rt_kprintf("\nsend error,close the socket.\r\n");
rt_free(recv_data);
break;
} else if (ret == 0) {
// 打印send函数返回值为0的警告信息
rt_kprintf("\n Send warning,send function return 0.\r\n");
}
}
return;
}
MSH_CMD_EXPORT(tcpclient, a tcp client sample);
int main(void)
{
while (1) {
//LOG_D("Hello RT-Thread!");
rt_thread_mdelay(1000);
}
return RT_EOK;
}
4、UDP服务端
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include <sys/socket.h>
#include <netdb.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define RESET_IO GET_PIN(D, 3)
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);
}
#define BUFSZ 1024
static void udpserv(int argc, char **argv)
{
int sock;
int bytes_read;
char *recv_data;
socklen_t addr_len;
struct sockaddr_in server_addr, client_addr;
// 分配接收用的数据缓冲
recv_data = rt_malloc(BUFSZ);
if (recv_data == RT_NULL) {
// 分配内存失败,返回
rt_kprintf("No memory\n");
return;
}
// 创建一个socket,类型是SOCK_DGRAM,UDP类型
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
rt_kprintf("Socket error\n");
// 释放接收用的数据缓冲
rt_free(recv_data);
return;
}
// 初始化服务端地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5000);
server_addr.sin_addr.s_addr = INADDR_ANY;
rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
// 绑定socket到服务端地址
if (bind(sock, (struct sockaddr *)&server_addr,
sizeof(struct sockaddr)) == -1) {
// 绑定地址失败
rt_kprintf("Bind error\n");
// 释放接收用的数据缓冲
rt_free(recv_data);
return;
}
addr_len = sizeof(struct sockaddr);
rt_kprintf("UDPServer Waiting for client on port 5000...\n");
while (1) {
// 从sock中收取最大BUFSZ - 1字节数据
bytes_read = recvfrom(sock, recv_data, BUFSZ - 1, 0,
(struct sockaddr *)&client_addr, &addr_len);
// UDP不同于TCP,基本不会出现收取的数据失败的情况,除非设置了超时等待
recv_data[bytes_read] = '\0';
// 输出接收的数据
rt_kprintf("\n(%s , %d) said : ", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
rt_kprintf("%s", recv_data);
// 如果接收数据是exit,退出
if (strcmp(recv_data, "exit") == 0) {
closesocket(sock);
// 释放接收用的数据缓冲
rt_free(recv_data);
break;
}
}
return;
}
MSH_CMD_EXPORT(udpserv, a udp server sample);
int main(void)
{
while (1) {
//LOG_D("Hello RT-Thread!");
rt_thread_mdelay(1000);
}
return RT_EOK;
}
5、UDP客户端
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include <sys/socket.h>
#include <netdb.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define RESET_IO GET_PIN(D, 3)
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);
}
const char send_data[] = "This is UDP Client from RT-Thread.\n";
void udpclient(int argc, char **argv)
{
int sock, port, count;
struct hostent *host;
struct sockaddr_in server_addr;
const char *url;
if (argc < 3) {
rt_kprintf("Usage: udpclient URL PORT [COUNT = 10]\n");
rt_kprintf("Like: tcpclient 192.168.12.44 5000\n");
return ;
}
url = argv[1];
port = strtoul(argv[2], 0, 10);
if (argc > 3)
count = strtoul(argv[3], 0, 10);
else
count = 10;
// 通过函数入口参数url获得host地址(如果是域名,会做域名解析)
host = (struct hostent *) gethostbyname(url);
// 创建一个socket,类型是SOCK_DGRAM,UDP类型
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
rt_kprintf("Socket error\n");
return;
}
// 初始化预连接的服务端地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
// 总计发送count次数据
while (count) {
// 发送数据到服务远端
sendto(sock, send_data, strlen(send_data), 0,
(struct sockaddr *)&server_addr, sizeof(struct sockaddr));
// 线程休眠一段时间
rt_thread_delay(50);
// 计数值减一
count --;
}
// 关闭这个socket
closesocket(sock);
}
MSH_CMD_EXPORT(udpclient, a udp client sample);
int main(void)
{
while (1) {
//LOG_D("Hello RT-Thread!");
rt_thread_mdelay(1000);
}
return RT_EOK;
}