[讨论] 【STM32H5开发板】TCP/IP UDP通讯测试

bigbat   2023-5-17 21:04 楼主

我的项目在以前的应用主要是FreeRTOS+lwIP的组合,但是这个组合只能说能用,但是其中的问题也比较多。其中重要的问题是断线重连问题,这个从检测到重连的时间较长,往往中断都在50秒的样子有时更久,也可能是我的水平问题,所以很想找一个比较靠谱的TCP/IP协议栈。这次我看到H563ZI板子有ThreadX和NETX的组合支持,所以就来测试一番。

首先就是从已有的例程开始,这个例程我选择了Nx_SNTP_Client,这个例程是从NTP服务器对时的应用。这个应用非常的实用,因为在一些和实时时钟的应用中大多数都需要校时任务。首先打开Nx_SNTP_Client.ioc文件,

image.png 可以看到应用支持,ThreadX和NETX的应用,应用层支持DNS和SNTP应用,在以前使用过lwIP的DNS,说实话比较难用步骤很是繁琐,尤其是出错处理很是麻烦。底层也是支持DHCP、ICMP、TCP支持,再次的吐槽lwIP的DHCP,这个应用有时很长时间无法获得IP,所以需要不停的检测和重试,而且重试很多次都无法获得IP地址。有一次我不得不换了个牌子的路由器,这个我真是伤透了脑筋。

硬件接口设置就很简单了接口选RMII,PHY设置LAN8742,MAC地址00:80:E1:00:00:00(这是一个测试地址,如果在应用中最好是修改一下)硬件开启全局中断。

还有串口设置,还有printf设置,注意编译时选择microLIB库,要不无法收到串口信息。

下面来分析应用,Main.C基本上不需要动,主要是硬件程序化和MX_ThreadX_Init();函数。

/**
  * [url=home.php?mod=space&uid=159083]@brief[/url] 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_ICACHE_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  MX_ThreadX_Init();

  /* We should never get here as control is now taken by the scheduler */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

可以看到程序非常的简洁,微软的建议是将硬件的初始化尽可能的放在main.C中进行,这里放入printf的设置。不要放入太多的逻辑代码。应用的初始化尽可能在tx_application_define函数中进行。这里的初始化过程被封装到status = MX_NetXDuo_Init(memory_ptr)中了,在这个MX_NetXDuo_Init函数中在进行线程的初始化。主要有三个线程和一个回调函数。

/* Private function prototypes -----------------------------------------------*/
static VOID App_Main_Thread_Entry (ULONG thread_input);
static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);
/* USER CODE BEGIN PFP */
static VOID App_SNTP_Thread_Entry(ULONG thread_input);
static VOID App_Link_Thread_Entry(ULONG thread_input);

对我启发较大的是App_Link_Thread_Entry线程,这个线程定期的检测网络物理连接。

接下来是App_Main_Thread_Entry线程,这个线程主要是完成联网的准备工作。还有就是对App_Link_Thread_Entry线程数据的初始化。

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */
#define PRINT_IP_ADDRESS(addr)    do { \
                                       printf("STM32 %s: %lu.%lu.%lu.%lu \n", #addr, \
                                       (addr >> 24) & 0xff,                          \
                                       (addr >> 16) & 0xff,                          \
                                       (addr >> 8) & 0xff,                           \
                                       (addr & 0xff));                               \
                                     } while(0)

主要的工作线程是App_SNTP_Thread_Entry,这个线程完成整个过程

/**
* @brief  SNTP thread entry.
* @param thread_input: ULONG user argument used by the thread entry
* @retval none
*/
/* Define the client thread.  */
static void App_SNTP_Thread_Entry(ULONG info)
{
  UINT ret;
  RtcHandle.Instance = RTC;
  ULONG  seconds, fraction;
  ULONG  events = 0;
  UINT   server_status;
  NXD_ADDRESS sntp_server_ip;
  NX_PARAMETER_NOT_USED(info);

  sntp_server_ip.nxd_ip_version = 4;

  /* Create a DNS client */
  ret = dns_create(&DnsClient);

  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /* Look up SNTP Server address. */
  ret = nx_dns_host_by_name_get(&DnsClient, (UCHAR *)SNTP_SERVER_NAME,
                                &sntp_server_ip.nxd_ip_address.v4, NX_APP_DEFAULT_TIMEOUT);

    /* Check for error. */
  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

   /* Create the SNTP Client */
  ret =  nx_sntp_client_create(&SntpClient, &NetXDuoEthIpInstance, iface_index, &NxAppPool, NULL, kiss_of_death_handler, NULL);

  /* Check for error. */
  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /* Setup time update callback function. */
   nx_sntp_client_set_time_update_notify(&SntpClient, time_update_callback);

  /* Use the IPv4 service to set up the Client and set the IPv4 SNTP server. */
  ret = nx_sntp_client_initialize_unicast(&SntpClient, sntp_server_ip.nxd_ip_address.v4);

  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /* Run whichever service the client is configured for. */
  ret = nx_sntp_client_run_unicast(&SntpClient);

  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }
  else
  {
    PRINT_CNX_SUCC();
  }

  /* Wait for a server update event. */
  tx_event_flags_get(&SntpFlags, SNTP_UPDATE_EVENT, TX_OR_CLEAR, &events, PERIODIC_CHECK_INTERVAL);

  if (events == SNTP_UPDATE_EVENT)
  {
    /* Check for valid SNTP server status. */
    ret = nx_sntp_client_receiving_updates(&SntpClient, &server_status);

    if ((ret != NX_SUCCESS) || (server_status == NX_FALSE))
    {
      /* We do not have a valid update. */
      Error_Handler();
    }
    /* We have a valid update.  Get the SNTP Client time.  */
    ret = nx_sntp_client_get_local_time_extended(&SntpClient, &seconds, &fraction, NX_NULL, 0);

    ret = nx_sntp_client_utility_display_date_time(&SntpClient,buffer,64);

    if (ret != NX_SUCCESS)
    {
      printf("Internal error with getting local time 0x%x\n", ret);
      Error_Handler();
    }
    else
    {
      printf("\nSNTP update :\n");
      printf("%s\n\n",buffer);
    }
  }
  else
  {
    Error_Handler();
  }

  /* Set Current time from SNTP TO RTC */
  rtc_time_update(&SntpClient);

  /* We can stop the SNTP service if for example we think the SNTP server has stopped sending updates */
  ret = nx_sntp_client_stop(&SntpClient);
  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /* When done with the SNTP Client, we delete it */
  ret = nx_sntp_client_delete(&SntpClient);
  if (ret != NX_SUCCESS)
  {
    Error_Handler();
  }

  /* Toggling LED after a success Time update */
  while(1)
  {
    /* Display RTC time each second  */
    display_rtc_time(&RtcHandle);
    HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
    /* Delay for 1s */
    tx_thread_sleep(100);
  }
}

这个程序主要是完成SNTP获取,然后设置到操作系统参数和RTC设置中,然后就可以不断的获取时间了。

image.png  可以看到时间显示出来了。但是这里需要注意一个“坑”,就是发现时间好像和计算机显示的时间对不上,呵呵我突然想起来,这个时间时UTC时间这个时间是有时区的,随即找到以下程序,

/* EPOCH_TIME_DIFF is equivalent to 70 years in sec
calculated with www.epochconverter.com/date-difference
This constant is used to delete difference between :
Epoch converter (referenced to 1970) and SNTP (referenced to 1900) */
time_t timestamp = client_ptr->nx_sntp_current_server_time_message.receive_time.seconds - EPOCH_TIME_DIFF;

将程序修改成:

/* EPOCH_TIME_DIFF is equivalent to 70 years in sec
calculated with www.epochconverter.com/date-difference
This constant is used to delete difference between :
Epoch converter (referenced to 1970) and SNTP (referenced to 1900) */
time_t timestamp = client_ptr->nx_sntp_current_server_time_message.receive_time.seconds - EPOCH_TIME_DIFF + 28800U;

这样转换成UTC 0800时区时间

image.png  这次时间就对上了。后记:

image.png  这个程序我长时间的测试了3个小时的时间,发现其中没有发现任何中断的现象。反复的ping单片机地址,也是TTL值一直很稳定,但是lwIP的程序就没有这么好了。

回复评论 (1)

ThreadX+NETX组合看来优势非常明显,这次的测试让我了解一种新的RTOS

点赞  2023-5-18 17:09
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复