我的项目在以前的应用主要是FreeRTOS+lwIP的组合,但是这个组合只能说能用,但是其中的问题也比较多。其中重要的问题是断线重连问题,这个从检测到重连的时间较长,往往中断都在50秒的样子有时更久,也可能是我的水平问题,所以很想找一个比较靠谱的TCP/IP协议栈。这次我看到H563ZI板子有ThreadX和NETX的组合支持,所以就来测试一番。
首先就是从已有的例程开始,这个例程我选择了Nx_SNTP_Client,这个例程是从NTP服务器对时的应用。这个应用非常的实用,因为在一些和实时时钟的应用中大多数都需要校时任务。首先打开Nx_SNTP_Client.ioc文件,
可以看到应用支持,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设置中,然后就可以不断的获取时间了。
可以看到时间显示出来了。但是这里需要注意一个“坑”,就是发现时间好像和计算机显示的时间对不上,呵呵我突然想起来,这个时间时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时区时间
这个程序我长时间的测试了3个小时的时间,发现其中没有发现任何中断的现象。反复的ping单片机地址,也是TTL值一直很稳定,但是lwIP的程序就没有这么好了。