UART - DLPS

该示例演示系统支持 DLPS 时,UART 与 PC 终端进行数据通信。

当系统处于 IDLE 状态时,会自动进入 DLPS 状态。

PC 终端程序(如 PUTTY 或 UartAssist)发送数据,UART_RX_PIN 出现低电平会将系统从 DLPS 唤醒。 PC 终端程序再次发送数据,SoC 接收到数据,并将相同数据发回给 PC 终端,在 PC 终端观察收到相同数据。

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

更多信息请参考 快速入门

硬件连线

连接 P3_0(UART TX)和 FT232 的 RX,P3_1(UART RX)和 FT232 的 TX。

编译和下载

该示例的工程路径如下:

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

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

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译 APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MP Tool 给出的步骤将 app bin 烧录至 EVB 内。

  5. 按下 reset 按键,开始运行。

测试验证

准备阶段

启动 PuTTY 或 UartAssist 等 PC 终端,连接到使用的 COM 端口,并进行以下 UART 设置:

  • 波特率: 115200

  • 8 数据位

  • 1 停止位

  • 无校验

  • 无硬件流控

测试阶段

  1. 当 EVB 复位后,该示例开始发送 ### Welcome to use UART demo ###\r\n,观察 PC 终端上出现的字符串,随后系统进入 DLPS 状态,在 Debug Analyzer 内显示进入 DLPS 信息。

    enter dlps
    
  2. PC 端发送任意数据,UART_RX_PIN 出现低电平会将系统从 DLPS 唤醒,在 Debug Analyzer 内显示退出 DLPS 信息。

    exit dlps
    
  3. PC 终端程序再次发送数据,SoC 接收到数据,并将相同数据发回给 PC 终端,在 PC 终端端观察收到相同数据。

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

  • 工程路径: sdk\board\evb\io_sample\UART\DLPS

  • 源码路径: sdk\src\sample\io_sample\UART\DLPS

该工程的工程文件代码结构如下:

└── 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

初始化

当 EVB 复位启动时,调用 main() 函数,将执行以下流程:

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;
}

备注

le_gap_init()gap_lib_init()app_le_gap_initapp_le_profile_init 等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。

与外设相关的初始化流程具体如下:

  1. global_data_init 中,执行 global_data_uart_init ,该函数为全局初始化,包含如下流程:

    1. 将标志位 IO_UART_DLPS_Enter_Allowed 置为 PM_CHECK_PASS ,表示可以进入 DLPS 状态。

    2. 初始化 UART 接收计数器 UART_RX_Count ,初始化 UART 接收数组 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. board_init 中,执行 board_uart_init ,该函数为 PAD/PINMUX 设置,包含如下流程:

    1. 配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出失能。

    2. 配置 PINMUX:配置引脚分别为 UART0_TX 和 UART0_RX 功能。

  3. 在执行 os_sched_start() 开启任务调度后,在 app_main_task 主任务内,执行 driver_init 对外设驱动进行初始化配置。

  4. driver_init 中执行 driver_uart_init ,该函数为 UART 外设的初始化,包含如下流程:

    1. 使能 RCC 时钟。

    2. 默认配置 UART 的波特率为 115200。

    3. 配置 UART 接收中断 UART_INT_RD_AVA 和 UART 接收空闲中断 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. 执行 pwr_mgr_init ,该函数为 DLPS 的电压模式设置,包含如下流程:

    1. 注册用户进入 DLPS 回调函数 app_enter_dlps_config ,注册用户退出 DLPS 回调函数 app_exit_dlps_config

      1. app_enter_dlps_config 内执行 io_uart_dlps_enter 函数,设置引脚为 SW 模式,设置 DLPS 唤醒方式。

        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. app_exit_dlps_config 内执行 io_uart_dlps_exit 函数,设置引脚为 PINMUX 模式。

        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. 注册硬件控制回调函数 DLPS_IO_EnterDlpsCbDLPS_IO_ExitDlpsCb ,进入 DLPS 会保存 CPU、PINMUX、Peripheral 等,退出 DLPS 会恢复 CPU、PINMUX、Peripheral 等。

    3. 设置电源模式为 DLPS 模式。

    4. 设置唤醒 DLPS 方式为 ADC_DLPS_WAKEUP_PIN 低电平唤醒。

    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
    }
    

功能实现

  1. app_main_task 内,初始化 UART 外设后,发送字符串 ### Welcome to use UART demo ###\r\n 至 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. 当系统处于 IDLE 状态时,会自动进入 DLPS 状态。当 PC 端发送数据至 SoC 时,UART_RX_PIN 出现低电平,唤醒系统,退出 DLPS 状态。当系统被唤醒时,进入 System_Handler

    1. 清除唤醒中断挂起位。

    2. 失能唤醒功能。

    3. 设置全局变量 IO_UART_DLPS_Enter_AllowedPM_CHECK_FAIL ,代表不可进入 DLPS 状态。

    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. 当 PC 端再次发送数据时,SoC 接收到数据,触发 UART_INT_RD_AVAUART_INT_RX_IDLE 中断,进入中断处理函数。

  4. 在 UART 中断处理函数中,若 UART 正在接收数据会触发 UART_INT_RD_AVA 中断,处理流程如下:

    1. 关闭 UART_INT_RD_AVA 中断。

    2. 执行 UART_GetIID() ,获得中断标志 ID 类型。

      1. 当 ID 为 UART_INT_ID_RX_LEVEL_REACH (RX FIFO 数据长度达到 RX FIFO 阈值 UART_RxThdLevel )时,接收 FIFO 数据,保存到 UART_RX_Buffer 中。

      2. 当 ID 为 UART_INT_ID_RX_DATA_TIMEOUT (RX FIFO 中至少有一个 UART 数据,并且不再有数据进来保持 4 个字节时间)时,接收 FIFO 数据,保存到 UART_RX_Buffer 中。

    3. 开启 UART_INT_RD_AVA 中断。

    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. 在 UART 中断处理函数中,UART 完成接收数据会触发 UART_FLAG_RX_IDLE 中断(读空 RX FIFO 数据后,在 RX 空闲超时时间内没有数据进入 RX FIFO),工作流程如下:

    1. 失能 UART_INT_RX_IDLE 中断。

    2. 定义消息类型,发送消息给 app task。主 task 检测到 UART 消息后,在 io_uart_handle_msg 内对消息进行处理。

      1. SoC 将收到的消息发回至 PC 端。

      2. 执行全局初始化函数,重置 UART 接收数组和接收数量。

      3. 等待 UART 发送数据完毕后,将 IO_UART_DLPS_Enter_Allowed 置为 PM_CHECK_PASS,代表可以进入 DLPS 状态。

    3. 清除接收 FIFO,重新使能 UART_INT_RX_IDLE 中断。

    4. 退出中断处理函数后,系统重新进入 DLPS 状态。

    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; }
        }
    }