在嵌入式开发中,以太网通信是一个常见但容易让初学者感到困惑的领域。特别是当涉及到STM32系列微控制器和特定PHY芯片(如DM9161)的组合时,很多开发者会在配置和调试阶段遇到各种"坑"。本文将带你从零开始,一步步实现STM32F429与DM9161的UDP通信,避开那些常见的陷阱。
首先,确保你手头有以下硬件组件:
硬件连接时需要注意几个关键点:
RMII接口连接:DM9161通过RMII接口与STM32F429通信,确保以下引脚正确连接:
| STM32F429引脚 | DM9161引脚 | 功能描述 |
|---|---|---|
| PG14 | TXD0 | 数据发送0 |
| PG13 | TXD1 | 数据发送1 |
| PC4 | RXD0 | 数据接收0 |
| PC5 | RXD1 | 数据接收1 |
| PA7 | CRS_DV | 载波侦听 |
| PA1 | REF_CLK | 参考时钟 |
| PA2 | MDIO | 管理数据IO |
| PC1 | MDC | 管理数据时钟 |
时钟配置:DM9161需要一个25MHz的外部时钟输入,检查你的板子是否已经提供,否则需要额外晶振电路。
在开始编码前,需要安装以下软件:
提示:建议使用STM32CubeMX 6.x或更高版本,因为早期版本对DM9161的支持可能不完善。
打开STM32CubeMX,点击"New Project"
选择你的STM32F429型号
在"Pinout & Configuration"界面进行以下设置:
进入ETH配置页面,关键设置如下:
在"Parameter Settings"选项卡中:
c复制/* ETH参数示例 */
#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE /* 缓冲区大小 */
#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE
#define ETH_RX_BUF_NUM 4 /* 接收缓冲区数量 */
#define ETH_TX_BUF_NUM 4 /* 发送缓冲区数量 */
LWIP是STM32以太网通信的核心协议栈,需要特别注意以下配置:
在"LWIP"配置页面:
高级参数调整:
c复制/* lwipopts.h中的关键参数 */
#define MEM_SIZE (16*1024) /* 内存池大小 */
#define PBUF_POOL_SIZE 16 /* pbuf池大小 */
#define TCPIP_THREAD_STACKSIZE 1024 /* TCP/IP线程栈大小 */
#define DEFAULT_UDP_RECVMBOX_SIZE 6 /* UDP邮箱大小 */
创建一个udp_client.c文件,实现基本的UDP客户端功能:
c复制#include "udp_client.h"
#include "lwip/udp.h"
#include "lwip/ip_addr.h"
#include <string.h>
#define UDP_REMOTE_PORT 8080 /* 服务器端口 */
#define UDP_LOCAL_PORT 1234 /* 本地端口 */
static struct udp_pcb *upcb;
/* 接收回调函数 */
static void udp_recv_callback(void *arg, struct udp_pcb *pcb,
struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
if(p != NULL) {
/* 处理接收到的数据 */
printf("Received %d bytes from %d.%d.%d.%d:%d\n",
p->tot_len,
ip4_addr1(addr), ip4_addr2(addr),
ip4_addr3(addr), ip4_addr4(addr),
port);
/* 回显数据 */
udp_sendto(pcb, p, addr, port);
pbuf_free(p);
}
}
/* 初始化UDP客户端 */
void udp_client_init(void)
{
ip_addr_t server_ip;
/* 创建UDP控制块 */
upcb = udp_new();
if(upcb == NULL) {
printf("Failed to create UDP PCB\n");
return;
}
/* 绑定本地端口 */
if(udp_bind(upcb, IP_ADDR_ANY, UDP_LOCAL_PORT) != ERR_OK) {
printf("Failed to bind UDP port\n");
udp_remove(upcb);
return;
}
/* 设置接收回调 */
udp_recv(upcb, udp_recv_callback, NULL);
/* 设置服务器IP(示例) */
IP4_ADDR(&server_ip, 192, 168, 1, 2);
/* 发送测试数据 */
udp_client_send(&server_ip, "Hello from STM32");
}
/* 发送UDP数据 */
void udp_client_send(const ip_addr_t *addr, const char *data)
{
struct pbuf *p;
p = pbuf_alloc(PBUF_TRANSPORT, strlen(data), PBUF_RAM);
if(p == NULL) {
printf("Failed to allocate pbuf\n");
return;
}
if(pbuf_take(p, data, strlen(data)) != ERR_OK) {
printf("Failed to copy data to pbuf\n");
pbuf_free(p);
return;
}
if(udp_sendto(upcb, p, addr, UDP_REMOTE_PORT) != ERR_OK) {
printf("Failed to send UDP packet\n");
}
pbuf_free(p);
}
对于需要接收数据的场景,可以创建一个简单的UDP服务器:
c复制#include "udp_server.h"
#include "lwip/udp.h"
#include "lwip/ip_addr.h"
#define UDP_SERVER_PORT 8080
static struct udp_pcb *upcb;
/* 接收回调函数 */
static void udp_server_recv(void *arg, struct udp_pcb *pcb,
struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
if(p != NULL) {
/* 处理接收到的数据 */
printf("Server received: %.*s\n", p->len, (char*)p->payload);
/* 可以在这里添加业务逻辑 */
pbuf_free(p);
}
}
/* 初始化UDP服务器 */
void udp_server_init(void)
{
upcb = udp_new();
if(upcb == NULL) {
printf("Failed to create UDP PCB\n");
return;
}
if(udp_bind(upcb, IP_ADDR_ANY, UDP_SERVER_PORT) != ERR_OK) {
printf("Failed to bind UDP port\n");
udp_remove(upcb);
return;
}
udp_recv(upcb, udp_server_recv, NULL);
printf("UDP server started on port %d\n", UDP_SERVER_PORT);
}
这是最常见的问题之一,可能的原因和解决方法:
c复制uint32_t phy_id = 0;
if(ETH_ReadPHYRegister(DP83848_PHY_ADDRESS, PHY_ID1_REG, &phy_id) != HAL_OK) {
printf("PHY read error\n");
} else {
printf("PHY ID: 0x%04X\n", phy_id);
}
RMII时钟问题
IP地址冲突
如果发现部分UDP数据包丢失,可以考虑以下优化:
增加缓冲区数量
c复制#define ETH_RX_BUF_NUM 8
#define ETH_TX_BUF_NUM 8
调整LWIP内存配置
c复制#define MEM_SIZE (20*1024)
#define PBUF_POOL_SIZE 32
优化接收处理速度
c复制void udp_send_zero_copy(const char *data, int len)
{
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_REF);
if(p) {
p->payload = (void*)data;
udp_send(upcb, p);
pbuf_free(p);
}
}
c复制/* 在ETH配置中 */
#define ETH_RXBUFNB 8U /* 接收缓冲区数量 */
#define ETH_TXBUFNB 8U /* 发送缓冲区数量 */
c复制HAL_NVIC_SetPriority(ETH_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);