STM32CubeMX_以太网_RMII_LwIP_UDP

文章目录

    • 前言
    • RMII
    • STM32CubeMX新建F767工程
    • ETH配置
    • LwIP配置
    • 生成代码
    • UDP_ECHOSERVER
    • 工程代码
    • 微信公众号

前言

STM32CubeMX_环境搭建_GPIO_外部中断
STM32CubeMX_定时器中断_PWM
STM32CubeMX_UART_printf_接收中断_DMA空闲中断_LPUART

前三节简单的总结了GPIO, EXTI, TIMER, UART的相关用法, 本节总结一下STM32的LwIP, UDP的用法, 主要有 STM32F107, F2x7, F4x7, F4x9, F7, H7, MP系列, 这几种都有Ethernet接口, 自带以太网MAC, 外扩PHY芯片就能通信的. 至于SPI扩展W5500之类的不在本节的探讨范围.

RMII

仍以NCULEO-F767ZI的板子为例, 板载了LAN8742的PHY:
STM32CubeMX_以太网_RMII_LwIP_UDP_第1张图片
STM32F767 通过RMII 接口连接PHY芯片LAN8742, 然后经过百兆网络变压器RJ45接口.

RMII对应的引脚为:

RMII接口 STM32引脚
RMII_TXEN PG11
RMII_TXD0 PG13
RMII_TXD1 PB13
RMII_RXD0 PC4
RMII_RXD1 PC5
RMII_CRS_DV PA7
RMII_MDC PC1
RMII_MDIO PA2
RMII_REF_CLK PA1

还有PHY芯片LAN8742nRST复位引脚连接到了STM32F767的复位引脚, 两者可以同时复位.

看完硬件连接, 回过头来简单说下MIIRMII, 前者STM32也支持, 但已经很少用了:

  • MII(Media Independent Interface)即媒体独立接口,MII接口是MAC与PHY连接的标准接口, 百兆以太网收发各有4根线, 需要25MHz时钟. 8数据线 + 6控制线 + 2时钟线 + 2PHY配置线 + 选配TX_ER = 18根线, 太太太多了…
  • RMII (Reduced Media Independant Interface) 即简化媒体独立接口, 百兆以太网收发各有2根线, 时钟升到50MHz. 4数据线 + 3控制线 + 1时钟线 + 选配RX_ER = 10根线. 上图中没有RX_ER, 实际只需要9根线. 注意RMIIPYH时钟MAC时钟都是50MHz.

使用RMII接口时, 50MHz时钟怎么来呢? 方法有以下几种:

  • 一个50MHz的外部晶振同时给MACPHY供应50MHz时钟, MCU其他的部分用自己独立的时钟, 该干嘛干嘛:
    STM32CubeMX_以太网_RMII_LwIP_UDP_第2张图片
  • 上图中MCU自己需要外挂一颗晶振(不考虑内部振荡器), 有点浪费, 干脆 MAC, PHY, MCU都用一颗50MHz外部晶振统一供给, 对应STM32CubeMX -> RCC -> HSE选择BYPASS Clock Source(可以1~50M, 而Crystal/Ceramic Resonator只能4~26M):
    STM32CubeMX_以太网_RMII_LwIP_UDP_第3张图片
  • 第三种方法是第二种的变种, 把50MHz换成25MHz, 通过STM32MCO引脚可以输出同样的25MHz时钟给PHY, 然后用PHY内部的PLL配置出50MHz时钟反补给STM32MAC, 这样就满足了PHYMAC都是50MHz:
    STM32CubeMX_以太网_RMII_LwIP_UDP_第4张图片
  • 而我们的板子MCUST_Link过来的8MHz时钟, 和上面两种方法完全不匹配, 还好PHY挂了一颗25MHz的晶振, 这就有了第四种供应50MHz时钟的方法, STM32内部和其他外设自己去浪, PHY自己有25MHz时钟, 然后经过PLL配置出50MHzSTM32MAC. 这里就图略了.

至于PLL(锁相环), 引用一段:

锁相环的工作原理是检测输入信号和输出信号的相位差,并将检测出的相位差信号通过鉴相器转换成电压信号输出,经低通滤波器滤波后形成压控振荡器(VCO)的控制电压,对振荡器输出信号的频率实施控制,再通过反馈通路把振荡器输出信号的频率、相位反馈到鉴相器。

STM32CubeMX新建F767工程

步骤如下:

  • MCU选择: 打开 STM32CubeMX, 点击 ACCESS TO MCU SELECTOR, 选择 STM32F767ZI
  • 调试端口配置为SWD: Pinout & Configuration -> System Core -> SYS -> Debug 选择 Serial Wire
  • Pinout & Configuration -> System Core -> RCC -> HSE 选择 Crystal/Ceramic Resonator
  • Clock Configuration:
    STM32CubeMX_以太网_RMII_LwIP_UDP_第5张图片

ETH配置

Pinout & Configuration -> Connectivity -> ETH -> Mode 选择 RMII:
STM32CubeMX_以太网_RMII_LwIP_UDP_第6张图片
然后发现引脚中默认的和原理图不匹配, 主要是ETH_TX_ENETH_TXD0:
STM32CubeMX_以太网_RMII_LwIP_UDP_第7张图片
不慌, 找到原理图中的引脚, 单击自己去重映射就好了:
STM32CubeMX_以太网_RMII_LwIP_UDP_第8张图片
原理图中LAN8742PHYAD0引脚下拉到地:
STM32CubeMX_以太网_RMII_LwIP_UDP_第9张图片
Datasheet中:
STM32CubeMX_以太网_RMII_LwIP_UDP_第10张图片
所以ETHConfigurationPHY Address设为0:
STM32CubeMX_以太网_RMII_LwIP_UDP_第11张图片
Configuration -> Advanced Parameters 我们默认不动, 因为Advanced Parameters里面PHY默认的就是LAN8742, 和板子是一致的, 如果是DP83848, PHY可以直接选择, 如果是LAN8740或者其他PHY芯片, 这里面PHYuser PHY, 需要配几个参数, 主要是Extended: External PHY Configuration要特别注意, 具体参考LAN8742的Datasheet 或者网络, 这里暂略:
STM32CubeMX_以太网_RMII_LwIP_UDP_第12张图片
Configuration -> NVIC Setting 里面, 接收数据的模式有轮询和中断, 但中断要配合操作系统如FreeRTOS使用, 我们本节不用FreeRTOS, 用原生的RAW API, 只能轮询, 中断保持默认不勾选:
STM32CubeMX_以太网_RMII_LwIP_UDP_第13张图片

LwIP配置

Pinout & Configuration -> Middleware -> LWIP 勾选 Enabled.

General Setting 选项卡配置可参考下图:

  • DisableDHCP, 改用静态IP, IP地址 设为 192.168.0.10.
  • 常用的ping用的是ICMP协议, 要保持Enable.
  • 本节以UDP为例, 不用TCP, 所以TCP可以Disabled.
    STM32CubeMX_以太网_RMII_LwIP_UDP_第14张图片
    其它选项卡保持默认, 暂不配置.

生成代码

Project Manager -> Project -> Browse 选择工程位置(Project Location), 填入工程名(Project Name), Toolchain/IDE 选择 MDK-ARM.

Project Manager -> Code Generator -> 勾选Copy only the necessary library files, 还有Generate peripheral initialization as a pair of .c/.h files per periphral

点击右上角 GENERATE CODE 按钮生成代码, 打开工程.

Keil 点击魔术棒或者Project -> Options for Target ..., 默认配置DebugST-link Debugger, 点击Setting -> Flash Download -> 勾选Reset and Run, 这样下载后可以自动复位运行.

如有疑问, 仍然去参考 STM32CubeMX_环境搭建_GPIO_外部中断 一节.

UDP_ECHOSERVER

把官方的示例程序中的app_ethernet.c, udp_echoserver.c以及这两个.c对应的.h文件拷贝到工程相应的srcinc中去, 并添加两个.c文件到工程中去.

app_ethernet.c 文件修改如下:

#include "lwip/opt.h"
#include "main.h"
#include "lwip/dhcp.h"
#include "app_ethernet.h"
#include "ethernetif.h"

//lwip.c
extern struct netif gnetif;
extern ip4_addr_t ipaddr;
extern ip4_addr_t netmask;
extern ip4_addr_t gw;
extern uint8_t IP_ADDRESS[4];
extern uint8_t NETMASK_ADDRESS[4];
extern uint8_t GATEWAY_ADDRESS[4];

//Notify the User about the nework interface config status
void User_notification(struct netif *netif) {
	if (netif_is_up(netif)) {
		uint8_t iptxt[20];
    	sprintf((char *)iptxt, "%s", ip4addr_ntoa((const ip4_addr_t *)&netif->ip_addr));
    	//printf("Static IP address: %s\n", iptxt);
    } else {
  		//printf("Link Failed");
    }
}

//This function notify user about link status changement.
void ethernetif_notify_conn_changed(struct netif *netif) {
	if(netif_is_link_up(netif)) {
		IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
		IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
		IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);
		netif_set_addr(netif, &ipaddr , &netmask, &gw); 
		/* When the netif is fully configured this function must be called.*/
    	netif_set_up(netif); 
	} else {
		/*  When the netif link is down this function must be called.*/
		netif_set_down(netif);
	}
}

需要看绑定的PC的IP, 就初始化调用 User_notification() 函数, 尴尬的发现ethernetif_notify_conn_changed 没有用, opt.h 不让改, 可能哪里没配好, 这里先放放. 不用的话, app_ethernet.c 不要也行. 实际测试, 初始化连着网线, 初始化后拔掉网线再插上可以自动重连, 但初始化就没插还是不大可以.

udp_echoserver.c不变, 可以看下里面的内容, 主要是定义端口, 初始化中绑定端口和回调函数, 回调函数中把接收到的数据回传:

#include "main.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
#include "lwip/tcp.h"
#include 
#include 
#include "udp_echoserver.h"

#define UDP_SERVER_PORT    7   /* define the UDP local connection port */
#define UDP_CLIENT_PORT    7   /* define the UDP remote connection port */

void udp_echoserver_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);

void udp_echoserver_init(void)
{
   struct udp_pcb *upcb;
   err_t err;
   
   /* Create a new UDP control block  */
   upcb = udp_new();
   
   if (upcb)
   {
     /* Bind the upcb to the UDP_PORT port */
     /* Using IP_ADDR_ANY allow the upcb to be used by any local interface */
      err = udp_bind(upcb, IP_ADDR_ANY, UDP_SERVER_PORT);
      
      if(err == ERR_OK)
      {
        /* Set a receive callback for the upcb */
        udp_recv(upcb, udp_echoserver_receive_callback, NULL);
      }
      else
      {
        udp_remove(upcb);
      }
   }
}

void udp_echoserver_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{

  /* Connect to the remote client */
  udp_connect(upcb, addr, UDP_CLIENT_PORT);
    
  /* Tell the client that we have accepted it */
  udp_send(upcb, p);

  /* free the UDP connection, so we can accept new clients */
  udp_disconnect(upcb);
	
  /* Free the p buffer */
  pbuf_free(p);
   
}

udp_echoserver.h 文件:

#ifndef __ECHO_H__
#define __ECHO_H__

void udp_echoserver_init(void);

#endif

找不到官方的这两个文件的, 直接复制这里的代码也是可以的.

然后main.c中包含udp_echoserver.h 头文件, 调用UDP初始化函数:

  /* USER CODE BEGIN 2 */
	udp_echoserver_init();
  /* USER CODE END 2 */

连接好网线到PC, PC注意切换以太网口, 别搞成WiFi了, IP地址如下:
STM32CubeMX_以太网_RMII_LwIP_UDP_第15张图片
因为STM32CubeMX里面配置的静态IP是 192.168.0.10, 所以PC的IP需要同一网段任意其他IP即可.

编译下载, 打开windows终端, ping一下192.168.0.10:
STM32CubeMX_以太网_RMII_LwIP_UDP_第16张图片
没有问题, 再打开网络调试助手, UDP, 本机192.168.0.110, 端口上面程序设置的7, 远程主机192.168.0.10:7, 发送可以看到原封不动回传回来, 发送了一小段时间24万字节不丢帧:
STM32CubeMX_以太网_RMII_LwIP_UDP_第17张图片
Tips: 打开网络调试助手, UDP连上后, 测试可以放一边, 这个时候可以打开WiFi联网, Win10测试是这样…

如果想在接收的数据后面追加三个感叹号再回传, 可以这么改接收函数:

struct udp_pcb *upcb_client;

void udp_echoserver_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
	uint8_t recdata[53]={0};
	int length = p -> len;
	
	if(length < 50) {
		memcpy((uint8_t *)recdata, p -> payload, p -> len);
		recdata[length] = '!';
		recdata[length+1] = '!';
		recdata[length+2] = '!';
			
		upcb_client = udp_new();
		if (upcb_client!=NULL) {
			udp_connect(upcb_client, addr, UDP_CLIENT_PORT);
			struct pbuf *p1;
			p1 = pbuf_alloc(PBUF_TRANSPORT,(length+3), PBUF_POOL);
			if (p1 != NULL) {
				pbuf_take(p1, (uint8_t*)recdata, (length+3));
				udp_send(upcb_client, p1);
				pbuf_free(p1);
				udp_remove(upcb_client);
			} else {
				udp_remove(upcb_client);
			}
		} else {
			udp_remove(upcb_client);
		}
	}
	
	pbuf_free(p);
	

//  /* Connect to the remote client */
//  udp_connect(upcb, addr, UDP_CLIENT_PORT);
//    
//  /* Tell the client that we have accepted it */
//  udp_send(upcb, p);

//  /* free the UDP connection, so we can accept new clients */
//  udp_disconnect(upcb);
//	
//  /* Free the p buffer */
//  pbuf_free(p);
   
}

编译下载, 效果如下:
STM32CubeMX_以太网_RMII_LwIP_UDP_第18张图片
一夜发了1亿多字节依然可用, 事实上, 发送参考的是lwip例程中的udp_echoclient的内容, 有兴趣的自己去翻官方例程去, 官方是按一下按键发一条UDP信息.

工程代码

https://download.csdn.net/download/weifengdq/11965246

微信公众号

欢迎扫描二维码关注本人微信公众号, 及时获取或者发送给我最新消息:
在这里插入图片描述

你可能感兴趣的:(STM32)