How to Setup DNS on a STM32 µP with freeRTOS


Introduction

In this tutorial, we will show how to Setup DNS on a STM32 µP with freeRTOS. This will execute a basic test to return the IP address of a famous website. We’ve chosen the www.facebook.com website because its DNS resolution will only return a consistent single IP address every time. Unlike some Domain Names, which can round-robin IP’s based upon virtual environments of a hosted website.

Once networking is established, and we’ve shown how to do this via a previous tutorial in: “How to Setup a Network Interface on a STM32 µP with freeRTOS”, you can then proceed to communicate with various servers or websites. In this case we will be reaching out to a DNS server on the public/private Internet, and requesting that it return to us the IP address that is known for a particular hostname web site.



Materials List


FTDI Pinouts

FTDI to USB Pinout from right to left

  • Pin 1 – GND
  • Pin 2 – CTS
  • Pin 3 – VCC
  • Pin 4 – TX
  • Pin 5 – RX
  • Pin 6 – DTR
  • USB Mini – Connect to PC via USB cable

Make sure the jumper is set for 5v or 3v which ever is being used to power the FTDI device. If we look below at the schematic, it is showing in this case as 5v.


Nucleo-F439ZI


Schematic


Pinouts & Configurations

For this tutorial rather than trying to capture screenshots for each pinout & configuration screen, we generated a PDF of all of the settings within the IOC file, which is included below. Parameters that have changed from the default values are highlighted by being in BOLD with an asterisk “*

freeRTOS Task List


Exploring the Source Code

Project Structure


main.h

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.h
  * @brief          : Header for main.c file.
  *                   This file contains the common defines of the application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/
#define RMII_MDC_Pin GPIO_PIN_1
#define RMII_MDC_GPIO_Port GPIOC
#define RMII_REF_CLK_Pin GPIO_PIN_1
#define RMII_REF_CLK_GPIO_Port GPIOA
#define RMII_MDIO_Pin GPIO_PIN_2
#define RMII_MDIO_GPIO_Port GPIOA
#define RMII_CRS_DV_Pin GPIO_PIN_7
#define RMII_CRS_DV_GPIO_Port GPIOA
#define RMII_RXD0_Pin GPIO_PIN_4
#define RMII_RXD0_GPIO_Port GPIOC
#define RMII_RXD1_Pin GPIO_PIN_5
#define RMII_RXD1_GPIO_Port GPIOC
#define RMII_TXD1_Pin GPIO_PIN_13
#define RMII_TXD1_GPIO_Port GPIOB
#define USB_PowerSwitchOn_Pin GPIO_PIN_6
#define USB_PowerSwitchOn_GPIO_Port GPIOG
#define USB_OverCurrent_Pin GPIO_PIN_7
#define USB_OverCurrent_GPIO_Port GPIOG
#define RMII_TX_EN_Pin GPIO_PIN_11
#define RMII_TX_EN_GPIO_Port GPIOG
#define RMII_TXD0_Pin GPIO_PIN_13
#define RMII_TXD0_GPIO_Port GPIOG

/* USER CODE BEGIN Private defines */

#define REDIRECT_PRINTF

/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

main.c

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "lwip.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include "stdio.h"
#include "string.h"
#include "stdbool.h"

#include "dnsLookup.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* Definitions for networkTask */
osThreadId_t networkTaskHandle;
const osThreadAttr_t networkTask_attributes = {
  .name = "networkTask",
  .stack_size = 768 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for TestDNSTask */
osThreadId_t TestDNSTaskHandle;
const osThreadAttr_t TestDNSTask_attributes = {
  .name = "TestDNSTask",
  .stack_size = 512 * 4,
  .priority = (osPriority_t) osPriorityLow,
};
/* USER CODE BEGIN PV */

bool waitingOnNetwork = true;
bool dhcpReady = false;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
void networkInitializationTask(void *argument);
void TestDNSFunction(void *argument);

/* USER CODE BEGIN PFP */

void link_myCallBack(struct netif *netif);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

#ifdef REDIRECT_PRINTF
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif

#ifdef REDIRECT_PRINTF

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}
#endif

#define ETH_DEVICE	0

void link_myCallBack(struct netif *netif) {
	uint32_t regValue = 0;

    if (HAL_ETH_ReadPHYRegister(&heth, ETH_DEVICE, PHY_BSR, &regValue) == HAL_OK) {
        if((regValue & PHY_LINKED_STATUS) == (uint16_t)RESET) {
			if (!netif_is_link_up(netif)) {	// Link status = disconnected
				netif_set_down(netif);
				printf("Network cable is now disconnected!!!\r\n");
				netif_set_link_down(netif);
			}
        } else {
				if (netif_is_link_up(netif)) {	// Link status = connected
					printf("Network cable is now connected!!!\r\n");
					printf("Rebooting the system!!!\r\n");
					osDelay(2000);

					// We could try to write some sort of recovery logic to handle a reconnection, however.... (Boom!!!)
					NVIC_SystemReset();	// Reboot the microprocessor and start the application over.
				}
			}
        }
}

/* USER CODE END 0 */

/**
  * @brief  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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* creation of networkTask */
  networkTaskHandle = osThreadNew(networkInitializationTask, NULL, &networkTask_attributes);

  /* creation of TestDNSTask */
  TestDNSTaskHandle = osThreadNew(TestDNSFunction, NULL, &TestDNSTask_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

  /* USER CODE BEGIN RTOS_EVENTS */
  /* add events, ... */
  /* USER CODE END RTOS_EVENTS */

  /* Start scheduler */
  osKernelStart();

  /* 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 */
}

/**
  * @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_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 180;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Activate the Over-Drive mode
  */
  if (HAL_PWREx_EnableOverDrive() != 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();
  }
}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 19200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(USB_PowerSwitchOn_GPIO_Port, USB_PowerSwitchOn_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : USB_PowerSwitchOn_Pin */
  GPIO_InitStruct.Pin = USB_PowerSwitchOn_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(USB_PowerSwitchOn_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : USB_OverCurrent_Pin */
  GPIO_InitStruct.Pin = USB_OverCurrent_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(USB_OverCurrent_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/* USER CODE BEGIN Header_networkInitializationTask */
/**
  * @brief  Function implementing the networkTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_networkInitializationTask */
void networkInitializationTask(void *argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN 5 */
	osDelay(2000);

	extern struct netif gnetif;
	while (ip4_addr_isany_val(*netif_ip4_addr(&gnetif))) {
	  osDelay(200);
	}

	printf("\x1b[2J\x1b[H");	// Clear the dumb terminal screen
	printf("Starting Network Initialization......\r\n");

	char buffer[1024];

	printf("==============================================================================\n\r");

	char *ipstr = ip4addr_ntoa(netif_ip4_addr(&gnetif));

	sprintf(buffer, "Networking Ready!!!\r\n");
	printf(buffer);

	sprintf(buffer, "IP Address: %s assigned to this device by the DHCP Server\r\n", ipstr);
	printf(buffer);

	const ip_addr_t *ipaddr;

	ipaddr = dns_getserver(0);

	dhcpReady = !(ip4_addr_isany_val(*ipaddr));

	while (!dhcpReady) {
	  osDelay(200);
	  ipaddr = dns_getserver(0);
	  dhcpReady = !(ip4_addr_isany_val(*ipaddr));
	}

	sprintf(buffer, "DHCP Assigned DNS SERVER IP: %s\r\n", ip4addr_ntoa(ipaddr));
	printf(buffer);

	// Here we override the default callback defined in the MX_LWIP_Init() function which defaults to:
	// netif_set_link_callback(&gnetif, ethernet_link_status_updated);
	//
	// Setup our callback to handle ethernet cable disconnect/reconnect and override the default callback of: ethernet_link_status_updated
	netif_set_link_callback(&gnetif, link_myCallBack);

	sprintf(buffer, "Networking Initialization Complete!!!\r\n");
	printf(buffer);

	printf("Task Done!!!\r\n");
	printf("==============================================================================\n\r");

	waitingOnNetwork = false;

	/* Infinite loop */
	for(;;)
	{
	osDelay(1);
	}
  /* USER CODE END 5 */
}

/* USER CODE BEGIN Header_TestDNSFunction */
/**
* @brief Function implementing the TestDNSTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_TestDNSFunction */
void TestDNSFunction(void *argument)
{
  /* USER CODE BEGIN TestDNSFunction */

	dnsGetHostName_t getHostNameResp = {0};

	while (waitingOnNetwork) {
		osDelay(10);
	}

	printf("Starting DNS Testing......\r\n");

	getHostNameResp.name = "www.facebook.com";
	getHostNameResp.done = false;

	printf("getHostByName: %s  Should return: 157.240.3.35\r\n", getHostNameResp.name);

	bool found = getHostByName(getHostNameResp.name, &getHostNameResp);

	while (!found || getHostNameResp.done == false) {
		osDelay(10);
	}

	printf("DNS Lookup Completed for %s,\r\n\twhich returned IP Address: %s\r\n", getHostNameResp.name, ip4addr_ntoa(&getHostNameResp.addr));

	printf("Task Done!!!\r\n");
	printf("==============================================================================\n\r");

  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END TestDNSFunction */
}

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM6 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM6) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

dnsLookup.h

C
/*
 * dnsLookup.h
 *
 *  Created on: Jun 7, 2024
 *      Author: johng
 */

#ifndef DNSLOOKUP_H_
#define DNSLOOKUP_H_
#include "cmsis_os.h"
#include "stm32f4xx_hal.h"
#include "lwip/dns.h"
#include "err.h"
#include "lwip.h"
#include "stdbool.h"
#include "lwip/apps/sntp.h"


typedef struct dnsGetHostName {
  const char *name;
  ip_addr_t addr;
  bool done;
} dnsGetHostName_t;

//extern dnsGetHostName_t *getHostNameResp;

void hostNameFoundCallBack(const char *, const ip_addr_t *, void *);
bool getHostByName(const char *, dnsGetHostName_t *);

typedef  void (*callBackFuncPtr)(char *, ip_addr_t *, void *);
typedef void (*dns_found_callback)(const char *name, const ip_addr_t *ipaddr, void *callback_arg);

#endif /* DNSLOOKUP_H_ */

dnsLookup.c

C
#include "dnsLookup.h"

dnsGetHostName_t *getHostNameRespPtr;

void hostNameFoundCallBack(const char *hostname, const ip_addr_t *ipaddr, void *arg)
{
	char buffer[128];

  if (ipaddr != NULL) {
    /* Address resolved, send request */

	  getHostNameRespPtr->addr = *ipaddr;
	  getHostNameRespPtr->done = true;
  } else {
	  sprintf(buffer, "DNS Failed to resolve. ipaddr == NULL\r\n");
	  printf(buffer);
	  Error_Handler();
  }
}

bool getHostByName(const char *name, dnsGetHostName_t *response) {
	bool retVal = false;
	int counter = 0;
	char buffer[128];

	getHostNameRespPtr = response;

	/*
	 * The following function call is used to get the a host name via a DNS server.  The DNS server had to be previously setup during
	 * static setup or through DHCP initialization.
	 * Once a response from the DNS Server is received, the callback function specified in the 3rd parameter is invoked.
	 * In this case the callback function is called: hostNameFoundCallBack
	 */
	err_enum_t err = dns_gethostbyname(name, &getHostNameRespPtr->addr, (dns_found_callback) hostNameFoundCallBack, NULL);

	if (err == ERR_INPROGRESS) {
	  while(getHostNameRespPtr->done != true) {
		  osDelay(100);
		  counter ++;
		  if (counter > 1000) {
			  sprintf(buffer, "getHosNameRespPtr never returned TRUE after X Number of tries.\r\n");
			  printf(buffer);
			  Error_Handler();
		  }
	  }
	  retVal = true;
	} else if (err == ERR_OK) {
	  getHostNameRespPtr->done = true;
	  retVal = true;
	} else {
		sprintf(buffer, "dns_gethostbyname ERROR: %d", err);
		printf(buffer);
		Error_Handler();
	}

	return retVal;
}

USART Dumb Terminal Output