This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

基于TM4C123x的数据采集与记录实验程序解析



//数据采集与记录实验程序解析

//头文件
#include <string.h>
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "inc/hw_sysctl.h"
#include "driverlib/debug.h"
#include "driverlib/fpu.h"
#include "driverlib/adc.h"    //使用ADC驱动函数的头文件
#include "driverlib/hibernate.h"    //冬眠模式的API
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "driverlib/rom.h"
#include "driverlib/pin_map.h"
#include "grlib/grlib.h"
#include "grlib/widget.h"
#include "grlib/canvas.h"     //画布控件的原型
#include "usblib/usblib.h"     //USB库的主要头文件
#include "drivers/cfal96x64x16.h"
#include "drivers/buttons.h"
#include "utils/ustdlib.h"      //简单的标准库函数
#include "driverlib/gpio.h"
#include "driverlib/uart.h"
#include "drivers/slidemenuwidget.h"   //滑动窗口菜单的原型
#include "stripchartwidget.h"
#include "clocksetwidget.h"
#include "qs-logger.h"
#include "images.h"
#include "menus.h"
#include "acquire.h"
#include "inc/hw_gpio.h"
#include "inc/hw_hibernate.h"   //冬眠模式的定义和宏
#include "usbstick.h"
#include "usbserial.h"
#include "flashstore.h"

#define CLOCK_RATE              100
#define MS_PER_SYSTICK (1000 / CLOCK_RATE)
static volatile unsigned long g_ulTickCount;

//定义计时器中断的时钟速率已经系统时钟节拍的计时器。计时器中断用于
//程序中基本的计时
static unsigned long g_ulKeyFocusWidgetHandle;
//控件的控件处理函数,应该能接受到任何按钮事件

typedef enum
{
    STATE_IDLE,
    STATE_LOGGING,
    STATE_VIEWING,
    STATE_SAVING,
    STATE_ERASING,
    STATE_FREEFLASH,
    STATE_CLOCKSET,
    STATE_CLOCKEXIT,
} tLoggerState;
static tLoggerState g_sLoggerState = STATE_IDLE;
//记录数据采集器状态
static tConfigState g_sConfigState;
//程序的配置,保存睡眠时需要保存的信息
volatile tUSBMode g_eCurrentUSBMode = USB_MODE_NONE;
//基于探测模式下系统的USB OTG的当前状态
#define HCD_MEMORY_SIZE         128
//主控制器内存字节池的大小
unsigned char g_pHCDPool[HCD_MEMORY_SIZE];
//定义主控制器驱动的内存
static void ClockSetOkCallback(tWidget *pWidget, tBoolean bOk);
//用于时钟设置控件的回调函数的前置声明

//该函数提供一个简单的功能使程序的其它部分可以更新状态显示
void
SetStatusText(const char *pcTitle, const char *pcLine1, const char *pcLine2,
              const char *pcLine3)
{
    static const char cBlankLine[] = "                ";
 //以下为检查每个元素是否被传递,如果是,则更新其在状态显示上的文本
    pcTitle = pcTitle ? pcTitle : cBlankLine;
    MenuUpdateText(TEXT_ITEM_STATUS_TITLE, pcTitle);
    pcLine1 = pcLine1 ? pcLine1 : cBlankLine;
    MenuUpdateText(TEXT_ITEM_STATUS1, pcLine1);
    pcLine2 = pcLine2 ? pcLine2 : cBlankLine;
    MenuUpdateText(TEXT_ITEM_STATUS2, pcLine2);
    pcLine3 = pcLine3 ? pcLine3 : cBlankLine;
    MenuUpdateText(TEXT_ITEM_STATUS3, pcLine3);
//以下为当所有状态文本更新后控件的重新绘制
    WidgetPaint(WIDGET_ROOT);
    WidgetMessageQueueProcess();
}

//该函数为处理计时器超时中断的函数
void
SysTickIntHandler(void)
{
    g_ulTickCount++;
}

//该函数返回从上一次函数调用到现在经历的时钟节拍数
unsigned long
GetTickms(void)
{
    unsigned long ulRetVal;
    unsigned long ulSaved;
    static unsigned long ulLastTick = 0;

    ulRetVal = g_ulTickCount;
    ulSaved = ulRetVal;

    if(ulSaved > ulLastTick)
    {
        ulRetVal = ulSaved - ulLastTick;
    }
    else
    {
        ulRetVal = ulLastTick - ulSaved;
    }

    ulLastTick = ulSaved;

    return(ulRetVal * MS_PER_SYSTICK);
}

static void
ModeCallback(unsigned long ulIndex, tUSBMode eMode)
{
    g_eCurrentUSBMode = eMode;   //保存新的模式

//USB OTG模式改变的回调函数
switch(eMode)
    {
        case USB_MODE_HOST:
        {
            break;
        }
        case USB_MODE_DEVICE:
        {
            break;
        }
        case USB_MODE_NONE:
        {
            break;
        }
        default:
        {
            break;
        }
    }
}

static int
//该函数作用为从电池供电的内存获取记录器配置。配置从冬眠模块的的
//内存中读取配置,如果有效,函数返回0,如果无效,返回非0 的数。
GetSavedState(tConfigState *pState)
{
    unsigned long ulStateLen = sizeof(tConfigState) / 4;
    unsigned short usCrc16;

    ASSERT(pState);
    if(!pState)
    {
        return(1);
    }
    
    //从休眠内存中读取一块到程序状态结构中
    HibernateDataGet((unsigned long *)pState, ulStateLen);  

    if(pState->ulCookie != STATE_COOKIE)  //检查缓存状态值是正确的
    {
        return(1);
    }

    //找到块的16位循环校验。它存储在最后的位置。
    usCrc16 = ROM_Crc16Array(ulStateLen - 1, (const unsigned long *)pState); 

    //如果循环冗余校验不匹配,则这个块是有错的
    if(pState->ulCrc16 != (unsigned long)usCrc16)   
    {
        return(1);
    }

    return(0);
}

//该函数作用为从电池供电的内存中存储记录器配置。
//配置保存缓存值和一个CRC校验来确定有效性。
static void
SetSavedState(tConfigState *pState)
{
    unsigned long ulStateLen = sizeof(tConfigState) / 4;
    unsigned short usCrc16;

    ASSERT(pState);
    if(pState)
    {
        //将缓存值写入块中
        pState->ulCookie = STATE_COOKIE;    

        usCrc16 = ROM_Crc16Array(ulStateLen - 1,
                                 (const unsigned long *)pState);

        //将计算的CRC保存到结构中
        pState->ulCrc16 = (unsigned long)usCrc16;  

        //将整个块写入冬眠存储器中
        HibernateDataSet((unsigned long *)pState, ulStateLen); 
    }
}

static void
//用缺省值填充程序配置
GetDefaultState(tConfigState *pState)
{
    ASSERT(pState);
    if(pState)
    {
        MenuGetDefaultState(pState);  //从菜单系统中获取缺省值

        pState->cFilename[0] = 0;   //文件名设置为空

        pState->ulFlashStore = 0;    //为闪存设置伪地址

        pState->ulSleepLogging = 0;   //关闭睡眠记录
    }
}

//发送按键信息
static void
SendWidgetKeyMessage(unsigned long  ulMsg)
{
    WidgetMessageQueueAdd(WIDGET_ROOT, ulMsg, g_ulKeyFocusWidgetHandle, 0, 1, 1);
}


//该函数为菜单部件的回调函数。不论何时菜单激活一个与菜单相连的
//子控件,这个函数将被调用。当控件被停用,控制返回到菜单控件是
//也调用这个函数。它可以根据选择的菜单来触发不同的动作,并跟踪应用
//程序和用户界面控制的状态。这个函数在控件树消息处理的内容中被调用,
//故在做任何会影响显示或控件树的动作时要注意。
static void
WidgetActivated(tWidget *pWidget, tSlideMenuItem *pMenuItem,
                tBoolean bActivated)
{
    char *pcMenuText;

   //处理带状图的激活或停用。当用户选择“开始”菜单,
   //带状图部件就被激活
    if(pWidget == &g_sStripChart.sBase)
    {
        if(bActivated)  //如果带状图被激活,则启动记录器运行
        {
            //获取当前菜单状态
            MenuGetState(&g_sConfigState); 

            //在电池供电的存储器中保存状态
            SetSavedState(&g_sConfigState); 

            AcquireStart(&g_sConfigState); //启动记录器并更新记录器
            g_sLoggerState = STATE_LOGGING;
        }

        else  //如果带状图没有被激活,则停止记录器
        {
            AcquireStop();
            g_sLoggerState = STATE_IDLE;
        }
    }

    //以下为处理用于以数字形式显示获得的数据的容器的激活或者停用,
    //这发生在VIEW菜单使用时。
    else if((pWidget == &g_sAINContainerCanvas.sBase) ||
            (pWidget == &g_sAccelContainerCanvas.sBase) ||
            (pWidget == &g_sCurrentContainerCanvas.sBase) ||
            (pWidget == &g_sClockContainerCanvas.sBase) ||
            (pWidget == &g_sTempContainerCanvas.sBase))
    {
        if(bActivated)  //一个观测计被激活
        {
            static tConfigState sLocalState;

            //获取当前菜单配置状态,将其保存到本地存储中
            MenuGetState(&sLocalState); 

           //以下条语句为修改状态设置适合观测计使用的值,接收速率
           //为0.5S。所有的通道被选定。存储介质被设为“viewer”,
           //故获得模块将会把采集到的数据希尔合适的观测布。
            sLocalState.ucStorage = CONFIG_STORAGE_VIEWER;
            sLocalState.ulPeriod = 0x00000040;
            sLocalState.usSelectedMask = 0x3ff;
           
           //开启采集模块
            AcquireStart(&sLocalState);
            g_sLoggerState = STATE_VIEWING;
        }

        Else //观测器被停用,停止采集模块
        {
            AcquireStop();
            g_sLoggerState = STATE_IDLE;
        }
    }
   
    //处理状态显示被激活的情况。当任意几个菜单项被选定的时候
    //都会发生
    else if(pWidget == &g_sStatusContainerCanvas.sBase)
    {
        //获取当前菜单项的文本指针
if(pMenuItem)
        {
            pcMenuText = pMenuItem->pcText;
        }
        else
        {
            return;
        }
        
        //如果激活SAVE菜单,则flash的数据需要被保存到U盘中,
        //进入保存状态
        if(!strcmp(pcMenuText, "SAVE"))
        {
            if(bActivated)
            {
                g_sLoggerState = STATE_SAVING;
            }
            else
            {
                g_sLoggerState = STATE_IDLE;
            }
        }

        //如果激活ERASE菜单,则flash数据需要被擦除,进入擦除状态
        else if(!strcmp(pcMenuText, "ERASE DATA?"))
        {
            if(bActivated)
            {
                g_sLoggerState = STATE_ERASING;
            }
            else
            {
                g_sLoggerState = STATE_IDLE;
            }
        }

       //如果激活FLASH SPACE菜单,则用户将会看到flash中剩余空间
       //的大小,进入报告状态
        else if(!strcmp(pcMenuText, "FLASH SPACE"))
        {
            if(bActivated)
            {
                g_sLoggerState = STATE_FREEFLASH;
            }
            else
            {
                g_sLoggerState = STATE_IDLE;
            }
        }
    }

    //处理时钟设置控件的激活,停用是通过一个分离的回调函数处理的
    else if(pWidget == &g_sClockSetter.sBase)
    {
        unsigned long ulRTC;

        if(bActivated)  //如果时钟设置被激活,装载时间结构
        {
            //几秒内从RTC(实时时钟芯片)上获取当前时间
            ulRTC = HibernateRTCGet();

            //将RTC时间转换成时间结构
            ulocaltime(ulRTC, &g_sTimeClock); 

            //设置时钟设置控件停用时的回调函数。
            //由于时钟设置控件需要接管按键事件,当完成的时候需要
            //一个单独的回调函数
            ClockSetCallbackSet((tClockSetWidget *)pWidget,
                                ClockSetOkCallback); 
            //将按键事件交给时钟设置控件
            g_ulKeyFocusWidgetHandle = (unsigned long)pWidget;
            g_sLoggerState = STATE_CLOCKSET;
        }
    }
}

//当用户在时钟设置控件中点击OK或CANCEL时调用的函数
static void
ClockSetOkCallback(tWidget *pWidget, tBoolean bOk)
{
    unsigned long ulRTC;

    if(bOk)  //当选择OK按键时,只更新RTC
    {
        //将时钟设置控件替代的时间格式转换为秒
        ulRTC = umktime(&g_sTimeClock); 

        //如果转换是有效的,则将更新过的时钟写入到冬眠RTC中
        if(ulRTC != (unsigned long)(-1)) 
        {
            HibernateRTCSet(ulRTC);
        }
    }

    //时钟退出设置状态,主循环可以做一些清理工作
    g_sLoggerState = STATE_CLOCKEXIT;  
}

//初始化并运行数据记录器
int
main(void)
{
    tContext sDisplayContext;
    tContext sBufferContext;
    unsigned long uX;
    unsigned long uY;
    unsigned long ulHibIntStatus;
    tBoolean bSkipSplash = false;
    unsigned long ulSysClock;

    FPUEnable();
    FPULazyStackingEnable();

    ROM_SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ |SYSCTL_OSC_MAIN);
    ulSysClock = ROM_SysCtlClockGet();

    //初始化数据采集模块,这初始化ADC硬件。详见acquire.c 
    AcquireInit();   

    //使能冬眠外设
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_HIBERNATE); 

    //检查冬眠模块是否已经启动,如果启动,就读取保存的配置状态。
    //如果都可以了,然后检查我们是否是利用睡眠模式记录数据
    if(HibernateIsActive() && !GetSavedState(&g_sConfigState))  
    {
        //读取冬眠模块的状态的中断模式
        ulHibIntStatus = HibernateIntStatus(1); 

       //如果引脚打开,则意味着用户按下选择按键,应该终止睡眠状态的
       //记录器。在这种情况下,将跳出这个条件部分,进入下面的正常启
       //动,但是需要跳过启动画面,使用户得到立即响应
        if(ulHibIntStatus & HIBERNATE_INT_PIN_WAKE)  
        {
            HibernateIntClear(HIBERNATE_INT_PIN_WAKE); //清除中断标志
            bSkipSplash = 1;
        }

      //否则如果从冬眠状态缓解,但不是一个引脚打开,那么它一定是来
      //自RTC匹配。检查我们是否是在睡眠状态的记录,如果是这样,进
      //行一个简短的启动来采集数据,然后再进入睡眠状态
        else if(g_sConfigState.ulSleepLogging &&
                (ulHibIntStatus & HIBERNATE_INT_RTC_MATCH_0))
        {
           //启动记录器,传递配置。记录器应该配置成采集一个样本
            AcquireStart(&g_sConfigState);
            g_sLoggerState = STATE_LOGGING;
           //进入无限循环,运行采集,直到一个新的样本被采集并存储
            while(!AcquireRun())
            {
            }

            //数据采集已经结束,可以进入继续睡眠状态。
            //保存配置,然后激活冬眠。
            SetSavedState(&g_sConfigState); 

            //设置唤醒引脚或RTC匹配的条件,然后使进程处于冬眠状态
            HibernateWakeSet(HIBERNATE_WAKE_PIN | HIBERNATE_WAKE_RTC);
            HibernateRequest();

           //永远等待直到冬眠激活,处理器能源移除
            for(;;)  
            {
            }
        }
    }

   //否则,或者冬眠模块没有激活,或者保存的配置不正确。
   //将配置初始化到缺省状态。
    else
    {
        GetDefaultState(&g_sConfigState);
    }

    HibernateEnableExpClk(SysCtlClockGet());  //启动冬眠模块

    HibernateRTCTrimSet(0x7FFF); //设置RTC predivider trim寄存器的值

    HibernateRTCEnable();  //启动RTC
    g_sConfigState.ulSleepLogging = 0;
    SetSavedState(&g_sConfigState);

    CFAL96x64x16Init();

    ButtonsInit(); //初始化按键驱动
    MenuSetState(&g_sConfigState); //将存储的状态传递给菜单系统

    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0); //使能USB

//为USB运行配置需要的引脚
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
    ROM_GPIOPinConfigure(GPIO_PG4_USB0EPEN);
    ROM_GPIOPinTypeUSBDigital(GPIO_PORTG_BASE, GPIO_PIN_4);
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
    ROM_GPIOPinTypeUSBAnalog(GPIO_PORTL_BASE, GPIO_PIN_6 | GPIO_PIN_7);
    ROM_GPIOPinTypeUSBAnalog(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);

    if(CLASS_IS_BLIZZARD && REVISION_IS_A1)   //Erratum workaround for silicon revision A1.  VBUS must have pull-down
    {
        HWREG(GPIO_PORTB_BASE + GPIO_O_PDR) |= GPIO_PIN_1;
    }

    //初始化USB栈模式
USBStackModeSet(0, USB_MODE_OTG, ModeCallback);  

    USBStickInit();  //初始化将要被U盘使用的栈

    USBSerialInit();  //初始化将要被串行设备使用的栈

    //初始化USB控制器为2ms刷新率的双模式操作
USBOTGModeInit(0, 2000, g_pHCDPool, HCD_MEMORY_SIZE); 

    //初始化菜单模式。这个模式将控制用户界面的菜单系统
MenuInit(WidgetActivated);  

//配置时钟产生周期性中断 
   g_ulTickCount = 0;
    ROM_SysTickPeriodSet(ulSysClock / CLOCK_RATE);
    ROM_SysTickIntEnable();
    ROM_SysTickEnable();

GrContextInit(&sDisplayContext, &g_sCFAL96x64x16);
    GrContextInit(&sBufferContext, &g_sOffscreenDisplayA);

    //如果不跳过的话就显示启动界面。跳过的唯一的原因是程序在睡眠
//记录模式,用户只是利用select按键将其唤醒
if(!bSkipSplash)  
    {
        const unsigned char *pucSplashLogo = g_pucImage_TI_Black;

//绘制TI logo。动态滑入屏幕。允许select按键中断动画
        for(uX = 0; uX < 96; uX++)
        {
            if(ButtonsPoll(0, 0) & SELECT_BUTTON)
            {
                break;
            }
            GrImageDraw(&sDisplayContext, pucSplashLogo, 95 - uX, 0);
        }

//让logo在屏幕上留一小段时间。监视按键这样如果用户选择select
//按键,logo显示终止,应用程序立即开始
        while(g_ulTickCount < 400)
        {
            if(ButtonsPoll(0, 0) & SELECT_BUTTON)
            {
                break;
            }
        }

        if(ButtonsPoll(0, 0) & UP_BUTTON)  //扩展启动队列
        {
            for(uX = 0; uX < 96; uX += 4)
            {
                GrImageDraw(&sDisplayContext, g_ppucImage_Splash[(uX / 4) & 3], (long)uX - 96L, 0);
                GrImageDraw(&sDisplayContext, pucSplashLogo, uX, 0);
                ROM_SysCtlDelay(ulSysClock / 12);
            }
            ROM_SysCtlDelay(ulSysClock / 3);
            pucSplashLogo = g_ppucImage_Splash[4];
            GrImageDraw(&sDisplayContext, pucSplashLogo, 0, 0);
            ROM_SysCtlDelay(ulSysClock / 12);
        }

        //将初始菜单绘入屏幕外缓冲中
SlideMenuDraw(&g_sMenuWidget, &sBufferContext, 0);  

//绘制TI logo启动屏幕同时初始化屏幕上的菜单,移动坐标是logo
//滑出屏幕,菜单滑入屏幕   
for(uY = 0; uY < 64; uY++)
        {
            GrImageDraw(&sDisplayContext, pucSplashLogo, 0, -uY);
            GrImageDraw(&sDisplayContext, g_pucOffscreenBufA, 0, 63 - uY);
        }
    }
//将菜单控件增加到控件树中,发送初始化绘制的请求
    WidgetAdd(WIDGET_ROOT, (tWidget *)&g_sMenuWidget);
    WidgetPaint(WIDGET_ROOT);
//设置菜单控件的中心处理。任何按键事件都会被送到这个控件
    g_ulKeyFocusWidgetHandle = (unsigned long)&g_sMenuWidget;
    
    //无限循环运行程序
    while(1)
    {
        static unsigned long ulLastTickCount = 0;
       
        //每次时钟节拍一次,处理按键事件
        if(g_ulTickCount != ulLastTickCount)
        {
            unsigned char ucButtonState;
            unsigned char ucButtonChanged;

            ulLastTickCount = g_ulTickCount; //记录上一次节拍计数

            //读去抖动的按钮状态
            ucButtonState = ButtonsPoll(&ucButtonChanged, 0); 

           //将任何按键动作传递给控件消息处理机制。
           //有按键事件的控件将会获得这些事件
            if(BUTTON_PRESSED(SELECT_BUTTON, ucButtonState, ucButtonChanged))
            {
                SendWidgetKeyMessage(WIDGET_MSG_KEY_SELECT);
            }
            if(BUTTON_PRESSED(UP_BUTTON, ucButtonState, ucButtonChanged))
            {
                SendWidgetKeyMessage(WIDGET_MSG_KEY_UP);
            }
            if(BUTTON_PRESSED(DOWN_BUTTON, ucButtonState, ucButtonChanged))
            {
                SendWidgetKeyMessage(WIDGET_MSG_KEY_DOWN);
            }
            if(BUTTON_PRESSED(LEFT_BUTTON, ucButtonState, ucButtonChanged))
            {
                SendWidgetKeyMessage(WIDGET_MSG_KEY_LEFT);
            }
            if(BUTTON_PRESSED(RIGHT_BUTTON, ucButtonState, ucButtonChanged))
            {
                SendWidgetKeyMessage(WIDGET_MSG_KEY_RIGHT);
            }
        }

        //告诉OTG库距离上次调用已经过去多长时间
        USBOTGMain(GetTickms());  
        //根据需要调用函数来保持主机或设备运行
        if(g_eCurrentUSBMode == USB_MODE_DEVICE)
        {
            USBSerialRun();
        }
        else if(g_eCurrentUSBMode == USB_MODE_HOST)
        {
            USBStickRun();
        }
       //如果是在记录状态,则调用记录器运行功能。这保持数据不断采集
        if((g_sLoggerState == STATE_LOGGING) ||
           (g_sLoggerState == STATE_VIEWING))
        {
            if(AcquireRun() && g_sConfigState.ulSleepLogging)
            {
              //如果SLEEP模式启动,则已经存储了第一项数据,现在
                //保存数据开始冬眠。等待断电
                SetSavedState(&g_sConfigState);
                HibernateWakeSet(HIBERNATE_WAKE_PIN | HIBERNATE_WAKE_RTC);
                HibernateRequest();
                for(;;)
                {
                }
            }
            //如果VIEW取代记录,那么需要重绘来保证视图窗口更新
            if(g_sLoggerState == STATE_VIEWING)
            {
                WidgetPaint(WIDGET_ROOT);
            }
        }
        //如果在保存状态,则从闪存中保存数据到U盘
        if(g_sLoggerState == STATE_SAVING)
        {
            FlashStoreSave();  //从flash保存数据到USB

            g_sLoggerState = STATE_IDLE;  //回到闲置状态
        }
        //如果是在ERASE状态,擦除flash中的数据
        if(g_sLoggerState == STATE_ERASING)
        {
            FlashStoreErase();  //擦除

            g_sLoggerState = STATE_IDLE;  //返回闲置状态
        }
        //如果在flash report状态,则显示已经使用和剩下的闪存的大小
        if(g_sLoggerState == STATE_FREEFLASH)
        {
            FlashStoreReport(); //报告空闲的flash空间

            g_sLoggerState = STATE_IDLE;
        }
        //如果退出时钟设置控件,则意味着控制需要回到菜单系统
        if(g_sLoggerState == STATE_CLOCKEXIT)
        {
            //按键事件给菜单系统
           g_ulKeyFocusWidgetHandle = (unsigned long)&g_sMenuWidget; 
           
           //发送按键事件到菜单空间,这意味着左键被按下。这标志着
           //菜单空间停止当前自空间。菜单空间关闭时钟设置控件,
           //恢复对显示屏控制
            SendWidgetKeyMessage(WIDGET_MSG_KEY_LEFT);
            g_sLoggerState = STATE_IDLE;
        }

        //处理在控件队列中的任何消息。这使得用户交互运行
        WidgetMessageQueueProcess();
    }
}