STM32F407 系列文章 - ETH-LWIP-CubeMX(十五)
目錄
前言
一、軟件設計
二、CubeMX實現
1.配置前準備
2.CubeMX配置
1.ETH模塊配置
2.時鐘模塊配置
3.中斷模塊配置
4.RCC及SYS配置
5.LWIP模塊配置
3.生成代碼
1.main文件
2.用戶層源文件
3.用戶層頭文件
4.效果演示
總結
前言
一般對于許多嵌入式系統或單片機,在其資源受限的環境下,要想實現網絡通訊,并保證資源的高效利用和穩定的網絡通信,我們一般采用一種輕量級的網絡協議lwIP。TI公司的STM32芯片一般都會自帶一路以太網口,用于網絡通訊,但因其內存資源受限,所以都用采用一種小型化、輕量級的lwIP網絡協議,只需十幾KB的RAM和大約40K的ROM即可運行,既可以在無操作系統環境下工作,也可以與各種操作系統配合使用,使其成為資源受限的嵌入式系統的理想選擇。一般市場上所賣的板子都帶這一功能的,需準備STM32F407開發板一塊和網線一根。
一、軟件設計
前面上一篇博文STM32之LWIP網絡通訊設計介紹(十四)-CSDN博客講述了對STM32實現LWIP網絡通訊的前提性要求介紹,包含使用到的網絡協議、MAC內核、PHY驅動芯片、通訊連接示意圖、以及硬件電路原理設計圖,為網絡通訊軟件開發提供了設計指導。今天將完成STM32網絡通訊的軟件設計,采用通過可視化工具STM32CubeMX完成對lwIP通訊的配置,一鍵化生成工程代碼,這是博主比較推薦的一種方法,簡便快捷。
二、CubeMX實現
?STM32CubeMX是一個圖形化配置工具,主要用于配置STM32微控制器和微處理器。它通過直觀的圖形用戶界面,幫助用戶選擇STM32 MCU型號、配置引腳、設置系統時鐘和外設參數,并生成相應的初始化C代碼。這里對CubeMX可視化工具不做詳細的介紹,改天會專門寫篇文章介紹它。
1.配置前準備
在進行可視化工具CubeMX設置前,需要先了解處理器網絡通訊的電路圖實現,主要是了解到處理器使用到的IO引腳以及網絡驅動芯片,根據上一篇文章介紹網絡通訊設計-上(十四)(有提供stm32f407手冊數據和YT8512C驅動芯片數據),線連接到處理器的IO引腳PC4、PC5、PG13、PG14、PG11、PC1、PA2、PA7、PD3,如下圖所示。
2.CubeMX配置
打開CubeMX工具,如下所示。
1.ETH模塊配置
上圖,在右邊Pinout View上面,根據電氣原理圖設置stm32相關IO引腳,如上圖所示,設置完相關引腳后在上圖左邊ETH項,打開可以看到配置的相關引腳PC4、PC5、PG13、PG14、PG11、PC1、PA2、PA7、PD3如下所示。
在上圖上,我們選擇網絡模式為RMII,根據YT8512C驅動芯片數據手冊,MAC地址、PHY地址、PHY狀態寄存器地址、PHY速度、PHY雙工狀態、以及PHY相關參數設置如下。
需要注意的是,在PHY使用型號上,我們也可以使用常見LAN8472芯片,選擇它后,cube自動會幫你配置參數,不像自定義user PHY需要自己配置。
并使能Eth中斷配置,如下。
根據電氣設計圖,設置處理器IO引腳PD3為ETH復位引腳,如下所示。
2.時鐘模塊配置
根據圖紙參數,外部時鐘為8MHz,處理器最高位168MHz,完成相關設置如下。
3.中斷模塊配置
4.RCC及SYS配置
5.LWIP模塊配置
最后,別忘記使能LWIP模塊,完成對其配置,打開第三方插件Middleware,勾選LWIP,里面可以看到LWIP使用的版本號,并完成LWIP的地址、掩碼、TCP、BUF等參數的設置,如下所示。
下圖中key Options參數在生成的lwipopts.h文件上可以看到。
提一句,CubeMX這里面用的LWIP跟外部官網上是一樣,因為其開源免費的特性,CubeMX直接將其內嵌在軟件里面使用。
在第三方插件Middleware界面上,還可以看到FATFS通用文件系統設置、以及freeRTOS線程設置等等,說明使用CubeMX可視化工具,很方便、功能也挺多。
3.生成代碼
上面完成配置引腳、設置系統時鐘、以及外設參數設置后,點擊GENERATE CODE直接生成代碼和工程文件,在保存的路徑上面查看。
??
打開工程,查看代碼如下。
1.main文件
這里將完成相關GPIO引腳、LWIP、定時器初始化配置,首先重置所有外圍設備,初始化Flash接口和Systick,配置系統時鐘,初始化所有已配置的外圍設備;然后初始化用戶UDP網絡設置、和用戶參數,并啟動1毫秒定時器;最后進入主循環,處理網絡上的數據,主要為MX_LWIP_Process()函數和UDP_Data_Process()函數完成,其中MX_LWIP_Process函數為CubeMX工具生成的函數,主要作用為處理網絡上接收到的數據,傳遞給注冊的接收回調函數UDP_Receive_Callback解析處理。代碼示例如下。
/********************************************************************************* @file : main.c* @brief : Main program body******************************************************************************* @attention** <h2><center>© Copyright (c) 2025 STMicroelectronics.* All rights reserved.</center></h2>** This software component is licensed by ST under Ultimate Liberty license* SLA0044, the "License"; You may not use this file except in compliance with* the License. You may obtain a copy of the License at:* www.st.com/SLA0044********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "lwip.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
#include "eth_user.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/*** @brief The application entry point.* @retval int*/
int main(void)
{/* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM6_Init();MX_LWIP_Init();MX_TIM7_Init();User_UDP_Init();HAL_TIM_Base_Start_IT(&htim6); // 啟動1ms定時器/* Infinite loop */while (1){Delay_us(100);MX_LWIP_Process();UDP_Data_Process();}
}
/*** @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_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 8;RCC_OscInitStruct.PLL.PLLN = 320;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();}HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_4);
}
2.用戶層源文件
這里將完成網絡用戶層設置,主要為網絡IP初始化設置、以及網絡收發數控制,代碼示例如下。
#include "eth_user.h"
#include "stm32f4xx_hal.h"
#include "lwip.h"
#include "lwip/igmp.h"#define UDP_PORT 8888 // 網絡端口
static struct udp_pcb *upcb; // UDP通訊的控制塊對象
static DeviceStatus SendUDPSt; // UDP通道的通訊狀態
UDP_Message RecvUDPMes;
UDP_Message AckUDPMes;
UDP_Message SendUDPMes;
/******************************************************************************* 描述 : 初始化UDP所用的相關數據對象* 參數 : 無* 返回 : 無
******************************************************************************/
void UDP_Data_Init(void)
{memset(&RecvUDPMes, 0, sizeof(UDP_Message));memset(&AckUDPMes, 0, sizeof(UDP_Message));memset(&SendUDPMes, 0, sizeof(UDP_Message));memset(&SendUDPSt, 0, sizeof(DeviceStatus));SendUDPSt.ID = 0x10;
}
/******************************************************************************* 描述 : 接收回調函數編寫* 參數 : upcb UDP協議控制塊* p 收到的數據包緩沖區* addr 接收數據包的遠程IP地址 * port 接收數據包的遠程端口* 返回 : 無
******************************************************************************/
static void UDP_Receive_Callback(void *arg, struct udp_pcb *upcb,struct pbuf *p, const ip_addr_t *addr, u16_t port)
{uint8_t udprecvbuf[MAX_UDP_FRAME_LEN] = {0};struct pbuf *ptmp = p;uint32_t data_len = 0;if(upcb != NULL && p != NULL){ip_addr_t remote_source_ip;IP4_ADDR(&remote_source_ip, 192,168,1,113); if((port == UDP_PORT) && ((addr->addr) == remote_source_ip.addr)) //判斷地址和端口從哪里來的{for (ptmp = p; ptmp != NULL; ptmp = ptmp->next) //遍歷完整個buf鏈表{ //判斷要拷貝到buf中的數據是否大于UDP_DEMO_RX_BUFSIZE的剩余空間,//如果大于的話就只拷貝剩余長度的數據,否則的話就拷貝所有的數據if (ptmp->len <= (MAX_UDP_FRAME_LEN - data_len)){ memcpy(udprecvbuf + data_len, ptmp->payload, ptmp->len);data_len += ptmp->len;}else{ memcpy(udprecvbuf + data_len, ptmp->payload, (MAX_UDP_FRAME_LEN - data_len)); //拷貝數據data_len = MAX_UDP_FRAME_LEN;}//超出UDP最大數據包,則跳出循環if (data_len > MAX_UDP_FRAME_LEN){ break; }}// 初步處理memcpy(&AckUDPMes, udprecvbuf, data_len);SendUDPSt.LastRecvTime = g_1msTick;SendUDPSt.ComSt = COM_ST_PENDING;if(*((uint32_t*)udprecvbuf) != UDP_FRAME_HEAD) { // 同步頭校驗AckUDPMes.AnsResult = ACK_HEAD_ERROR;}else if(AckUDPMes.Len != (data_len-4)) { // 幀長度校驗AckUDPMes.AnsResult = ACK_LEN_ERROR;}else if(RecvUDPMes.CRC16 != CRC16_Calculate((uint8_t*)&RecvUDPMes.SN, RecvUDPMes.Len-4)) { // CRC校驗AckUDPMes.AnsResult = ACK_CRC_ERROR;}else SendUDPSt.ComSt = COM_ST_INIT;}}pbuf_free(p); // 釋放內存
}/******************************************************************************* 描述 : 創建udp客戶端* 參數 : 無* 返回 : 無
******************************************************************************/
void User_UDP_Init(void)
{ip_addr_t local_ip;ip_addr_t remote_ip;err_t err;/* 初始化UDP數據消息 */UDP_Data_Init();// 組播配置//IP4_ADDR(&local_ip, 224,1,1,201); //配置本地接收組播的地址//IP4_ADDR(&remote_ip, 224,1,1,113);//獨播IP4_ADDR(&local_ip, 192,168,1,201);IP4_ADDR(&remote_ip, 192,168,1,113);upcb = udp_new(); // 創建udp控制塊if (upcb!=NULL){ upcb->local_port = UDP_PORT; // 配置本地端口upcb->local_ip.addr = local_ip.addr; // 配置本地地址upcb->remote_port = UDP_PORT; // 綁定遠程端口upcb->remote_ip.addr = remote_ip.addr; // 綁定遠程地址//加入組播//err= igmp_joingroup(IP_ADDR_ANY,&local_ip); //只需要將接收地址放入igmp組,發送的不需要//連接遠程服務器IP和端口err= udp_connect(upcb, &remote_ip, UDP_PORT);if(err == ERR_OK){err = udp_bind(upcb,&local_ip,UDP_PORT); //只能收到綁定的本地接收ip地址和端口的數據//err = udp_bind(upcb,IP_ADDR_ANY,UDP_LOCAL_PORT); //可以收到固定端口上任意ip地址的數// 注冊接收回調函數,只要接收到數據,這個回調函數會被lwip內核調用udp_recv(upcb, UDP_Receive_Callback, NULL); // 函數初始化時注冊-接收回調函數 }else{//離開組播地址//igmp_leavegroup(IP_ADDR_ANY,&local_ip);// 斷開UDP連接udp_remove(upcb);}}
}/******************************************************************************* 描述 : 發送數據* 參數 : (in)pData 發送數據的指針* 返回 : 無
******************************************************************************/
int8_t UDP_Send_Data(struct udp_pcb *upcb, uint8_t *pData, uint16_t len)
{struct pbuf *p;int8_t ret = ERR_BUF;if (upcb != NULL && pData != NULL && len > 0){/* 分配緩沖區空間 */p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_POOL);if(p != NULL){/* 填充緩沖區數據 */ret = pbuf_take(p, pData, len);if(ret == ERR_OK){/* 發送udp數據 */ret = udp_send(upcb, p);} /* 釋放緩沖區空間 */pbuf_free(p);}}return ret;
}// 處理單項控制命令
CMD_Ack UDP_Single_Ctrl(UDP_Message msg)
{CMD_Ack ret = ACK_OK;if((uint8_t*)&msg != NULL){switch(msg.Param2){case 0:break;case 1:break;case 2:break;default:break;}}else{ret = ACK_ERROR;}return ret;
}/******************************************************************************* 描述 : 將接收到的UDP數據包解析處理* 參數 : 無* 返回 : 無
******************************************************************************/
void UDP_Msgs_Process(void)
{if(SendUDPSt.ComSt == COM_ST_INIT) {AckUDPMes.AnsResult = UDP_Single_Ctrl(RecvUDPMes);SendUDPSt.ComSt = COM_ST_TRANS;}
}/******************************************************************************
* 描述 : 向以太網上發送UDP數據包* 參數 : 無* 返回 : 無
******************************************************************************/
void UDP_Send_Msgs(void)
{static uint32_t lastime = 0;/* deal ack data */if(SendUDPSt.ComSt != COM_ST_STOP) {AckUDPMes.Head = UDP_FRAME_HEAD;AckUDPMes.Len = UDP_MSG_LEN;AckUDPMes.CRC16 = CRC16_Calculate((uint8_t*)&AckUDPMes.SN, AckUDPMes.Len-4);UDP_Send_Data(upcb, (uint8_t*)&AckUDPMes, UDP_FULL_FRAM_LEN(AckUDPMes.Len));SendUDPSt.ComSt = COM_ST_STOP;memset(&AckUDPMes, 0, sizeof(UDP_Message));}/*d eal heartbeat data */if(g_1msTick - lastime > 1000) {lastime = g_1msTick;SendUDPMes.Head = UDP_FRAME_HEAD;SendUDPMes.Len = UDP_MSG_LEN + 2;SendUDPMes.SN += 1;SendUDPMes.Param1 = SUB_CMD_KEEPALIVE;SendUDPMes.Param2 = RUN_IDLE;SendUDPMes.AnsResult = ACK_PENDING;SendUDPMes.Data[0] = 0xff;SendUDPMes.Data[1] = 0xff;SendUDPMes.CRC16 = CRC16_Calculate((uint8_t*)&SendUDPMes.SN, SendUDPMes.Len-4);UDP_Send_Data(upcb, (uint8_t*)&SendUDPMes, UDP_FULL_FRAM_LEN(SendUDPMes.Len));}
}/******************************************************************************* 描述 : UDP處理流程(處理現有消息,調度發送隊列)* 參數 : 無* 返回 : 無
******************************************************************************/
void UDP_Data_Process(void)
{UDP_Msgs_Process();UDP_Send_Msgs();
}
3.用戶層頭文件
#ifndef __UDP_USER_H
#define __UDP_USER_H#ifdef __cplusplusextern "C" {
#endif#include "stdint.h"
#include "string.h"
#include "crc.h"
#include "udp.h"
#include "timer.h"
#define MAX_UDP_BUF_SIZE 1000
typedef struct
{uint32_t Head; // 同步頭uint16_t Len; // 整幀長度(UDP_MSG_LEN+Data長度)uint16_t CRC16; // CRC校驗結果(UDP_MSG_LEN-4+Data長度的數據)uint16_t SN; // 幀序號uint32_t Param1; // 參數1uint32_t Param2; // 參數2uint32_t AnsResult; // 應答結果uint8_t Data[MAX_UDP_BUF_SIZE]; // 數據內容(不能超過1024字節)
}__attribute__((packed)) UDP_Message;#define UDP_FRAME_HEAD 0xC3A53C5A // 幀頭
#define UDP_MSG_LEN 22 // 幀頭長度
#define UDP_FULL_FRAM_LEN(msgLen) ((msgLen)+4) // 幀頭+整幀長度
#define MAX_UDP_FRAME_LEN (4+UDP_MSG_LEN+MAX_UDP_BUF_SIZE)//sizeof(UDP_Message) UDP最大幀長度typedef enum
{ACK_PENDING = 0, // 等待反饋ACK_OK = 0x10, // 命令執行結束,且結果正常ACK_ERROR = 0x11, // 命令執行過程發生異常ACK_WARNING = 0x12, // 命令執行結束,但結果異常ACK_HEAD_ERROR = 0x20, // Head校驗錯誤ACK_CRC_ERROR = 0x21, // CRC校驗錯誤ACK_LEN_ERROR = 0x22, // 長度校驗錯誤ACK_CMD_INVALID = 0x23, // 命令參數錯誤,無法執行ACK_RUNNING = 0x22, // 有其他命令正在執行
}CMD_Ack; // 回應類型}Sub_CMD_Type;typedef enum
{RUN_IDLE = 0x01, // 空閑RUN_TEST_CTRL = 0x02, // 測試RUN_BURN_IN = 0x03, // 老煉RUN_UPGRADE = 0x04 // 在線升級
}RunningStType; // 軟件運行狀態類型typedef enum
{COM_ST_UNKNOWN = 0x00, // 未知COM_ST_PENDING = 0x01, // 等待回應COM_ST_INIT = 0x02, // 初步OKCOM_ST_TRANS = 0x03, // 可傳輸COM_ST_STOP = 0x04 // 已停止
}DeviceComStatus; // 設備通訊狀態標識void User_UDP_Init(void);
int8_t UDP_Send_Data(struct udp_pcb *upcb, uint8_t *pData, uint16_t len);
void UDP_Msgs_Process(void);
void UDP_Send_Msgs(void);
void UDP_Data_Process(void);
#ifdef __cplusplus
}
#endif#endif
4.效果演示
編寫完用戶代碼后,進行編譯運行,連接仿真器在線調式、或者直接燒寫到板子中,打開網絡監控助手,效果如下。
總結
下面提供的代碼,基于STM32F407ZGT芯片編寫,可直接在原子開發板上運行,也可運行在各工程項目上,但需要注意各接口以及相應的引腳應和原子開發板上保持一致。
相應的代碼鏈接:單片機STM32F407-Case程序代碼例程-CSDN文庫