正点原子STM32H743单片机实现tcp通信

目标

使用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配置

ETH对应IO配置
LWIP配置
LWIP外设

生成代码

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, &regvalue) < 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客户端通信的功能,目前的版本可能存在通信不稳定的问题,定位的原因可能有时钟频率不稳定,通信处理逻辑不严谨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值