目标
使用STM32CubeMX工具,配置ETH和LWIP相关参数,实现将STM32H743单片机作为TCP客户端与服务器通信的功能。
一直使用的都是Keil进行开发,由于Keil工具需要许可证,公司禁止使用破解软件,因此改为使用STM32CubeIDE进行开发。使用过程中遇到了一点小问题,本章顺便提醒下。
开发工具
STM32CubeMX:Version 6.14.0
STM32CubeIDE:Version: 1.18.0
硬件开发板


原理图
如上图所示,以太网使用YT8512C芯片,各功能引脚如下:
PC1 ------> ETH_MDC
PA1 ------> ETH_REF_CLK
PA2 ------> ETH_MDIO
PA7 ------> ETH_CRS_DV
PC4 ------> ETH_RXD0
PC5 ------> ETH_RXD1
PB11 ------> ETH_TX_EN
PG13 ------> ETH_TXD0
PG14 ------> ETH_TXD1
STM32CubeMX配置





ETH对应IO配置,默认所有IO都是无上拉无下拉电阻,且output speed都设为High-高速即可。
LWIP外设选择LAN8742即可,与YT8512C兼容。
这里生成代码使用的是STM32Cube FW_H7 V1.12.0版本,生成STM32CubeIDE代码。
YT8512C芯片手册
通过芯片手册可以得出,芯片上电先验证芯片ID(寄存器0x02的值=0x00、寄存器0x03的值=0x128),网上查的一些资料都是需要寄存器0x03的值=0x13,但是实际操作过程中发现是0x128,不确定有没有什么影响,之后进行软复位(芯片寄存器0x00,写入0x8000),等待100-500ms,当Link_Status置一时才能正常启动以太网(寄存器0x01,读取Link_Status的值)。
代码解析
由于一直了FreeRtos微系统,LWIP初始化放在线程中,初始化接口为MX_LWIP_Init();
在STM32CubeMX配置LWIP时,设置的静态IP地址,默认在初始化LWIP时已经处理好了。
最终定位到low_level_init接口中,增加自定义接口ETH_PHYConfig用于复位PHY芯片以及配置相关寄存器。
static void low_level_init(struct netif *netif)
{
HAL_StatusTypeDef hal_eth_init_status = HAL_OK;
/* USER CODE BEGIN OS_THREAD_ATTR_CMSIS_RTOS_V2 */
osThreadAttr_t attributes;
/* USER CODE END OS_THREAD_ATTR_CMSIS_RTOS_V2 */
uint32_t duplex, speed = 0;
int32_t PHYLinkState = 0;
ETH_MACConfigTypeDef MACConf = {0};
/* 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_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */
/* create a binary semaphore used for informing ethernetif of frame reception */
RxPktSemaphore = osSemaphoreNew(1, 0, NULL);
/* create a binary semaphore used for informing ethernetif of frame transmission */
TxPktSemaphore = osSemaphoreNew(1, 0, NULL);
/* create the task that handles the ETH_MAC */
/* USER CODE BEGIN OS_THREAD_NEW_CMSIS_RTOS_V2 */
memset(&attributes, 0x0, sizeof(osThreadAttr_t));
attributes.name = "EthIf";
attributes.stack_size = INTERFACE_THREAD_STACK_SIZE;
attributes.priority = osPriorityRealtime;
osThreadNew(ethernetif_input, netif, &attributes);
/* USER CODE END OS_THREAD_NEW_CMSIS_RTOS_V2 */
/* USER CODE BEGIN PHY_PRE_CONFIG */
ETH_PHYConfig();
/* 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)
{
PHYLinkState = LAN8742_GetLinkState(&LAN8742);
printf("PHYLinkState = %d\r\n", PHYLinkState);
/* Get link state */
if(PHYLinkState <= LAN8742_STATUS_LINK_DOWN)
{
netif_set_link_down(netif);
netif_set_down(netif);
}
else
{
switch (PHYLinkState)
{
case LAN8742_STATUS_100MBITS_FULLDUPLEX:
duplex = ETH_FULLDUPLEX_MODE;
speed = ETH_SPEED_100M;
break;
case LAN8742_STATUS_100MBITS_HALFDUPLEX:
duplex = ETH_HALFDUPLEX_MODE;
speed = ETH_SPEED_100M;
break;
case LAN8742_STATUS_10MBITS_FULLDUPLEX:
duplex = ETH_FULLDUPLEX_MODE;
speed = ETH_SPEED_10M;
break;
case LAN8742_STATUS_10MBITS_HALFDUPLEX:
duplex = ETH_HALFDUPLEX_MODE;
speed = ETH_SPEED_10M;
break;
default:
duplex = ETH_FULLDUPLEX_MODE;
speed = ETH_SPEED_100M;
break;
}
/* Get MAC Config MAC */
HAL_ETH_GetMACConfig(&heth, &MACConf);
MACConf.DuplexMode = duplex;
MACConf.Speed = speed;
HAL_ETH_SetMACConfig(&heth, &MACConf);
HAL_ETH_Start_IT(&heth);
netif_set_up(netif);
netif_set_link_up(netif);
/* USER CODE BEGIN PHY_POST_CONFIG */
/* USER CODE END PHY_POST_CONFIG */
}
}
else
{
Error_Handler();
}
#endif /* LWIP_ARP || LWIP_ETHERNET */
/* USER CODE BEGIN LOW_LEVEL_INIT */
/* USER CODE END LOW_LEVEL_INIT */
}
在 ETH_PHYConfig接口中,主要实现复位PHY、配置芯片寄存器,等待复位正常后,初始化PHY芯片调用LAN8742_Init接口。
void ETH_PHYConfig(void)
{
uint32_t phyreg;
// 1. 复位PHY
HAL_ETH_WritePHYRegister(&heth, PHY_ADDRESS, LAN8742_BCR, LAN8742_BCR_SOFT_RESET);
osDelay(100); // 等待复位完成
// 2. 检查PHY ID确认芯片型号
HAL_ETH_ReadPHYRegister(&heth, PHY_ADDRESS, LAN8742_PHYI1R, &phyreg);
printf("ID1 = 0x%x\r\n", phyreg);
HAL_ETH_ReadPHYRegister(&heth, PHY_ADDRESS, LAN8742_PHYI2R, &phyreg);
printf("ID2 = 0x%x\r\n", phyreg);
// 3. 配置YT8512C
// 启用自协商 + 100Mbps + 全双工
HAL_ETH_WritePHYRegister(&heth, PHY_ADDRESS, LAN8742_BCR,
LAN8742_BCR_AUTONEGO_EN | 0x20 | LAN8742_BCR_DUPLEX_MODE);
// 4. 重启自协商过程
HAL_ETH_WritePHYRegister(&heth, PHY_ADDRESS, LAN8742_BCR,
LAN8742_BCR_AUTONEGO_EN | 0x20 | LAN8742_BCR_DUPLEX_MODE | LAN8742_BCR_RESTART_AUTONEGO);
// 5. 等待自协商完成
do {
HAL_ETH_ReadPHYRegister(&heth, PHY_ADDRESS, LAN8742_BSR, &phyreg);
osDelay(100);
} while (!(phyreg & 0x20));
}
在LAN8742_Init接口中按照默认即可,也可将pObj->DevAddr地址值设为1,即addr=0。
int32_t LAN8742_Init(lan8742_Object_t *pObj)
{
uint32_t regvalue = 0, addr = 0;
int32_t status = LAN8742_STATUS_OK;
if(pObj->Is_Initialized == 0)
{
if(pObj->IO.Init != 0)
{
/* GPIO and Clocks initialization */
pObj->IO.Init();
}
/* for later check */
pObj->DevAddr = LAN8742_MAX_DEV_ADDR + 1;
/* Get the device address from special mode register */
for(addr = 0; addr <= LAN8742_MAX_DEV_ADDR; addr ++)
{
if(pObj->IO.ReadReg(addr, LAN8742_SMR, ®value) < 0)
{
status = LAN8742_STATUS_READ_ERROR;
/* Can't read from this device address
continue with next address */
continue;
}
if((regvalue & LAN8742_SMR_PHY_ADDR) == addr)
{
pObj->DevAddr = addr;
status = LAN8742_STATUS_OK;
break;
}
}
if(pObj->DevAddr > LAN8742_MAX_DEV_ADDR)
{
status = LAN8742_STATUS_ADDRESS_ERROR;
}
/* if device address is matched */
if(status == LAN8742_STATUS_OK)
{
pObj->Is_Initialized = 1;
}
}
return status;
}
其次LAN8742_GetLinkState接口用于获取LINK状态,主要是以太网速率和模式,注意,要是没有调用ETH_PHYConfig接口,获取到的状态可能不正常,导致无法正常联网。
以下为新建tcp连接、tcp接收、tcp发送、断开tcp连接的源代码,在FreeRtos现成中调用即可。
static int sock = -1;
uint8_t tcp_client_init(void)
{
uint8_t ret = 0;
struct sockaddr_in server_addr;
// 创建socket
sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
printf("Socket creation error\r\n");
return ret;
}
// 配置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TCP_SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(TCP_SERVER_IP);
// 连接服务器
if (lwip_connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("Connection failed\r\n");
lwip_close(sock);
sock = -1;
return ret;
}
printf("Connected to server\r\n");
return 1;
}
uint8_t tcp_client_send(const char *data, uint16_t len)
{
uint8_t ret = 0;
if (sock < 0) {
printf("Socket not connected\r\n");
return ret;
}
ret = 1;
// 发送数据
int sent = lwip_send(sock, data, len, 0);
if (sent < 0) {
printf("Send failed\r\n");
lwip_close(sock);
sock = -1;
ret = 0;
return ret;
}
return ret;
}
uint8_t tcp_client_receive(void)
{
uint ret = 0;
if (sock < 0) {
printf("Socket not connected\r\n");
return ret;
}
char buffer[128];
int received = lwip_recv(sock, buffer, sizeof(buffer)-1, 0);
if (received > 0) {
buffer[received] = '\0'; // 确保字符串终止
printf("Received: %s\r\n", buffer);
ret = 1;
return ret;
} else if (received == 0) {
printf("Connection closed by server\r\n");
lwip_close(sock);
sock = -1;
return ret;
} else {
printf("Receive error\r\n");
lwip_close(sock);
sock = -1;
return ret;
}
}
uint8_t tcp_client_close(void)
{
if (sock >= 0) {
lwip_close(sock);
sock = -1;
printf("Connection closed\r\n");
}
}
以上为STM32H743单片机使用LWIP实现socket-tcp客户端通信的功能,目前的版本可能存在通信不稳定的问题,定位的原因可能有时钟频率不稳定,通信处理逻辑不严谨。