STM32F4 UDP组播通信:填一填ST官方HAL库的坑
先说写作本文的原因,由于开项目开发中需要用到UDP组播接收的功能,但是ST官方没有提供合适的参考,使用STM32CubeMX生成的代码也是不能直接使用的,而我在网上找了一大圈,也没有一个能够直接解决的方案,deepseek、ChatGPT给的方案也不能直接用,羡慕迷茫阶段。然而随着项目进度告急,我也只能硬着头皮摸着石头过河了,总结了大多数资料的观点,加上deepseek的帮助,最终找到了关键点,因此希望写一个记录文档,同时也给大家参考,若有高手也可以指点指点。
开发环境
单片机型号是STM32F407VGT6,以太网控制器是LAN7820,软件版本如下
STM32CubeMX6.12.1
IAR 9.50.2
STM32Cube FW_F4 V1.28.1
使用STM32CubeMX生成代码
配置JTAG
配置以太网外设,选择RMII,打开以太网全局中断
打开串口1方便调试,添加DMA
启用LWIP
这里的关键是勾选右上角的Show Advance Parameters
,然后使能LWIP_MULTICAST_TX_OPTONS (Multicast Tx support)
使能LWIP_IGMP (IGMP module)
PHY选择LAN8742,实际上使用的是LAN8720
设置PHY的复位引脚,默认输出为高
最后配置时钟
生成代码
关键代码
生成的代码是不能直接使用的,需要做一些关键修改,这里添加了组播初始化函数和回调函数
/* USER CODE BEGIN Header */
/********************************************************************************* @file : main.c* @brief : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "lwip.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include "lwip/udp.h"
#include "lwip/igmp.h"
/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// 定义组播地址和端口
u16_t multicast_port=8554;
uint8_t multicast_ip[4]={226,0,0,80};void multicast_receive_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) { if (p != NULL) {static uint8_t buff[128];memcpy(buff, p->payload, p->len);HAL_UART_Transmit_DMA(&huart1, (uint8_t *)&buff, p->len);pbuf_free(p);}
}// 初始化MULTICAST接收协议控制块
void multicast_receive_init(void) {struct udp_pcb *pcb;ip4_addr_t ip;IP4_ADDR(&ip, multicast_ip[0], multicast_ip[1], multicast_ip[2], multicast_ip[3]);igmp_joingroup(IP4_ADDR_ANY, &ip);pcb = udp_new();pcb->so_options |= SOF_REUSEADDR; // 允许地址重用
// pcb->mcast_ttl = 1; // 组播TTL(默认1,限制在本地网络)if (pcb != NULL) {udp_bind(pcb, IP4_ADDR_ANY, multicast_port);udp_recv(pcb, multicast_receive_callback, NULL);}
}
/* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();MX_LWIP_Init();/* USER CODE BEGIN 2 */multicast_receive_init();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){MX_LWIP_Process();/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Configure the main internal regulator output voltage*/__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;RCC_OscInitStruct.PLL.PLLM = 8;RCC_OscInitStruct.PLL.PLLN = 168;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 4;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//*** @brief Period elapsed callback in non blocking mode* @note This function is called when TIM1 interrupt took place, inside* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment* a global variable "uwTick" used as application time base.* @param htim : TIM handle* @retval None*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{/* USER CODE BEGIN Callback 0 *//* USER CODE END Callback 0 */if (htim->Instance == TIM1) {HAL_IncTick();}/* USER CODE BEGIN Callback 1 *//* USER CODE END Callback 1 */
}/*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
另外一个关键的地方是需要在.\LWIP\Target\ethernetif.c
文件中的low_level_init
函数添加 强制接收所有组播包 以及 确保启用 IGMP 支持
/*******************************************************************************LL Driver Interface ( LwIP stack --> ETH)
*******************************************************************************/
/*** @brief In this function, the hardware should be initialized.* Called from ethernetif_init().** @param netif the already initialized lwip network interface structure* for this ethernetif*/
static void low_level_init(struct netif *netif)
{HAL_StatusTypeDef hal_eth_init_status = HAL_OK;/* Start ETH HAL Init */uint8_t MACAddr[6] ;heth.Instance = ETH;MACAddr[0] = 0x00;MACAddr[1] = 0x80;MACAddr[2] = 0xE1;MACAddr[3] = 0x00;MACAddr[4] = 0x00;MACAddr[5] = 0x00;heth.Init.MACAddr = &MACAddr[0];heth.Init.MediaInterface = HAL_ETH_RMII_MODE;heth.Init.TxDesc = DMATxDscrTab;heth.Init.RxDesc = DMARxDscrTab;heth.Init.RxBuffLen = 1536;/* USER CODE BEGIN MACADDRESS *//* USER CODE END MACADDRESS */hal_eth_init_status = HAL_ETH_Init(&heth);memset(&TxConfig, 0 , sizeof(ETH_TxPacketConfig));TxConfig.Attributes = ETH_TX_PACKETS_FEATURES_CSUM | ETH_TX_PACKETS_FEATURES_CRCPAD;TxConfig.ChecksumCtrl = ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC;TxConfig.CRCPadCtrl = ETH_CRC_PAD_INSERT;/* End ETH HAL Init *//* Initialize the RX POOL */LWIP_MEMPOOL_INIT(RX_POOL);#if LWIP_ARP || LWIP_ETHERNET/* set MAC hardware address length */netif->hwaddr_len = ETH_HWADDR_LEN;/* set MAC hardware address */netif->hwaddr[0] = heth.Init.MACAddr[0];netif->hwaddr[1] = heth.Init.MACAddr[1];netif->hwaddr[2] = heth.Init.MACAddr[2];netif->hwaddr[3] = heth.Init.MACAddr[3];netif->hwaddr[4] = heth.Init.MACAddr[4];netif->hwaddr[5] = heth.Init.MACAddr[5];/* maximum transfer unit */netif->mtu = ETH_MAX_PAYLOAD;/* Accept broadcast address and ARP traffic *//* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */#if LWIP_ARPnetif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;#elsenetif->flags |= NETIF_FLAG_BROADCAST;#endif /* LWIP_ARP *//* USER CODE BEGIN PHY_PRE_CONFIG *//* USER CODE END PHY_PRE_CONFIG *//* Set PHY IO functions */LAN8742_RegisterBusIO(&LAN8742, &LAN8742_IOCtx);/* Initialize the LAN8742 ETH PHY */if(LAN8742_Init(&LAN8742) != LAN8742_STATUS_OK){netif_set_link_down(netif);netif_set_down(netif);return;}if (hal_eth_init_status == HAL_OK){/* Get link state */ethernet_link_check_state(netif);}else{Error_Handler();}
#endif /* LWIP_ARP || LWIP_ETHERNET *//* USER CODE BEGIN LOW_LEVEL_INIT */// --- 核心修复代码:直接操作寄存器 ---ETH->MACFFR |= ETH_MACFFR_PAM; // 强制接收所有组播包netif->flags |= NETIF_FLAG_IGMP; // 确保启用 IGMP 支持/* USER CODE END LOW_LEVEL_INIT */
}
上位机软件
为了配合测试,这里用python写了一个简单的上位机测试软件
import socket
import structMCAST_GROUP = '226.0.0.80'
MCAST_PORT = 8554
LOCAL_IP = '192.168.88.2' # 替换为你的实际 IPv4 地址# 创建 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', MCAST_PORT))# 通过 IP 地址绑定到指定接口(Windows 兼容方案)
sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,socket.inet_aton(MCAST_GROUP) + socket.inet_aton(LOCAL_IP)
)print(f"监听组播 {MCAST_GROUP}:{MCAST_PORT}...")
while True:data, addr = sock.recvfrom(1024)print(f"来自 {addr} 的消息: {data.decode()}")
# sender.py
import socketMCAST_GROUP = '226.0.0.80'
MCAST_PORT = 8554
INTERFACE_IP = '192.168.88.2' # <--- 替换为你的实际 IPv4 地址sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(INTERFACE_IP))message = "Multicast Test"
sock.sendto(message.encode(), (MCAST_GROUP, MCAST_PORT))
print("消息已发送")
sock.close()
实测效果
可以看到,使用脚本发送数据可以上位机软件成功接收。同时单片机收到数据之后通过串口发送到了串口助手,实验成功。
参考代码
https://github.com/dwgan/STM32-Multicast
相关文章:

STM32F4 UDP组播通信:填一填ST官方HAL库的坑
先说写作本文的原因,由于开项目开发中需要用到UDP组播接收的功能,但是ST官方没有提供合适的参考,使用STM32CubeMX生成的代码也是不能直接使用的,而我在网上找了一大圈,也没有一个能够直接解决的方案,deepse…...

基于python大数据的招聘数据可视化与推荐系统
博主介绍:资深开发工程师,从事互联网行业多年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有…...
10. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--Ocelot 网关--认证
在微服务架构中,通过在网关层实现身份认证、权限校验和数据加密,可以有效防范恶意攻击和非法访问,保障内部服务安全。采用JWT、OAuth等主流认证机制,使每次请求均经过严格验证,降低安全漏洞风险。同时,统一…...

DeepSeek 3FS:端到端无缓存的存储新范式
在 2025 年 2 月 28 日,DeepSeek 正式开源了其高性能分布式文件系统 3FS【1】,作为其开源周的压轴项目,3FS 一经发布便引发了技术圈的热烈讨论。它不仅继承了分布式存储的经典设计,还通过极简却高效的架构,展现了存储技…...
vue3组合式API怎么获取全局变量globalProperties
设置全局变量 main.ts app.config.globalProperties.$category { index: 0 } 获取全局变量 const { appContext } getCurrentInstance() as ComponentInternalInstance console.log(appContext.config.globalProperties.$category) 或是 const { proxy } getCurrentInstance…...
【YOLOv12改进trick】多尺度大核注意力机制MLKA模块引入YOLOv12,实现多尺度目标检测涨点,含创新点Python代码,方便发论文
🍋改进模块🍋:多尺度大核注意力机制(MLKA) 🍋解决问题🍋:MLKA模块结合多尺度、门控机制和空间注意力,显著增强卷积网络的模型表示能力。 🍋改进优势🍋:超分辨的MLKA模块对小目标和模糊目标涨点很明显 🍋适用场景🍋:小目标检测、模糊目标检测等 🍋思路…...

网络安全之端口扫描(一)
前置介绍 什么是DVWA? DVWA(Damn Vulnerable Web Application)是一个专门设计用于测试和提高Web应用程序安全技能的开源PHP/MySQL Web应用程序。它是一个具有多个安全漏洞的故意不安全的应用程序,供安全专业人员、渗透测试人员、…...
HCIE云计算学什么?怎么学?未来职业发展如何?
随着云计算成为IT行业发展的主流方向,HCIE云计算(华为认证云计算专家)作为华为认证体系中的高端认证之一,逐渐成为了许多网络工程师和IT从业者提升职业竞争力的重要途径。 那么,HCIE云计算究竟学什么内容,如…...

upload-labs文件上传
第一关 上传一个1.jpg的文件,在里面写好一句webshell 保留一个数据包,将其中截获的1.jpg改为1.php后重新发送 可以看到,已经成功上传 第二关 写一个webshell如图,为2.php 第二关在过滤tpye的属性,在上传2.php后使用b…...

操作系统控制台-健康守护我们的系统
引言基本准备体验功能健康守护系统诊断 收获提升结语 引言 阿里云操作系统控制平台作为新一代云端服务器中枢平台,通过创新交互模式重构主机管理体验。操作系统控制台提供了一系列管理功能,包括运维监控、智能助手、扩展插件管理以及订阅服务等。用户可以…...

财务会计域——合并报表系统设计
摘要 本文主要介绍了合并报表系统的设计,包括其背景、业务流程和系统架构设计。合并报表系统可自动化生成数据,减少人为错误,确保报表合规。其业务流程涵盖数据收集、标准化、合并调整、报表生成、审核及披露等环节。系统架构设计包括数据接…...

教务考试管理系统-Sprintboot vue
一、前言 1.1 实践目的和要求 本次实践的目的是为了帮助学生强化对实践涉及专业技术知识的理解,掌握专业领域中软件知识的应用方法,并了解软件工程在具体行业领域的发展趋势。通过培养学生利用软件工程方法分析、设计并完成具体行业软件开发的能力&…...

vue实现一个pdf在线预览,pdf选择文本并提取复制文字触发弹窗效果
[TOC] 一、文件预览 1、安装依赖包 这里安装了disjs-dist2.16版本,安装过程中报错缺少worker-loader npm i pdfjs-dist2.16.105 worker-loader3.0.8 2、模板部分 <template><div id"pdf-view"><canvas v-for"page in pdfPages&qu…...
【CSS 】Class Variance Authority CSS 类名管理工具库
1.背景、什么是 CVA? Class Variance Authority (CVA) 是一个用于管理 CSS 类名 的工具库,特别适合在 React 或 Vue 等前端框架中使用。它可以帮助你更轻松地处理组件的 样式变体(Variants),比如按钮的不同状态&#…...
自然语言处理:文本分类
介绍 大家好,我这个热衷于分享知识的博主又来啦!之前我们一起深入探讨了自然语言处理领域中非常重要的两个方法:朴素贝叶斯和逻辑斯谛回归。在探索的过程中,我们剖析了朴素贝叶斯如何基于概率原理和特征条件独立假设,…...
刷题记录 HOT100 贪心-2:45. 跳跃游戏 II
题目:45. 跳跃游戏 II 难度:中等 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i j] 处: 0 &l…...
7.2 奇异值分解的基与矩阵
一、奇异值分解 奇异值分解(SVD)是线性代数的高光时刻。 A A A 是一个 m n m\times n mn 的矩阵,可以是方阵或者长方形矩阵,秩为 r r r。我们要对角化 A A A,但并不是把它化成 X − 1 A X X^{-1}A X X−1AX 的形…...
PDFMathTranslate安装使用
PDF全文翻译!!!! PDFMathTranslate安装使用 它是个啥 PDFMathTranslate 可能是一个用于 PDF 文件的数学公式翻译 工具。它可能包含以下功能: 提取 PDF 内的数学公式 将数学公式转换成 LaTeX 代码 翻译数学公式的内…...

STL之list的使用(超详解)
目录 一、list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 iterator的使用 1.2.3capacity(容量相关) 1.2.4 element access(元素访问) 1.2.5 modifiers(链表修改)…...

动态 SQL 的使用
目录 1、< if> 标签2、< trim> 标签3、< where> 标签4、< set> 标签5、< foreach> 标签 1、< if> 标签 < if test“条件语句”> xxxx < /if> 只有当条件语句满足条件,才会拼接 < if> 标签内容,因…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...