Cellular X-CUBE-CELLULAR How To

Revision as of 15:07, 17 July 2023 by Registered User (→‎How to low power / power off device properly)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Click here for Cellular overview

1. How To...

1.1. Introduction

This wiki article is a guideline that describes how to perform the main activities with X-CUBE-CELLULAR.
The section "How To" is divided into three parts. The first part concerns common remarks related to Azure RTOS and Free RTOS. The second part is specific to FreeRTOS while the last part relates to Azure RTOS .

1.2. How To applicable to FreeRTOS and Azure RTOS versions

1.2.1. How to start the delivered binary firmware

The binary firmware is provided for each supported board. The example below describes how to run the STM32L496 binary for the BG96 modem, the process is he same with other hardware setups. The steps are:

  1. Insert an activated SIM into the BG96 modem STMod+ board.
  2. Plug the BG96 modem board (module up, SIM down) into the STM32L496 board, more details on Cellular hardware setup.
  3. Plug an USB cable to the STLINK labeled plug on the board (the one near the blue quad-switch) on the STM32L496 board. After that a new volume (DIS_L496ZG) should be displayed on your PC).
  4. Flash the firmware (drag and drop the .bin onto the newly appearing volume).
  5. Open a TeraTerm serial terminal and set the right parameters (see the section below How to setup Teraterm parameters).
  6. If required (by default it is empty), enter the right SIM APN using the APN setting as defined in the related "How To" section detailed below.

1.2.2. How to find the delivered binary required

All the binaries provided are available at: \Projects\<STM32 board name>\Demonstrations\<Demonstration name>\Binaries\
Example: \Projects\B-U585I-IOT02A\Demonstrations\Cellular\Binaries\
The name of the binaries indicates its purpose, <STM32 board>_<modem>_<IP stack>.bin

  • u585_bg96_stm32ip.bin is the binary for the U585 discovery board associated to the BG96 modem and the IP stack selected is the IP in the STM32.
  • u585_bg96_modemip.bin is the binary for the U585 discovery board associated to the BG96 modem, and the IP stack selected is in the modem.

1.2.3. How to set up Tera Term parameters

  • Terminal
    • [New line]
      • [Receive]: Auto
      • [Transmit]: CR
    • [Local echo] selected
  • Serial
    • [Baud rate]: 115200
    • [Data]: 8 bi
    • [Parity]: none
    • [Stop]: 1 bit
    • [Flow control]: none
    • [Transmit delay]: 10 ms each

1.2.4. How to list and get help on CLI commands

Hit "enter" then type "help" then hit "enter" the list of commands is displayed.

List of commands example:


  • help: help command
  • trace: trace management
  • comlib: com library commands
  • cst: cellular service task management
  • atcmd: send an at command
  • modem: modem configuration management


Therefore, to send an AT command to the modem just enter: atcmd AT+QIRC

1.2.5. How to APN selection and configuration

See complete article about APN management and configuration

1.2.6. How to get the modem and cellular network information

1.2.6.1. using console commands
  1. Open a Tera Term serial terminal and set the right parameters.
  2. Remove the trace display by typing the commands:
  • trace off
  • cst poll off

Then type the Cellular Service commands:

  • cst
  • cst state
  • cst info
  • cst config
1.2.6.2. using control API

Here is a code example to retreive and display information about modem and cellular

  #include "cellular_control_api.h"

  cellular_sim_info_t           my_sim_info;
  cellular_sim_info_index_t     my_sim_info_index;
  cellular_operator_selection_t my_operator_selection;
  cellular_info_t               my_cellular_info;
  cellular_signal_info_t        my_signal_info;
  cellular_ip_info_t            my_ip_info;

  cellular_get_sim_info(&my_sim_info);

  /* print actual selected sim */
  PRINT_FORCE("Sim Selected (slot type > status) : %d > %d", my_sim_info.sim_slot_type, my_sim_info.sim_status)
  /* For each defined SIM slot, and in priority order, display : */
  /*   the slot interface */
  /*   the result of sim activation */
  /*   the APN information */
  PRINT_FORCE("All sim information :")
  for (uint8_t n = 0U; n < my_sim_info.sim_slot_nb; n++)
  {
    if (cellular_get_sim_info_from_index(n, &my_sim_info_index) == CELLULAR_SUCCESS)
    {
      PRINT_FORCE("- Sim (slot type : status) %d : %d", my_sim_info_index.sim_slot_type, my_sim_info_index.sim_status)
      if(my_sim_info_index.sim_status == CA_SIM_READY)
      {
        /* ICCID and IMSI are known only for sim initialized and ready */
        PRINT_FORCE("    ICCID : %s", my_sim_info.iccid.value)
        PRINT_FORCE("    IMSI : %s", my_sim_info.imsi.value)
      }
      PRINT_FORCE("    APN : %s", my_sim_info_index.pdn.apn.value)
      PRINT_FORCE("    APN username : %s", my_sim_info_index.pdn.username.value)
      PRINT_FORCE("    APN password : %s", my_sim_info_index.pdn.password.value)
      PRINT_FORCE("    CID : %d", my_sim_info_index.pdn.cid)
      if (my_sim_info_index.pdn.apn_send_to_modem == CA_APN_SEND_TO_MODEM)
      {
        PRINT_FORCE("    APN is sent to modem")
      }
      else
      {
        PRINT_FORCE("    APN is not sent to modem")
      }
    }
  }
  /* Display NFMC information */
  cellular_get_nfmc_info(&my_nfmc_info);
  if (my_nfmc_info.enable != 0U)
  {
    /* NFMC feature active. Displays the temporization list */
    /* Display all NFMC tempo */
    for (i = 0U; i < my_nfmc_info.tempo_nb ; i++)
    {
      PRINT_FORCE("nfmc tempo %ld   : %ld", i + 1U, my_nfmc_info.tempo_values[i])
    }
  }
  else
  {
    PRINT_FORCE("No nfmc tempo set");
  }
  /* Operator registration configuration */
  cellular_get_operator(&my_operator_selection);
  PRINT_FORCE("Network register mode: %d", my_operator_selection.ntw_registration_mode);
  if (my_operator_selection.ntw_registration_mode != CA_NTW_REGISTRATION_AUTO)
  {
    /* If registration is not automatic, display also : */
    /*   Operator name format */
    PRINT_FORCE("Operator name format: %d",
                my_operator_selection.operator_name_format);
    /*   Operator name */
    PRINT_FORCE("Operator name: %s\n\r", my_operator_selection.operator_name.value);
  }
  /* Access techno present or not */
  if (my_operator_selection.access_techno_present == CA_ACT_PRESENT)
  {
    PRINT_FORCE("Access techno present: Present");
    /* If access techno is present, display the techno to used */
    PRINT_FORCE("Network register mode: %d", my_operator_selection.access_techno);
  }
  else
  {
    PRINT_FORCE("Access techno present: Not present");
  }

 /* reads cellular info from cellular API */
  cellular_get_cellular_info(&my_cellular_info);
  /* reads signal info from cellular API */
  cellular_get_signal_info(&my_signal_info);
  /* reads IP address from cellular API */
  cellular_get_ip_info(&my_ip_info);
  PRINT_FORCE("Cellular Service Infos ")
  /* Dynamic modem related information */
  PRINT_FORCE("Modem state          : %d", my_cellular_info.modem_state)
  PRINT_FORCE("Signal Quality       : %d", my_signal_info.signal_strength.raw_value)
  PRINT_FORCE("Signal level(dBm)    : %ld", my_signal_info.signal_strength.db_value)
  /* SIM related information */
  PRINT_FORCE("IMEI                 : %s", my_cellular_info.imei.value)
  /* Modem hardware related information */
  PRINT_FORCE("Manuf name           : %s", my_cellular_info.identity.manufacturer_id.value)
  PRINT_FORCE("Model                : %s", my_cellular_info.identity.model_id.value)
  PRINT_FORCE("Revision             : %s", my_cellular_info.identity.revision_id.value)
  PRINT_FORCE("Serial Number        : %s", my_cellular_info.identity.serial_number_id.value)
  /* Cellular access techno used */
  PRINT_FORCE("Network bearer (AcT) : %d", my_signal_info.access_techno)
  PRINT_FORCE("IP address           : %d.%d.%d.%d",
              (uint8_t)(my_ip_info.ip_addr.addr & (uint8_t)0xFF),
              (uint8_t)((my_ip_info.ip_addr.addr >> 8) & (uint8_t)0xFF),
              (uint8_t)((my_ip_info.ip_addr.addr >> 16) & (uint8_t)0xFF),
              (uint8_t)((my_ip_info.ip_addr.addr >> 24) & (uint8_t)0xFF))

1.2.7. How to set modem in a particular mode

1.2.7.1. Switch modem off

This will switch off the modem. Modem power will be cut off. Switching modem off can be done in any state of the modem :

  • on without sim or network management
  • on restricted to sim access
  • on with connection to network
1.2.7.1.1. At boot time

In the file plf_cellular_config.h, change the value of the define PLF_CELLULAR_TARGET_STATE to 0 :

#define PLF_CELLULAR_TARGET_STATE        (0U)
1.2.7.1.2. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the commands:
  • trace off
  • cst poll off

Then type the Cellular Service command : cst modem off

1.2.7.1.3. Using control API
#include "cellular_control_api.h"

/* API call to stop the modem. */
(void)cellular_modem_stop();
1.2.7.2. Switch modem on restricted to sim access

This will switch on the modem. Only sim access will be activated, without network and data transfer management. Sim only mode equivalent to AT+CFU=4 AT command.
Switching to modem on restricted to sim access can be done in the following states of the modem :

  • off
  • on with connection to network
1.2.7.2.1. At boot time

In the file plf_cellular_config.h, change the value of the define PLF_CELLULAR_TARGET_STATE to 1 :

#define PLF_CELLULAR_TARGET_STATE        (1U)
1.2.7.2.2. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the commands:
  • trace off
  • cst poll off

Then type the Cellular Service command : cst modem sim

1.2.7.2.3. Using control API
#include "cellular_control_api.h"

/* API call to set modem in sim only mode. */
(void)cellular_disconnect();
1.2.7.3. Switch modem on with connection to network

This will switch on the modem with network activation.
Switching on with connection to network can be done in the following states of the modem :

  • off
  • on restricted to sim access
1.2.7.3.1. At boot time

In the file plf_cellular_config.h, change the value of the define PLF_CELLULAR_TARGET_STATE to 2 :

#define PLF_CELLULAR_TARGET_STATE        (2U)
1.2.7.3.2. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the commands:
  • trace off
  • cst poll off

Then type the Cellular Service command : cst modem connected

1.2.7.3.3. Using control API
#include "cellular_control_api.h"

/* API call to start and attach the modem. */
(void)cellular_connect();
1.2.7.4. Switch modem on without sim or network management

This will switch on the modem without sim access nor network activation. The modem is on, in standalone mode.
Switch the modem to this state is only possible if the modem is off.
If the modem is in any other state, switch the modem on without sim nor network management will have no effect.

1.2.7.4.1. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the commands:
  • trace off
  • cst poll off

Then type these Cellular Service command : The modem should be first shut off
cst modem off
then
cst modem on

1.2.7.4.2. Using control API
#include "cellular_control_api.h"

/* API call to stop the modem. Needed before to switch modem to on only state */
(void)cellular_modem_stop();

/* API call to start the modem in power on only. */
(void)cellular_modem_start();
1.2.7.5. Restart the modem

This will switch off, and then switch on the modem. When switch back on, the modem will be in connected mode, that is with sim access ans network activated.

1.2.7.5.1. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the commands:
  • trace off
  • cst poll off

Then type the Cellular Service command : cst modem restart

1.2.7.5.2. Using control API

Using control API, you may choose the sim only mode of the modem after restart. Just use cellular_disconnect() instead of cellular_connect() in the code bellow.

#include "cellular_control_api.h"

/* API call to stop the modem. */
(void)cellular_modem_stop();
/* API call to start and attach the modem. */
(void)cellular_connect();

1.2.8. How to modify network poling period

1.2.8.1. Stop polling

This will stop the poling of network signal.

1.2.8.1.1. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the command :
  • trace off

Then type the Cellular Service command : cst polling off

1.2.8.1.2. Modifying define in include file

In plf_sw_config.h modify the CST_MODEM_POLLING_PERIOD define. Stop polling :

#define CST_MODEM_POLLING_PERIOD            (0U)
1.2.8.2. Set/change polling period

This will enable the poling of network signal according to a certain period.

1.2.8.2.1. Using console command
  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the command :
  • trace off

Then type the Cellular Service command to enable the polling with the period set in the code : cst polling on

1.2.8.2.2. Modifying define in include file

In plf_sw_config.h modify the CST_MODEM_POLLING_PERIOD define. set polling according a period of 15 seconds. The define value unit is milliseconds :

#define CST_MODEM_POLLING_PERIOD            (15000U)

1.2.9. How to manage bearer/techno used

  • Using a BG96 modem, techno/bearer used may be modified using console command or control API as describe below.
  • Using a Type 1SC modem, techno/bearer used may be modified only using specific AT commands.
  • Using a GM01Q modem, techno/bearer used may be modified only by reflashing a new firmware of the modem.
1.2.9.1. Force techno to use
1.2.9.1.1. Using console command

Only to use with BG96

  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the command :
  • trace off

Then type the Cellular Service command to set the wanted techno/bearer to be used :
cst techno on <Techno/bearer to use>
<Techno/bearer to use> may be one of :

  • 0 for GSM
  • 1 for E UTRAN = cat M = cat M1
  • 2 for E_UTRAN_NBS1 = cat NB1 = NB-IoT
1.2.9.1.2. Using control API

Only to use with BG96
Code example to set techno/bearer to use :

cellular_operator_selection_t my_operator_selection;
/* Get data */
cellular_get_operator(&my_operator_selection);
/* Set access techno is present */
my_operator_selection.access_techno_present = CA_ACT_PRESENT;
/* Set now the techno to use */
/* For GSM */
my_operator_selection.access_techno = CA_ACT_GSM;
/* For E UTRAN */
/* my_operator_selection.access_techno = CA_ACT_E_UTRAN */
/* For E UTRAN NBS1 */
/* my_operator_selection.access_techno = CA_ACT_E_UTRAN_NBS1 */
/* Save data */
(void)cellular_set_operator(&my_operator_selection);
/* Need to restart the modem for new techno/bearer to be taken into account */
/* API call to stop the modem. */
(void)cellular_modem_stop();
/* API call to start and attach the modem. */
(void)cellular_connect();
1.2.9.1.3. Using AT command

Only to use with Type 1SC
To set CATM : at at%setacfg=radiom.config.preferred_rat_list,"CATM"
To set NBIOT : at at%setacfg=radiom.config.preferred_rat_list,"NBIOT"

1.2.9.2. automatic techno selection

In this mode, you let the modem choose techno/bearer to use toward it's own policy. Available only for BG96 modem

1.2.9.2.1. Using console command

Only to use with BG96

  1. Open a serial terminal and set the right parameters.
  2. Remove the trace display by typing the command :
  • trace off

Then type the Cellular Service command to set the wanted techno/bearer to be used :
cst techno off

1.2.9.2.2. Using control API

Only to use with BG96
Code example to set techno/bearer to use :

cellular_operator_selection_t my_operator_selection;
/* Get data */
cellular_get_operator(&my_operator_selection);
/* Set access techno is not present : automatic mode use*/
my_operator_selection.access_techno_present = CA_ACT_NOT_PRESENT;
/* Save data */
(void)cellular_set_operator(&my_operator_selection);

1.2.10. How to configure the modem radio band

  1. Open a Tera Term serial terminal and set the right parameters
  2. Start the firmware and select Modem power on option in the boot menu
  3. Type the modem command to get help of the modem configuration.

1.2.11. How to add FreeRTOS support to Azure RTOS package

The detailed information provided in this article allows users to add FreeRTOS support over Azure RTOS.
The main idea is to download an add-on and to apply it over XCC (based on Azure RTOS).
It adds all the required files to get Cellular using FreeRTOS / LwIP up & running.
Both X-CUBE-CELLULAR, delivery are based on Azure RTOS and XCC_FreeRTOS, add-on to add FreeRTOS support are available on www.st.com.
Once X-CUBE-CELLULAR is extracted, only Azure RTOS is available. To add new needed middlewares (FreeRTOS and LwIP) and FreeRTOS examples, users must extract XCC_FreeRTOS over X-CUBE-CELLULAR.
Thus, new middlewares, BSP for B-L462I-CELL1 board and FreeRTOS based "Demonstrations" are added. All Azure RTOS parts remain available.

1.2.12. How to setup iSIM for GMS01Q

There are two STMod+ boards with Monarch Sequans modem, one with GM01Q (the default in the X-CUBE-CELLULAR delivery) and one with GMS01Q.
The GMS01Q is a GM01Q with an iSIM.
To use GMS01Q, a small modification must be done in the source code, .h file.
The file to update is Projects\<board>\Demonstrations\<Demonstration name>\STM32_Cellular\Config\plf_cellular_config.h
With <board> = B-L4S5I-IOT01A or 32L496GDISCOVERY or STWINKT1 or ... according to the STM32 board with which you are working on.
With <Demonstration name> = Cellular, CellularIoT, Nx_TCP_Echo_Client ...
Patch the file as below and rebuild the binary.

  1. define PLF_CELLULAR_SIM_SLOT_NB (1U)
  2. define PLF_CELLULAR_SIM_INDEX_0 (2U) /*!< SIM SLOT 0 interface */
  3. define PLF_CELLULAR_SIM_INDEX_1 (99U) /*!< SIM SLOT 1 interface */
  4. define PLF_CELLULAR_SIM_INDEX_2 (99U) /*!< SIM SLOT 2 interface */
  5. define PLF_CELLULAR_SIM_INDEX_3 (99U) /*!< SIM SLOT 3 interface */

1.2.13. How to manage EEPROM on B-L462E-CELL1

The EEPROM is filled at factory process with some data, the main information is extracted from the modem, however a PSK automatically generated is also added. Thus, in case of needs to write the EEPROM, backup the parameters to restore them, specially the PSK first. Below is the EEPROM first bytes structure (length is 274 bytes):

// Global parameters
uint8_t Board[20]; //={"B-L462-CELL1"};
uint8_t Version [20]; // Version FG displayed on sticker {"EBL462ECELL1$KU4"};
uint8_t Revision [20]; // Revision displayed on sticker {"MB1508-L462E-C01"};
uint8_t Serial[20]; // Serial number displayed on sticker {"K192700022"};
// Modem Related
uint8_t BaseBoard[20];//={"LBAD0XX1SE"};
uint8_t Brand[16]; //={"MURATA"};
uint8_t Model[16]; //={"ALT125X"}; // returned by AT+GMM
uint8_t ModemSoftRevNumber[30]; //={"RK_02_01_02_00_57"} // returned by AT+GMR
uint8_t IMEI[16]; // returned by AT+CGSN
uint8_t PSK[20]; // Pre shared Key - randomly generated
// eSim related
uint8_t eICCID[24]; // EMBEDDED SIM CHIP, returned by AT%CCID
uint8_t eIMSI[16]; // EMBEDDED SIM CHIP, returned by AT+CIMI
uint8_t EID [32];   ///262//270
uint32_t signature;  ///266//274

Note that it is easy to read and write (using HAL services HAL_I2C_Mem_Read / HAL_I2C_Mem_Write) the EEPROM after calling the I2C initialization.

1.2.14. How to activate the eSIM with B-L462E-CELL1

Pre-requisite 1: check that the country where the board must run is covered by Truphone Network footprint. You can find the list of covered countries at Truphone Web[1].
Pre-requisite 2: activate the Truphone eSIM as described in the board user manual. To enable the eSIM with Truphone connectivity, users can follow the instructions provided in the B-L462E-CELL1[2] in the chapter eSIM.
This video[3] also shows the available user experience .

Depending on the version of the modem FW configuration, the network operator could be set to "DEFAULT". In some countries, the IMSI switch may not work as expected. To solve the issue, the modem firmware must be configured to use TRUPHONE as network operator. To do this, the end-user is asked to enter the at commands manually as follows:

Step 1: download the latest version of the X-CUBE-CELLULAR

Step 2: connect the device to a PC using the STLINK USB Port and program the binary \Projects\B-L462E-CELL1\Demonstrations\CellularIoT\Binaries\xxx.bin to the board. The procedure to program the binary is defined in this How To article.

Step 3: start a terminal application on the PC in order to enter the commands to configure the device and also to read logs and traces.

Step 4: When the board boots up, check that the SIM card is successfully identified and CCID is automatically read and displayed on the terminal.

 AT%CCID<CR>
   <CR><LF>
   %CCID: 8944477700000005819<CR><LF>
   <CR><LF>
   OK<CR><LF>


Step 5: press the "return" key, the user sees prompt on the terminal as shown below $>

Step 6: enter the command "at at%nwoper?", the user sees the result below:

 $>at at%nwoper?
   ATParser:*** SEND (size=11) ***
   at%nwoper?<CR>
   <CR><LF>
   %NWOPER: "TRUPHONE"<CR><LF>
   <CR><LF>
   <CR><LF>
   OK<CR><LF>
 $>


Step 7: If DEFAULT is shown instead of TRUPHONE then enter the AT command at at%nwoper="TRUPHONE" to force the modem firmware to use TRUPHONE configuration. This setting is persistent as it is stored in the modem flash memory.

 $>at at%nwoper="TRUPHONE"
   ATParser:*** SEND (size=21) ***
   at%nwoper="TRUPHONE"<CR>
   <CR><LF>
   OK<CR><LF>



Step 8: verify that TRUPHONE is correctly written into the modem flash memory by reading the existing nwoper by entering again at at%nwoper. Verify that TRUPHONE is displayed.

Step 9: now Truphone as NWOPERator is set, the user can reboot the board by pressing the reset button (black button) or enter the reset command at the prompt

Step 10: after the restart, let the modem starts normally. It automatically searches network and register to Truphone network. It may take up to 10 to 15 minutes to find a network and register.

Step 11: if the registration is still not successful after the latter step, record the trace from the terminal and share it with ST support team via ST community.

1.2.15. How to use another modem

The aim is to change the current modem in an existing project with a new modem.
For example to replace BG96 with GM01Q in an IAR (EWARM) L496 project.
Custom files of all the supported modem are available in the package.

  • Start IDE by double cliking on .eww (project file)
  • In Drivers/BSP/X_STMOD_PLUS_MODEMS, remove BG96 group
BG96 Group in EWARM/IAR
  • In Drivers/BSP/X_STMOD_PLUS_MODEMS, create a new group : GM01Q
GM01Q Group in EWARM/IAR
  • Select files for this newly created group, from Drivers/BSP/X_STMOD_PLUS_MODEMS/MONARCH/AT_modem_monarch/Src
  • Change path in project options [ Options then C/C++ Compiler]
    • Replace $PROJ_DIR$\..\..\..\..\..\Drivers\BSP\X_STMOD_PLUS_MODEMS\BG96\AT_modem_bg96\Inc
    • By $PROJ_DIR$\..\..\..\..\..\Drivers\BSP\X_STMOD_PLUS_MODEMS\MONARCH\AT_modem_monarch\Inc

1.2.16. How to low power / power off device properly

To switch off device for low power you need first to power off the modem properly, and then change STM32 state to shutdown or standby.

step 1 : ask the modem to power off : make a call to cellular_modem_stop()
Beware that cellular_modem_stop() is asynchronous. The function will return immediately, but the modem won't be powered off at that time. You need to pool modem state or register a callback to be notified of modem state change. Modem state value when modem is off is : CA_MODEM_POWER_OFF. You need to check this value to be sure modem is off before to continue to power off the device.

step 2 - pooling case : Loop reading modem state value using cellular_get_cellular_info, wait a bit (200 ms for example) till modem_state is CA_MODEM_POWER_OFF

/* reads cellular info from cellular API */
cellular_get_cellular_info(&my_cellular_info);
/* wait a bit */
(void)rtosalDelay(200);  /* waiting for 10ms */

step 2 - callback case : register a callback using cellular_info_cb_registration, and, within callback function code, check the value of the field modem_state.

if (cellular_info_cb_registration(app_cellular_info_status_cb, (void *) NULL) != CELLULAR_SUCCESS)
{
   /* Manage error */
}

In callback function app_cellular_info_status_cb, check that modem_state is CA_MODEM_POWER_OFF. In that case, continue treatment in your app.

step 3 : Put STM32 in low consumption mode

1.3. How To applicable only to FreeRTOS version

1.3.1. How to change default behavior of the application

The delivered firmware starts Cellular_App application. The default behavior is that a single echo service is started. This service sends data to an Echo server which replies with the same data. Once an answer from Echo server is received, the echo client resends data to Echo server after a delay. A second instance of the Echo client can be started to test multiple socket configurations. The address of the Echo server can be changed. It is also possible to send a ping command while the Echo client is running. It is also possible to start a perfomance test. To see all the possibilities, type the following in the Tera Term: cellularapp help

1.3.2. How to implement your own FreeRTOS application

The easiest way to implement your application is to adapt the Cellular_App content to your requirements.
Another method is to add a new entry task for this new application and start it from main (do not forget to remove entry task start for Cellular_App to avoid conflicts).
Your application is in charge of initializing and starting the Cellular middleware (for this, copy and paste the required lines from Cellular_App).

1.3.3. How to select components to include in the cellular firmware

The firmware can be customized by adding or removing components or applications.
The configuration files under Projects\<board>\Demonstrations\Cellular\STM32_Cellular\Config allows the selection of the component to include in the cellular firmware by define/undefine environment variables (if using CellularApp overwrites configuration by changing the define in plf_cellular_app_config.h).
Refer to the user manual (UM2426) on how to customize the software.

1.3.4. How to regenerate from an existing ioc

1.3.4.1. Regenerate from an existing ioc L4 EWARM for FreeRTOS

Create empty directory (named e.g. Cellular_ioc) in parallel of Cellular directory.

Copy from Cellular and paste to Cellular_ioc the following directory and files:

  • STM32_Cellular\
  • Cellular.ioc
  • .extSettings

Start CubeMX with .ioc file
Select EWARM (default version is 8.32) from "Project Manager" window
Generate the code.


AFTER CubeMX Generation, add following patches in generated files (original files can be used to copy / paste patches):

FreeRTOSConfig.h:

Add plf_thread_config.h

/* USER CODE BEGIN Includes */
/* Section where include file can be added */ 
#include "plf_thread_config.h"
/* USER CODE END Includes */

Replace in the following #defines numeric values by #defines

#define configTOTAL_HEAP_SIZEt            ((size_t)TOTAL_HEAP_SIZE)
#define configTIMER_TASK_STACK_DEPTH       FREERTOS_TIMER_THREAD_STACK_SIZE

freertos.c:

Add calls to cellular and remove default task creation (it is useless and impossible to remove in CubeMX)

/* Private includes --------------------------------- */
/* USER CODE BEGIN Includes */
extern void application_init(void);
extern void application_start(void);
/* USER CODE END Includes */
/* definitions and of defaultTask */
//osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
//defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* USER CODE BEGIN RTOS_THREAD */
/* add threads, ...*/
application_init();
application_start();
/* USER CODE END RTOS_THREAD */

main.c

Add needed includes for cellular

/* USER CODE BEGIN Includes */
#include <stdio.h>
#if defined(__ICCARM__)
#include <LowLevelIOInterface.h>
#endif /* __ICCARM__ */

/* CELLULAR BEGIN Includes */
#include "plf_config.h"
#include "ipc_uart.h"
#include "at_modem_api.h"
#if (USE_CMD_CONSOLE == 1)
#include "cmd.h"
#endif  /* (USE_CMD_CONSOLE == 1) */
/* CELLULAR END Includes */
/* USER CODE END Includes */

Add macros for printf

/* USER CODE BEGIN PFP */
#if defined(__ICCARM__)
/* New definition from EWARM V9, compatible with EWARM8 */
int iar_fputc(int ch);
#define PUTCHAR_PROTOTYPE int iar_fputc(int ch)
#elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION)
/* ARM Compiler 5/6*/
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#elif defined(__GNUC__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif /* __ICCARM__ */
/* USER CODE END PFP */

Add the weak callback overload (previously in board_interrupts.c)

/* Private user code --------------------------------- */
/* USER CODE BEGIN 0 */
/* CELLULAR BEGIN Callbacks */
/**
  * @brief  Callback to treat EXTI
  * @param  GPIO_Pin - GPIO Pin value
  * @retval -
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == MODEM_RING_PIN)
  {
    GPIO_PinState gstate = HAL_GPIO_ReadPin(MODEM_RING_GPIO_PORT, MODEM_RING_PIN);
    atcc_hw_event(DEVTYPE_MODEM_CELLULAR, HWEVT_MODEM_RING, gstate);
  }
}

/**
  * @brief  Callback to treat UART Rx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_RxCpltCallback(huart);
  }
#if (USE_CMD_CONSOLE == 1)
  else if (huart->Instance == TRACE_INTERFACE_INSTANCE)
  {
    CMD_RxCpltCallback(huart);
  }
#endif  /* USE_CMD_CONSOLE */
  else
  {
    __NOP(); /* Nothing to do */
  }
}

/**
  * @brief  Callback to treat UART Tx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_TxCpltCallback(huart);
  }
}

/**
  * @brief  Callback to treat Error
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_ErrorCallback(huart);
  }
}
/* CELLULAR END Callbacks */
/* USER CODE END 0 */
/* USER CODE BEGIN 4 */
#if defined(__ICCARM__)
size_t __write(int file, unsigned char const *ptr, size_t len)
{
  size_t idx;
  unsigned char const *pdata = ptr;

  for (idx = 0; idx < len; idx++)
  {
    iar_fputc((int)*pdata);
    pdata++;
  }
  return len;
}
#endif /* __ICCARM__ */

/**
  * @brief  Retargets the C library printf function to the USART.
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of putchar here */
  /* e.g. write a character to the USART3 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}
/* USER CODE END 4 */

End of macros for printf


Depending on the board used, the file generated to manage Interrupt Service Routines has to be modified:

For L496-Disco add EXTI2_IRQn


stm32l4xx_it.h

/* USER CODE BEGIN EFP */
void EXTI2_IRQHandler(void);
/* USER CODE END EFP */

stm32l4xx_it.c

/* USER CODE BEGIN 1 */
/**
  * @brief This function handles EXTI line2 interrupt.
  *        This function is not generated by the .ioc file provided because MODEM_RING is initialized
  *        as an input PIN by default. But if modem Low Power is activated, this PIN is reconfigured
  *        as an EXTI PIN and, thus, corresponding IRQHandler has to be defined.  
  */
void EXTI2_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(MODEM_RING_Pin);
}
/* USER CODE END 1 */

For L462 board add EXTI15_10_IRQn


stm32l4xx_it.h

/* USER CODE BEGIN EFP */
void EXTI15_10_IRQHandler(void);
/* USER CODE END EFP */

stm32l4xx_it.c

/* USER CODE BEGIN 1 */
/**
  * @brief This function handles EXTI line[15:10] interrupts.
  *        This function is not generated by the .ioc file provided because MODEM_RING is initialized
  *        as an input PIN by default. But if modem Low Power is activated, this PIN is reconfigured
  *        as an EXTI PIN and, thus, corresponding IRQHandler has to be defined.  
  */
void EXTI15_10_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(MODEM_RING_Pin);
}
/* USER CODE END 1 */


Do the following for IDE (EWARM) configuration Options (Options for node "Cellular):

  • General Options->Library Options 1
    • Printf formater: Select "Auto" and check the "Enable multiple support" box
    • Scanf formater: Select "Auto" and check the "Enable multiple support" box
  • C/C++ Compiler->Language 1
    • C dialect: Check the "C++ inline semantics" box
  • Assembler->Preprocessor
    • Add include path for Assembler files in "Additional include directories" field (portasm.s):
$PROJ_DIR$/../STM32_Cellular/Target
  • Output Converter
    • Output format: Select "Raw binary"
    • Output file: Uncheck the "Override default" box
1.3.4.2. Regenerate from an existing ioc L4 MDK-ARM for FreeRTOS

Create empty directory (named e.g. Cellular_ioc) in parallel of Cellular directory.

Copy from Cellular and paste to Cellular_ioc the following directorie and files:

  • STM32_Cellular\
  • Cellular.ioc
  • .extSettings

Start CubeMX with .ioc file

  • Select MDK-ARM (default version is 5.32)
  • Generate the code

AFTER CubeMX Generation:

  • Add the relevant patches (same than for EWARM - original files can be used to copy / paste patches)
  • Do the following for IDE (MDK-ARM) configuration Options (Options for Target 'Cellular'):
    • C/C++->Preprocessor Symbols->Define:
      • Replace "" with <> in the text to be able to build (it is a CubeMX issue)
    • Target->Code Generation:
      • Check the "Use MicroLIB" box to be able to use printf traces
    • User->After Build/Rebuild
      • Check the "Run #1" box
      • Put in the corresponding "User Command" field the following command "fromelf #L --bin --output=$L@L.bin"
1.3.4.3. Regenerate from an existing ioc L4 CubeIDE for FreeRTOS

Create empty directory (named e.g. Cellular_ioc) in parallel of Cellular directory.

Copy from Cellular and paste to Cellular_ioc the following directorie and files:

  • STM32_Cellular\
  • Cellular.ioc
  • .extSettings

Start CubeMX with .ioc file

  • Select CubeIDE (no version to select but uncheck "Generate Under Root" box)
  • Generate the code (To the question related to USE_NEWLIB_REENTRANT, select Yes to continue generation)

AFTER CubeMX Generation:

  • Add the relevant patches (same than for EWARM - original files can be used to copy / paste patches)
  • Do the following for IDE (CubeIDE) configuration Options (Properties for Cellular):
    • C/C++ Build->Settings->MCU Post build outputs
      • Check the "Convert to binary file" box (To be done both for Configuration "Debug" and "Release")
1.3.4.4. Regenerate from an existing ioc U5 for FreeRTOS

STM32CubeMX generation:
Note: For U5, FreeRTOS is brought from .extSettings (not from CubeMX like L4 projects) Create empty directory (named e.g. Cellular_ioc) in parallel of Cellular directory. Copy from Cellular and paste to Cellular_ioc the following directorie and files:

  • STM32_Cellular\
  • Cellular.ioc
  • .extSettings_xxx (xxx being either EWARM, MDK-ARM or CubeIDE according to the targetted IDE)

Rename .extSettings_xxx to .extSettings

Start STM32CubeMX by double clicking on Cellular.ioc In STM32CubeMx:

  • Select IDE from "Project Manager" sheet
  • Generate the code by clicking on "GENERATE CODE" button (for STM32CubeIDE "Generate Under Root" box must be unchecked)

Generated files update:
AFTER CubeMX Generation:

  • Add relevant patches in file main.c (same than for L4 EWARM) then add the one hereafter specific to U5 (original file can be used to copy/paste patches)
  /* USER CODE BEGIN 2 */

  /* Call init function for freertos objects (in freertos.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();
  /* USER CODE END 2 */
  • Add freertos.c and FreeRTOSConfig.h respectively to generated directories Core\Src and Core\Inc

IDE update: Add the freertos.c file in IDE

  • Application/User/Core->Add-Add Files for EWARM
  • Application/User/Core->Add Existing Files to Groupe 'Application/User/Core' for MDK-ARM
  • Link drag & drop and 'Link to files' for CubeIDE

IAR EWARM configurations options

  • General Options->Library Options 1
    • Printf formater: Select "Auto" and check the "Enable multibyte support" box
    • Scanf formater: Select "Auto" and check the "Enable multiple support" box
  • C/C++ Compiler->Language 1
    • C dialect: Check the "C++ inline semantics" box
  • Assembler->Preprocessor
    • Add include path for Assembler files in "Additional include directories" field (portasm.s):
$PROJ_DIR$/../Core/Inc
$PROJ_DIR$/../STM32_Cellular/Target
  • Output Converter
    • Output format: Select "Raw binary"
    • Output file: Uncheck the "Override default" box

Keil MDK-ARM configurations options

  • Target->Code Generation:
    • Check the "Use MicroLIB" box
  • User->After Build/Rebuild
    • Check the "Run #1" box
    • Put in the corresponding "User Command" field the following command "fromelf #L --bin --output=$L@L.bin" (without double-quote)
  • C/C++->Language/Code Generation->Optimization
    • Select "-Oz image size"
    • Misc Control: Put "-Wno-format" (without double-quote) in the field

STM32CubeIDE configurations options

  • C/C++ Build->Settings->MCU Post build outputs
    • Check the "Convert to binary file" box (To be done both for Configuration "Debug" and "Release")

1.4. How To applicable only to Azure RTOS version

1.4.1. How to regenerate from an existing ioc

1.4.1.1. Regenerate from an existing ioc U5 for AzureRTOS - Nx_TCP_Echo_Client
1.4.1.1.1. New environment generation using STM32CubeMx and provided .ioc

Create empty directory (named e.g. Nx_TCP_Echo_Client_ioc) in parallel of Nx_TCP_Echo_Client directory. Copy the following files from the old environment to the brand new one:

  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client\NetXDuo\App\tcp_echo_client.c
  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client\NetXDuo\Target
  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client\.extSettings
  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client\Nx_TCP_Echo_Client.ioc

Double click on "Nx_TCP_Echo_Client.ioc" to start the load of .ioc in STM32CubeMx
In STM32CubeMx:

  • Select the relevant "Toolchain/IDE" (EWARM, MDK-ARM or STM32CubeIDE) from "Project Manager" sheet
  • Generate the code by clicking on "GENERATE CODE" button (for STM32CubeIDE "Generate Under Root" box must be unchecked)
1.4.1.1.2. Update of generated .h and .c files


AFTER CubeMX Generation, add following patches in generated files (original files can be used to copy / paste patches):

Core\Inc\stm32u5xx_it.h

/* USER CODE BEGIN EFP */
void EXTI6_IRQHandler(void);
/* USER CODE END EFP */

Core\Inc\tx_user.h

/* USER CODE BEGIN 2 */
/* Define the CMSIS RTOS2 stack size. */
#define RTOS2_BYTE_POOL_STACK_SIZE  (10*1024)
/* Define the CMSIS RTOS2 heap size. */
#define RTOS2_BYTE_POOL_HEAP_SIZE   (4*1024)
/* Define the type of memory allocation. */
#define USE_DYNAMIC_MEMORY_ALLOCATION
/* USER CODE END 2 */

Core\Src\app_threadx.c

/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */
  /* USER CODE BEGIN App_ThreadX_MEM_POOL */
  TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr;
  (void)byte_pool;
  /* USER CODE END App_ThreadX_MEM_POOL */
  /* USER CODE BEGIN  Before_Kernel_Start */
  osKernelInitialize();
  osKernelStart();
#if 0 /* Ensure generated code not called */
  /* USER CODE END  Before_Kernel_Start */
  /* USER CODE BEGIN  Kernel_Start_Error */
#endif /* Ensure generated code not called */
  /* USER CODE END  Kernel_Start_Error */

Core\Src\main.c

/* USER CODE BEGIN Includes */
#if defined(__ICCARM__)
#include <LowLevelIOInterface.h>
#endif /* __ICCARM__ */
#include <stdio.h>

/* CELLULAR BEGIN Includes */
#include "plf_config.h"
#include "ipc_uart.h"
#include "at_modem_api.h"
#if (USE_CMD_CONSOLE == 1)
#include "cmd.h"
#endif  /* (USE_CMD_CONSOLE == 1) */
/* CELLULAR END Includes */
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
/* CELLULAR BEGIN Callbacks */
/**
  * @brief  Callback to treat EXTI
  * @param  GPIO_Pin - GPIO Pin value
  * @retval -
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == MODEM_RING_PIN)
  {
    GPIO_PinState gstate = HAL_GPIO_ReadPin(MODEM_RING_GPIO_PORT, MODEM_RING_PIN);
    atcc_hw_event(DEVTYPE_MODEM_CELLULAR, HWEVT_MODEM_RING, gstate);
  }
}

/**
  * @brief  Callback to treat UART Rx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_RxCpltCallback(huart);
  }
#if (USE_CMD_CONSOLE == 1)
  else if (huart->Instance == TRACE_INTERFACE_INSTANCE)
  {
    CMD_RxCpltCallback(huart);
  }
#endif  /* USE_CMD_CONSOLE */
  else
  {
    __NOP(); /* Nothing to do */
  }
}

/**
  * @brief  Callback to treat UART Tx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_TxCpltCallback(huart);
  }
}

/**
  * @brief  Callback to treat Error
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_ErrorCallback(huart);
  }
}
/* CELLULAR END Callbacks */
/* USER CODE END 0 */
/* USER CODE BEGIN 4 */
#if defined(__ICCARM__)
/* New definition from EWARM V9, compatible with EWARM8 */
int iar_fputc(int ch);
#define PUTCHAR_PROTOTYPE int iar_fputc(int ch)
#elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION)
/* ARM Compiler 5/6 */
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#elif defined(__GNUC__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif /* __ICCARM__ */

#if defined(__ICCARM__)
size_t __write(int file, unsigned char const *ptr, size_t len)
{
  size_t idx;
  unsigned char const *pdata = ptr;

  for (idx = 0; idx < len; idx++)
  {
    iar_fputc((int)*pdata);
    pdata++;
  }
  return len;
}
#endif /* __ICCARM__ */

/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART1 and Loop until the end of transmission */
  HAL_UART_Transmit(&TRACE_INTERFACE_UART_HANDLE, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}

/* MX_RNG_Init() MUST be called before usage of this function */
int hardware_rand(void)
{
  uint32_t random; /* return value */
  if (HAL_RNG_GenerateRandomNumber(&hrng, &random) != HAL_OK)
  {
    random = (uint32_t)rand();
  }
  return (random);
}
/* USER CODE END 4 */

Core\Src\stm32u5xx_it.c

/* USER CODE BEGIN 1 */
/**
  * @brief This function handles EXTI Line6 interrupt.
  *        This function is not generated by the .ioc file provided because MODEM_RING is initialized
  *        as an input PIN by default. But if modem Low Power is activated, this PIN is reconfigured
  *        as an EXTI PIN and, thus, corresponding IRQHandler has to be defined.    
  */
void EXTI6_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(MODEM_RING_Pin);
}
/* USER CODE END 1 */

NetXDuo\App\nx_user.h

/* USER CODE BEGIN 2 */
extern int hardware_rand(void);
#define NX_RAND hardware_rand
/* USER CODE END 2 */

NetXDuo\App\app_netxduo.h

/* USER CODE BEGIN EC */
#define DEFAULT_MEMORY_SIZE         1024
#define DEFAULT_PRIORITY            5U

#define NULL_ADDRESS                IP_ADDRESS(0, 0, 0, 0)

#define PACKET_COUNT                30
#define PAYLOAD_SIZE                1544
#define NX_PACKET_POOL_SIZE         ((PAYLOAD_SIZE + sizeof(NX_PACKET)) * PACKET_COUNT)
/* USER CODE END EC */
/* USER CODE BEGIN EM */
#define PRINT_IP_ADDRESS(string, addr)     do { \
                                                printf(string " %lu.%lu.%lu.%lu \r\n", \
                                                ((addr) >> 24) & 0xff,                 \
                                                ((addr) >> 16) & 0xff,                 \
                                                ((addr) >> 8) & 0xff,                  \
                                                ((addr) & 0xff));                      \
                                              } while(0)
/* USER CODE END EM */

NetXDuo\App\app_netxduo.c

/* USER CODE BEGIN Includes */
#include "app_azure_rtos.h"
#include "main.h"
#include "nx_ip.h"
#include "nxd_dns.h"
#include "nxd_sntp_client.h"

#include "nx_driver_stm32_cellular.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PTD */
TX_THREAD AppMainThread;

TX_SEMAPHORE IpAddrSemaphore; /* release when an IpAddr is obtained */

NX_PACKET_POOL        AppPool;
NX_IP                 IpInstance;
static NX_DNS         DnsClient;
static NX_SNTP_CLIENT SntpClient;

ULONG   IpAddress;
ULONG   NetMask;
/* USER CODE END PTD */
/* USER CODE BEGIN PD */
#define DEFAULT_MAIN_PRIORITY       10
#define THREAD_MEMORY_SIZE          (2 * DEFAULT_MEMORY_SIZE)

/* Default time. GMT: Friday, Jan 1, 2022 12:00:00 AM. Epoch timestamp: 1640995200.  */
#ifndef SYSTEM_TIME
#define SYSTEM_TIME              1640995200
#endif /* SYSTEM_TIME  */

/* 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) */
#define EPOCH_TIME_DIFF          2208988800

#define SNTP_SYNC_MAX            (uint32_t)30
#define SNTP_UPDATE_MAX          (uint32_t)10
#define SNTP_UPDATE_INTERVAL     (NX_IP_PERIODIC_RATE / 2)

#define IP_ADDR_TIMEOUT          (50*NX_IP_PERIODIC_RATE)

#define SAMPLE_IP_THREAD_PRIORITY     DEFAULT_PRIORITY
#define SAMPLE_IP_THREAD_STACK_SIZE   (2 * DEFAULT_MEMORY_SIZE)

#define SAMPLE_NETWORK_CONFIGURE_START          nx_driver_stm32_cellular_configure
#define SAMPLE_NETWORK_DRIVER                   nx_driver_stm32_cellular
/* USER CODE END PD */
/* USER CODE BEGIN PV */
/* System clock time for UTC.  */
static ULONG            unix_time_base;

static const char *sntp_servers[] =
{
  "0.pool.ntp.org",
  "1.pool.ntp.org",
  "2.pool.ntp.org",
  "3.pool.ntp.org"
};

static UINT sntp_server_index;

static ULONG dns_server_address[3];
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
static VOID App_Main_Thread_Entry(ULONG thread_input);

static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);

static UINT dns_create(NX_DNS *dns_ptr);
static UINT unix_time_get(ULONG *unix_time);
static UINT sntp_time_sync_internal(ULONG sntp_server_address);
static UINT sntp_time_sync(VOID);

/* Include the sample interface. */
extern VOID sample_init(void);
extern VOID sample_start(NX_IP *ip_ptr, NX_PACKET_POOL *pool_ptr, NX_DNS *dns_ptr,
                         UINT(*unix_time_callback)(ULONG *unix_time));
/* USER CODE END PFP */
   /* USER CODE BEGIN App_NetXDuo_MEM_POOL */
  //(void)byte_pool;
  /* USER CODE END App_NetXDuo_MEM_POOL */
  /* USER CODE BEGIN MX_NetXDuo_Init */
  printf("Nx_TCP_Echo_Client application started.\n");

  CHAR *pointer;

  /* Allocate the memory for packet_pool.  */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_PACKET_POOL_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (packet_pool) fail\r\n");
    return TX_POOL_ERROR;
  }

  /* Create the Packet pool to be used for packet allocation */
  ret = nx_packet_pool_create(&AppPool, "Main Packet Pool", PAYLOAD_SIZE, pointer, NX_PACKET_POOL_SIZE);

  if (ret != NX_SUCCESS)
  {
    printf("nx_packet_pool_create fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Allocate the memory for Ip_Instance */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, SAMPLE_IP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (Ip_Instance) fail\r\n");
    return TX_POOL_ERROR;
  }

  printf("Create IP instance...\r\n");

  /* Create the main NX_IP instance */
  ret = nx_ip_create(&IpInstance, "Main Ip instance", NULL_ADDRESS, NULL_ADDRESS, &AppPool, SAMPLE_NETWORK_DRIVER,
                     pointer, SAMPLE_IP_THREAD_STACK_SIZE, SAMPLE_IP_THREAD_PRIORITY);

  if (ret != NX_SUCCESS)
  {
    printf("nx_ip_create fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the ICMP */
  ret = nx_icmp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_icmp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the UDP protocol required for DHCP communication */
  ret = nx_udp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_udp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the TCP protocol */
  ret = nx_tcp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_tcp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Initialize sample */
  sample_init();

  /* Allocate the memory for main thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, THREAD_MEMORY_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (main thread) fail\r\n");
    return TX_POOL_ERROR;
  }

  /* Create the main thread */
  ret = tx_thread_create(&AppMainThread, "App Main thread", App_Main_Thread_Entry, 0, pointer, THREAD_MEMORY_SIZE,
                         DEFAULT_MAIN_PRIORITY, DEFAULT_MAIN_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);

  if (ret != TX_SUCCESS)
  {
    printf("tx_thread_create (App Main thread) fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Create IpAddr semaphore */
  tx_semaphore_create(&IpAddrSemaphore, "IpAddr Semaphore", 0);
  /* USER CODE END MX_NetXDuo_Init */
/* USER CODE BEGIN 1 */
/**
  * @brief  Main thread entry.
  * @param thread_input: ULONG user argument (unused argument)
  * @retval none
  */
static VOID App_Main_Thread_Entry(ULONG thread_input)
{
  UINT ret = NX_SUCCESS;

  NX_PARAMETER_NOT_USED(thread_input);

  printf("Get IP Address...\r\n");

  ret = nx_ip_address_change_notify(&IpInstance, ip_address_change_notify_callback, NULL);
  if (ret != NX_SUCCESS)
  {
    printf("nx_ip_address_change_notify fail: %u\r\n", ret);
    Error_Handler();
  }

  SAMPLE_NETWORK_CONFIGURE_START(&IpInstance, &dns_server_address[0]);

  /* wait until an IP address is ready */
  if (tx_semaphore_get(&IpAddrSemaphore, IP_ADDR_TIMEOUT) != TX_SUCCESS)
  {
    printf("IP address timeout fail\r\n");
    Error_Handler();
  }

  ret = nx_ip_address_get(&IpInstance, &IpAddress, &NetMask);

  if (ret != TX_SUCCESS)
  {
    printf("nx_ip_address_get fail: %u\r\n", ret);
    Error_Handler();
  }

  PRINT_IP_ADDRESS("STM32 IP Address: ", IpAddress);

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

  if (ret != NX_SUCCESS)
  {
    printf("dns_create fail: %u\r\n", ret);
    Error_Handler();
  }

  /* Sync up time by SNTP at start up. */
  ret = sntp_time_sync();

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("SNTP Time Sync failed.\r\n");
    Error_Handler();
  }

  /* Start sample. */
  sample_start(&IpInstance, &AppPool, &DnsClient, unix_time_get);

  /* this thread is not needed any more, we relinquish it */
  tx_thread_relinquish();
}

/**
  * @brief ip address change callback.
  * @param ip_instance: NX_IP instance
  * @param ptr: user data
  * @retval none
  */
static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr)
{
  /* release the semaphore as soon as an IP address is available */
  tx_semaphore_put(&IpAddrSemaphore);
}

/**
  * @brief  DNS Create Function.
  * @param dns_ptr
  * @retval ret
  */
static UINT dns_create(NX_DNS *dns_ptr)
{
  UINT status;

  /* Create a DNS instance for the Client */
  status = nx_dns_create(dns_ptr, &IpInstance, (UCHAR *)"DNS Client");

  /* Is the DNS client configured for the host application to create the packet pool?  */
#ifdef NX_DNS_CLIENT_USER_CREATE_PACKET_POOL
  if (status == NX_SUCCESS)
  {
    /* Yes, use the packet pool created above which has appropriate payload size
       for DNS messages.  */
    status = nx_dns_packet_pool_set(dns_ptr, IpInstance.nx_ip_default_packet_pool);
    if (status != NX_SUCCESS)
    {
      nx_dns_delete(dns_ptr);
    }
  }
#endif /* NX_DNS_CLIENT_USER_CREATE_PACKET_POOL */

#ifdef USER_DNS_ADDRESS
  dns_server_address[0] = USER_DNS_ADDRESS;
#endif /* USER_DNS_ADDRESS */

  if (status != NX_SUCCESS)
  {
    printf("nx_dns_create fail: %u\r\n", status);
    Error_Handler();
  }

  /* Initialize DNS instance with a DNS server address */
  status = nx_dns_server_add(dns_ptr, dns_server_address[0]);
  if (status != NX_SUCCESS)
  {
    printf("nx_dns_server_add fail: %u\r\n", status);
    Error_Handler();
  }

  PRINT_IP_ADDRESS("DNS Server address:", dns_server_address[0]);

  return (status);
}


static UINT unix_time_get(ULONG *unix_time)
{
  /* Return number of seconds since Unix Epoch (1/1/1970 00:00:00).  */
  *unix_time =  unix_time_base + (tx_time_get() / TX_TIMER_TICKS_PER_SECOND);

  return (NX_SUCCESS);
}

/* Sync up the local time with a known SNTP server. */
static UINT sntp_time_sync_internal(ULONG sntp_server_address)
{
  UINT ret;

  /* Create the SNTP Client to run in broadcast mode.. */
  ret = nx_sntp_client_create(&SntpClient, &IpInstance, 0, &AppPool,
                              NX_NULL,
                              NX_NULL,
                              NX_NULL /* no random_number_generator callback */);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_create fail: %u\r\n", ret);
    return ret;
  }

  PRINT_IP_ADDRESS("Trying SNTP server:", sntp_server_address);

  /* Use the IPv4 service to initialize the Client and set the IPv4 SNTP server. */
  ret = nx_sntp_client_initialize_unicast(&SntpClient, sntp_server_address);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_initialize_unicast fail: %u\r\n", ret);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Set local time to 0 */
  ret = nx_sntp_client_set_local_time(&SntpClient, 0, 0);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_set_local_time fail: %u\r\n", ret);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Run Unicast client */
  ret = nx_sntp_client_run_unicast(&SntpClient);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_run_unicast fail: %u\r\n", ret);
    nx_sntp_client_stop(&SntpClient);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Wait till updates are received */
  for (uint32_t i = 0; i < SNTP_UPDATE_MAX; i++)
  {
    UINT server_status;

    /* First verify we have a valid SNTP service running. */
    ret = nx_sntp_client_receiving_updates(&SntpClient, &server_status);

    /* Check status.  */
    if ((ret == NX_SUCCESS) && (server_status == NX_TRUE))
    {
      /* Server status is good. Now get the Client local time. */
      ULONG sntp_seconds;
      ULONG sntp_fraction;
      ULONG system_time_in_seconds;

      /* Get the local time. */
      ret = nx_sntp_client_get_local_time_extended(&SntpClient,
                                                   &sntp_seconds, &sntp_fraction,
                                                   NULL, 0);

      /* Check status. */
      if (ret != NX_SUCCESS)
      {
        continue;
      }

      /* Get the system time in second. */
      system_time_in_seconds = tx_time_get() / TX_TIMER_TICKS_PER_SECOND;

      /* Convert to Unix epoch and minus the current system time.  */
      unix_time_base = (sntp_seconds - (system_time_in_seconds + EPOCH_TIME_DIFF));

      /* Stop and delete SNTP. */
      nx_sntp_client_stop(&SntpClient);
      nx_sntp_client_delete(&SntpClient);

      return NX_SUCCESS;
    }

    /* Sleep.  */
    tx_thread_sleep(SNTP_UPDATE_INTERVAL);
  }

  /* Time sync failed.  */

  /* Stop and delete SNTP.  */
  nx_sntp_client_stop(&SntpClient);
  nx_sntp_client_delete(&SntpClient);

  /* Return success.  */
  return NX_NOT_SUCCESSFUL;
}

/* walk through the list of SNTP servers to find one to synchronize time */
static UINT sntp_time_sync(VOID)
{
  UINT status;
  ULONG sntp_server_address[3];

  /* If no NTP server address obtained with DHCP then try with official list */
  for (uint32_t i = 0; i < SNTP_SYNC_MAX; i++)
  {
    printf("SNTP Time Sync... %s\r\n", sntp_servers[sntp_server_index]);

    /* Make sure the link is up. */
    ULONG link_status;
    status = nx_ip_interface_status_check(&IpInstance, 0U, NX_IP_LINK_ENABLED, &link_status, NX_WAIT_FOREVER);
    if (status == NX_SUCCESS)
    {
      /* Look up SNTP Server address. */
      status = nx_dns_host_by_name_get(&DnsClient, (UCHAR *)sntp_servers[sntp_server_index], &sntp_server_address[0],
                                       5 * NX_IP_PERIODIC_RATE);
    }

    /* Check status.  */
    if (status == NX_SUCCESS)
    {
      /* Start SNTP to sync the local time. */
      status = sntp_time_sync_internal(sntp_server_address[0]);

      /* Check status.  */
      if (status == NX_SUCCESS)
      {
        return NX_SUCCESS;
      }
    }

    /* Switch SNTP server every time. */
    sntp_server_index = (sntp_server_index + 1) % (sizeof(sntp_servers) / sizeof(sntp_servers[0]));
  }

  return NX_NOT_SUCCESSFUL;
}
/* USER CODE END 1 */
1.4.1.1.3. IDE files update

EWARM - file stm32u585xx_flash.icf

Add at the end of the file the following line:

place in RAM_region   { last section FREE_MEM};

MDK-ARM

No change.

STM32CubeIDE - file STM32U585AIIXQ_FLASH.ld

Add the threadx_heap section just after the user_heap_stack section

  ._threadx_heap :
  {
	. = ALIGN(8);
	__RAM_segment_used_end__ = .;
	. = . + 64K;
	. = ALIGN(8);
  } >RAM AT> RAM
1.4.1.1.4. IDE options configuration

IAR EWARM

  • General Options->Library Options 1
    • Printf formater: Select "Auto" and check the "Enable multibyte support" box
    • Scanf formater: Select "Auto" and check the "Enable multiple support" box
  • C/C++ Compiler->Language 1
    • C dialect: Check the "C++ inline semantics" box
  • Assembler->Preprocessor
    • Add the following symbol in "Defined symbols" field:
USE_DYNAMIC_MEMORY_ALLOCATION
  • Output Converter
    • Output format: Select "Raw binary"
    • Output file: Uncheck the "Override default" box

Save the modification and start the build

Keil MDK-ARM configurations options

  • Target->Code Generation:
    • Check the "Use MicroLIB" box
  • User->After Build/Rebuild
    • Check the "Run #1" box
    • Put in the corresponding "User Command" field the following command:
fromelf #L --bin --output=$L@L.bin
  • C/C++->Language/Code Generation->Optimization
    • Select "-Oz image size"
    • Misc Control: Put "-Wno-format" (without double-quote) in the field
  • Asm->Conditional Assembly Control Symbols
    • Add the following symbol in the "Define" field
,USE_DYNAMIC_MEMORY_ALLOCATION

Save the modification and start the build

STM32CubeIDE configurations options

  • C/C++ Build->Settings->MCU Post build outputs
    • Check the "Convert to binary file" box for both Configuration "Debug" and "Release"
  • C/C++ Build->Settings->MCU GCC Assembler->Preprocessor
    • Add the following in "Define Symbols (-D)" field for both "Debug" and "Release" Configurations
,USE_DYNAMIC_MEMORY_ALLOCATION

Save the modification and start the build

1.4.1.2. Regenerate from an existing ioc U5 for AzureRTOS - Nx_TCP_Echo_Client_PPP
1.4.1.2.1. New environment generation using STM32CubeMx and provided .ioc

Create empty directory (named e.g. Nx_TCP_Echo_Client_PPP_ioc) in parallel of Nx_TCP_Echo_Client_PPP directory. Copy the following files from the old environment to the brand new one:

  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client_PPP\NetXDuo\App\tcp_echo_client.c
  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client_PPP\NetXDuo\Target
  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client_PPP\.extSettings
  • Projects\B-U585I-IOT02A\Demonstrations\Nx_TCP_Echo_Client_PPP\Nx_TCP_Echo_Client_PPP.ioc

Double click on "Nx_TCP_Echo_Client_PPP.ioc" to start the load of .ioc in STM32CubeMx
In STM32CubeMx:

  • Select the relevant "Toolchain/IDE" (EWARM, MDK-ARM or STM32CubeIDE) from "Project Manager" sheet
  • Generate the code by clicking on "GENERATE CODE" button (for STM32CubeIDE "Generate Under Root" box must be unchecked)
1.4.1.2.2. Update of generated .h and .c files


AFTER CubeMX Generation, add following patches in generated files (original files can be used to copy / paste patches):

Core\Inc\stm32u5xx_it.h

/* USER CODE BEGIN EFP */
void EXTI6_IRQHandler(void);
/* USER CODE END EFP */

Core\Inc\tx_user.h

/* USER CODE BEGIN 2 */
/* Define the CMSIS RTOS2 stack size. */
#define RTOS2_BYTE_POOL_STACK_SIZE  (10*1024)
/* Define the CMSIS RTOS2 heap size. */
#define RTOS2_BYTE_POOL_HEAP_SIZE   (4*1024)
/* Define the type of memory allocation. */
#define USE_DYNAMIC_MEMORY_ALLOCATION
/* USER CODE END 2 */

Core\Src\app_threadx.c

/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */
  /* USER CODE BEGIN App_ThreadX_MEM_POOL */
  TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr;
  (void)byte_pool;
  /* USER CODE END App_ThreadX_MEM_POOL */
  /* USER CODE BEGIN  Before_Kernel_Start */
  osKernelInitialize();
  osKernelStart();
#if 0 /* Ensure generated code not called */
  /* USER CODE END  Before_Kernel_Start */
  /* USER CODE BEGIN  Kernel_Start_Error */
#endif /* Ensure generated code not called */
  /* USER CODE END  Kernel_Start_Error */

Core\Src\main.c

/* USER CODE BEGIN Includes */
#if defined(__ICCARM__)
#include <LowLevelIOInterface.h>
#endif /* __ICCARM__ */
#include <stdio.h>

/* CELLULAR BEGIN Includes */
#include "plf_config.h"
#include "ipc_uart.h"
#include "at_modem_api.h"
#if (USE_CMD_CONSOLE == 1)
#include "cmd.h"
#endif  /* (USE_CMD_CONSOLE == 1) */
/* CELLULAR END Includes */
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
/* CELLULAR BEGIN Callbacks */
/**
  * @brief  Callback to treat EXTI
  * @param  GPIO_Pin - GPIO Pin value
  * @retval -
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == MODEM_RING_PIN)
  {
    GPIO_PinState gstate = HAL_GPIO_ReadPin(MODEM_RING_GPIO_PORT, MODEM_RING_PIN);
    atcc_hw_event(DEVTYPE_MODEM_CELLULAR, HWEVT_MODEM_RING, gstate);
  }
}

/**
  * @brief  Callback to treat UART Rx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_RxCpltCallback(huart);
  }
#if (USE_CMD_CONSOLE == 1)
  else if (huart->Instance == TRACE_INTERFACE_INSTANCE)
  {
    CMD_RxCpltCallback(huart);
  }
#endif  /* USE_CMD_CONSOLE */
  else
  {
    __NOP(); /* Nothing to do */
  }
}

/**
  * @brief  Callback to treat UART Tx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_TxCpltCallback(huart);
  }
}

/**
  * @brief  Callback to treat Error
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_ErrorCallback(huart);
  }
}
/* CELLULAR END Callbacks */
/* USER CODE END 0 */
/* USER CODE BEGIN 4 */
#if defined(__ICCARM__)
/* New definition from EWARM V9, compatible with EWARM8 */
int iar_fputc(int ch);
#define PUTCHAR_PROTOTYPE int iar_fputc(int ch)
#elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION)
/* ARM Compiler 5/6 */
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#elif defined(__GNUC__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif /* __ICCARM__ */

#if defined(__ICCARM__)
size_t __write(int file, unsigned char const *ptr, size_t len)
{
  size_t idx;
  unsigned char const *pdata = ptr;

  for (idx = 0; idx < len; idx++)
  {
    iar_fputc((int)*pdata);
    pdata++;
  }
  return len;
}
#endif /* __ICCARM__ */

/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART1 and Loop until the end of transmission */
  HAL_UART_Transmit(&TRACE_INTERFACE_UART_HANDLE, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}

/* MX_RNG_Init() MUST be called before usage of this function */
int hardware_rand(void)
{
  uint32_t random; /* return value */
  if (HAL_RNG_GenerateRandomNumber(&hrng, &random) != HAL_OK)
  {
    random = (uint32_t)rand();
  }
  return (random);
}
/* USER CODE END 4 */

Core\Src\stm32u5xx_it.c

/* USER CODE BEGIN 1 */
/**
  * @brief This function handles EXTI Line6 interrupt.
  *        This function is not generated by the .ioc file provided because MODEM_RING is initialized
  *        as an input PIN by default. But if modem Low Power is activated, this PIN is reconfigured
  *        as an EXTI PIN and, thus, corresponding IRQHandler has to be defined.    
  */
void EXTI6_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(MODEM_RING_Pin);
}
/* USER CODE END 1 */

NetXDuo\App\nx_user.h

/* USER CODE BEGIN 2 */
extern int hardware_rand(void);
#define NX_RAND hardware_rand
/* USER CODE END 2 */

NetXDuo\App\app_netxduo.h

/* USER CODE BEGIN EC */
#define DEFAULT_MEMORY_SIZE         1024
#define DEFAULT_PRIORITY            5U

#define NULL_ADDRESS                IP_ADDRESS(0, 0, 0, 0)

#define PACKET_COUNT                30
#define PAYLOAD_SIZE                1544
#define NX_PACKET_POOL_SIZE         ((PAYLOAD_SIZE + sizeof(NX_PACKET)) * PACKET_COUNT)
/* USER CODE END EC */
/* USER CODE BEGIN EM */
#define PRINT_IP_ADDRESS(string, addr)     do { \
                                                printf(string " %lu.%lu.%lu.%lu \r\n", \
                                                ((addr) >> 24) & 0xff,                 \
                                                ((addr) >> 16) & 0xff,                 \
                                                ((addr) >> 8) & 0xff,                  \
                                                ((addr) & 0xff));                      \
                                              } while(0)
/* USER CODE END EM */

NetXDuo\App\app_netxduo.c

/* USER CODE BEGIN Includes */
#include "app_azure_rtos.h"
#include "main.h"
#include "nx_ip.h"
#include "nxd_dns.h"
#include "nxd_sntp_client.h"

#include "nx_driver_stm32_cellular.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PTD */
TX_THREAD AppMainThread;

TX_SEMAPHORE IpAddrSemaphore; /* release when an IpAddr is obtained */

NX_PACKET_POOL        AppPool;
NX_IP                 IpInstance;
static NX_DNS         DnsClient;
static NX_SNTP_CLIENT SntpClient;

ULONG   IpAddress;
ULONG   NetMask;
/* USER CODE END PTD */
/* USER CODE BEGIN PD */
#define DEFAULT_MAIN_PRIORITY       10
#define THREAD_MEMORY_SIZE          (2 * DEFAULT_MEMORY_SIZE)

/* Default time. GMT: Friday, Jan 1, 2022 12:00:00 AM. Epoch timestamp: 1640995200.  */
#ifndef SYSTEM_TIME
#define SYSTEM_TIME              1640995200
#endif /* SYSTEM_TIME  */

/* 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) */
#define EPOCH_TIME_DIFF          2208988800

#define SNTP_SYNC_MAX            (uint32_t)30
#define SNTP_UPDATE_MAX          (uint32_t)10
#define SNTP_UPDATE_INTERVAL     (NX_IP_PERIODIC_RATE / 2)

#define IP_ADDR_TIMEOUT          (50*NX_IP_PERIODIC_RATE)

#define SAMPLE_IP_THREAD_PRIORITY     DEFAULT_PRIORITY
#define SAMPLE_IP_THREAD_STACK_SIZE   (2 * DEFAULT_MEMORY_SIZE)

#define SAMPLE_NETWORK_CONFIGURE_START          nx_driver_stm32_cellular_configure
#define SAMPLE_NETWORK_DRIVER                   nx_driver_stm32_cellular
/* USER CODE END PD */
/* USER CODE BEGIN PV */
/* System clock time for UTC.  */
static ULONG            unix_time_base;

static const char *sntp_servers[] =
{
  "0.pool.ntp.org",
  "1.pool.ntp.org",
  "2.pool.ntp.org",
  "3.pool.ntp.org"
};

static UINT sntp_server_index;

static ULONG dns_server_address[3];
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
static VOID App_Main_Thread_Entry(ULONG thread_input);

static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);

static UINT dns_create(NX_DNS *dns_ptr);
static UINT unix_time_get(ULONG *unix_time);
static UINT sntp_time_sync_internal(ULONG sntp_server_address);
static UINT sntp_time_sync(VOID);

/* Include the sample interface. */
extern VOID sample_init(void);
extern VOID sample_start(NX_IP *ip_ptr, NX_PACKET_POOL *pool_ptr, NX_DNS *dns_ptr,
                         UINT(*unix_time_callback)(ULONG *unix_time));
/* USER CODE END PFP */
   /* USER CODE BEGIN App_NetXDuo_MEM_POOL */
  //(void)byte_pool;
  /* USER CODE END App_NetXDuo_MEM_POOL */
  /* USER CODE BEGIN MX_NetXDuo_Init */
  printf("Nx_TCP_Echo_Client application started.\n");

  CHAR *pointer;

  /* Allocate the memory for packet_pool.  */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_PACKET_POOL_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (packet_pool) fail\r\n");
    return TX_POOL_ERROR;
  }

  /* Create the Packet pool to be used for packet allocation */
  ret = nx_packet_pool_create(&AppPool, "Main Packet Pool", PAYLOAD_SIZE, pointer, NX_PACKET_POOL_SIZE);

  if (ret != NX_SUCCESS)
  {
    printf("nx_packet_pool_create fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Allocate the memory for Ip_Instance */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, SAMPLE_IP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (Ip_Instance) fail\r\n");
    return TX_POOL_ERROR;
  }

  printf("Create IP instance...\r\n");

  /* Create the main NX_IP instance */
  ret = nx_ip_create(&IpInstance, "Main Ip instance", NULL_ADDRESS, NULL_ADDRESS, &AppPool, SAMPLE_NETWORK_DRIVER,
                     pointer, SAMPLE_IP_THREAD_STACK_SIZE, SAMPLE_IP_THREAD_PRIORITY);

  if (ret != NX_SUCCESS)
  {
    printf("nx_ip_create fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the ICMP */
  ret = nx_icmp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_icmp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the UDP protocol required for DHCP communication */
  ret = nx_udp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_udp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the TCP protocol */
  ret = nx_tcp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_tcp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Initialize sample */
  sample_init();

  /* Allocate the memory for main thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, THREAD_MEMORY_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (main thread) fail\r\n");
    return TX_POOL_ERROR;
  }

  /* Create the main thread */
  ret = tx_thread_create(&AppMainThread, "App Main thread", App_Main_Thread_Entry, 0, pointer, THREAD_MEMORY_SIZE,
                         DEFAULT_MAIN_PRIORITY, DEFAULT_MAIN_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);

  if (ret != TX_SUCCESS)
  {
    printf("tx_thread_create (App Main thread) fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Create IpAddr semaphore */
  tx_semaphore_create(&IpAddrSemaphore, "IpAddr Semaphore", 0);
  /* USER CODE END MX_NetXDuo_Init */
/* USER CODE BEGIN 1 */
/**
  * @brief  Main thread entry.
  * @param thread_input: ULONG user argument (unused argument)
  * @retval none
  */
static VOID App_Main_Thread_Entry(ULONG thread_input)
{
  UINT ret = NX_SUCCESS;

  NX_PARAMETER_NOT_USED(thread_input);

  printf("Get IP Address...\r\n");

  ret = nx_ip_address_change_notify(&IpInstance, ip_address_change_notify_callback, NULL);
  if (ret != NX_SUCCESS)
  {
    printf("nx_ip_address_change_notify fail: %u\r\n", ret);
    Error_Handler();
  }

  SAMPLE_NETWORK_CONFIGURE_START(&IpInstance, &dns_server_address[0]);

  /* wait until an IP address is ready */
  if (tx_semaphore_get(&IpAddrSemaphore, IP_ADDR_TIMEOUT) != TX_SUCCESS)
  {
    printf("IP address timeout fail\r\n");
    Error_Handler();
  }

  ret = nx_ip_address_get(&IpInstance, &IpAddress, &NetMask);

  if (ret != TX_SUCCESS)
  {
    printf("nx_ip_address_get fail: %u\r\n", ret);
    Error_Handler();
  }

  PRINT_IP_ADDRESS("STM32 IP Address: ", IpAddress);

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

  if (ret != NX_SUCCESS)
  {
    printf("dns_create fail: %u\r\n", ret);
    Error_Handler();
  }

  /* Sync up time by SNTP at start up. */
  ret = sntp_time_sync();

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("SNTP Time Sync failed.\r\n");
    Error_Handler();
  }

  /* Start sample. */
  sample_start(&IpInstance, &AppPool, &DnsClient, unix_time_get);

  /* this thread is not needed any more, we relinquish it */
  tx_thread_relinquish();
}

/**
  * @brief ip address change callback.
  * @param ip_instance: NX_IP instance
  * @param ptr: user data
  * @retval none
  */
static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr)
{
  /* release the semaphore as soon as an IP address is available */
  tx_semaphore_put(&IpAddrSemaphore);
}

/**
  * @brief  DNS Create Function.
  * @param dns_ptr
  * @retval ret
  */
static UINT dns_create(NX_DNS *dns_ptr)
{
  UINT status;

  /* Create a DNS instance for the Client */
  status = nx_dns_create(dns_ptr, &IpInstance, (UCHAR *)"DNS Client");

  /* Is the DNS client configured for the host application to create the packet pool?  */
#ifdef NX_DNS_CLIENT_USER_CREATE_PACKET_POOL
  if (status == NX_SUCCESS)
  {
    /* Yes, use the packet pool created above which has appropriate payload size
       for DNS messages.  */
    status = nx_dns_packet_pool_set(dns_ptr, IpInstance.nx_ip_default_packet_pool);
    if (status != NX_SUCCESS)
    {
      nx_dns_delete(dns_ptr);
    }
  }
#endif /* NX_DNS_CLIENT_USER_CREATE_PACKET_POOL */

#ifdef USER_DNS_ADDRESS
  dns_server_address[0] = USER_DNS_ADDRESS;
#endif /* USER_DNS_ADDRESS */

  if (status != NX_SUCCESS)
  {
    printf("nx_dns_create fail: %u\r\n", status);
    Error_Handler();
  }

  /* Initialize DNS instance with a DNS server address */
  status = nx_dns_server_add(dns_ptr, dns_server_address[0]);
  if (status != NX_SUCCESS)
  {
    printf("nx_dns_server_add fail: %u\r\n", status);
    Error_Handler();
  }

  PRINT_IP_ADDRESS("DNS Server address:", dns_server_address[0]);

  return (status);
}


static UINT unix_time_get(ULONG *unix_time)
{
  /* Return number of seconds since Unix Epoch (1/1/1970 00:00:00).  */
  *unix_time =  unix_time_base + (tx_time_get() / TX_TIMER_TICKS_PER_SECOND);

  return (NX_SUCCESS);
}

/* Sync up the local time with a known SNTP server. */
static UINT sntp_time_sync_internal(ULONG sntp_server_address)
{
  UINT ret;

  /* Create the SNTP Client to run in broadcast mode.. */
  ret = nx_sntp_client_create(&SntpClient, &IpInstance, 0, &AppPool,
                              NX_NULL,
                              NX_NULL,
                              NX_NULL /* no random_number_generator callback */);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_create fail: %u\r\n", ret);
    return ret;
  }

  PRINT_IP_ADDRESS("Trying SNTP server:", sntp_server_address);

  /* Use the IPv4 service to initialize the Client and set the IPv4 SNTP server. */
  ret = nx_sntp_client_initialize_unicast(&SntpClient, sntp_server_address);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_initialize_unicast fail: %u\r\n", ret);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Set local time to 0 */
  ret = nx_sntp_client_set_local_time(&SntpClient, 0, 0);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_set_local_time fail: %u\r\n", ret);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Run Unicast client */
  ret = nx_sntp_client_run_unicast(&SntpClient);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_run_unicast fail: %u\r\n", ret);
    nx_sntp_client_stop(&SntpClient);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Wait till updates are received */
  for (uint32_t i = 0; i < SNTP_UPDATE_MAX; i++)
  {
    UINT server_status;

    /* First verify we have a valid SNTP service running. */
    ret = nx_sntp_client_receiving_updates(&SntpClient, &server_status);

    /* Check status.  */
    if ((ret == NX_SUCCESS) && (server_status == NX_TRUE))
    {
      /* Server status is good. Now get the Client local time. */
      ULONG sntp_seconds;
      ULONG sntp_fraction;
      ULONG system_time_in_seconds;

      /* Get the local time. */
      ret = nx_sntp_client_get_local_time_extended(&SntpClient,
                                                   &sntp_seconds, &sntp_fraction,
                                                   NULL, 0);

      /* Check status. */
      if (ret != NX_SUCCESS)
      {
        continue;
      }

      /* Get the system time in second. */
      system_time_in_seconds = tx_time_get() / TX_TIMER_TICKS_PER_SECOND;

      /* Convert to Unix epoch and minus the current system time.  */
      unix_time_base = (sntp_seconds - (system_time_in_seconds + EPOCH_TIME_DIFF));

      /* Stop and delete SNTP. */
      nx_sntp_client_stop(&SntpClient);
      nx_sntp_client_delete(&SntpClient);

      return NX_SUCCESS;
    }

    /* Sleep.  */
    tx_thread_sleep(SNTP_UPDATE_INTERVAL);
  }

  /* Time sync failed.  */

  /* Stop and delete SNTP.  */
  nx_sntp_client_stop(&SntpClient);
  nx_sntp_client_delete(&SntpClient);

  /* Return success.  */
  return NX_NOT_SUCCESSFUL;
}

/* walk through the list of SNTP servers to find one to synchronize time */
static UINT sntp_time_sync(VOID)
{
  UINT status;
  ULONG sntp_server_address[3];

  /* If no NTP server address obtained with DHCP then try with official list */
  for (uint32_t i = 0; i < SNTP_SYNC_MAX; i++)
  {
    printf("SNTP Time Sync... %s\r\n", sntp_servers[sntp_server_index]);

    /* Make sure the link is up. */
    ULONG link_status;
    status = nx_ip_interface_status_check(&IpInstance, 0U, NX_IP_LINK_ENABLED, &link_status, NX_WAIT_FOREVER);
    if (status == NX_SUCCESS)
    {
      /* Look up SNTP Server address. */
      status = nx_dns_host_by_name_get(&DnsClient, (UCHAR *)sntp_servers[sntp_server_index], &sntp_server_address[0],
                                       5 * NX_IP_PERIODIC_RATE);
    }

    /* Check status.  */
    if (status == NX_SUCCESS)
    {
      /* Start SNTP to sync the local time. */
      status = sntp_time_sync_internal(sntp_server_address[0]);

      /* Check status.  */
      if (status == NX_SUCCESS)
      {
        return NX_SUCCESS;
      }
    }

    /* Switch SNTP server every time. */
    sntp_server_index = (sntp_server_index + 1) % (sizeof(sntp_servers) / sizeof(sntp_servers[0]));
  }

  return NX_NOT_SUCCESSFUL;
}
/* USER CODE END 1 */
1.4.1.2.3. IDE files update

EWARM - file stm32u585xx_flash.icf

Add at the end of the file the following line:

place in RAM_region   { last section FREE_MEM};

MDK-ARM

No change.

STM32CubeIDE - file STM32U585AIIXQ_FLASH.ld

Add the threadx_heap section just after the user_heap_stack section

  ._threadx_heap :
  {
	. = ALIGN(8);
	__RAM_segment_used_end__ = .;
	. = . + 64K;
	. = ALIGN(8);
  } >RAM AT> RAM
1.4.1.2.4. IDE options configuration

IAR EWARM

  • General Options->Library Options 1
    • Printf formater: Select "Auto" and check the "Enable multibyte support" box
    • Scanf formater: Select "Auto" and check the "Enable multiple support" box
  • C/C++ Compiler->Language 1
    • C dialect: Check the "C++ inline semantics" box
  • Assembler->Preprocessor
    • Add the following symbol in "Defined symbols" field:
USE_DYNAMIC_MEMORY_ALLOCATION
  • Output Converter
    • Output format: Select "Raw binary"
    • Output file: Uncheck the "Override default" box

Save the modification and start the build

Keil MDK-ARM configurations options

  • Target->Code Generation:
    • Check the "Use MicroLIB" box
  • User->After Build/Rebuild
    • Check the "Run #1" box
    • Put in the corresponding "User Command" field the following command:
fromelf #L --bin --output=$L@L.bin
  • C/C++ (AC6)->Language/Code Generation->Optimization
    • Select "-Oz image size"
    • Misc Control: Put "-Wno-format" (without double-quote) in the field
  • Asm->Conditional Assembly Control Symbols
    • Add the following in the "Define" field
,USE_DYNAMIC_MEMORY_ALLOCATION

Save the modification and start the build

STM32CubeIDE configurations options

  • C/C++ Build->Settings->MCU Post build outputs
    • Check the "Convert to binary file" box for both "Debug" and "Release" Configurations
  • C/C++ Build->Settings->MCU GCC Assembler->Preprocessor
    • Add the following symbol in "Define Symbols (-D)" field for both "Debug" and "Release" Configurations
,USE_DYNAMIC_MEMORY_ALLOCATION

Save the modification and start the build

1.4.1.3. Regenerate from an existing ioc L4 for AzureRTOS - Nx_TCP_Echo_Client
1.4.1.3.1. New environment generation using STM32CubeMx and provided .ioc

Create empty directory (named e.g. Nx_TCP_Echo_Client_ioc) in parallel of Nx_TCP_Echo_Client directory. Copy the following files from the old environment to the brand new one:

  • Projects\B-L462E-CELL1\Demonstrations\Nx_TCP_Echo_Client\NetXDuo\App\tcp_echo_client.c
  • Projects\B-L462E-CELL1\Demonstrations\Nx_TCP_Echo_Client\NetXDuo\Target
  • Projects\B-L462E-CELL1\Demonstrations\Nx_TCP_Echo_Client\.extSettings
  • Projects\B-L462E-CELL1\Demonstrations\Nx_TCP_Echo_Client\Nx_TCP_Echo_Client.ioc

Start STM32CubeMx and install software pack X-CUBE-AZRTOS-L4 (version V1.0.0) :

  • "Software Packs", "Manage Software Packs", "STMicroelectronics", "X-CUBE-AZRTOS-L4"
  • Select "Azure RTOS STM32Cube expansion package from STM32L4 series"
  • Press "Install"

Double click on "Nx_TCP_Echo_Client.ioc" to start the load of .ioc in STM32CubeMx
In STM32CubeMx:

  • Select the relevant "Toolchain/IDE" (EWARM, MDK-ARM or STM32CubeIDE) from "Project Manager" sheet
  • Generate the code by clicking on "GENERATE CODE" button (for STM32CubeIDE "Generate Under Root" box must be unchecked)
1.4.1.3.2. Update of generated .h and .c files


AFTER CubeMX Generation, add following patches in generated files (original files can be used to copy / paste patches):

AZURE_RTOS\App\app_azure_rtos.h

/* USER CODE BEGIN Includes */
#include "stm32l4xx_hal.h"
/* USER CODE END Includes */

AZURE_RTOS\App\app_azure_rtos.c

/* USER CODE BEGIN  App_ThreadX_Init_Error */
while(1)
{
}
/* USER CODE END  App_ThreadX_Init_Error */
	
/* USER CODE BEGIN MX_NetXDuo_Init_Error */
while(1)
{
}
/* USER CODE END MX_NetXDuo_Init_Error */

Core\Inc\stm32l4xx_it.h

/* USER CODE BEGIN EFP */
void EXTI15_10_IRQHandler(void);
/* USER CODE END EFP */

Core\Src\app_threadx.c

/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */
  /* USER CODE BEGIN  Before_Kernel_Start */
  osKernelInitialize();
  osKernelStart();
#if 0 /* Ensure generated code not called */
  /* USER CODE END  Before_Kernel_Start */
  /* USER CODE BEGIN  Kernel_Start_Error */
#endif /* Ensure generated code not called */
  /* USER CODE END  Kernel_Start_Error */

Core\Src\main.c

/* USER CODE BEGIN Includes */
#if defined(__ICCARM__)
#include <LowLevelIOInterface.h>
#endif /* __ICCARM__ */
#include <stdio.h>

/* CELLULAR BEGIN Includes */
#include "plf_config.h"
#include "ipc_uart.h"
#include "at_modem_api.h"
#if (USE_CMD_CONSOLE == 1)
#include "cmd.h"
#endif  /* (USE_CMD_CONSOLE == 1) */
/* CELLULAR END Includes */
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
/* CELLULAR BEGIN Callbacks */
/**
  * @brief  Callback to treat EXTI
  * @param  GPIO_Pin - GPIO Pin value
  * @retval -
  */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == MODEM_RING_PIN)
  {
    GPIO_PinState gstate = HAL_GPIO_ReadPin(MODEM_RING_GPIO_PORT, MODEM_RING_PIN);
    atcc_hw_event(DEVTYPE_MODEM_CELLULAR, HWEVT_MODEM_RING, gstate);
  }
}

/**
  * @brief  Callback to treat UART Rx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_RxCpltCallback(huart);
  }
#if (USE_CMD_CONSOLE == 1)
  else if (huart->Instance == TRACE_INTERFACE_INSTANCE)
  {
    CMD_RxCpltCallback(huart);
  }
#endif  /* USE_CMD_CONSOLE */
  else
  {
    __NOP(); /* Nothing to do */
  }
}

/**
  * @brief  Callback to treat UART Tx complete
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_TxCpltCallback(huart);
  }
}

/**
  * @brief  Callback to treat Error
  * @param  huart - pointer on UART handle
  * @retval -
  */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == MODEM_UART_INSTANCE)
  {
    IPC_UART_ErrorCallback(huart);
  }
}
/* CELLULAR END Callbacks */
/* USER CODE END 0 */
/* USER CODE BEGIN 4 */
#if defined(__ICCARM__)
/* New definition from EWARM V9, compatible with EWARM8 */
int iar_fputc(int ch);
#define PUTCHAR_PROTOTYPE int iar_fputc(int ch)
#elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION)
/* ARM Compiler 5/6 */
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#elif defined(__GNUC__)
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif /* __ICCARM__ */

#if defined(__ICCARM__)
size_t __write(int file, unsigned char const *ptr, size_t len)
{
  size_t idx;
  unsigned char const *pdata = ptr;

  for (idx = 0; idx < len; idx++)
  {
    iar_fputc((int)*pdata);
    pdata++;
  }
  return len;
}
#endif /* __ICCARM__ */

/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART1 and Loop until the end of transmission */
  HAL_UART_Transmit(&TRACE_INTERFACE_UART_HANDLE, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}

/* MX_RNG_Init() MUST be called before usage of this function */
int hardware_rand(void)
{
  uint32_t random; /* return value */
  if (HAL_RNG_GenerateRandomNumber(&hrng, &random) != HAL_OK)
  {
    random = (uint32_t)rand();
  }
  return (random);
}
/* USER CODE END 4 */

Core\Src\stm32l4xx_it.c

/* USER CODE BEGIN 1 */
/**
  * @brief This function handles EXTI Line6 interrupt.
  *        This function is not generated by the .ioc file provided because MODEM_RING is initialized
  *        as an input PIN by default. But if modem Low Power is activated, this PIN is reconfigured
  *        as an EXTI PIN and, thus, corresponding IRQHandler has to be defined.    
  */
void EXTI15_10_IRQHandler(void)
{
   HAL_GPIO_EXTI_IRQHandler(MODEM_RING_Pin);
}

/* USER CODE END 1 */

NetXDuo\App\nx_user.h

/* USER CODE BEGIN 2 */
#define NX_ENABLE_TCPIP_OFFLOAD

extern int hardware_rand(void);
#define NX_RAND hardware_rand
/* USER CODE END 2 */

NetXDuo\App\app_netxduo.h

/* USER CODE BEGIN EC */
#define DEFAULT_MEMORY_SIZE         1024
#define DEFAULT_PRIORITY            5U

#define NULL_ADDRESS                IP_ADDRESS(0, 0, 0, 0)

#define PACKET_COUNT                30
#define PAYLOAD_SIZE                1544
#define NX_PACKET_POOL_SIZE         ((PAYLOAD_SIZE + sizeof(NX_PACKET)) * PACKET_COUNT)
/* USER CODE END EC */
/* USER CODE BEGIN EM */
#define PRINT_IP_ADDRESS(string, addr)     do { \
                                                printf(string " %lu.%lu.%lu.%lu \r\n", \
                                                ((addr) >> 24) & 0xff,                 \
                                                ((addr) >> 16) & 0xff,                 \
                                                ((addr) >> 8) & 0xff,                  \
                                                ((addr) & 0xff));                      \
                                              } while(0)
/* USER CODE END EM */

NetXDuo\App\app_netxduo.c

/* USER CODE BEGIN Includes */
#include "app_azure_rtos.h"
#include "main.h"
#include "nx_ip.h"
#include "nxd_dns.h"
#include "nxd_sntp_client.h"

#include "nx_driver_stm32_cellular.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PTD */
TX_THREAD AppMainThread;

TX_SEMAPHORE IpAddrSemaphore; /* release when an IpAddr is obtained */

NX_PACKET_POOL        AppPool;
NX_IP                 IpInstance;
static NX_DNS         DnsClient;
static NX_SNTP_CLIENT SntpClient;

ULONG   IpAddress;
ULONG   NetMask;
/* USER CODE END PTD */
/* USER CODE BEGIN PD */
#define DEFAULT_MAIN_PRIORITY       10
#define THREAD_MEMORY_SIZE          (2 * DEFAULT_MEMORY_SIZE)

/* Default time. GMT: Friday, Jan 1, 2022 12:00:00 AM. Epoch timestamp: 1640995200.  */
#ifndef SYSTEM_TIME
#define SYSTEM_TIME              1640995200
#endif /* SYSTEM_TIME  */

/* 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) */
#define EPOCH_TIME_DIFF          2208988800

#define SNTP_SYNC_MAX            (uint32_t)30
#define SNTP_UPDATE_MAX          (uint32_t)10
#define SNTP_UPDATE_INTERVAL     (NX_IP_PERIODIC_RATE / 2)

#define IP_ADDR_TIMEOUT          (50*NX_IP_PERIODIC_RATE)

#define SAMPLE_IP_THREAD_PRIORITY     DEFAULT_PRIORITY
#define SAMPLE_IP_THREAD_STACK_SIZE   (2 * DEFAULT_MEMORY_SIZE)

#define SAMPLE_NETWORK_CONFIGURE_START          nx_driver_stm32_cellular_configure
#define SAMPLE_NETWORK_DRIVER                   nx_driver_stm32_cellular
/* USER CODE END PD */
/* USER CODE BEGIN PV */
/* System clock time for UTC.  */
static ULONG            unix_time_base;

static const char *sntp_servers[] =
{
  "0.pool.ntp.org",
  "1.pool.ntp.org",
  "2.pool.ntp.org",
  "3.pool.ntp.org"
};

static UINT sntp_server_index;

static ULONG dns_server_address[3];
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
static VOID App_Main_Thread_Entry(ULONG thread_input);

static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr);

static UINT dns_create(NX_DNS *dns_ptr);
static UINT unix_time_get(ULONG *unix_time);
static UINT sntp_time_sync_internal(ULONG sntp_server_address);
static UINT sntp_time_sync(VOID);

/* Include the sample interface. */
extern VOID sample_init(void);
extern VOID sample_start(NX_IP *ip_ptr, NX_PACKET_POOL *pool_ptr, NX_DNS *dns_ptr,
                         UINT(*unix_time_callback)(ULONG *unix_time));
/* USER CODE END PFP */
   /* USER CODE BEGIN App_NetXDuo_MEM_POOL */
  //(void)byte_pool;
  /* USER CODE END App_NetXDuo_MEM_POOL */
  /* USER CODE BEGIN MX_NetXDuo_Init */
  printf("Nx_TCP_Echo_Client application started.\n");

  CHAR *pointer;

  /* Allocate the memory for packet_pool.  */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, NX_PACKET_POOL_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (packet_pool) fail\r\n");
    return TX_POOL_ERROR;
  }

  /* Create the Packet pool to be used for packet allocation */
  ret = nx_packet_pool_create(&AppPool, "Main Packet Pool", PAYLOAD_SIZE, pointer, NX_PACKET_POOL_SIZE);

  if (ret != NX_SUCCESS)
  {
    printf("nx_packet_pool_create fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Allocate the memory for Ip_Instance */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, SAMPLE_IP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (Ip_Instance) fail\r\n");
    return TX_POOL_ERROR;
  }

  printf("Create IP instance...\r\n");

  /* Create the main NX_IP instance */
  ret = nx_ip_create(&IpInstance, "Main Ip instance", NULL_ADDRESS, NULL_ADDRESS, &AppPool, SAMPLE_NETWORK_DRIVER,
                     pointer, SAMPLE_IP_THREAD_STACK_SIZE, SAMPLE_IP_THREAD_PRIORITY);

  if (ret != NX_SUCCESS)
  {
    printf("nx_ip_create fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the ICMP */
  ret = nx_icmp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_icmp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the UDP protocol required for DHCP communication */
  ret = nx_udp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_udp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Enable the TCP protocol */
  ret = nx_tcp_enable(&IpInstance);

  if (ret != NX_SUCCESS)
  {
    printf("nx_tcp_enable fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Initialize sample */
  sample_init();

  /* Allocate the memory for main thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer, THREAD_MEMORY_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    printf("tx_byte_allocate (main thread) fail\r\n");
    return TX_POOL_ERROR;
  }

  /* Create the main thread */
  ret = tx_thread_create(&AppMainThread, "App Main thread", App_Main_Thread_Entry, 0, pointer, THREAD_MEMORY_SIZE,
                         DEFAULT_MAIN_PRIORITY, DEFAULT_MAIN_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);

  if (ret != TX_SUCCESS)
  {
    printf("tx_thread_create (App Main thread) fail: %u\r\n", ret);
    return NX_NOT_ENABLED;
  }

  /* Create IpAddr semaphore */
  tx_semaphore_create(&IpAddrSemaphore, "IpAddr Semaphore", 0);
  /* USER CODE END MX_NetXDuo_Init */
/* USER CODE BEGIN 1 */
/**
  * @brief  Main thread entry.
  * @param thread_input: ULONG user argument (unused argument)
  * @retval none
  */
static VOID App_Main_Thread_Entry(ULONG thread_input)
{
  UINT ret = NX_SUCCESS;

  NX_PARAMETER_NOT_USED(thread_input);

  printf("Get IP Address...\r\n");

  ret = nx_ip_address_change_notify(&IpInstance, ip_address_change_notify_callback, NULL);
  if (ret != NX_SUCCESS)
  {
    printf("nx_ip_address_change_notify fail: %u\r\n", ret);
    Error_Handler();
  }

  SAMPLE_NETWORK_CONFIGURE_START(&IpInstance, &dns_server_address[0]);

  /* wait until an IP address is ready */
  if (tx_semaphore_get(&IpAddrSemaphore, IP_ADDR_TIMEOUT) != TX_SUCCESS)
  {
    printf("IP address timeout fail\r\n");
    Error_Handler();
  }

  ret = nx_ip_address_get(&IpInstance, &IpAddress, &NetMask);

  if (ret != TX_SUCCESS)
  {
    printf("nx_ip_address_get fail: %u\r\n", ret);
    Error_Handler();
  }

  PRINT_IP_ADDRESS("STM32 IP Address: ", IpAddress);

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

  if (ret != NX_SUCCESS)
  {
    printf("dns_create fail: %u\r\n", ret);
    Error_Handler();
  }

  /* Sync up time by SNTP at start up. */
  ret = sntp_time_sync();

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("SNTP Time Sync failed.\r\n");
    Error_Handler();
  }

  /* Start sample. */
  sample_start(&IpInstance, &AppPool, &DnsClient, unix_time_get);

  /* this thread is not needed any more, we relinquish it */
  tx_thread_relinquish();
}

/**
  * @brief ip address change callback.
  * @param ip_instance: NX_IP instance
  * @param ptr: user data
  * @retval none
  */
static VOID ip_address_change_notify_callback(NX_IP *ip_instance, VOID *ptr)
{
  /* release the semaphore as soon as an IP address is available */
  tx_semaphore_put(&IpAddrSemaphore);
}

/**
  * @brief  DNS Create Function.
  * @param dns_ptr
  * @retval ret
  */
static UINT dns_create(NX_DNS *dns_ptr)
{
  UINT status;

  /* Create a DNS instance for the Client */
  status = nx_dns_create(dns_ptr, &IpInstance, (UCHAR *)"DNS Client");

  /* Is the DNS client configured for the host application to create the packet pool?  */
#ifdef NX_DNS_CLIENT_USER_CREATE_PACKET_POOL
  if (status == NX_SUCCESS)
  {
    /* Yes, use the packet pool created above which has appropriate payload size
       for DNS messages.  */
    status = nx_dns_packet_pool_set(dns_ptr, IpInstance.nx_ip_default_packet_pool);
    if (status != NX_SUCCESS)
    {
      nx_dns_delete(dns_ptr);
    }
  }
#endif /* NX_DNS_CLIENT_USER_CREATE_PACKET_POOL */

#ifdef USER_DNS_ADDRESS
  dns_server_address[0] = USER_DNS_ADDRESS;
#endif /* USER_DNS_ADDRESS */

  if (status != NX_SUCCESS)
  {
    printf("nx_dns_create fail: %u\r\n", status);
    Error_Handler();
  }

  /* Initialize DNS instance with a DNS server address */
  status = nx_dns_server_add(dns_ptr, dns_server_address[0]);
  if (status != NX_SUCCESS)
  {
    printf("nx_dns_server_add fail: %u\r\n", status);
    Error_Handler();
  }

  PRINT_IP_ADDRESS("DNS Server address:", dns_server_address[0]);

  return (status);
}


static UINT unix_time_get(ULONG *unix_time)
{
  /* Return number of seconds since Unix Epoch (1/1/1970 00:00:00).  */
  *unix_time =  unix_time_base + (tx_time_get() / TX_TIMER_TICKS_PER_SECOND);

  return (NX_SUCCESS);
}

/* Sync up the local time with a known SNTP server. */
static UINT sntp_time_sync_internal(ULONG sntp_server_address)
{
  UINT ret;

  /* Create the SNTP Client to run in broadcast mode.. */
  ret = nx_sntp_client_create(&SntpClient, &IpInstance, 0, &AppPool,
                              NX_NULL,
                              NX_NULL,
                              NX_NULL /* no random_number_generator callback */);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_create fail: %u\r\n", ret);
    return ret;
  }

  PRINT_IP_ADDRESS("Trying SNTP server:", sntp_server_address);

  /* Use the IPv4 service to initialize the Client and set the IPv4 SNTP server. */
  ret = nx_sntp_client_initialize_unicast(&SntpClient, sntp_server_address);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_initialize_unicast fail: %u\r\n", ret);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Set local time to 0 */
  ret = nx_sntp_client_set_local_time(&SntpClient, 0, 0);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_set_local_time fail: %u\r\n", ret);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Run Unicast client */
  ret = nx_sntp_client_run_unicast(&SntpClient);

  /* Check status.  */
  if (ret != NX_SUCCESS)
  {
    printf("nx_sntp_client_run_unicast fail: %u\r\n", ret);
    nx_sntp_client_stop(&SntpClient);
    nx_sntp_client_delete(&SntpClient);
    return ret;
  }

  /* Wait till updates are received */
  for (uint32_t i = 0; i < SNTP_UPDATE_MAX; i++)
  {
    UINT server_status;

    /* First verify we have a valid SNTP service running. */
    ret = nx_sntp_client_receiving_updates(&SntpClient, &server_status);

    /* Check status.  */
    if ((ret == NX_SUCCESS) && (server_status == NX_TRUE))
    {
      /* Server status is good. Now get the Client local time. */
      ULONG sntp_seconds;
      ULONG sntp_fraction;
      ULONG system_time_in_seconds;

      /* Get the local time. */
      ret = nx_sntp_client_get_local_time_extended(&SntpClient,
                                                   &sntp_seconds, &sntp_fraction,
                                                   NULL, 0);

      /* Check status. */
      if (ret != NX_SUCCESS)
      {
        continue;
      }

      /* Get the system time in second. */
      system_time_in_seconds = tx_time_get() / TX_TIMER_TICKS_PER_SECOND;

      /* Convert to Unix epoch and minus the current system time.  */
      unix_time_base = (sntp_seconds - (system_time_in_seconds + EPOCH_TIME_DIFF));

      /* Stop and delete SNTP. */
      nx_sntp_client_stop(&SntpClient);
      nx_sntp_client_delete(&SntpClient);

      return NX_SUCCESS;
    }

    /* Sleep.  */
    tx_thread_sleep(SNTP_UPDATE_INTERVAL);
  }

  /* Time sync failed.  */

  /* Stop and delete SNTP.  */
  nx_sntp_client_stop(&SntpClient);
  nx_sntp_client_delete(&SntpClient);

  /* Return success.  */
  return NX_NOT_SUCCESSFUL;
}

/* walk through the list of SNTP servers to find one to synchronize time */
static UINT sntp_time_sync(VOID)
{
  UINT status;
  ULONG sntp_server_address[3];

  /* If no NTP server address obtained with DHCP then try with official list */
  for (uint32_t i = 0; i < SNTP_SYNC_MAX; i++)
  {
    printf("SNTP Time Sync... %s\r\n", sntp_servers[sntp_server_index]);

    /* Make sure the link is up. */
    ULONG link_status;
    status = nx_ip_interface_status_check(&IpInstance, 0U, NX_IP_LINK_ENABLED, &link_status, NX_WAIT_FOREVER);
    if (status == NX_SUCCESS)
    {
      /* Look up SNTP Server address. */
      status = nx_dns_host_by_name_get(&DnsClient, (UCHAR *)sntp_servers[sntp_server_index], &sntp_server_address[0],
                                       5 * NX_IP_PERIODIC_RATE);
    }

    /* Check status.  */
    if (status == NX_SUCCESS)
    {
      /* Start SNTP to sync the local time. */
      status = sntp_time_sync_internal(sntp_server_address[0]);

      /* Check status.  */
      if (status == NX_SUCCESS)
      {
        return NX_SUCCESS;
      }
    }

    /* Switch SNTP server every time. */
    sntp_server_index = (sntp_server_index + 1) % (sizeof(sntp_servers) / sizeof(sntp_servers[0]));
  }

  return NX_NOT_SUCCESSFUL;
}
/* USER CODE END 1 */


1.4.1.3.3. IDE files update

EWARM - file stm32l462xx_flash.icf

Add at the end of the file the following line:

place in RAM_region   { last section FREE_MEM};


MDK-ARM

No change.

STM32CubeIDE - file STM32L462REYX_FLASH.ld

Add the threadx_heap section just after the user_heap_stack section

  ._threadx_heap :
  {
	. = ALIGN(8);
	__RAM_segment_used_end__ = .;
	. = . + 32K;
	. = ALIGN(8);
  } >RAM2
1.4.1.3.4. IDE options configuration

IAR EWARM

  • General Options->Library Options 1
    • Printf formater: Select "Auto" and check the "Enable multibyte support" box
    • Scanf formater: Select "Auto" and check the "Enable multiple support" box
  • C/C++ Compiler->Language 1
    • C dialect: Check the "C++ inline semantics" box
  • Assembler->Preprocessor
    • Add the following symbol in "Defined symbols" field:
USE_DYNAMIC_MEMORY_ALLOCATION
  • Output Converter
    • Output format: Select "Raw binary"
    • Output file: Uncheck the "Override default" box

Save the modification and start the build

Keil MDK-ARM configurations options

  • Target->Code Generation:
    • Check the "Use MicroLIB" box
  • User->After Build/Rebuild
    • Check the "Run #1" box
    • Put in the corresponding "User Command" field the following command:
fromelf #L --bin --output=$L@L.bin
  • C/C++ (AC6)->Language/Code Generation->Optimization
    • Select "-Oz image size"
    • Misc Control: Put "-Wno-format" (without double-quote) in the field
  • Asm->Conditional Assembly Control Symbols
    • Add the following in the "Define" field
USE_DYNAMIC_MEMORY_ALLOCATION

Save the modification and start the build

STM32CubeIDE configurations options

  • C/C++ Build->Settings->MCU Post build outputs
    • Check the "Convert to binary file" box for both "Debug" and "Release" Configurations
  • C/C++ Build->Settings->MCU GCC Assembler->Preprocessor
    • Add the following symbol in "Define Symbols (-D)" field for both "Debug" and "Release" Configurations
USE_DYNAMIC_MEMORY_ALLOCATION

Save the modification and start the build

2. References