STM32F407VEt6+DP83848适配uIP栈实现Ping及UDP通信求助
STM32F407VEt6 + DP83848 适配uIP栈的实现指导
问题背景
基于STM32F407VEt6开发以太网与上位机通信项目,此前使用LWIP栈存在首次启动失败、Ping成功率低的问题,计划改用轻量型uIP栈,但适配步骤不明确,且针对DP83848的适配资料极少,需要最简现成项目或具体步骤指导。
核心疑问
以太网底层数据发送如何实现?例如其他物理层芯片有enc28j60_send_packet((uint8_t *) uip_buf, uip_lan);这类专用发送函数,但DP83848没有,如何正确实现以太网帧的发送及初始化?
现有代码
int main(void) { uip_ip4addr_t addrm; uip_ip4addr_t addrt; struct uip_udp_conn *c; uip_ipaddr(&addrt, 192,168,1,1); uip_ipaddr(&addrm, 1192,168,1,123); // 此处IP地址错误,应为192开头 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ETH_Init(); MX_TIM6_Init(); hello_world_init(); c = uip_udp_new(&addrt, HTONS(12345)); uip_connect(&addrt, HTONS(12345)); HAL_TIM_Base_Start_IT(&htim6); while (1) { uip_udp_conn = c; uip_appdata = &my_udp_buf; uip_udp_packet_send(c, uip_appdata, strlen(uip_appdata)); uip_send(uip_appdata, my_udp_buf_len); uip_udp_packet_send(c,uip_appdata, 8); uip_udp_packet_sendto(c,uip_appdata, 8 , &addrm, 12345); } } void uip_udp_packet_sendto(struct uip_udp_conn *c, const void *data, int len, const uip_ipaddr_t *toaddr, uint16_t toport) { uip_ipaddr_t curaddr; uint16_t curport; if(toaddr != NULL) { /* Save current IP addr/port. */ uip_ipaddr_copy(&curaddr, &c->ripaddr); curport = c->rport; /* Load new IP addr/port */ uip_ipaddr_copy(&c->ripaddr, toaddr); c->rport = toport; uip_udp_packet_send(c, data, len); /* Restore old IP addr/port */ uip_ipaddr_copy(&c->ripaddr, &curaddr); c->rport = curport; } } void uip_udp_packet_send(struct uip_udp_conn *c, const void *data, int len) { if(data != NULL && len <= (UIP_BUFSIZE - (UIP_LLH_LEN + UIP_IPUDPH_LEN))) { uip_udp_conn = c; uip_len = len; memmove(&uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN], data, len); uip_process(UIP_UDP_SEND_CONN); } }
适配步骤与底层实现
1. 核心适配逻辑
STM32F4的ETH外设是MAC层,DP83848是PHY层,二者通过RMII接口通信,无需像SPI型PHY芯片那样的专用收发函数,而是通过STM32 ETH外设的DMA完成帧的收发,uIP栈只需对接ETH外设的HAL接口即可。
2. 初始化流程
- CubeMX配置:
- 选择ETH外设,接口类型设为RMII,PHY地址设为0x01(DP83848默认地址)
- 配置RMII对应的GPIO引脚:REF_CLK、MDIO、MDC、CRS_DV、RXD0、RXD1、TX_EN、TXD0、TXD1
- 开启ETH外设时钟,配置TIM6为定时中断(建议100ms周期,用于uIP的ARP维护、超时处理)
- uIP栈初始化:
在MX_ETH_Init()之后添加uIP初始化代码:uip_init(); uip_ipaddr_t ipaddr, netmask, gw; uip_ipaddr(&ipaddr, 192, 168, 1, 123); // 本地IP uip_ipaddr(&netmask, 255, 255, 255, 0); // 子网掩码 uip_ipaddr(&gw, 192, 168, 1, 1); // 网关 uip_sethostaddr(&ipaddr); uip_setnetmask(&netmask); uip_setdraddr(&gw); uip_arp_init();
3. 底层发送实现
uIP处理完协议栈后,uip_buf中已包含完整的以太网帧(MAC头+IP头+UDP头+应用数据),只需调用STM32 HAL的ETH发送函数即可:
- 修正
uip_udp_packet_send函数,在uip_process之后添加发送逻辑:void uip_udp_packet_send(struct uip_udp_conn *c, const void *data, int len) { if(data != NULL && len <= (UIP_BUFSIZE - (UIP_LLH_LEN + UIP_IPUDPH_LEN))) { uip_udp_conn = c; uip_len = len; memmove(&uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN], data, len); uip_process(UIP_UDP_SEND_CONN); // 发送完整以太网帧:uip_len是应用层+IP+UDP长度,加上UIP_LLH_LEN(14字节)为总帧长 HAL_ETH_Transmit(&heth, (uint8_t*)uip_buf, uip_len + UIP_LLH_LEN, HAL_MAX_DELAY); } }
4. 底层接收实现
在TIM6中断或主循环中轮询ETH接收状态,处理收到的帧:
// 示例:在TIM6中断回调函数中处理接收 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM6) { uint32_t frame_len = 0; // 检查是否有收到的帧 if(HAL_ETH_GetReceivedFrame(&heth, &frame_len) == HAL_OK) { if(frame_len > 0 && frame_len <= UIP_BUFSIZE) { // 将帧数据读取到uip_buf HAL_ETH_ReadData(&heth, (uint8_t*)uip_buf, frame_len); // 交给uIP处理 uip_input(); // 如果处理后有需要发送的响应,调用发送 if(uip_len > 0) { HAL_ETH_Transmit(&heth, (uint8_t*)uip_buf, uip_len, HAL_MAX_DELAY); } } } // uIP定时处理(ARP超时、UDP超时等) uip_periodic(0); if(uip_len > 0) { HAL_ETH_Transmit(&heth, (uint8_t*)uip_buf, uip_len, HAL_MAX_DELAY); } } }
代码修正建议
- 修正main函数中的错误IP地址:
uip_ipaddr(&addrm, 192,168,1,123) - 主循环中不要反复调用发送函数,会导致频繁发包,建议放在事件触发(如按键)或定时触发逻辑中
- 确保
my_udp_buf已正确初始化,且my_udp_buf_len为有效长度
内容的提问来源于stack exchange,提问作者Alleftinna
相关产品推荐
相关产品推荐

