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之类的不在本节的探讨范围.
仍以NCULEO-F767ZI
的板子为例, 板载了LAN8742
的PHY:
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
芯片LAN8742
的nRST
复位引脚连接到了STM32F767的复位引脚, 两者可以同时复位.
看完硬件连接, 回过头来简单说下MII
和RMII
, 前者STM32也支持, 但已经很少用了:
25MHz
时钟. 8数据线
+ 6控制线
+ 2时钟线
+ 2PHY配置线
+ 选配TX_ER
= 18根线
, 太太太多了…50MHz
. 4数据线
+ 3控制线
+ 1时钟线
+ 选配RX_ER
= 10根线
. 上图中没有RX_ER
, 实际只需要9根线. 注意RMII
的PYH时钟
与MAC时钟
都是50MHz
.使用RMII接口时, 50MHz
时钟怎么来呢? 方法有以下几种:
50MHz
的外部晶振同时给MAC
和PHY
供应50MHz
时钟, MCU
其他的部分用自己独立的时钟, 该干嘛干嘛:MAC
, PHY
, MCU
都用一颗50MHz
外部晶振统一供给, 对应STM32CubeMX
-> RCC
-> HSE
选择BYPASS Clock Source
(可以1~50M
, 而Crystal/Ceramic Resonator
只能4~26M
):50MHz
换成25MHz
, 通过STM32
的MCO
引脚可以输出同样的25MHz
时钟给PHY
, 然后用PHY
内部的PLL
配置出50MHz
时钟反补给STM32
的MAC
, 这样就满足了PHY
和MAC
都是50MHz
:MCU
是ST_Link
过来的8MHz
时钟, 和上面两种方法完全不匹配, 还好PHY挂了一颗25MHz
的晶振, 这就有了第四种供应50MHz
时钟的方法, STM32
内部和其他外设自己去浪, PHY
自己有25MHz
时钟, 然后经过PLL
配置出50MHz
给STM32
的MAC
. 这里就图略了.至于PLL(锁相环)
, 引用一段:
锁相环的工作原理是检测输入信号和输出信号的相位差,并将检测出的相位差信号通过鉴相器转换成电压信号输出,经低通滤波器滤波后形成压控振荡器(VCO)的控制电压,对振荡器输出信号的频率实施控制,再通过反馈通路把振荡器输出信号的频率、相位反馈到鉴相器。
步骤如下:
STM32CubeMX
, 点击 ACCESS TO MCU SELECTOR
, 选择 STM32F767ZI
Pinout & Configuration
-> System Core
-> SYS
-> Debug
选择 Serial Wire
Pinout & Configuration
-> System Core
-> RCC
-> HSE
选择 Crystal/Ceramic Resonator
Pinout & Configuration
-> Connectivity
-> ETH
-> Mode
选择 RMII
:
然后发现引脚中默认的和原理图不匹配, 主要是ETH_TX_EN
和 ETH_TXD0
:
不慌, 找到原理图中的引脚, 单击自己去重映射就好了:
原理图中LAN8742
的PHYAD0
引脚下拉到地:
Datasheet
中:
所以ETH
的 Configuration
中PHY Address
设为0
:
Configuration
-> Advanced Parameters
我们默认不动, 因为Advanced Parameters
里面PHY
默认的就是LAN8742
, 和板子是一致的, 如果是DP83848
, PHY
可以直接选择, 如果是LAN8740
或者其他PHY芯片
, 这里面PHY
选user PHY
, 需要配几个参数, 主要是Extended: External PHY Configuration
要特别注意, 具体参考LAN8742的Datasheet
或者网络, 这里暂略:
Configuration
-> NVIC Setting
里面, 接收数据的模式有轮询和中断, 但中断要配合操作系统如FreeRTOS
使用, 我们本节不用FreeRTOS
, 用原生的RAW API
, 只能轮询, 中断保持默认不勾选:
Pinout & Configuration
-> Middleware
-> LWIP
勾选 Enabled
.
General Setting
选项卡配置可参考下图:
Disable
掉 DHCP
, 改用静态IP
, IP地址
设为 192.168.0.10
.ping
用的是ICMP
协议, 要保持Enable
.UDP
为例, 不用TCP
, 所以TCP
可以Disabled
.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 ...
, 默认配置Debug
为ST-link Debugger
, 点击Setting
-> Flash Download
-> 勾选Reset and Run
, 这样下载后可以自动复位运行.
如有疑问, 仍然去参考 STM32CubeMX_环境搭建_GPIO_外部中断 一节.
把官方的示例程序中的app_ethernet.c
, udp_echoserver.c
以及这两个.c对应的.h文件拷贝到工程相应的src
和inc
中去, 并添加两个.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里面配置的静态IP是 192.168.0.10
, 所以PC的IP需要同一网段任意其他IP即可.
编译下载, 打开windows终端, ping一下192.168.0.10
:
没有问题, 再打开网络调试助手, UDP, 本机192.168.0.110
, 端口上面程序设置的7
, 远程主机192.168.0.10:7
, 发送可以看到原封不动回传回来, 发送了一小段时间24万字节不丢帧:
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);
}
编译下载, 效果如下:
一夜发了1亿多字节依然可用, 事实上, 发送参考的是lwip
例程中的udp_echoclient
的内容, 有兴趣的自己去翻官方例程去, 官方是按一下按键发一条UDP信息.
https://download.csdn.net/download/weifengdq/11965246
欢迎扫描二维码关注本人微信公众号, 及时获取或者发送给我最新消息: