搜索
bottom↓
回复: 96

浅析轮询和任务切换的优劣!

  [复制链接]

出0入0汤圆

发表于 2013-11-21 21:35:36 | 显示全部楼层 |阅读模式
         如今,人人都觉得上个os倍儿有面子,有时候就仅仅3-5个任务,2-3级优先级别,都得上个os,感觉上os肯定比轮询更高效率!
其实,mikal告诉大家,很多时候,不是这么回事情。轮询也是有价值的,我们不应该抛弃!切忌,切忌,轮询不是裸奔!
这里,笔者将以arm7为例,向大家展示,轮询也是有它自己的价值存在的。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入98汤圆

发表于 2013-11-21 21:46:20 | 显示全部楼层
开发周期

我以为项目开发周期太短
都是做好了OS 把task加进去就行

所以谈的不是效率 而是开发周期

出0入0汤圆

发表于 2013-11-21 21:48:51 | 显示全部楼层
等待展示

出0入0汤圆

 楼主| 发表于 2013-11-21 22:14:04 | 显示全部楼层
首先我们以 UCOS 为例,先看看ucos 在arm7中thumb指令下的 任务切换汇编代码:

OSCtxSw
;1.        保存当前处理器寄存器;        <<<
                STMFD         sp!, {lr}                                ; push pc 实际上push lr 是为了在pop的时候 直接获得新pc值
                STMFD         sp!, {r0-r12,lr}                ; push lr & register file
               
_OSCtxSw
;2.        将当前任务的堆栈指针保存到当前任务的OS_TCB中:OSTCBCur->OSTCBStkPtr = sp;
                LDR         r4, =OSTCBCur                    ; Get current task TCB address
                LDR         r5, [r4]
                STR         sp, [r5]                                 ; store sp in preempted tasks s TCB

;3.        调用用户定义的OSTaskSwHook();
                BL         OSTaskSwHook                     ; call Task Switch Hook

;4.        OSTCBCur  = OSTCBHighRdy;
                LDR                r4, =OSTCBHighRdy
                LDR                r4, [r4]
                LDR                r5, =OSTCBCur
                STR                r4, [r5]                                 ; OSTCBCur = OSTCBHighRdy

;5.        OSPrioCur = OSPrioHighRdy;
                LDR                r6, =OSPrioHighRdy
                LDRB        r6, [r6]
                LDR                r5, =OSPrioCur
                STRB        r6, [r5]                                 ; OSPrioCur = OSPrioHighRdy

;6.        得到需要恢复的任务的堆栈指针: sp = OSTCBHighRdy->OSTCBStkPtr;
                LDR         r6, =OSTCBHighRdy                ; Get highest priority task TCB address
                LDR         r6, [r6]
                LDR         sp, [r6]                                ; get new task s stack pointer
               

;7.8.将寄存器从新任务的堆栈中恢复出来(切换现场并return);

                LDMFD         sp!, {r0-r12,lr,pc}     ; pop new task r0-r12,lr & pc
;        end of OSCtxSw

出0入0汤圆

 楼主| 发表于 2013-11-21 22:31:57 | 显示全部楼层
上面仅仅是任务切换时候,针对寄存器操作的指令数,除开1和78,通常函数都需要的指令外,2-6,共14条语句(这里也按照14个指令周期算)【除开hook】;
从这个方面我们就可以意思到,在任务切换的时候,他需要14个指令周期,然后我们再往上推算,任务调度最少路径:
    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {                           /* Schedule only if all ISRs done and ...       */
        if (OSLockNesting == 0) {                      /* ... scheduler is not locked                  */
            y             = OSUnMapTbl[OSRdyGrp];      /* Get pointer to HPT ready to run              */
            OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
            if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
#endif
                OSCtxSwCtr++;                          /* Increment context switch counter             */
                OS_TASK_SW();                          /* Perform a context switch                     */
            }
        }
    }
    OS_EXIT_CRITICAL();

出0入0汤圆

 楼主| 发表于 2013-11-21 22:43:51 | 显示全部楼层
上面任务最少路径:c语言转换汇编分析,最少需要花费10条汇编指令;加上任务切换,总计消耗了24条汇编指令(这里不包括函数调用的压栈和出栈的开销)。
然后我们在分析,需要准备任务调度的函数开销。这个函数是 OS_EventTaskRdy,这个函数大概在17条汇编指令;好了,这里初步估算下,总计需要41条汇编指令周期;
当然,如果加上外围的,然后函数开销,总计在60条左右;由于时间有限,是刚刚临时分析的,所以对于启动任务调度一些相关的语句都没有做计算;当然,ucos并不是任务调度最优的os,所以,做大概的统计也够了。
现在各位看官,我们明白,一个ucos,即使什么事情都不干,就为了运行切换任务,其就消耗总计最少60个指令周期;

出0入0汤圆

 楼主| 发表于 2013-11-21 22:48:49 | 显示全部楼层
ok,各位列官,关于ucos的大体消耗我们也做好了初步统计,如果有不同意见的,请斧正。我对ucos也不是很懂,就知道这些,就怕漏了什么!

然后,我们开始以5个任务,3级优先级来讨论,ucos的配置和轮询配置,看看谁的开销大!

出100入101汤圆

发表于 2013-11-21 23:09:52 | 显示全部楼层
听课学习!

出0入0汤圆

 楼主| 发表于 2013-11-21 23:15:50 | 显示全部楼层
rclong 发表于 2013-11-21 21:46
开发周期

我以为项目开发周期太短

轮询也是可以加加task的,开发周期比os更短更好理解,关键资源还耗的少!起码任务深度控制比os更精准,而不会浪费一个字节!

出0入0汤圆

发表于 2013-11-21 23:34:42 来自手机 | 显示全部楼层
这牛x!

出0入0汤圆

 楼主| 发表于 2013-11-22 12:30:03 | 显示全部楼层
任务设定,按照通用嵌入式的基本功能,我们设定这样几个任务,1、key 任务;  2、uart 任务; 3、串口数据处理任务 ;4、按键菜单处理任务;

各位再添加几个吧,如果都是我自己确定,貌似不公平,等任务确定后,我们再讨论基于ucos的任务设计和基于笔者看重的轮询机制设计2者的优劣!

大家来添加几个任务吧!速度快!

出0入0汤圆

发表于 2013-11-22 12:56:32 | 显示全部楼层
mark学习

出0入0汤圆

发表于 2013-11-22 13:26:55 | 显示全部楼层
我的用法是一个Tick做一次切换,任务不用cpu是换其它任务
轮询会随着任务数增加而延迟吧。

出0入0汤圆

发表于 2013-11-22 14:07:06 | 显示全部楼层
轮询程序的编写比OS麻烦,对工程师的要求高,我在现在的公司推广无阻塞编程的时候,发现有些人连状态机是什么都不知道~
习惯阻塞式的任务编写模式,又不懂状态机,使用我提供的接口的时候,无从下手(我将读/写FLASH的操作统一用一个接口来管理,
写flash的时候,只是发送控制结构体,并没有实际写入flash,实际写完的时候才会置位用户提供的标志字节)。

特别是在通讯命令解析的时候,不用状态机,在这种无阻塞轮询环境下尤为麻烦,隐含BUG(状态机是根据过去的状态和当前的信号进行动作的,
如果不用状态机,只是用if*else来判断命令数组中的内容,在一个判断分支中轮询flash读取完毕的时候,可能会因为新的命令被写入数组,而导致
这个分支不再入!本来读取完毕将要进行的动作,就不会进行了,但目前那个工程师用一个很不优雅的方式 暂时解决了问题,但我没细看究竟
还会引出什么新的BUG)。

而且轮询,其实已经可以算是初步的多任务了,所以RTOS里的很多设计要点都要涉及到,如果不是因为程序要在51上运行,真心不想和别人合作轮询的项目

出0入0汤圆

发表于 2013-11-23 23:17:35 | 显示全部楼层
楼主需要清楚一点, rtos是一个框架,可以减少开发周期,利于维护。rtos 的实时性不等于运行速度快,牺牲一定的效率来换取实时性。

出0入0汤圆

发表于 2013-11-24 11:20:16 | 显示全部楼层
OS 方便扩展啊

出0入0汤圆

 楼主| 发表于 2013-11-24 15:11:56 | 显示全部楼层
chencc8 发表于 2013-11-22 14:07
轮询程序的编写比OS麻烦,对工程师的要求高,我在现在的公司推广无阻塞编程的时候,发现有些人连状态机是什 ...

理解你的想法和处境!

出0入0汤圆

发表于 2013-11-24 17:37:46 | 显示全部楼层
听课学习

出0入0汤圆

 楼主| 发表于 2013-11-25 09:17:39 | 显示全部楼层
worldsing 发表于 2013-11-22 13:26
我的用法是一个Tick做一次切换,任务不用cpu是换其它任务
轮询会随着任务数增加而延迟吧。 ...

任务增加,延时肯定增加。
但是,用150M以下 cpu做的产品,你觉得任务会很多嘛!
又或者说,中国式的1到2人的嵌入式软件开发,有必要要操作系统吗?

出0入0汤圆

 楼主| 发表于 2013-11-25 09:19:09 | 显示全部楼层
lulu爱 发表于 2013-11-23 23:17
楼主需要清楚一点, rtos是一个框架,可以减少开发周期,利于维护。rtos 的实时性不等于运行速度快,牺牲一 ...

您也需要了解一点,我一再强调,轮询也是一个框架! 也可以减少开发周期,利于维护!且在小规模任务下,速度和实时性都好!

出0入0汤圆

发表于 2013-11-25 09:33:04 | 显示全部楼层
博弈的问题。看你需要的是什么

出0入0汤圆

 楼主| 发表于 2013-11-25 09:42:36 | 显示全部楼层
Etual 发表于 2013-11-25 09:33
博弈的问题。看你需要的是什么

您说的对!
所以,我在开头已经设定了条件!

做为一个嵌入式软件工程师,需要的是严谨!

出0入0汤圆

发表于 2013-11-25 10:09:05 | 显示全部楼层
mikal 发表于 2013-11-25 09:17
任务增加,延时肯定增加。
但是,用150M以下 cpu做的产品,你觉得任务会很多嘛!
又或者说,中国式的1到2 ...

首先要澄清一点,我本人也不喜欢os
时间先不说,每个任务都是独立的栈,很费RAM
另外想看看楼主的轮询是怎么做的。

出0入0汤圆

 楼主| 发表于 2013-11-25 10:33:59 | 显示全部楼层
   ok,相信很多人,都是云里雾里,因为,刚接触,写轮询,然后前辈或者老同事训斥,什么年代还用这老土方法,
赶紧给我上rtos!然后上了rtos,天哪,好东西!确实很棒。但是,遇到一些深层次bug,搞死人啊,然后,自我
安慰,好东西总得有小小瑕疵吧,可以原谅!今天,在这里,我抛出小规模嵌入式代码,完全没有必要上rtos论点,
这个小规模限定是bin 64k以内(不包括资源)。相信很多人都没有见过code bin 30M 的规模,所以,我仅仅以
小规模来探讨了。ok,列了4个任务,然后无人补充,我在这里可以等待,因为我们的轮询是可以随时加任务的,
这样,告诉很多唯rtos快捷任务添加论者,条条道路通罗马!当你看到一个轮询系统给你提供了如下的函数,然后
没有汇编的porting,你是什么感触?

SOS_PostHiPriMessage   发送3级消息 ,阻塞式;
SOS_PostLoPriMessage   发送4级消息 ,阻塞式;


已经做的的内存管理,你需要的仅仅是按照你的系统设计合适的内存块和数量;
SOS_Malloc
SOS_Free

1级定时回调任务,精确定时式
SOS_SystemStartTimer
SOS_SystemStopTimer   

2级定时回调任务,定时式
SOS_UIStartTimer                        
SOS_UIStopTimer


这里的优先级由高到低是: 1->2->3;

1级: 精度依靠系统cpu中的timer响应时间,通常都是1us以内;
2级: 精度一般可以做到200us,如果48Mcpu以上,100us也很轻松;
3级: 精度不可预计,最快1us以内,最慢看系统1,2级任务处理情况;
4级: 精度不可预计,最快1us以内,最慢看系统1,2,3级任务处理情况;

获取当前tick数,64 bit;
SOS_GetTick

好东西 ,串口,或者其他spi口数据轮询任务,时间可以自己设置,
轮询到数据后,直接消息到应用层;
DML_PollingCallback

底层uart驱动挂接函数,
Uart_Init
Uart_Read
Uart_Write

底层spi驱动挂接函数,
SPI_Init
SPI_Read
SPI_Write

获取系统编译的时间,是时间,不是版本
SOS_GetDate

或取和设置系统的版本;
SOS_GetVersion
SOS_SetVersion


然后,其他需要用户配置的就一个函数;c语言,没有汇编
/* Initialize hardware timer as system tick timer. */
SOS_TIME_INIT();

  ok,轮询的相关使用的接口函数就这么多;
  系统代码1.2K,内存大头在消息队列,内存和timercallback,这个看系统任务大小设定的;
  每个消息消耗16 bytes,定时任务每个消耗16 bytes,内存看使用大小,一般系统跑起来,
  基本在300 bytes ram ,然后系统就一个stack,这个通常256足够了。

出0入0汤圆

 楼主| 发表于 2013-11-25 10:42:25 | 显示全部楼层
1、key 任务;  2、uart 任务; 3、串口数据处理任务 ;4、按键菜单处理任务;

首先根据不通的cpu ,配置 SOS_TIME_INIT();

然后启动了一个任务;
SOS_UIStartTimer(按键扫描程序);
然后按键扫描程序中调用  SOS_PostLoPriMessage;

然后应用层建立一个app_key 函数,填入到系统框架 void App_Task(S_MSG_TAG * pstMess)对应的case中;

串口任务只要按照要求写好
Uart_Init
Uart_Read
Uart_Write

  三个函数,然后,直接在框架函数 App_ReceiveData中添加自己的处理函数,就ok!
  
  好了,这样的要求,就这么几部完成了!
  
  各位觉得,和rtos比较,哪个移植最方便?

出0入0汤圆

 楼主| 发表于 2013-11-25 10:50:57 | 显示全部楼层
  然后各种需求来了,ok,加入tcp/IP;
  
  TCP/IP ,应用层,可以直接添加到App_ReceiveData中,
  然后TCP/IP底层驱动,你可以采用,轮询+SOS_PostLoPriMessage;也可以直接中断SOS_PostLoPriMessage;
  就是这么简单;
  
  ok,添加gui,
   用 SOS_UIStartTimer 建立一个刷屏任务;
   然后在 app_key 处理gui ;如果想把gui单独划分一个任务,也可以在 app_key中直接 SOS_PostLoPriMessage。

出0入0汤圆

 楼主| 发表于 2013-11-25 10:55:20 | 显示全部楼层
各位看官,到这里,明白了吧,简单吧! 这个事情告诉我们一个道理,学任何东西,要有自己的思想,然后找到
  一个平衡点,然后积累自己的东西!当你自己的系统用了无数年,只会越用越稳定。这就是财富!
  
  另外,请不要索要我的os,我不会开源的。我可以分享经验,但不习惯分享代码!

出0入0汤圆

发表于 2013-11-25 11:03:34 | 显示全部楼层
搬个凳子听讲

出0入0汤圆

 楼主| 发表于 2013-11-25 11:03:53 | 显示全部楼层
  ok,我要建立一个ad采样处理,
  好吧,ad是中断,那就在中断中 SOS_PostLoPriMessage;上层添加ad处理函数,填到case中;
  如果向ad循环,ok,那 就SOS_UIStartTimer
  
  ok,我要添加flash读写;
  同步写,就调函数,和本系统无关;读通常是同步,直接call函数,异步写,那就
  先在应用层添加一个写函数,然后SOS_PostLoPriMessage消息给他,应用层建立flash写可以做2个深度,支持2个任务
  同时写。

出0入0汤圆

 楼主| 发表于 2013-11-25 11:04:23 | 显示全部楼层
结贴了!写东西好累啊!

出0入0汤圆

发表于 2013-11-25 12:02:31 | 显示全部楼层
ok,看完了。表示自己还需要大量学习。

出0入0汤圆

发表于 2013-11-25 17:36:26 | 显示全部楼层
小单片机 用调度器,大单片机 用os !!论坛里 已经有很好的开源代码了·····

出0入0汤圆

发表于 2013-11-25 19:39:32 | 显示全部楼层
楼主是牛人,不过你这种想法06年 有一篇文档 “通用程序设计” 农民讲习所 已经奉上了,讲得很清楚
楼主能否作个比较你的有什么改进?

出0入0汤圆

发表于 2013-11-25 19:58:05 | 显示全部楼层
我表示看不懂。。
上OS有个好处是响应时间是可预计的,高优先任务不会因为总任务数的多少而响应时间变差

出0入4汤圆

发表于 2013-11-26 00:16:34 | 显示全部楼层
慢慢看   

出0入0汤圆

发表于 2013-11-26 07:01:06 | 显示全部楼层
单片机没上过OS,裸奔挺爽。

出0入0汤圆

发表于 2013-11-26 07:55:03 | 显示全部楼层
认真学习中~!

出0入264汤圆

发表于 2013-11-26 08:41:22 | 显示全部楼层
本帖最后由 mcu_lover 于 2013-11-26 08:47 编辑

其实楼主就是在传统的superloop基础之上添加了message 机制。
不知道楼主的任务有没有做优先级处理,如果没有处理的话,最坏的任务响应时间是所有任务轮巡一遍的时间。
这种结构,相比传统的superloop来说,扩展性,编写程序方便性确实有很大的提高。紧急事件靠中断处理,非紧急事件postmessage到队列,然后主循环处理。
SOS_PostHiPriMessage   发送3级消息 ,阻塞式;
SOS_PostLoPriMessage   发送4级消息 ,阻塞式;
楼主提到上面两个函数是阻塞式,是否意味着,在postmessage时候,直接调用了消息处理函数。这样就存在嵌套的可能性。应该分别提供阻塞式与非阻塞式的消息发送方式。
采用消息的方式进行程序设计,首先需要转变的就是程序设计思路,由传统的顺序处理,转变为事件处理。只有厘清了这些关系,方能最大程度利用其优势。
此外,消息机制的方式,在进行液晶界面设计时候,非常非常的方便。
static EUISTATE UITestModeChooseScreen(SYSMSG* pMsg)
{
        EUISTATE eUIState = E_UI_STATE_NULL;

        //----------------------------------------------------------//
        static  uint8 s_u8Cursor = 0, s_u8CurSel = 0;               
                uint8 u8Redraw = 0;
        //---------------------------------------------------------//
                       
                 switch(pMsg->uMsg)
        {
                                //进入本界面时候,触发一次进入事件
                case MSG_UI_ENTER_SCREEN:
                        //clear screen first
                        LcdClearScreen(0x00);

                                                   //可以从消息参数中得知,是从上层界面进入,还是从下层界面返回本界面
                                  //从上层进入,一般需要初始化某些参数
                         if(pMsg->wm == UI_ENTER_SCREEN_FROM_PARENT)
                         {

                                 //--------------------------------------------//
                                 //show choose menu
                                 MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, u8Redraw,  pMsg);

                                 //-------------------------------------------//
                         }
                                                  //从下层界面返回,不需要初始化,当然也可以根据你自己的需要进行处理
                         else if(pMsg->wm == UI_ENTER_SCREEN_FROM_CHILD)
                         {
                                 //--------------------------------------------//
                                 //show choose menu
                                 MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, 1,  pMsg);
                                 
                                 //-------------------------------------------//
                         }

                break;
               
                                 //退出本界面时候,触发一次此消息,可以在此消息处理中,完成资源的回收       
                case MSG_UI_EXIT_SCREEN:
                         if(pMsg->wm == UI_EXIT_SCREEN_TO_PARENT)
                         {
                                 s_u8Cursor = 0;
                                 s_u8CurSel = 0;
                         }

                break;
               
                                 //自定义的消息事件,一般由程序中的其它任务产生。
                case MSG_USER_TEST_MODE_CONFIRM:
                         //enter test screen
                        if(s_u8CurSel < sizeof(g_ModeItems)/sizeof(g_ModeItems[0]))
                        {
                                UIProcess_WndSwitch(g_ModeItems[s_u8CurSel].pTestFuncUI, UI_EXIT_SCREEN_TO_CHILD, s_u8CurSel);       
                        }

                break;

                                 //屏幕刷新消息
                case MSG_UI_PAINT_SCREEN:
                        LcdClearScreen(0x00);
                        MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, 1,  pMsg);
                break;

                                //按键消息处理
                case MSG_KEY:
                        if(pMsg->wm == (KEY_UI_ESC|KEY_DOWN))
                        {                       
                                //reset cursor
                                s_u8Cursor = 0;
                                s_u8CurSel = 0;       
                                UIProcess_WndSwitch(UIMainScreen, UI_EXIT_SCREEN_TO_PARENT,0);
                        }
                        else if((pMsg->wm == (KEY_UI_RIGHT|KEY_DOWN)) ||
                                (pMsg->wm == (KEY_UI_RIGHT|KEY_REPEAT)))
                        {
                                s_u8CurSel += BITMAP_MAXITEMS_ON_SCREEN;
                                MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, 1,  pMsg);
                        }
                       
                        else if((pMsg->wm == (KEY_UI_DOWN|KEY_DOWN)) ||
                                (pMsg->wm == (KEY_UI_DOWN|KEY_REPEAT)))
                        {
                                s_u8Cursor++;
                                s_u8CurSel++;
                                MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, u8Redraw,  pMsg);
                        }
                        else if((pMsg->wm == (KEY_UI_DOWN|KEY_UP)))
                        {
                                MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, u8Redraw,  pMsg);
                        }
                        else if((pMsg->wm == (KEY_UI_UP|KEY_DOWN)) ||
                                ((pMsg->wm == (KEY_UI_UP|KEY_REPEAT))))
                        {
                                if(s_u8Cursor)
                                {
                                        s_u8Cursor--;                 
                                }
                                if(s_u8CurSel)
                                {
                                        s_u8CurSel--;
                                }
                               
                                MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, u8Redraw,  pMsg);
                        }
                        else if((pMsg->wm == (KEY_UI_UP|KEY_UP)))
                        {
                                MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, u8Redraw,  pMsg);
                        }
                        else if((pMsg->wm == (KEY_UI_LEFT|KEY_DOWN)) ||
                                ((pMsg->wm == (KEY_UI_LEFT|KEY_REPEAT))))
                        {
                                if(s_u8CurSel >= BITMAP_MAXITEMS_ON_SCREEN)
                                {
                                        s_u8CurSel -= BITMAP_MAXITEMS_ON_SCREEN;
                                }
                               
                                MenuChooseTestMode(g_ModeItems, BITMAP_MAXITEMS_ON_SCREEN, sizeof(g_ModeItems)/sizeof(g_ModeItems[0]), &s_u8Cursor, &s_u8CurSel, 1,  pMsg);
                        }
                        else if(pMsg->wm == (KEY_UI_ENTER|KEY_DOWN))
                        {
                                UIProcess_DisplayChildWnd(UIConfirmChildScreen);
                        }
                break;

                                //定时器消息处理
                                 case MSG_TIMER:
                                 break;

      }

          return eUIState;
}
上面这个函数,是一个菜单界面的处理过程,可以非常清晰地看到,在进入菜单屏幕界面,退出菜单屏幕界面,在菜单屏幕界面循环,按键处理,自定义消息处理等等。
消息可以根据具体应用进行扩展,这样传统的程序设计,就变成了基于消息方式的处理。

出0入0汤圆

发表于 2013-11-26 09:02:33 | 显示全部楼层
顶,可以置酷了

出0入4汤圆

发表于 2013-11-26 09:19:48 | 显示全部楼层
轮询的时间可控性比os好点,

出0入0汤圆

发表于 2013-11-26 09:34:31 | 显示全部楼层
火钳名流+!

出0入93汤圆

发表于 2013-11-26 10:50:13 | 显示全部楼层
要是编译器从语言层面支持异步编程的话,那多幸福啊!可惜没有,就只好通过时间片、状态机、事件等等方法来模拟了。基于时间片的OS本质上就是轮询的,怎么可能会比轮询效率更高?

出0入0汤圆

发表于 2013-11-26 13:46:52 | 显示全部楼层
xlwq 发表于 2013-11-25 17:36
小单片机 用调度器,大单片机 用os !!论坛里 已经有很好的开源代码了····· ...

刚开始接触,能推荐几个比较好的代码学习吗?

出0入0汤圆

发表于 2013-11-26 18:36:33 | 显示全部楼层
mcu_lover 发表于 2013-11-26 08:41
其实楼主就是在传统的superloop基础之上添加了message 机制。
不知道楼主的任务有没有做优先级处理,如果没 ...

方便分享个比较完整的例子吗
最近在弄菜单的部分
谢谢

出0入0汤圆

 楼主| 发表于 2013-11-28 11:05:05 | 显示全部楼层
这里纠正下:
SOS_PostHiPriMessage   发送3级消息 ,阻塞式;
SOS_PostLoPriMessage   发送4级消息 ,阻塞式;
应为
SOS_PostHiPriMessage   发送3级消息 ,非阻塞式;
SOS_PostLoPriMessage   发送4级消息 ,非阻塞式;

由于是提取部分函数,原阻塞式函数是:
SOS_SendMessage  阻塞式;结合menu使用;

出0入0汤圆

 楼主| 发表于 2013-11-28 11:07:46 | 显示全部楼层
mcu_lover 发表于 2013-11-26 08:41
其实楼主就是在传统的superloop基础之上添加了message 机制。
不知道楼主的任务有没有做优先级处理,如果没 ...

我所讲的2级消息都是非阻塞式,我自己写错了;
另外我的系统是有4级优先级别的。也就是说,有抢占式。分为tick和中断2类抢占方式,另外是tick优先轮询机制!

出0入0汤圆

 楼主| 发表于 2013-11-28 11:26:29 | 显示全部楼层
rootxie 发表于 2013-11-25 19:39
楼主是牛人,不过你这种想法06年 有一篇文档 “通用程序设计” 农民讲习所 已经奉上了,讲得很清楚
楼主能 ...

今天花了点时间简单看了一下!
他是06年发表的。
我这个系统大概是05年有想法,07年才写好。
从我的看法分析,我这个轮询系统大体和他一样;
但从实现角度看,我整合了队列和消息于消息一体;
同时多了新功能,任务添加器,我这个系统是可以添加任务的;而他的我看只有消息机制;
另外,我这系统具备4级优先级,也就是说我的系统已经具备rtos的特性,但与rtos不同的是,我是没有任务栈和任务切换的;
另外,我的系统又封装了一层通讯任务;从移植角度更简便!

出0入36汤圆

发表于 2013-11-28 11:37:44 | 显示全部楼层
这个帖子好,向大牛学习学习

出0入0汤圆

发表于 2013-12-1 18:26:37 | 显示全部楼层
楼主想要做的,和没有做的都在micro raw os 里面已经实现了。基于状态机的事件触发机制。带消息以及优先级,没有任务切换。
http://www.raw-os.org/module.html

出0入0汤圆

发表于 2013-12-1 18:36:21 | 显示全部楼层
lulu爱 发表于 2013-12-1 18:26
楼主想要做的,和没有做的都在micro raw os 里面已经实现了。基于状态机的事件触发机制。带消息以及优先级 ...

这东西好厉害

出0入0汤圆

发表于 2013-12-8 12:13:04 | 显示全部楼层
直接表示看不懂,需要深入学习

出0入0汤圆

发表于 2014-1-28 09:26:36 | 显示全部楼层
轮询在时间控制上不如阻塞方式进行的灵活       但是轮询系统最大优点是时间精确可控的    做出的系统相对稳定些      各个任务的调度效率开销相对也比较低  尤其适合于短任务系统
轮询最大的缺陷就是运行那些长任务了,当然也可以通过状态机或者分散长任务来执行    但是设计起来就发现麻烦多了

出0入0汤圆

发表于 2014-2-27 11:03:47 | 显示全部楼层
需求决定方法,lz说到很对,不要一味否定一种

出0入0汤圆

发表于 2014-3-7 10:27:34 | 显示全部楼层
学习了。喜欢这样的贴子。

出0入0汤圆

发表于 2014-3-30 14:51:09 | 显示全部楼层
http://hi.baidu.com/anymcu/item/0915f860ad7281156995e649

这篇文章我觉得对2者之间的区别解释的比较清楚。

超级循环和事件驱动式框架
在QP状态机框架的免费支持论坛上,最近有位工程师提了一个问题:比较超级循环和事件驱动式框架,这个问题的全文我引用在下面。我认为这个问题是非常普遍的,因此我认为state-space博客的读者也可能感兴趣。

问题
我按经典的编程方法用switch语句来编写状态机,每个case表示一个不同的状态。超级循环(while(1))持续运行并基于过去来判断是否到达某个不同的状态,直到这条代码运行为止。
我最近在练习使用状态图的反应式对象方法,但我有点困惑了。首先,这里没有一个明确的超级循环,取而代之的是一个‘事件调度器’任务,它把事件输送给目标反应式对象的状态机,这个事件调度器任务可能为多个对象服务。
请解释这个新方法和传统的switch-case方法的差别。
你的状态机代码需要多个事件的动态实例化,用来存储这些事件,并在结束时删除它们。而使用经典方法时,并没有这些事件动态管理的需求,这让我认为新方法是个比较繁琐的方案。
我想听到一些和“你能可视化的设计状态”这种说法不同的东西。
有哪些事是用switch-case语句的经典方法不能做到,而带有事件管理的实时状态机框架能做到的呢?

回答
多年前我也习惯使用一个很好的老式超级循环来编程,它可以调用使用嵌套的switch语句组成的不同的状态机。它并没有一个明确的事件(event)概念,相反,这些状态机直接轮询全局变量或者硬件寄存器。例如,一个接收UART的状态机会直接轮询接受完成(receive-ready)寄存器。

尽管这个方法可能使用在比较简单的系统上,但它相当脆弱,低效,难以扩展。我知道这些,因为我在找它的各种难以捉摸的bug上花了许多夜晚和周末。一些主要的问题和系统内部的通讯有关。

一类问题和这个事实有关:一个全局变量或一个硬件寄存器只有保存一个事件的容量(深度为1的队列),因此当超级循环没有足够快的访问它时,会偶然的丢失一些事件实例。但更糟糕的不止这点。有时候,超级循环的一个状态机还在忙于处理某个事件,这时一个中断触发执行,并修改了和这个事件相关的一些全局变量。当这个状态机从中断恢复后,它发现自己在一个损毁的状态中,这个状态处理了旧事件的一部分和新事件的一部分。这些问题有一个补救办法,就是在访问全局变量时禁止中断,但这将导致长时间处理代码的时序被破坏。

另一类问题发生在超级循环内部各个互相通讯的状态机之间。直接的方法是从一个状态机调用另一个状态机,但这仅仅在后者没有同时企图调用前者时才能工作。不能工作的理由是,所有状态机形式,从最简单的switch语句到最复杂的UML状态图,都需要运行到完成RTC的事件处理过程。RTC意味着一个状态机必须在处理第二个事件前结束对当前事件的处理。那么在这种循环式调用场景里,第一个状态机还在处理某个事件,这时第二个状态机又调用它,请求它处理下一个事件。

我相信讨论到目前为止可以得到一个相当明显的推论:事件驱动系统需要将这些事件排队。但你不能对全局变量或寄存器进行排队。你需要一些事件对象。你还需要起码一个事件队列,但只有一个事件队列并不能让你容易的把事件区分优先级。因此,最后你拥了有多个带有优先级的队列。一旦你同意对队列进行优先级排队,你就需要一种机制按优先级顺序来调用这些状态机同时,它也确保符合RTC的事件处理过程。RTC起码需要满足2个条件:1) 当某个状态机还在处理上个事件时,不去调用它。2)当某个事件还在被处理时,不要改变这个事件。而且,你还需要一个事件驱动机制去派发超时事件。最后,所有这些必须用一种线程安全的方式去做,这样你就不需要担心被中断搞得状态被损毁。

现在,如果你再考虑事件,我希望你明白,它们需要携带 "什么发生了" 以及和这次发生(occurrence)有关的一些量化信息。例如,从以太控制器得到的一个PACKET_RECEIVED事件,它通知已经接收到一个以太包和这个包里的全部载荷。只有这样,一个状态机才能得到它需要处理这种事件的全部信息。把信号(signal)"什么发生了"和参数封装在一起的美妙之处在于,这些事件可以用线程安全的方法派送,在事件被需要时它们不会改变。但这个便利需要付出代价。一个事件,可能体积(size)很大,它必须在需要时一直存在,并尽快的被复用从而节省内存。QP框架提供动态事件管理来处理这类工作。

总而言之,现在对我来说相当明显,为了确保状态机是真正的健壮,可伸缩,高效,能用于实时系统,你需要一个围绕它的基础设施。一个简陋的超级循环不能够使一个状态机真正能够实用。在任何基于状态机的系统里,你需要事件,队列,RTC担保,时间事件。QP是这类基础设施里最简单和最轻量级的实现之一。当然,它需要一个学习曲线,但如果正确的看待QP的复杂性,QP实际上比最小的简陋型RTOS或全功能的printf()函数还要小。

原文链接
http://embeddedgurus.com/state-s ... t-driven-framework/

译注
超级循环 superloop  http://en.wikibooks.org/wiki/Emb ... r_Loop_Architecture

也被称为 前后台 foreground/background system,前台用一些中断处理例程ISR及时的处理来自外部的异步事件,后台是一个无限循环在处理较少时间约束的事件。

也被称为主程序+中断结构 main + ISR 。

出0入42汤圆

发表于 2014-3-30 15:36:35 | 显示全部楼层
对楼主的做法表示赞同,只有实践是检验一切真理的唯一标准。

出0入0汤圆

发表于 2014-6-30 15:43:07 | 显示全部楼层
如果程序内有很多延时ms级的地方,光是这点用轮询的方法来做就够你受了,非常的麻烦,即使是用protothread这样的状态机方法来做,程序的结构对工程师来说也很有挑战性

出0入0汤圆

发表于 2014-7-21 14:37:30 | 显示全部楼层
好高深的贴,表示仍需努力学习

出0入0汤圆

发表于 2014-7-21 14:49:38 | 显示全部楼层
myxiaonia 发表于 2014-6-30 15:43
如果程序内有很多延时ms级的地方,光是这点用轮询的方法来做就够你受了,非常的麻烦,即使是用protothread ...

内部状态机搞定,不过挺绕的,而且不是很精确,要精确的,要是要用到定时器来整了。

出0入0汤圆

发表于 2014-8-28 19:18:45 | 显示全部楼层
我以前也做过类似的处理,相当于把任务中While(1)的中间内容转到大的状态机轮循的case项下,中断中发消息实际就是改为状态机,最后一项一般是睡眠。

出0入0汤圆

发表于 2014-9-10 14:01:47 | 显示全部楼层
mcu_lover 发表于 2013-11-26 08:41
其实楼主就是在传统的superloop基础之上添加了message 机制。
不知道楼主的任务有没有做优先级处理,如果没 ...

学习了

出0入0汤圆

发表于 2015-11-10 20:06:52 | 显示全部楼层
学习了,不过貌似还不是很明白……

出0入0汤圆

发表于 2015-11-10 21:25:24 | 显示全部楼层
学习学习再学习

出0入0汤圆

发表于 2015-11-11 08:47:20 | 显示全部楼层
在我看来,最大的区别就是,delay延时了; 用裸奔的框架,在某些程度上,会让你痛不欲生;整个程序框架,不好处理,会被打得支零破碎;

出0入10汤圆

发表于 2015-11-11 09:14:48 | 显示全部楼层
本帖最后由 10xjzheng 于 2015-11-11 09:15 编辑

我不同意楼主的观点,首先轮询在一些情况下我也喜欢,比如按键的轮询,但是RTOS有他的好处,我承认OS的开销不小(最近上RTOS然后优化一段us级别的代码,RTOS的函数都不敢用),但是RTOS在调度方面的很爽的,更加优化CPU运行时间,我就举个例子,MP3好了,MP3首先要解码,再放进DMA-IIS传输到DAC,解码和后面的DMA协调好的话,开出几个缓冲区,用于存放解码后的PCM数据,我用RTOS就不一样了,首先我开出两个任务和一个DMA传输完成中断,A任务用于解码,B任务用于传输数据,两个任务和中断通过信号量或者消息队列协调得很容易,CPU占有率根据不同音乐文件的数据速率(声道数,采样率等等)在F429下只占用1%~16%不等的,然后剩下的时候我就可以用来干其他事情了,在裸机上不是不可以实现,但是有很多地方需要阻塞,大大占用CPU的时间。

出0入10汤圆

发表于 2015-11-11 09:19:19 | 显示全部楼层
我觉得你用轮询省下来的时候,一需要阻塞,没有灵活协调好的话,这浪费的时候很多啊!码代码了,

出0入0汤圆

发表于 2016-3-4 11:07:44 | 显示全部楼层
很厉害,我自己也想自己搞一个属于自己的程序架构。

出0入0汤圆

发表于 2016-3-4 11:22:08 | 显示全部楼层
最开始我用轮询,后来发现太low用了os,现在又用轮询发现很高大上

出0入0汤圆

发表于 2016-3-4 21:54:29 | 显示全部楼层
wang1216 发表于 2016-3-4 11:22
最开始我用轮询,后来发现太low用了os,现在又用轮询发现很高大上

你的轮询系统是怎么弄的呢?能否说说思路?

出0入0汤圆

发表于 2016-3-5 14:00:37 | 显示全部楼层
os的价值并不是在于效率高。
如果只是简单的轮询的话,往往会导致阻塞!

出0入0汤圆

发表于 2016-3-7 09:26:28 | 显示全部楼层
Yvan 发表于 2016-3-4 21:54
你的轮询系统是怎么弄的呢?能否说说思路?

struct st_mo        //
{
    const char* name;   //模块名称
    int (* init)(void); //模块初始化函数
    int (* run)(void);   //模块执行函数
    int (*getAddrType)(const char* s,void**  addr,int* type);//模块获得地址和类型的函数
};
用这种结构图实现的,每个模块都有自己的初始化和执行函数指针。

出0入0汤圆

发表于 2016-3-7 12:24:25 | 显示全部楼层
wang1216 发表于 2016-3-7 09:26
struct st_mo        //
{
    const char* name;   //模块名称

我现在觉得用轮询和os的区别:
用os占用内存大,而且不能随心所欲控制(指的是,所有底层东西都是自己支配),对新手也不容易上手,首先
要学习操作系统知识,要自己划分任务。当然了,用轮训写程序也需要自己划分任务,但是它很自由,没有太多
的限制,写代码的时候自己怎么想就可以怎么写(新手会出现程序乱,全局变量多等问题)。

用轮询就可以省空间。很多公司为了成本都不用RTOS,选择51来做。而我的情况是,我一直用的是简单的基于时
间触发的轮询,近来想让自己的程序模块化更好,移植性更好,所以关注了RTOS,想方设法自己实现消息机制,
有点高大上的感觉。如果不用RTOS,自己又可以实现一个不那么简单的调度器,就像楼主介绍的系统一样,也是
很好的。这是介于简单轮询和RTOS之间的一个调度器,它的效果就是模块化好和移植性好,但是又不那么占用内
存,对于任务不是特别多、特别复杂的产品,是非常实用的。任务太复杂的也只能选择RTOS了。

出0入0汤圆

发表于 2016-3-7 13:52:19 | 显示全部楼层
Yvan 发表于 2016-3-7 12:24
我现在觉得用轮询和os的区别:
用os占用内存大,而且不能随心所欲控制(指的是,所有底层东西都是自己支 ...

其实我用了很长一段时间os,但任务真的多了,还是要切换回轮询,信号量互斥锁等会让程序员焦头烂额。

出0入0汤圆

发表于 2016-3-7 14:02:35 | 显示全部楼层
wang1216 发表于 2016-3-7 13:52
其实我用了很长一段时间os,但任务真的多了,还是要切换回轮询,信号量互斥锁等会让程序员焦头烂额。 ...

RTOS我没有应用经验,没用过。你说的让我有点怕怕啊。意思是还是用轮询方便是吗?

出0入0汤圆

发表于 2016-3-7 14:07:15 | 显示全部楼层
Yvan 发表于 2016-3-7 14:02
RTOS我没有应用经验,没用过。你说的让我有点怕怕啊。意思是还是用轮询方便是吗? ...

没接触过操作系统的,最好去看看关于线程间通信同步的知识,还要学习正在使用的RTOS的特点,写程序时要注意设备使用冲突,共享变量访问冲突,中断程序中共享变量冲突,以及优先级反转,堆栈溢出等问题。祝君好运!

出0入0汤圆

发表于 2016-3-7 14:12:01 | 显示全部楼层
wang1216 发表于 2016-3-7 14:07
没接触过操作系统的,最好去看看关于线程间通信同步的知识,还要学习正在使用的RTOS的特点,写程序时要注 ...

其实我明白RTOS的原理,也知道要注意你说的这些问题。只是没用RTOS开发过产品,所以不知道具体怎么规划任务。曾看完了ucos的源码。

出0入0汤圆

发表于 2016-3-7 14:15:19 | 显示全部楼层
Yvan 发表于 2016-3-7 14:12
其实我明白RTOS的原理,也知道要注意你说的这些问题。只是没用RTOS开发过产品,所以不知道具体怎么规划任 ...

其实也没什么,就是RTOS不同于电脑操作系统,他实时性要求高,所以说是各个任务互不影响,但实际上还是影响很大,现在win上的程序也是能不用多线程都用单线程了。

出0入0汤圆

发表于 2016-4-2 13:27:36 | 显示全部楼层
25-27楼写的内容感觉没什么条理,看的头大,建议LZ重新组织下语言

出870入263汤圆

发表于 2016-7-27 20:23:49 | 显示全部楼层
楼主这么牛逼,应该用C语言,在嵌入式(RAM约几十KB,ROM约几百KB)仅有的小资源平台上,实现一个类似javascript的异步模型出来。
javascript在浏览器宿主中,其实是基于单线程的;但是其异步编程模型确实很强大。

出870入263汤圆

发表于 2016-7-27 20:27:24 | 显示全部楼层
很久以前,用contiki做过一个演示。contiki就是一个单线程的模型,让人感觉任务被划分,而且每个任务的流程还顺序化了,看不出状态机的样子。

出0入0汤圆

发表于 2016-7-27 20:49:29 | 显示全部楼层
本帖最后由 stdio 于 2016-7-27 21:03 编辑

..........

出0入0汤圆

发表于 2016-8-17 19:43:09 | 显示全部楼层
太牛逼了 还需要多多学习

出0入0汤圆

发表于 2016-8-24 14:39:35 | 显示全部楼层
曾使用过轮询机制(定时器+消息机制)在stm32上做了一个控制器,代码bin大约60k左右,感觉用轮询机制没有像多线程的并发与同步,开发起来得心应手,结构清晰。但是遇到一个不好解决的问题:系统中有定时采样统计,ad采样是通过定时器中断(实时性可以保证),但是计算是在主循环中,因此很难保证1s内计算50个点。

出0入0汤圆

发表于 2016-8-24 14:49:31 | 显示全部楼层
我就想问如果一个任务需要很精确每隔10ms执行一次,如果是轮询机制可以解决吗?

出0入8汤圆

发表于 2016-8-24 15:33:55 | 显示全部楼层
syj0925 发表于 2016-8-24 14:49
我就想问如果一个任务需要很精确每隔10ms执行一次,如果是轮询机制可以解决吗? ...

肯定 可以解决。

出0入0汤圆

发表于 2016-8-24 16:36:49 | 显示全部楼层

可以指点一下吗?不胜感激

出0入8汤圆

发表于 2016-8-24 18:18:14 | 显示全部楼层
syj0925 发表于 2016-8-24 16:36
可以指点一下吗?不胜感激


最简单的 10ms中断一下  你这个函数运行时间多长,如果函数运行的时间很短的话 可以中断中调用,主函数轮询的话,需要保证 其他函数运行的时间总和不能超过10ms,如果有函数运行时间长的,想办法分步,减少单次的运行时间。

出0入0汤圆

发表于 2016-8-24 23:25:38 | 显示全部楼层
不用OS,也可以不轮询。

出0入0汤圆

发表于 2016-8-25 09:49:13 | 显示全部楼层
kebaojun305 发表于 2016-8-24 18:18
最简单的 10ms中断一下  你这个函数运行时间多长,如果函数运行的时间很短的话 可以中断中调用,主函数轮 ...

一般其它任务操作时间不会超过10ms,但是有些时候需要操作片内的eeprom,这个很耗时(上位机配置参数,连续写个100多个byte,延时都是上百ms),又不好分解步骤,实在头痛。

出0入8汤圆

发表于 2016-8-25 10:21:34 | 显示全部楼层
syj0925 发表于 2016-8-25 09:49
一般其它任务操作时间不会超过10ms,但是有些时候需要操作片内的eeprom,这个很耗时(上位机配置参数,连 ...

那就不要一次写完,按照eeprom的 最大页  分多次写入,时间就没有那么长了。

出0入0汤圆

发表于 2016-8-25 10:39:16 | 显示全部楼层
kebaojun305 发表于 2016-8-25 10:21
那就不要一次写完,按照eeprom的 最大页  分多次写入,时间就没有那么长了。 ...

这样确实可以解决,但是感觉这样程序太碎片化了,而且实现还挺费劲的。

出0入8汤圆

发表于 2016-8-25 10:52:32 | 显示全部楼层
syj0925 发表于 2016-8-25 10:39
这样确实可以解决,但是感觉这样程序太碎片化了,而且实现还挺费劲的。 ...

为什么会碎片化,都是同一个函数。接口什么的都一样。

出0入0汤圆

发表于 2016-8-25 11:03:01 | 显示全部楼层
kebaojun305 发表于 2016-8-25 10:52
为什么会碎片化,都是同一个函数。接口什么的都一样。

说明一下:协议解析到上位机下发的任务设置,就会回调对应的处理函数,这个函数就是要把任务存储到eeprom中,如果要把这次存储操作分为几步来存储,应该是要记录状态,在mainloop中去储存了,这样增加了内存的开销。不知道又其它更好办法没?

出0入8汤圆

发表于 2016-8-25 11:05:20 | 显示全部楼层
syj0925 发表于 2016-8-25 11:03
说明一下:协议解析到上位机下发的任务设置,就会回调对应的处理函数,这个函数就是要把任务存储到eeprom ...

要么你干脆  中断中 10ms调用一下

出0入0汤圆

发表于 2016-8-25 11:11:44 | 显示全部楼层
kebaojun305 发表于 2016-8-25 11:05
要么你干脆  中断中 10ms调用一下

谢谢指教

出0入4汤圆

发表于 2016-9-12 15:41:40 | 显示全部楼层
本帖最后由 SCREA 于 2016-9-12 15:46 编辑
wang1216 发表于 2016-3-7 13:52
其实我用了很长一段时间os,但任务真的多了,还是要切换回轮询,信号量互斥锁等会让程序员焦头烂额。 ...


我现在头大, 做的射频通信,还要控制电机、采集数据,还TM低功耗,10uA以下。

任务几十个,没一个合适的,ucos也不成,耗电,只能轮训了。


这个项目定要做一个 低功耗的 操作系统,  接口标准且模块话,方便移植和增删改查,
争取自己把这颗种子做出来并种下发芽成长
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-24 05:41

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表