UART - DLPS

This example demonstrates data communication between the UART and a PC terminal when the system supports DLPS.

When the system is in the IDLE state, it will automatically enter the DLPS state.

When the PC terminal program (such as PUTTY or UartAssist) sends data to the SoC, a low level on the UART_RX_PIN will wake the system from DLPS. When the PC terminal program sends data again, the SoC receives the data and sends the same data back to the PC terminal. You can observe the received data on the PC terminal.

Requirements

The sample supports the following development kits:

Development Kits

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

For more requirements, please refer to Quick Start.

Wiring

Connect P3_0 (UART TX) to the RX pin of the FT232 and P3_1 (UART RX) to the TX pin of the FT232.

Building and Downloading

This sample can be found in the SDK folder:

Project file: board\evb\io_sample\UART\DLPS\mdk

Project file: board\evb\io_sample\UART\DLPS\gcc

Please follow these steps to build and run the example:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

Preparation Phase

Launch PuTTY or UartAssist or other PC terminals, connect to the used COM port, and configure the following UART settings:

  • Baud rate: 115200

  • 8 data bits

  • 1 stop bit

  • No parity

  • No hardware flow control

Testing Phase

  1. When the EVB resets, this example begins by sending ### Welcome to use UART demo ###\r\n. Observe the string appearing on the PC terminal, then the system enters DLPS mode, and the Debug Analyzer displays information about entering DLPS.

    enter dlps
    
  2. Type any data on the PC terminal, a low level on the UART_RX_PIN will wake the system from DLPS. The Debug Analyzer displays information about exiting DLPS.

    exit dlps
    
  3. When the PC terminal program sends data again,. the SoC receives the data and sends the same data back to the PC terminal. You can observe the received data on the PC terminal.

Code Overview

This chapter will be introduced according to the following several parts:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. Functional implementation after initialization will be introduced in chapter Function Implementation.

Source Code Directory

  • Project directory: sdk\board\evb\io_sample\UART\DLPS

  • Source code directory: sdk\src\sample\io_sample\UART\DLPS

Source files are currently categorized into several groups as below.

└── Project: dlps
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            └── ROM.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            ├── rtl876x_io_dlps.c
            └── rtl876x_uart.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            ├── main.c
            ├── ancs.c
            ├── app.c
            ├── app_task.c
            └── io_uart.c

Initialization

When the EVB reset is initiated, the main() function is called, and the following process will be executed:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    global_data_init();
    board_init();
    le_gap_init(APP_MAX_LINKS);
    gap_lib_init();
    app_le_gap_init();
    app_le_profile_init();
    pwr_mgr_init();
    task_init();
    os_sched_start();

    return 0;
}

Note

le_gap_init(), gap_lib_init(), app_le_gap_init, and app_le_profile_init are related to the initialization of the privacy management module. Refer to the initialization process description in LE Peripheral Privacy.

The specific initialization process related to peripherals is as follows:

  1. In global_data_init, execute global_data_uart_init. This function is for global initialization and includes the following processes:

    1. Set the flag IO_UART_DLPS_Enter_Allowed to PM_CHECK_PASS, indicating that DLPS state can be entered.

    2. Initialize the UART receive counter UART_RX_Count and the UART receive array UART_RX_Buffer.

    void global_data_uart_init(void)
    {
        IO_UART_DLPS_Enter_Allowed = PM_CHECK_PASS;
        UART_RX_Count = 0;
        memset(UART_RX_Buffer, 0, sizeof(UART_RX_Buffer));
    }
    
  2. In board_init, execute board_uart_init, which sets up PAD/PINMUX with the following steps:

    1. Configure PAD: set the pins, PINMUX mode, PowerOn, internal pull-up, and disable output.

    2. Configure PINMUX: set the pins to UART0_TX and UART0_RX functions.

  3. After executing os_sched_start() to start task scheduling, execute driver_init in the main task app_main_task to initialize and configure the peripheral drivers.

  4. In driver_init, execute driver_uart_init, which initializes the UART peripheral with the following steps:

    1. Enable the RCC clock.

    2. Set the default UART baud rate to 115200.

    3. Configure the UART receive interrupt UART_INT_RD_AVA and UART receive idle interrupt UART_INT_RX_IDLE.

    void driver_uart_init(void)
    {
        RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE);
    
        /* uart init */
        UART_InitTypeDef UART_InitStruct;
        UART_StructInit(&UART_InitStruct);
    
        UART_Init(UART0, &UART_InitStruct);
    
        //enable rx interrupt and line status interrupt
        UART_MaskINTConfig(UART0, UART_INT_RD_AVA, DISABLE);
        UART_MaskINTConfig(UART0, UART_INT_RX_IDLE, DISABLE);
        UART_INTConfig(UART0, UART_INT_RD_AVA, ENABLE);
        UART_INTConfig(UART0, UART_INT_RX_IDLE, ENABLE);
    
        /*  Enable UART IRQ  */
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel         = UART0_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelCmd      = (FunctionalState)ENABLE;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_Init(&NVIC_InitStruct);
    }
    
  5. Execute pwr_mgr_init, which is a function for setting the voltage mode of DLPS, including the following process:

    1. Register the user-entering DLPS callback function app_enter_dlps_config, and register the user-exiting DLPS callback function app_exit_dlps_config.

      1. In app_enter_dlps_config, execute the io_uart_dlps_enter function to set the pin to SW mode and configure the DLPS wake-up method.

        void io_uart_dlps_enter(void)
        {
            DBG_DIRECT("enter dlps");
            /* Switch pad to Software mode */
            Pad_ControlSelectValue(UART_TX_PIN, PAD_SW_MODE);
            Pad_ControlSelectValue(UART_RX_PIN, PAD_SW_MODE);
        
            System_WakeUpPinEnable(UART_RX_PIN, PAD_WAKEUP_POL_LOW, 0, 0);
        }
        
      2. Execute the io_uart_dlps_exit function within app_exit_dlps_config to set the pin to PINMUX mode.

        void io_uart_dlps_exit(void)
        {
            /* Switch pad to Pinmux mode */
            Pad_ControlSelectValue(UART_TX_PIN, PAD_PINMUX_MODE);
            Pad_ControlSelectValue(UART_RX_PIN, PAD_PINMUX_MODE);
            DBG_DIRECT("exit dlps");
        
        }
        
    2. Register hardware control callback functions: DLPS_IO_EnterDlpsCb and DLPS_IO_ExitDlpsCb. Entering DLPS will save the CPU, PINMUX, Peripheral, etc., while exiting DLPS will restore the CPU, PINMUX, Peripheral, etc.

    3. Set the power mode to DLPS mode.

    4. Set the wake-up method from DLPS to ADC_DLPS_WAKEUP_PIN low-level wake-up.

    void pwr_mgr_init(void)
    {
    #if DLPS_EN
        if (false == dlps_check_cb_reg(app_dlps_check_cb))
        {
            APP_PRINT_ERROR0("Error: dlps_check_cb_reg(app_dlps_check_cb) failed!");
        }
        DLPS_IORegUserDlpsEnterCb(app_enter_dlps_config);
        DLPS_IORegUserDlpsExitCb(app_exit_dlps_config);
        DLPS_IORegister();
        lps_mode_set(PLATFORM_DLPS_PFM);
    
        /* Config WakeUp pin */
        System_WakeUpPinEnable(ADC_DLPS_WAKEUP_PIN, PAD_WAKEUP_POL_LOW, 0, 0);
    #else
        lps_mode_set(LPM_ACTIVE_MODE);
    #endif
    }
    

Functional Implementation

  1. In app_main_task, after initializing the UART peripheral, send the string ### Welcome to use UART demo ###\r\n to the PC.

    void app_main_task(void *p_param)
    {
        ...
    
        driver_init();
    
        /* Send demo string */
        uint8_t demo_str_len = 0;
        char *demo_str = "### Welcome to use UART demo ###\r\n";
        demo_str_len = strlen(demo_str);
        memcpy(String_Buf, demo_str, demo_str_len);
        uart_senddata_continuous(UART0, String_Buf, demo_str_len);
    
        ...
    }
    
  2. When the system is in IDLE state, it will automatically enter DLPS state. When the PC sends data to the SoC, a low level appears on UART_RX_PIN, waking up the system and exiting the DLPS state. When the system is awakened, it enters the System_Handler.

    1. Clear the wake-up interrupt pending bit.

    2. Disable the wake-up function.

    3. Set the global variable IO_UART_DLPS_Enter_Allowed to PM_CHECK_FAIL, indicating that DLPS state cannot be entered.

    void System_Handler(void)
    {
        APP_PRINT_INFO0("[main] System_Handler");
        if (System_WakeUpInterruptValue(UART_RX_PIN) == SET)
        {
            System_WakeUpPinDisable(UART_RX_PIN);
            Pad_ClearWakeupINTPendingBit(UART_RX_PIN);
            IO_UART_DLPS_Enter_Allowed = PM_CHECK_FAIL;
        }
    }
    
  3. When the PC sends data again, the SoC receives the data, triggering the UART_INT_RD_AVA or UART_INT_RX_IDLE interrupt and enters the interrupt handling function.

  4. In the UART interrupt handling function, if UART is receiving data, it will trigger the UART_INT_RD_AVA interrupt, and the processing flow is as follows:

    1. Disable the UART_INT_RD_AVA interrupt.

    2. Execute UART_GetIID() to obtain the interrupt ID type.

      1. When the ID is UART_INT_ID_RX_LEVEL_REACH (RX FIFO data length reaches RX FIFO threshold UART_RxThdLevel), receive FIFO data and save it to UART_RX_Buffer.

      2. When the ID is UART_INT_ID_RX_DATA_TIMEOUT (at least one UART data in RX FIFO and no more data coming in for 4-byte time), receive FIFO data and save it to UART_RX_Buffer.

    3. Enable the UART_INT_RD_AVA interrupt.

    void UART0_Handler()
    {
        uint16_t rx_len = 0;
    
        /* Read interrupt id */
        uint32_t int_status = UART_GetIID(UART0);
    
        /* Disable interrupt */
        UART_INTConfig(UART0, UART_INT_RD_AVA | UART_INT_RX_LINE_STS, DISABLE);
    
        ...
    
        switch (int_status & 0x0E)
        {
        /* Rx time out(0x0C). */
        case UART_INT_ID_RX_DATA_TIMEOUT:
            rx_len = UART_GetRxFIFODataLen(UART0);
            UART_ReceiveData(UART0, &UART_RX_Buffer[UART_RX_Count], rx_len);
            UART_RX_Count += rx_len;
            break;
    
        /* Receive line status interrupt(0x06). */
        case UART_INT_ID_LINE_STATUS:
            break;
    
        /* Rx data valiable(0x04). */
        case UART_INT_ID_RX_LEVEL_REACH:
            rx_len = UART_GetRxFIFODataLen(UART0);
            UART_ReceiveData(UART0, &UART_RX_Buffer[UART_RX_Count], rx_len);
            UART_RX_Count += rx_len;
            break;
    
        /* Tx fifo empty(0x02), not enable. */
        case UART_INT_ID_TX_EMPTY:
            /* Do nothing */
            break;
        default:
            break;
        }
    
        /* enable interrupt again */
        UART_INTConfig(UART0, UART_INT_RD_AVA, ENABLE);
    }
    
  5. In the UART interrupt handler, the UART completes data reception and triggers the UART_FLAG_RX_IDLE interrupt (after reading the RX FIFO data, no data enters the RX FIFO within the RX idle timeout period). The workflow is as follows:

    1. Disable the UART_INT_RX_IDLE interrupt.

    2. Define the message type and send the message to the app task. After the main task detects the UART message, it processes the message in io_uart_handle_msg.

      1. The SoC sends the received message back to the PC.

      2. Execute the global initialization function, resetting the UART receive array and the receive count.

      3. After the UART finishes sending data, set IO_UART_DLPS_Enter_Allowed to PM_CHECK_PASS, indicating that it can enter the DLPS state.

    3. Clear the receive FIFO and re-enable the UART_INT_RX_IDLE interrupt.

    4. After exiting the interrupt handler, the system re-enters the DLPS state.

    void UART0_Handler()
    {
        ...
    
        if (UART_GetFlagStatus(UART0, UART_FLAG_RX_IDLE) == SET)
        {
            /* Clear flag */
            UART_INTConfig(UART0, UART_INT_RX_IDLE, DISABLE);
    
            /* Send msg to app task */
            T_IO_MSG int_uart_msg;
    
            int_uart_msg.type = IO_MSG_TYPE_UART;
            int_uart_msg.subtype = IO_MSG_UART_RX;
            UART_RX_Buffer[UART_RX_Count] = UART_RX_Count;
            int_uart_msg.u.buf = (void *)(&UART_RX_Buffer);
            APP_PRINT_INFO0("[io_uart] UART0_Handler: Send int_uart_msg");
            if (false == app_send_msg_to_apptask(&int_uart_msg))
            {
                APP_PRINT_INFO0("[io_uart] UART0_Handler: Send int_uart_msg failed!");
                //Add user code here!
                return;
            }
    //        IO_UART_DLPS_Enter_Allowed = PM_CHECK_PASS;
            UART_ClearRxFIFO(UART0);
            UART_INTConfig(UART0, UART_INT_RX_IDLE, ENABLE);
        }
    
        ...
    }
    
    void io_uart_handle_msg(T_IO_MSG *io_uart_msg)
    {
    //    uint8_t *p_buf = io_uart_msg.u.buf;
        uint16_t subtype = io_uart_msg->subtype;
        if (IO_MSG_UART_RX == subtype)
        {
            uart_senddata_continuous(UART0, UART_RX_Buffer, UART_RX_Count);
            global_data_uart_init();
            while (UART_GetFlagStatus(UART0, UART_FLAG_TX_FIFO_EMPTY) == 0) { IO_UART_DLPS_Enter_Allowed = PM_CHECK_PASS; }
        }
    }