搜索
bottom↓
回复: 194

看了"时间触发嵌入式系统设计",问几个时间触发的问题:请看过的网友及&qu

[复制链接]

出0入0汤圆

发表于 2008-3-6 09:15:00 | 显示全部楼层 |阅读模式
看了"时间触发嵌入式系统设计",问几个时间触发的问题:请看过的网友及"傻孩子","shaozh"帮忙。
1。书中的"任何时候只有一个中断,即调度器的时标中断",绝对是稳定的,但大家作系统时是否有必要必须这样用?是否能完全满足开发需要?
比如一个系统有上、下位机通讯,9600波特率,按键,128*64LCD显示,常用的作法:
void main(void)
{
        main_Init();
        while(1)
        {
                if(ms60_Flag)//60ms一个循环,类似调试器的时标
                {
                        Scan_Key();//查键
                        Key_Deal();//键处理
                        LCD();//128*64显示
                        TXUN_Down();//下位机通讯
                        ms60_Flag = 0;       
                }
        }       
}
void TXUN_Up(void) interrupt 4 using 1//与上位机的通讯中断程序
{
       
}
void Time0(void) interrupt 1 using 2
{
        ...
        ms60_Flag =1;       
}
但这样就违背了一个中断的原则,时标中断有与上位机的通讯中断发生。
大家是怎么处理的,用什么样的程序结构?

2.关于调度器的任务划分问题,有LCD显示的系统里,键处理除与菜单有关,更与显示有关,比如设置不同的参数有不同的界面。键处理Key_Deal()是作为一个任务添加到调度器的吗?那LCD()也是一个单独的任务吗?键处理包含了LCD(),或称要调用LCD(),如何划分这两个任务及处理内容的?LCD()还要在退出键处理时显示通讯传来的数据。
  Add_Task(Key_Deal(),1000,1000);
  Add_Task(LCD(),0,500);

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

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

出0入0汤圆

发表于 2008-3-6 11:57:37 | 显示全部楼层
支持一下该帖子的作者.我认为他确实经过自己的分析和思考,并提出了2个非常重要的问题.比那些所谓的在XXX上移植成功了XXX系统的帖子要深入的多.

"时间触发嵌入式系统设计"我也看过,感觉上这或许是一个能在8位机上使用的,编写稍微复杂系统软件的方式,含有OS的基本思想和方法,但比OS要小,在小系统中能实际的应用.

问题在于在这个"时间触发嵌入式"的系统中,其它中断的使用与时标中断的矛盾.在OS中,也需要使用一个T/C,产生任务切换的时标,其它中断出现后,OS作为优先级高的任务,切换到该任务执行.而在"时间触发嵌入式"的系统中,任务是按时间序列执行的,如果使用和发生了其它中断,就可能破坏了时间序列.

第2个问题是使用OS中的一个普遍存在的问题,就是任何划分"任务".目前市场上有众多的介绍OS的书和文章,但好象都没有介绍或给出任何划分"任务"的基本原则和方法.当然,在实际应用中可能会根据不同的应用有变化,但我也感觉有一个基本可作为分析和考虑的划分原则.

我曾经让我的研究生去探讨这2个题目,但他们都不感兴趣.另外一个(我个人的观点)原因是:研究OS的大都从软件的角度考虑问题.

LZ的问题提出一个课题,如何找到一个能适合8位系统的,界于目前编写系统软件的方式与采用0S之间的一种系统设计方法.能具有OS的基本优点并保留中断响应实时性好的特点. "时间触发嵌入式"是一种可尝试的方法,但好象有缺陷.

我也希望在这方面有经验和研究的提出自己的看法,大家一起讨论.

出0入0汤圆

发表于 2008-3-6 12:08:08 | 显示全部楼层
OS选来选去,最后选Protothreads。

Protothreads不是OS,含有协作式调度OS思想。 个人认为比“时间触发嵌入式”要好。

出0入0汤圆

发表于 2008-3-6 14:19:14 | 显示全部楼层
到现在为止我的东西都是裸跑。还没有设计到OS或系统。。菜菜,学习中

出0入0汤圆

 楼主| 发表于 2008-3-6 16:13:32 | 显示全部楼层
受machao表扬真是汗颜。我只是看了书,想将目前的项目用调度器来作,于是有了上面的问题。
问题的关键不在于是裸奔或有OS外壳,而是时间触发下的程序架构,流程。我未想出好方案。

出0入0汤圆

发表于 2008-3-6 19:33:08 | 显示全部楼层
仅个人意见:
    1:感谢马老师的"AVR 应用经验",使我接触到了“时间触发嵌入式系统设计”,并在广州购书中心买了一本收藏。  
    2: 感谢 Michael J.Pont 写了一本这么好的书。
    3:关于马老师在上面提到的问题1,我在程序是这样处理的,如UART,可以开中断接收,虽然违背了时间触发嵌入式系统的基本原则,但应不影响作者的主题的:时间控制程序的流程,并运行状态可预见,清晰明了。开UART接收中断只能是处理一下简单的数据,并在中断里使能全部中断。在UART_Update的更新函数里进行较复杂的处理,同时处理完后数据还在这个调度任务中进行定时性的发送,这种“中断加查询”的方式应不错的,马老师不也写了个类似的驱动器?我个人觉得这样做的话UART还在调度器的控制下,它的状态也可以预知的。同时对调度器的影响非常少,最多是几个机器周期的延时。再说AVR做调度最合适了,程序空间大,RAM也多,速度也快,至少比AT89C51好一些。
    4:对显示部分,如数码管,完全可以做成调度任务,接口只是要显示的数据,如两位数码管显示数据0-99,接口为:tByte DISP_DATA;其它任务只要对这个变量进行赋值即可,LCD12864的驱动也可按作者的方法进行驱动的。
    5: 对按键部分,作者写的比较完整,在主任务里只要执行读取按键的函数即可,同时作者的程序还可简化,取消缓冲的话还可以节省一大部分程序代码!有兴趣的可以试试。
    6:原作者的调度还可以进行简化,每个任务给一个标志字,在调度定时器里对每个标志字进行加一操作,在主程序里根据标志字判断本任务是否可以工作,可以的话清零标志字,进行相关操作。这样做可以达到作者的调度器同样的效果。代码可以做得非常小,本人做了一个控制板,用1051做的,里面有两位LED,几个指示灯,四个按键(具备读连续键,无缓冲,和显示复用IO),串口通信(有CRC8),用C写的不足1K,
    7:可以对休眠,看门狗进行很完美的安置,降低功耗,增强可靠性。
    8:调度器里最好有个:TASK_Update()主任务,对各种功能任务进行调用和控制,
    9:调度里最难缠还是那些非常多的时间性的任务,如继电器的控制,这种器件动作后需要一定的延时再进行下一步操作,但是调度器是查询式的,你在那里放个delay_ms();那你就别用调度器了,呵呵,又是马老师提醒了我,用上状态机就可以非常完美的解决这个难题,而且可以在继电器的动作时间里分析各种状态。
    10:时间调度器的大小问题:在ICC,GCC,IAR里都差不多大小,都是600字节左右吧!!
    最后:调度器还需本人的再好好学习,让大家见笑了!!

出0入0汤圆

发表于 2008-3-6 19:45:11 | 显示全部楼层
发一个我写的在时间调度器下工作的AD:八路可同连续性处理并存到ADC_Buffer[8],
#include "xj_adc.h"

// ------ Public variable definitions ------------------------------
tWord tw_ADC_Buffer[8];

// ------ Public function ------------------------------------------
//ADC initialize
// Conversion time: 416uS
void ADC_Initial(void)
{
#if ATMEGA8L
    ADCSR = 0x00; //disable adc
    ADMUX = 0x40;//参考电压为AVCC,通道为零
    ACSR  = 0x80;//关闭比较器
    ADCSR |= (1<<ADPS1)|(1<<ADPS0);//-----/32
    ADCSR |= (1<<ADEN)|(1<<ADSC);//单次转换
#endif

#if ATMEGA16L
    ADCSRA = 0x00; //disable adc
    ADMUX = 0x40;//参考电压为AVCC,通道为零
    ACSR  = 0x80;//关闭比较器
    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);//-----/32
    ADCSRA |= (1<<ADEN)|(1<<ADSC);//单次转换
#endif

#if ATMEGA48V
    ADCSRA = 0x00; //disable adc
    ADMUX = 0x40;  //参考电压为AVCC,通道为零
    ACSR  = 0x80;
    ADCSRB = 0x00;
    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);//-----/32
    ADCSRA |= (1<<ADEN)|(1<<ADSC);//单次转换
#endif
}

void ADC_Update(void)
{
    static tByte tb_ADC_NUM=0;
    tWord tw_Temp=0;
   
#if ATMEGA8L
    if(ADCSR&(1<<ADIF))
    {
        tw_Temp = (tWord)ADCL;
        tw_Temp |= ((tWord)ADCH)<<8;
        ADCSR |= (1<<ADIF);//ADIF=1;
        tw_ADC_Buffer[tb_ADC_NUM]=tw_Temp;

        tb_ADC_NUM++;
        if(tb_ADC_NUM>=0x08){tb_ADC_NUM=0;}
        ADMUX = 0x40 | tb_ADC_NUM;  //参考电压为AVCC,通道为 tb_ADC_NUM
        ADCSR |= (1<<ADSC);//单次转换   ,在这里不要再来使能(1<<ADEN)这个东东了,不然数据会跳步的
    }
#endif

#if ATMEGA16L || ATMEGA48V
    if(ADCSRA&(1<<ADIF))
    {
        tw_Temp = (tWord)ADCL;
        tw_Temp |= ((tWord)ADCH)<<8;
        ADCSRA |= (1<<ADIF);//ADIF=1;
        tw_ADC_Buffer[tb_ADC_NUM]=tw_Temp;

        //if(tb_ADC_NUM == 0x01){Filter_A(&(tw_ADC_Buffer[tb_ADC_NUM]));}//平均值滤波

        tb_ADC_NUM ++ ;
        if(tb_ADC_NUM >= 0x08){tb_ADC_NUM = 0;}
        ADMUX = 0x40 | tb_ADC_NUM;  //参考电压为AVCC,通道为 tb_ADC_NUM
        ADCSRA |= (1<<ADSC);//单次转换
    }
#endif
}

出0入0汤圆

发表于 2008-3-6 20:34:37 | 显示全部楼层
好帖都要顶上去,千万不要沉了。。。这个比元器件驱动实例含金量搞咯

出0入0汤圆

发表于 2008-3-6 20:56:26 | 显示全部楼层
你好像没有时间观念啊。60mS的Tick(看错了?)?我avr一般都是1mS的Tick,把任务用状态机的方法细分,这样每流一个Main循环基本时间是固定的。另外,时标需要相对精确,但是不用你这么绝对准确,建议使用查询的方法,在while最后等待Tick结束(每个Tick中的实际运行实际要相对稳定,即CPU使用率基本不变,这可以保证Tick不‘溢出’)。然后继续下一个Tick。带有中断的模块(程序模块)我一般都包含一个处理程序和中断接收(如果你能保证Tick小于接收溢出率,那么查询也无妨)。发送就不用说了,很简单。接收我一般模仿modbus的方式定义帧结构,接收程序中进行同步字符判断(1个或多个标志字符,我一般用2个然后跟着包头、地址、数据、CRC校验)、地址匹配,如果这两个都执行完了,如果不能通过这两步,就一直循环判断。如果通过,则状态转移,后面的数据全部放到接收缓冲区中。每接收一个字符就设定一个全局变量、主程序中的串口处理程序在接收状态只判断超时、每Tick查询一下那个全局变量,如果设置,则清除超时计时器和标志。否则计时器计时,大于N(我一般设定几个mS,modbus建议4.5byte传输时间)之后就关断接中断(为什么?帧结束了呗!)接着的事情大家都清楚了吧,先CRC校验、关键数据校验,校验成功后进行数据相关处理。
    经验之谈,由于在搞的是单片机,所以RTOS、什么的都不怎么了,使用类似"时间触发嵌入式系统设计"调度。

出0入0汤圆

发表于 2008-3-6 21:00:58 | 显示全部楼层
顶!

出0入0汤圆

发表于 2008-3-6 21:02:50 | 显示全部楼层
觉得这种方式是不错,但有时候需要混合调度,感觉不太好把握。

出0入0汤圆

发表于 2008-3-6 21:55:40 | 显示全部楼层
"时间触发嵌入式系统设计"中它的任务是分散的也就是任务基本上是不重叠的,除非有中断出现.按照你的任务可以这样来安排:查键与键处理用27ms,显示刷新251ms,下位机通讯直接用中断,但这个中断函数尽量要短.尽量要把每个任务的处理时间保证在设定的时间段内完成,如果完不成就需要细分没个任务.

interrupt [TIM0_OVF] void timer0_ovf_isr(void)   //2ms
{TCNT0=6;      
if(++Task2_count>11)   //22ms按键处理
   {Task2_count=0;
    Key_fg=1;
   }               
if(++Task4_count>51)   //102ms输出处理
   {Task4_count=0;
    Run_fg=1;
   }         
if(++Task5_count>124)  // 0.5妙闪烁
   {Task5_count=0;
    Falsh_fg^=1;
       }  
if(++Task6_count>101) //202ms数据打包
   {Task6_count=0;
    Disp_packet_fg=1;
   }   
Display();
}

while (1)
      {#asm("wdr")
        if(Disp_packet_fg){Disp_packet_fg=0;Disp_packet();}
        if(Key_fg){Key_fg=0;Key();}
        if(Run_fg){Run_fg=0;Run();}
      };

你可以参考这个结构.

出0入296汤圆

发表于 2008-3-7 05:28:48 | 显示全部楼层
既然楼主点了我的名,我就说说我看了这本书以后的感受:

1、首先,读这本书能领会到“时间”和“调度”两个概念在裸机状态下的实际意义就足够了,对于书中提到的一些原则和概念,不应该教条。
2、这本书介绍的调度器主要有两种,协作式调度器和抢占式调度器,以及所谓的混合式调度器。说句实话,在我这里从来都没有刻意去这样划分过。这些调度器的概念理解融会以后,并不需要将自己的代码使用哪种调度器上纲上线,只要功能上符合客户对系统的要求即可。
3、对于大家上面所说的任务划分问题,我的做法也许比较极端,但是效果很好:
   a、在我眼中,只有两种任务,一种在超级循环中,工作于协作模式下的任务;另外一种是工作于定时器中,经过人为分频以后的抢占式任务;当然,还包括一些工作于后台中断模式下,用于采集信息的小线程。
   b、所有的任务都是状态机,或者说都利用状态机将一个大任务划分成了若干小片段,一次只执行一小片段,大家轮换着执行。这实际上模拟了多个大任务在抢占式(软实时)操作系统下实际被“分割执行”的状况。
   c、对于所有的C语言程序,我都能进行状态机化的改造,或者说我已经习惯于用状态机描述所有的任务,因此,人物协作和相应的问题在我这里不是很突出。在我眼中,一个任务可以被看作是一个进程,进程中的每一个子状态可以被看作是一个个线程。

出0入296汤圆

发表于 2008-3-7 05:40:51 | 显示全部楼层
这是我自己写的一个简易调度器,大家从代码上很容易发现,它就是一个 协作式调度器

/***********************************************************
*   函数说明:任务处理函数                                 *
*   输入:    无                                           *
*   输出:    无                                           *
*   调用函数:无                                           *
***********************************************************/
void Process_Task(void)
{
    static uint8 n = 0;
   
    if (ProcPCB[n].IfProcAlive)                             //处理进程
    {
        ProcPCB[n].IfProcAlive = (*ProcPCB[n].Proc)();
    }        
   
    n ++;
    if (n >= g_cCOSPROCCounter)
    {
        n = 0;
        g_cScheduleTest = MIN(g_cScheduleTest + 1,254);
    }
}


这是对应的任务结构体或者说PCB
typedef struct Process
{
    #ifdef _USE_MESSAGE_PROC
    BOOL (*ProcIO)(volatile MESSAGE *MSG);              //支持事件驱动
    #endif
    BOOL (*Proc)(void);                                 //返回False,自动关闭任务
    volatile BOOL IfProcAlive;
}PROCESS;

typedef BOOL (*PROC_FUNCTION)(void);



我通过一个宏专门来注册任务,因此任务在执行时可以被动态分配
#ifdef _USE_MESSAGE_PROC
    # define PROC_REGISTER_PCB(IO,FUNC,STATE) \
                                             do\
                                             {\
                                                 ProcPCB[g_cCOSPROCCounter].ProcIO = IO;\
                                                 ProcPCB[g_cCOSPROCCounter].Proc = FUNC;\
                                                 ProcPCB[g_cCOSPROCCounter].IfProcAlive = STATE;\
                                                 g_cCOSPROCCounter ++;\
                                             }while(0);
#else
    # define PROC_REGISTER_PCB(FUNC,STATE) \
                                             do\
                                             {\
                                                 ProcPCB[g_cCOSPROCCounter].Proc = FUNC;\
                                                 ProcPCB[g_cCOSPROCCounter].IfProcAlive = STATE;\
                                                 g_cCOSPROCCounter ++;\
                                             }while(0);
#endif



大家也许注意到了
我的一个任务函数是有返回值的,这个BOOL型的返回用于告诉外界状态机是否继续执行,
显然,当返回TRUE,则表示这个任务希望自己下次仍然被执行,如果返回FALSE,则表示
这个任务完成了自己的工作申请退休。

注意调度器的代码片段
if (ProcPCB[n].IfProcAlive)                             //处理进程
    {
        ProcPCB[n].IfProcAlive = (*ProcPCB[n].Proc)();
    }   
我们可以发现,原来任务可以通过返回FALSE发送消息给调度器,调度器根据返回自动修改
该任务的IfProcAlive属性,从而关闭该任务。

这种设计是专门为了迎合状态机的。我的程序无论多大规模,都是由状态机和必要的后台
线程构成的。


我们来看一个傻孩子式的典型状态机结构

#define PE_ACTIOIN_FLAG       SET_BIT8_FORMAT(s_chActionFlag)          //这是一个将字节强制转换成位段的宏
#define PE_STOP_ALL_ACTION    s_chActionFlag = NULL;
#define PE_START              PE_ACTION_FLAG.BIT0
#define PE_DO_SOMETHING_A     PE_ACTION_FLAG.BIT1
#define PE_DO_SOMETHING_B     PE_ACTION_FLAG.BIT2

BOOL PROC_Example(void)
{
    static UINT8 s_chActionFlag = NULL;
   
    if (s_chActionFlag == NULL)
    {
        //状态机的起始标志
        PE_START = TRUE;
    }

    if (PE_START)
    {
        //双线程哈^_^
        PE_DO_SOMETHING_A = TRUE;
        PE_DO_SOMETHING_B = TRUE;

        PE_START = FALSE;
    }

    if (PE_DO_SOMETHING_A)
    {
        //一直 do something...
       if (满足了某一个条件)
       {
           //线程挂起了
           PE_DO_SOMETHING_A = FALSE;
       }
    }
   
    if (PE_DO_SOMETHING_B))
    {
        //一直监视着某些事情
        ...
        if (监视条件满足,比如超时)
        {
            //挂起整个进程
            PE_STOP_ALL_ACTION;
            //申请挂起整个任务
            return FALSE;
        }
    }
   
    //普通情况保持状态机运行
    return TRUE;
}



根据这种结构写下来的状态机之间,还可以存在嵌套,相当方便。
以上是我的一点小小的经验,希望大家喜欢。

出0入296汤圆

发表于 2008-3-7 06:04:04 | 显示全部楼层
状态机如何处理状态间的时序问题呢?
我有一个专门的库函数来处理

/***********************************************************
*   函数库说明:系统并行延时函数库                         *
*   版本:      v3.00                                      *
*   作者:      傻孩子                                     *
*   创建日期:  2006年9月18日                              *
* -------------------------------------------------------- *
*  [支持库]                                                *
*   库名称:    RD_MacroAndConst.h                         *
*   需要版本:  v0.01 &abv                                 *
*   支持库说明:系统常用宏定义库                           *
*                                                          *
*   库名称:    RD_UseDelay.h                              *
*   需要版本:  -----                                      *
*   支持库说明:系统并行延时声明库                         *
* -------------------------------------------------------- *
*  [版本更新]                                              *
*   修改:      傻孩子                                     *
*   修改日期:  2006月4月11日                              *
*   版本升级:  v1.0                                       *
*                                                          *
*   修改:      傻孩子                                     *
*   修改日期:  2006年4月16日                              *
*   版本:      v1.01                                      *
*                                                          *
*   修改:      傻孩子                                     *
*   修改日期:  2006年6月19日                              *
*   版本:      v1.10                                      *
*                                                          *
*   修改:      傻孩子                                     *
*   修改日期:  2006年6月25日                              *
*   版本:      v2.00                                      *
*                                                          *
*   修改:      傻孩子                                     *
*   修改日期:  2006年9月19日                              *
*   版本:      v3.00                                      *
* -------------------------------------------------------- *
*  [版本历史]                                              *
*      v1.0    从原来的程序代码中提取了该函数库,支持系统  *
*              并行延时,支持外部修改缓冲区大小。          *
*      v1.01   使用了统一的代码插入接口标准。              *
*      v1.10   为适应多任务的并行延时特性,增加一个取消延  *
*              时预约的函数,增强延时操作的灵活性。        *
*      v2.00   修改了系统句柄有效期的问题,使用结构体数组  *
*              记录延时状态,将返回的句柄改为unsigned int  *
*              型数据。                                    *
*      v3.00   该文件根据从前RD_UseSystemDelay.h修改得来。 *
*              修改了延时的计时方法,减小了系统资源的占用  *
* -------------------------------------------------------- *
*  [使用说明]                                              *
*           1、在您需要使用该函数之前引用该头文件          *
*           2、在您的毫秒定时器中断处理程序里面调用函数    *
*              SD_INSERT_TIMER_OVF_ISR_CODE以确保函数      *
*              库正常使用。                                *
*           3、在系统初始化程序里面增加对该系统初始化函数  *
*              Delay_System_Init()的调用                   *
*           4、通过调用addDelayItems(n)函数来添加一个延时  *
*              该操作成功将会返回一个用于查询的句柄;否则   *
*              返回FALSE(0)。                              *
*           5、通过函数If_Time_Out(句柄)来查询句柄代表的那 *
*              个延时是否完成TRUE/FALSE。如果出现错误会    *
*              返回SD_ERROR标志。                          *
*           6、通过在引用该头文件之前定义宏:              *
*              SD_DELAY_BUFF_COUNT来设定缓冲区的大小。     *
***********************************************************/

/********************
* 头 文 件 配 置 区 *
********************/
# include "RD_MacroAndConst.h"
# include "RD_UseDelay.h"

/********************
*   系 统 宏 定 义  *
********************/

/*------------------*
*   常 数 宏 定 义  *
*------------------*/
#ifndef SD_DELAY_BUFF_SIZE
    # error Need For SD_DELAY_BUFF_SIZE
#endif

/********************
*    结构体宏定义   *
********************/
typedef struct DelayItem
{
    uint16 ID;
    uint32 Timer;
}DELAYITEM;

/********************
*   函 数 声 明 区  *
********************/
void Delay_System_INIT(void);
uint16 Add_Delay_Item(uint16 wTime);
uint8 If_Time_Out(uint16 wDHandl);
void Cancel_Delay_Item(uint16 wDHandl);

/********************
*   模块变量声明区  *
********************/
static DELAYITEM s_dlDelayBuff[SD_DELAY_BUFF_SIZE];
static uint16  s_wDelayIDCounter = 0;

/********************
*   全局变量声明区  *
********************/
uint32 g_dDelayTimerCounter = 0;
uint8 g_cDelayItemCounter = 0;

/***********************************************************
*  函数说明:延时系统初始化                                *
*  输入:    无                                            *
*  输出:    无                                            *
*  调用函数:无                                            *
***********************************************************/
void Delay_System_INIT(void)
{
    uint8 n = 0;
   
    for (n = 0;n < SD_DELAY_BUFF_SIZE;n++)
    {
        s_dlDelayBuff[n].ID = NULL;
        s_dlDelayBuff[n].Timer = NULL;
    }
   
    g_dDelayTimerCounter = 0;
    g_cDelayItemCounter = 0;
    s_wDelayIDCounter = 0;
}

/***********************************************************
*  函数说明:添加一个延时                                  *
*  输入:    需要延时的时间                                *
*  输出:    查询用的句柄                                  *
*  调用函数:无                                            *
***********************************************************/
uint16 Add_Delay_Item(uint16 wTime)
{
    uint8 n = 0;
    if (g_cDelayItemCounter >= SD_DELAY_BUFF_SIZE)
    {
        return NULL;                                        //返回空表示错误
    }
   
    for (n = 0;n < SD_DELAY_BUFF_SIZE;n++)
    {
        if (!s_dlDelayBuff[n].ID)
        {
            if (s_wDelayIDCounter == 0xffff)                 //回避ID 0x0000的出现
            {
                s_wDelayIDCounter = 0;
            }
            s_dlDelayBuff[n].ID = ++s_wDelayIDCounter;
            
            SAFE_CODE_PERFORMANCE
            (
                s_dlDelayBuff[n].Timer = g_dDelayTimerCounter + wTime;
            )
            
            g_cDelayItemCounter++;
            
            return s_dlDelayBuff[n].ID;                       //返回查询句柄
        }
    }
   
    return NULL;                                            //异常情况
}

/***********************************************************
*  函数说明:判断延时是否超时                              *
*  输入:    查询用的句柄                                  *
*  输出:    是否超时,如果句柄错误将得到SD_ERROR          *
*  调用函数:无                                            *
***********************************************************/
uint8 If_Time_Out(uint16 wDHandl)
{
    uint8 n = 0;
    uint32 dTempDelayTimerCounter = 0;
   
    if (!wDHandl)                                           //空句柄
    {
        return SD_ERROR;
    }
   
    SAFE_CODE_PERFORMANCE
    (
        dTempDelayTimerCounter = g_dDelayTimerCounter;
    )
   
    for (n = 0;n < SD_DELAY_BUFF_SIZE;n++)
    {
        if (s_dlDelayBuff[n].ID == wDHandl)
        {
            if (s_dlDelayBuff[n].Timer <= dTempDelayTimerCounter)
            {
                s_dlDelayBuff[n].ID = NULL;
                s_dlDelayBuff[n].Timer = NULL;
               
                g_cDelayItemCounter--;
                return TRUE;
            }
            else
            {
                return FALSE;
            }
        }
    }
   
    return SD_ERROR;                                        //ID已经过期
}

/***********************************************************
*  函数说明:取消延时                                      *
*  输入:    查询用的句柄                                  *
*  输出:    无                                            *
*  调用函数:无                                            *
***********************************************************/
void Cancel_Delay_Item(uint16 wDHandl)
{
    uint8 n = 0;
   
    if (!wDHandl)                                            //空句柄
    {
        return ;
    }
   
    for (n = 0;n < SD_DELAY_BUFF_SIZE;n++)
    {
        if (s_dlDelayBuff[n].ID == wDHandl)
        {
            s_dlDelayBuff[n].ID = NULL;
            s_dlDelayBuff[n].Timer = NULL;
            
            g_cDelayItemCounter--;
            
            return ;
        }
    }
}



当我想申请一个延时的时候,我只需要调用函数
Add_Delay_Item,并填入要延时的长度,函数会返回一个句柄,用于我们下次查询这个延时的状态,例如:

wDelayHandle = Add_Delay_Item(10000);              //十秒的延时

我们每次只需要通过一个函数If_Time_Out就可以查询这个延时是否到期。别忘了要填入查询用的句柄。
例如
if (If_Time_Out(wDelayHandle))
{
    //如果到期了
}
else
{
    //如果没有到期
}

这个函数库历经多个版本,主要问题就是如何处理句柄的有效期,并合理的分配延时资源(这里作为一
个类似内存分配的问题来看待)。句柄是有有效期的。每次调用If_Time_Out函数时,如果获得了TRUE的返回
那么对应的延时资源会被回收,句柄也就被注销,成为无效句柄。句柄并不等同于数组下标,否则,一个无效
的句柄有可能导致一个同名的还未被查询的延时资源被系统回收调,导致程序错误。所以,我专门就这个问题
作了处理。有兴趣的朋友可以一起探讨一下。



这样处理延时资源,就是为了适应状态机的使用。如果您长期关注我的库函数,您也许会发现,我的库函数也是
依照能够在状态机条件下使用为标准的。

出0入0汤圆

 楼主| 发表于 2008-3-7 10:29:01 | 显示全部楼层
任务的划分的疑问,比如:SPI访问,这对程序非常重要的,关系到参数可能被不当修改,我甚至认为应该关所有中断,保证写入过程正常且不被打断(这又要与作者的原则相矛盾了,但非常关键之处总要采取点非常措施吧,大家是如何处理的?)。在程序中通常应有三处:1。在AD转换后要读取5045中的校正值,2。在按键处理中设置的参数要存入5045.3。在通讯的校正命令下,要将校正参数写入5045。我该如何划分访问SPI、AD、键处理、通讯这四个任务呢?还是设置访问5045的缓冲区,象显示缓冲区那样。一个问题就是安全性无法保证。

To dejun
感谢dejun的实践经验:
  1。"原作者的调度还可以进行简化,每个任务给一个标志字..." 能将您的调度器显示一下吗?
.2。关于UART,您的意思是说,在UART中断里,仅是简单接收是不会引起时标的很大延迟甚至是不稳定的。发送不在中断里处理,如下:
  void TXUN_Up(void) interrupt 4 using 1//与上位机的通讯中断程序
{
        if(TI) return;
        //下面处理接收
        ....
}
void UART_Updata()
{
        //发送在此
        if(TI)
        SBUF = TXDData;
        //下面处理接收
        ...
}

To zhiwei       
感谢zhiwei的通讯协议及实现过程的详细解说,对通讯状态的划分很成熟,很实用。
同步字符判断(2个字节)+地址匹配=3个字节是命令,RB8=1的,在接收中断里判断是否相同,若相同则作状态转移,其余的为数据RB8=0。在通讯处理程序中查是数据状态,作处理,是这样的吧?

To shaozh
看过您在“上传《时间触发嵌入式系统设计模式 8051系列微控制器开发可靠应用》PDF ---51单片机”帖子中的例程,下半部分的实例没有调用调度器的主函数。您的结构很清晰,一个任务一个计时单元,将各任务人为的分散(不重叠)。但通讯及LCD显示占用时间,可能必须得分状态了,60ms一个循环,则按键就会稍有迟顿。谢过您的方案。

To Gorgon Meducer
您的书总盼不来,只能点名提问了,请原谅。时间触发下的疑问已困扰我一个多月了,若再等一个月....
Gorgon Meducer的"在我眼中,一个任务可以被看作是一个进程,进程中的每一个子状态可以被看作是一个个线程"是最难理解也是最难应用的,但也许是最终的解决方案。看不懂,再看...,只能这样了。要不,您就再多贴点(这脸皮也忒厚了)。

出0入0汤圆

发表于 2008-3-7 12:43:39 | 显示全部楼层
to liqu
   问题1:
void TIMER0_INTERRUPT() interrupt TF0_VECTOR using 2
{
    TH0=0xfd;
    TL0=0x65;//重装初值

    DSP_RunMe ++;
    UART_RunMe = 1;
}

void main(void)
{
    DSP_Initial();
    UART_Initial();

    TIMER0_Initial();//启动调度器

    SET_ISR();//开中断

    while(1)
    {
        //
        if(DSP_RunMe > 50)  
        {
             DSP_RunMe = 0;
             DSP_Update();  
        }

        if(UART_RunMe == 1)
        {
             UART_RunMe = 0;  
             UART_Update();
        }

        PCON |= 0x01; //休息一下啦
    }
}

类似shaozh的程序,很简单,只是没有任务启动的时间延时,这点在开机的缓冲区数据初始化要特别注意。
第二个问题晚上再来聊聊,要 PCON |= 0x01;一下了,下午还要上班!!

出0入0汤圆

发表于 2008-3-7 13:08:42 | 显示全部楼层
我认为这样的程序模型很不好,实现 一个具体任务需要大量的状态标记,使程序的可读性大打折扣。
另外实时响应性不可实现,大家想一想使用了OS的系统还要用中断来完成一些任务,怎么可能"任何时候只有一个中断,即调度器的时标中断"呢。
还是把精力放在基本的前后台式系统上,努力处理好中断与主循环之间的协同工作为好。

以上仅对楼主的题目做个回复,其它楼的内容我没有仔细阅读!

出0入0汤圆

 楼主| 发表于 2008-3-7 16:08:33 | 显示全部楼层
To zhiwei
您的结构是这样的吧,请指正:
uchar Receive_State = 0;//全局变量,接收状态计数器
uchar Receive_Overtime = 0;//接收超时计数
void main(void)
{
        Main_Init();
        while(1)
        {
                Task_Key();//键处理
                Task_Uart();//通讯处理
                Task_Disp();//显示
                Tick_Flag = 0;
                while(Tick_Flag == 0);       
        }       
}
void Time0(void) interrupt 1 using 2//1ms
{
        ...
        Tick_Flag =1;       
}
//通讯如下:
void TXUN_Up(void) interrupt 4 using 1//与上位机的通讯中断程序,时间尽量短。
{
       
        static uchar Receive_Count = 0;//
        static uchar TXD_Count = 0;
        static uchar a,b,c;
        if(RI)
        {
                switch(Receive_State)
                {
                        case 0://状态0,检查同步
                        switch(Receive_Count)
                        {
                                case 0:
                                a = SBUF;
                                break;
                                case 1:
                                b = SBUF;
                                break;
                                case 2:
                                c = SBUF;
                                break;
                                default:
                                Receive_Count = 255;
                        }
                        Receive_Count++;
                        if((a=b==同步字符)&&(c==本机地址))        Receive_State = 1;
                        break;
                        case 1:
                        Receive_Overtime++;
                        ...//接收至缓冲区
                        break;
                }       
                RI = 0;
        }
        if(TI)
        {
                SBUF = TXD_Data[TXD_Count];
                TXD_Count++;
                TI = 0;
        }
}
void Task_Uart(void);//通讯处理
{
        if(Receive_Flag)//接收状态
        {
                if(Receive_State == 1)
                {
                        if(Receive_Overtime > 5)
                        {
                                ...//已5 Tick未接收了,接收结束
                                Receive_State = 2;       
                        }       
                }
                else if(Receive_State == 2)
                {
                        ...//看CRC校验是否正确,并作接收处理。
               
                }
        }
        else//发送状态  
        {
                ...//处理发送缓冲区
                SBUF = 同步字符;
                Receive_Flag = 1;//发送第一个字符,其余交给中断,转接收状态
        }
}

出0入0汤圆

发表于 2008-3-7 16:16:31 | 显示全部楼层
这个东西不懂。
我只是感觉我的一个产品里面有很多中断和许多时时控制和显示的东西。但是经常被时间中断占用我的CPU我可能接受不了。并且里面也不是单独的一个模块一个模块的程序。很多模块之间都有关联的。我的显示(一些产品需要用到数码管扫描显示的)一般用在中断里面。但是如果是LCD显示的话,都是直接放如主程序中。要不然放在中断里面需要浪费的时间太多了。直接在主程序里面记数,循环N次后就显示一次。很少用中断来处理东西。除非要求时时响应的为部中断和需要记时的全局“时钟”

看来还 要多向你们请教请教啊。。哈哈

出0入0汤圆

发表于 2008-3-7 16:19:46 | 显示全部楼层
没做过产品,但人机接口(控制和显示)部分我是尽量不用中断(而是用线程)。
真正需要高实时性的才用中断。

出0入8汤圆

发表于 2008-3-7 16:55:34 | 显示全部楼层
我的程序结构和【18楼】一样

主要特点:状态机 + fifo

出0入0汤圆

发表于 2008-3-7 19:56:27 | 显示全部楼层
具体做法就不用深究了,我也懒得看长篇的程序哦,只要你理解了我的思路,能搞定它就行,呵呵。通信中我一般不使用9bit数据,做地址判断,不使用奇偶位做校验。因为使用CRC已经足够了。至于同步,我认为是串口软件通信的关键,我接收一帧数据常用的方法是:上帧超时+ 本帧前导码(0xFF之类的) + 本帧同步码(特征字符)。帧超时意味着前面的一帧数据已经传输完毕,可以开始这帧数据接收,帧前导码一般是为了在物理上同步串口数据,防止帧数据错误。同步码匹配之后下面就是数据了。
我说的主要是针对问答式半双工通讯的,

出0入0汤圆

发表于 2008-3-7 23:28:22 | 显示全部楼层
支持时间调度器,不为别的,只因为它扩展了我的思路。

出0入0汤圆

 楼主| 发表于 2008-3-8 08:22:30 | 显示全部楼层
TO dejun:
“支持时间调度器,不为别的,只因为它扩展了我的思路”,说得好,它让我对程序的可靠性有了思考方向,开始考虑软件流程,仔细计算各任务的执行时间,对以前很随意的编程方式是个巨大改进。
“第二个问题晚上再来聊聊”怎么没有下文了,期待。
“5: 对按键部分,作者写的比较完整”,能将简化的查键,键处理显示一下吗?读这块感觉写的很繁琐,也不太清晰。还未读到您的程序。
通讯部分用通讯中断代替定时器中断作时标,很有创意,有这样用的吗?很可靠?
“8:调度器里最好有个:TASK_Update()主任务,对各种功能任务进行调用和控制”,时间调度,还用TASK_Update()干什么?能举例说明吗?

To zhiwei
“我avr一般都是1mS的Tick,把任务用状态机的方法细分,这样每流一个Main循环基本时间是固定的。”能再举个例子如何用状态机细分单片机的各任务吗?这是个难点。
"帧前导码一般是为了在物理上同步串口数据",接收是中断响应的,怎么需要物理上同步串口数据?

出0入0汤圆

发表于 2008-3-8 18:44:03 | 显示全部楼层
to:liqu
1:对UART中断来说,只能是简短的接收中断,同时可被时间调度用的定时器所中断,这样在AVR系列中,就算串口优先级比这个定时器高,串口中断后几个机器周期后又会被定时器所中断的,时间调度也只是被延后了了一些时间,但是定时器是自动重装的,下次的中断又可定时到达,这样串口中断对它来无伤大雅,而且对任务的执行也不会有所延时,再说那些任务基本上是查询的,快几个机器和慢几个周期没区别,但是要把握好总体时间不超过你的时间间隔,包括串口的中断时间。
2:#include "xj_key.h"

volatile tByte tb_KEY_Buffer;

void KEY_Initial(void)
{
    tb_KEY_Buffer = 0;
}
void KEY_Update(void)
{
    static tByte tb_KEY_Old = _LOW;
    static tByte tb_KEY_Last_Valid = _LOW;
    static tByte tb_KEY_Last_Valid_Time = 0;

    tByte tb_KEY_Temp = _LOW;

    if(KEY1_PORT == 0) tb_KEY_Temp = KEY1;//如果有IO口电平变低,即有按键按下了呀,呵呵。
    if(KEY2_PORT == 0) tb_KEY_Temp = KEY2;
    if(KEY3_PORT == 0) tb_KEY_Temp = KEY3;
    if(KEY4_PORT == 0) tb_KEY_Temp = KEY4;

    if (tb_KEY_Temp == _LOW)//无数据就清零所有静态全局变量
    {
        // No key pressed (or just a function key)
        tb_KEY_Old = _LOW;
        tb_KEY_Last_Valid = _LOW;
        tb_KEY_Last_Valid_Time = 0;

        return ;//
    }

    // 已经去抖动了,还有有效的按键
    if (tb_KEY_Temp == tb_KEY_Old)
    {
        if(tb_KEY_Temp == tb_KEY_Last_Valid)//auto repeat time = 500ms
        {
            tb_KEY_Last_Valid_Time ++ ;
            if(tb_KEY_Last_Valid_Time >= 0x0a)//50ms*10
            {
                tb_KEY_Last_Valid_Time = 0x05;
                //在这里可以有选择的保留连续键功能
                tb_KEY_Buffer = tb_KEY_Temp;//送连续按下的键
            }
        }
        else
        {
            tb_KEY_Last_Valid = tb_KEY_Temp; //保存最后一个有效的按键
            tb_KEY_Buffer = tb_KEY_Temp;//送单次按下键
        }
        return;
    }
   
    tb_KEY_Old = tb_KEY_Temp;  //按键第一次按下后保存初值,去抖动
}

出0入0汤圆

发表于 2008-3-8 18:54:17 | 显示全部楼层
void TASK_Update(void)
{
    if( tb_KEY_Buffer != _LOW)//如果有按键
    {
        TASK_State_OLD_G=TASK_State_G;

        switch ( tb_KEY_Buffer )
        {
            case KEY1: {
                       }break;
            case KEY2: {
                       }break;
            case KEY3: {
                       }break;
            case KEY4: {
                       }break;
            case KEY5: {
                       }break;
            default:break;
        }
        tb_KEY_Buffer = _LOW;//已经取了键值了,应清除键码,等待下次有效按键
    }

    if(TASK_State_G != TASK_State_OLD_G)//状态改变时间延时
    {
        TASK_Delay++;
        if(TASK_Delay < 0x04)
        {
            return;
        }
        TASK_Delay=0;
        TASK_State_OLD_G = TASK_State_G;
    }

    switch ( TASK_State_G )
    {
        case TASK_AAAA  :    { TASK_AAAA_Update(); }  break;
        case TASK_BBBB  :    { TASK_BBBB_Update(); }  break;
        case TASK_CCCC  :    { TASK_CCCC_Update(); }  break;
        case TASK_DDDD  :    { TASK_DDDD_Update(); }  break;
        case TASK_WAITING  : { TASK_WAITING_Update(); }  break;
        default:break;
    }
}
}

void TASK_Check_Update(void)
{
    static tWord timeout = 0;

    switch(TASK_CHECK_F)
    {
        case TASK_CHECK_INIT ://初始化各种变量
        {
            timeout = 0;
            
            TASK_CHECK_F = TASK_CHECK_DCF1;//转下一个机,并初始化
            DCF1_F = _HIGH;
        }break;
        
        case TASK_CHECK_DCF1 ://对电磁阀进行操作,只要等待即可,有时间要求的
        {
            timeout++;
            if(timeout > 200)//延时200ms
            {
                timeout = 0;
                TASK_CHECK_F = TASK_CHECK_ADC1;//转下一机
                ADC_STATE = ADC_STATE_CHECK;//让ADC工作在读取状态
            }
        }break;
        
        case TASK_CHECK_ADC1 :
        {
            if(ADC_F == _HIGH)
            {

                TASK_Value[TASK_HAND_R] = tb_ADC_Value;
            }   
        }break;
        
        default:break;
    }
}

出0入0汤圆

发表于 2008-3-8 18:55:54 | 显示全部楼层
代码有点乱,自已慢慢思考吧

出0入0汤圆

 楼主| 发表于 2008-3-10 07:41:02 | 显示全部楼层
先谢过dejun的再次热心回复。
谢过大家的热心回复。

To Gorgon Meducer
To zhiwei
对于状态机,如何用状态机来划分任务,我从未接触过,一塌糊涂,比如Gorgon Meducer的"我已经习惯于用状态机描述所有的任务",又如zhiwei的"把任务用状态机的方法细分,这样每流一个Main循环基本时间是固定的",如何用状态机来描述单片机常见的任务,比如键处理,比如LCD,5045访问...,若有时间请详细讲解。

但dejun的八路AD我是看明白了,就是用if替代for,用静态变量或全局变量替代局部变量,如下:
//不拆分的任务的程序结构
uchar AD_Ch;
for(AD_Ch = 0 ; AD_Ch < 8 ; AD_Ch++)
{
        Temp = Read_AD(AD_Ch);//AD转换,通道号AD_Ch由for语句自动加1
        AD_Result = Temp;//存入数组
}
//拆分任务
static uchar AD_Ch = 0;//或声明为全局变量
if(AD_Ch < 8)
{
        Temp = Read_AD(AD_Ch);//AD转换,
        AD_Result = Temp;//存入数组
        AD_Ch++;//通道号AD_Ch加1
}

出10入95汤圆

发表于 2008-3-10 08:44:45 | 显示全部楼层
先留个记号。

出0入0汤圆

发表于 2008-3-10 09:28:51 | 显示全部楼层
很强啊,记号

出0入0汤圆

 楼主| 发表于 2008-3-10 11:39:25 | 显示全部楼层
To dejun
在TASK_Updata()中
" if( tb_KEY_Buffer != _LOW)" 是键处理,并在按键状态下。
"if(TASK_State_G != TASK_State_OLD_G)" 这是什么状态转移时的延时,由键处理状态转非键状态吗?
"switch ( TASK_State_G ) " 是各任务的处理。
各任务都已执行,程序架构已完整。
void main(void)
{
  Main_Init();
  while(1)
  {
    Key_Update();
    Task_Update();
  }
}
那"void TASK_Check_Update(void) "函数有什么作用,是某一任务的各种状态吗?在主循环while(1)中吗?与TASK_Updata()函数在哪发生联系呢?请解释。

"switch ( TASK_State_G )"前是不是少了else,
if(TASK_State_G != TASK_State_OLD_G)//状态改变时间延时
{
       
}
else
{
        switch ( TASK_State_G )
        {
               
        }
}

出0入0汤圆

发表于 2008-3-10 16:58:42 | 显示全部楼层
留个记号

出0入0汤圆

发表于 2008-3-10 18:36:33 | 显示全部楼层
To liqu  
在TASK_Updata()中
" if( tb_KEY_Buffer != _LOW)" :是判断是否有键按下。

"if(TASK_State_G != TASK_State_OLD_G)" :在主任务TASK_Update()里的几个子任务状态机的变换可能需要延时,但不一定每个系统都需要那样做。但在这个判断中必需加入各状态任务状态字的复位。切换时每个子状态机必需初始化它的状态字,如"void TASK_Check_Update(void) "子状态任务的TASK_CHECK_F状态字在每次第一次进入状态任务时要清零。保证子状态任务在第二次调用时已经复位。

"void TASK_Check_Update(void) "就是上面程序中的类TASK_AAAA_Update();一样的调用,这些任务都是随按键的状态改变也改变性的执行。

"switch ( TASK_State_G )"前是不是少了else, ::::看自已编程的喜好和程序的组织结构了,你加个else话,下面的语句可要下次循环才可以执行了哦。

我写的那个AD采样程序很好用的,慢慢体会吧!!!

出0入0汤圆

发表于 2008-3-10 18:46:41 | 显示全部楼层
如果这个贴顶到100楼以上的后,我再开个新贴来晒干我写的时间调度器模块中的水分。
本人的模块中的写法
1:支持GCC,ICC,IAR,
2:多文件系统,如xj_adc.h ,xj_adc.c , xj_task.c ,xj_task.h , xj_sleep.c ,xj_sleep.h等等有十多个文件。
3:支持M8,M16,M48。
4:完整的时间调度器移植。
5:用UE编写,格式漂亮。
6:错误一大堆,谁让我是一只小小的菜鸟呢!!

出0入0汤圆

 楼主| 发表于 2008-3-11 09:05:58 | 显示全部楼层
To dejun
还有一点不明白,我只关注程序结构,时间调度流程,在while(1)中,是按状态分的,如下:
void main(void)
{
        Main_Init();
        while(1)       
        {
                if( tb_KEY_Buffer != _LOW)
                ...//键处理
                switch(TASK_State_G )
                {
                        case TASK_AAAA  :    { TASK_AAAA_Update(); }  break; //执行任务AAAA
                                  case TASK_BBBB  :    { TASK_BBBB_Update(); }  break;
                }
}
//具体任务执行,也是按状态拆分的,如下:
void TASK_AAAA_Update(void)
{
         static tWord timeout = 0;
        switch(TASK_CHECK_F)
        {
                case ... break;
                case ... break;       
        }
}
那又如何与时间调度很好结合的呢,请比如10ms一次查键,向LCD发送一个字符,500ms一次下位机通讯处理...如下:
void main(void)
{
     Main_Init();
     while(1)
     {
        //
        if(DSP_RunMe > 50)   
        {  
             DSP_RunMe = 0;
             DSP_Update();   
        }

        if(UART_RunMe == 1)  
        {  
             UART_RunMe = 0;   
             UART_Update();  
        }

        PCON |= 0x01; //休息一下啦
    }
}

To zhiwei
娓娓道来,如数家珍,清晰明了。

To dejun
不厌其烦,有问必答,古道心肠。

To Gorgon Meducer
高屋建瓴,海纳百川。

To shaozh
尽己所能,替人分忧。

To All
一个在大家眼中早已不是问题的问题,在一番穷追不舍,死缠滥打之下,在大家的热心指点下,
令我如如醍醐灌顶,大开眼界,原来大家的编程思维是如此之清晰,编程境界是如此之高。

出0入0汤圆

发表于 2008-3-11 10:17:42 | 显示全部楼层
如果一定要分类,时间触发嵌入式系统设计还是前后台系统。
那个时标是用来给后台系统使用的。对应的前台系统--中断的要求还是要尽可能的简短快速的运行完。
这样才能保证基于时标的后台系统能正确的运行。

第二个问题就更困难了。如何划分很重要,弄得不好需要狂多的标记才能让程序正确运行。马老师提出如何划分任务的研究真是太有才了。我是他研究生的话肯定会接收这个课题。
在 时间触发嵌入式系统 似乎是没有优先级这个概念的,也没有提出信号量这个概念。那么要处理相互关联的任务就只能靠全局变量作为标记。

介于抢占式的OS如ucos-II在8位机中只能用来做实验,但其中的非常多的思想是可以借鉴并移植到协作式的OS系统中,以方便程序的开发。所以我的观念同 ATmega32 cortex-m3 ,在8位机上喜欢用 协作式的OS系统。

在看了ucos-ii和时间触发嵌入式系统两本书后,就自己写了2个协作式的OS系统(当然,某些抢占式的狂热爱好者一定会任务这是前后台系统,不上档次。但在单片机开发中,没有最好的,只有最合适的!)。稍作整理,届时也发上来讨论一下吧。

出0入0汤圆

发表于 2008-3-11 10:28:06 | 显示全部楼层
Protothreads是一个很好的类协作式OS,本身应该不是OS,但有协作式OS调度。

个人非常喜欢用OS,打算用Protothreads打造一个自己的类OS平台,先用C++写,到比较完善的时候再转换成C。
不过我没有开发经验,C++也才学不久,程序中肯定会有错误的定方,请大家多指点指点。

http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=935503&bbs_page_no=1&bbs_id=1032



#include "config.h"

THR_Create(Thread1);
THR_Create(Thread2);
TMR_Create(timer1);
TMR_Create(timer2);

uint8 Thread1(Tpt *pt)
{
  PT_BEGIN(pt);
    while(1)
    {
      PORTA^=_BV(0);
      PT_TIMER_DELAY(&timer1,200);
    }
   PT_END(pt);
}

uint8 Thread2(Tpt *pt)
{
   PT_BEGIN(pt);
    while(1)
    {
      PORTA^=_BV(1);
      PT_TIMER_DELAY(&timer2,100);
    }
   PT_END(pt);
}

int main()
{

  DDRA=_BV(0)|_BV(1);
  PORTA=_BV(0)|_BV(1);
  TCNT0 = TCNT0_INIT;
  TCCR0 =  T0_CLK_DIV256;
  TIMSK|=_BV(0);
  sei();  

  while(1)
  {
     PT(Thread1)->execute();
     PT(Thread2)->execute();  
  }
}

出0入0汤圆

发表于 2008-3-11 10:34:10 | 显示全部楼层
to liqu
   在main()里的任务是和按键的响应是没多大的关系的,在TASK_Update()里的任务是由于按键的动作需要有选择的调用相关的分支状态任务,这样每种任务都用合理的方式来调用,尽量使程序清晰明了,大方得体。

出0入0汤圆

 楼主| 发表于 2008-3-11 12:02:50 | 显示全部楼层
To dejun
感觉查键,去抖,赋键值“void KEY_Update(void)”应10-40ms调用一次,怎么能与main()中任务无关?我可能理解错了,是如下结构吗?
void main(void)
{
  while(1)
  {
     if(Key_Time>4) Key_Update();//40ms查键,时标为10ms
     TASK_Update();//键处理或键响应
     if(Uart_Time>50) Uart_Down();//500ms与下位机通讯
     Disp();//显示
   }
}
感觉键处理的各分支任务不太好处理,包括的任务也多,比如LCD显示,比如5045的EEPROM存储,极端的甚至包括通讯(比如,按某键则叫分机执行调试任务)。

出0入0汤圆

发表于 2008-3-12 00:05:03 | 显示全部楼层
自己慢慢理解吧!!        :>

出0入0汤圆

发表于 2008-3-13 15:54:58 | 显示全部楼层
受益非浅!顶

出0入0汤圆

发表于 2008-3-13 16:31:11 | 显示全部楼层
有很多器件对时序要求很严格,如何实现驱动。比如DS18B20
读写DS18B20时,只是在部分时间段不允许中断(不超过64uS),如何解决这个64us问题?
void WriteDS18B20A(char ucData)
{
        char i;
        sbi(DDRB,DQA);
        asm("SEI");            //这里,必须关中断 64us,如何解决?
        for(i=0;i<8;i++)
        {
                cbi(PORTB,DQA);
                if( (ucData&1) ==0)
                {
                        asm("SEI");
                        delay(50);//60uS
                        asm("CLI");
                        sbi(PORTB,DQA);
                }
                else
                {      
                        delay(1);
                        sbi(PORTB,DQA);
                        asm("SEI");
                        delay(46);
                        asm("CLI");
                }
                ucData>>=1;
        }
        asm("SEI");
}
----------------------------------
我用定时器(中断) 10MS,在DS18B20中的数值转换等都用了状态机。但对于us级的中断系统开销太大了吧。用这个办法只能用在 器件的时序大于ms级的?

出0入0汤圆

 楼主| 发表于 2008-3-14 09:35:41 | 显示全部楼层
应该是的。
也不需要关中断的,时间调度要求在下一次调度前任务必须执行完,理想是只占用约50%时间是很可靠的。
我感觉难的不是在要求严的器件驱动上,而是比如同时有上位机,下位机通讯,这样系统有两个接收中断+时标中断,如何作到可靠?如何满足通讯协议,比如Tick为10mS,波特率为9600,如何满足MODBUS协议?

出0入296汤圆

发表于 2008-3-14 14:16:25 | 显示全部楼层
To liqu
    感觉你很热衷于一下搞清楚这些问题,不过我要泼一泼冷水:这些问题其实都是大的系统问题,需要长期的工程积累,不是一两个人苦口婆心能解释清楚的;这些问题都不是问题,因为只要你的代码行到了一定程度,只要你坚持在每一个新工程中都改进自己的工程结构,都能解决一两个小问题,时间久了,你就自然知道如何解决问题了——这个时候,即便看到有人提出同样的问题,你虽然知道如何处理,但是有一种“茶壶里煮饺子,有货倒不出”的感觉;因为这种感觉就来源于你的积累,来源于长期的工程经验,而这些东西是无法速成的。
    多做,多想,对于别人的做法多多思考,做到理解到什么程度,学习到什么程度,不要轻易改变自己过去思考的成果。
    希望这段话,比贴更多的代码对你有帮助。

出0入296汤圆

发表于 2008-3-14 14:30:19 | 显示全部楼层
to 【42楼】 yzz163
    对于 18B20 这样对时序要求很严格的系统,仍然有一种 “基于动态定时器的轻量级状态机” 可以解决。
    这种方法的主体思想是:利用一个独立的定时器,在其中断处理程序中根据状态的不同,动态的修改 TCNTn 的值, 并执行相应状态的代码。给进一步的提示:
    构建结构体:

typedef void (* LFSM_FUN)(void);

typedef struct Light_FSM
{
    UINT8       chDeltaT;
    LFSM_FUN    fnFSM;
    LFSM       *pNext;               //一定要形成一个环形链表
}LFSM;

对于ICC用户,记得用预编译指令CTASK对每一个状态函数进行声明,以祛除不必要的现场保护代码:
#pragma ctask FuncA FuncB;

LFSM FSM_18B20[] =
    {
        ……
    }

在定时器中断处理程序中

void Timer0_ISR(void)
{
    static LFSM *pFSM = FSM_18B20;
    LFSM *pTempFSM = pFSM;
   
    TCNT0 = pFSM->chDeltaT;                  //修改到达下一次中断的时间
    pFSM=pFSM->pNext;

    pTempFSM->fnFSM();                       //调用状态机(调用时,由于CTASK的修饰,不会进行现场保护,节省时间)
}



其实,以上的方法,只要稍作修改,加入少量的汇编,就可以变成一个抢占式的时间调度器了。

出0入0汤圆

发表于 2008-3-14 17:12:34 | 显示全部楼层
标记~~~~

出0入0汤圆

发表于 2008-3-15 00:31:36 | 显示全部楼层
困惑好久的问题终于有思路了,看来还是好好学状态机和时间调度了。只多用一个独立的定时器。

------------
43楼:我觉的你的问题用 马老师的 串口中断+缓冲,很好解决的。

比如一个系统有上、下位机通讯,9600波特率,按键,128*64LCD显示
--------------------
我用M16 4M 做了个万年历(1s计时),1999-2099农历查询,21个3寸8字LED(0.5s扫描),2个按钮单发、连_发(8ms扫描),温度(2秒读取一次),串口/485(9600bps/19200bps),支持多机,通讯协议看的充电器,连接上位机(PC delphi的spcomm控件写)自动校时,(目前精度0.5秒)。 还有 35%的空间,看看还搞点其他的功能。

只要不让MCU 白白的做 nop 延时。现在看到 有 delay_ms(); 就想办法换掉。

出0入0汤圆

发表于 2008-3-23 22:43:13 | 显示全部楼层
好东东,慢慢学一下这里的技术。

出0入13汤圆

发表于 2008-3-24 19:45:17 | 显示全部楼层
特此封赏本书和程序!我也看完了本书,打算用AVR实验一下!不过本书在可靠性探讨部分很不错啊!等会

出0入0汤圆

发表于 2008-3-24 20:18:13 | 显示全部楼层
Protothreads调度最简单

  while(1)
  {
     PT(Thread1)->execute();
     PT(Thread2)->execute();
     ………………
     ………………
     ………………
     PT(Threadn)->execute();
  }

出0入13汤圆

发表于 2008-3-24 22:26:21 | 显示全部楼层
点击此处下载ourdev_237856.rar(文件大小:2.94M)
上串的好不容易啊!

出0入13汤圆

发表于 2008-3-24 23:31:00 | 显示全部楼层
都传到现在了,还是不行啊!我给大家发邮件吧!有要的发信286202869@qq.com!

出0入13汤圆

发表于 2008-3-24 23:31:30 | 显示全部楼层
点击此处下载ourdev_237915.rar(文件大小:4.77M)
刚传完的第一部分

出0入13汤圆

发表于 2008-3-24 23:40:04 | 显示全部楼层
点击此处下载ourdev_237918.rar(文件大小:4.77M)

出0入13汤圆

发表于 2008-3-24 23:58:44 | 显示全部楼层
点击此处下载ourdev_237935.rar(文件大小:4.77M)
点击此处下载ourdev_237936.rar(文件大小:4.77M)
哈哈!继续,今晚一定OK!

出0入13汤圆

发表于 2008-3-25 00:03:41 | 显示全部楼层
点击此处下载ourdev_237943.rar(文件大小:1.03M)
点击此处下载ourdev_237944.rar(文件大小:4.77M)
哈哈!我传完了!

出0入13汤圆

发表于 2008-3-25 00:05:05 | 显示全部楼层
为了理想我们不断的努力,不断的拼搏,就是这样.今晚可睡个好觉了哦!

出0入0汤圆

发表于 2008-5-11 16:37:36 | 显示全部楼层
做个记号,下半年开始学操作系统

出0入0汤圆

发表于 2008-5-11 17:31:37 | 显示全部楼层
受益非浅!

出75入4汤圆

发表于 2008-5-11 17:56:41 | 显示全部楼层
学习了。记号。

出0入0汤圆

发表于 2008-5-11 21:44:12 | 显示全部楼层
好多人才啊!向各位高手学习。

出0入0汤圆

发表于 2008-5-12 10:30:44 | 显示全部楼层
mark~~~~谢谢

出0入0汤圆

发表于 2008-5-28 07:58:20 | 显示全部楼层
我也标记一下

出0入0汤圆

发表于 2008-5-28 08:51:08 | 显示全部楼层
标记一下

出0入0汤圆

发表于 2008-5-28 11:26:59 | 显示全部楼层
标记下下。

出0入0汤圆

发表于 2008-6-18 20:15:14 | 显示全部楼层
标记

出0入0汤圆

发表于 2008-6-26 16:41:31 | 显示全部楼层
傻孩子评价一下11楼的结构

出0入296汤圆

发表于 2008-6-26 16:54:57 | 显示全部楼层
to 【67楼】 wswh2o 水之影
    很标准的合作式信号量调度系统。不具备抢占功能。很简洁^_^

出0入0汤圆

发表于 2008-6-26 22:53:59 | 显示全部楼层
都是绝顶的人才,都有大将的风度。

出0入0汤圆

发表于 2008-6-27 00:44:20 | 显示全部楼层
仔细研究下!

出0入0汤圆

发表于 2008-7-9 16:11:52 | 显示全部楼层
为什么这么好的贴子没顶到100楼呢?我有心连顶30次,又怕阿莫扣我的分.唉,主要是想看到34楼的东东.

出75入4汤圆

发表于 2008-7-12 09:12:25 | 显示全部楼层
收益了。

出0入0汤圆

发表于 2008-7-12 18:39:02 | 显示全部楼层
研究ing

出210入8汤圆

发表于 2008-7-12 18:51:46 | 显示全部楼层
好贴。

出0入0汤圆

发表于 2008-7-13 21:33:22 | 显示全部楼层
最近也在看这本书。我们公司的程序结构和书中所说的类似。俺先做个记号,嘿嘿。

出0入0汤圆

发表于 2008-7-30 22:20:21 | 显示全部楼层
顶  好东西  谢谢

出0入0汤圆

发表于 2008-9-13 11:46:17 | 显示全部楼层
mark

出0入0汤圆

发表于 2008-9-13 23:29:55 | 显示全部楼层
有时间再看,

出0入0汤圆

发表于 2008-10-30 15:04:46 | 显示全部楼层
to&nbsp;45楼Gorgon&nbsp;Meducer&nbsp;傻孩子 “动态定时器的轻量级状态机”
第一回听说,这个名词,可有出处?

出0入0汤圆

发表于 2008-10-30 16:09:17 | 显示全部楼层
长见识,我跟楼主有一样的问题,想了好长时间,这次终于有点头绪了。

出0入0汤圆

发表于 2008-10-30 18:16:45 | 显示全部楼层
有人一起研究一下AVRX?

出0入0汤圆

发表于 2008-10-30 22:26:44 | 显示全部楼层
mark

出0入0汤圆

发表于 2008-10-31 09:48:22 | 显示全部楼层
to&nbsp;81楼
avrx,给大家介绍下&nbsp;先

出0入0汤圆

发表于 2008-10-31 10:40:19 | 显示全部楼层
hao'

出0入0汤圆

发表于 2008-10-31 15:49:26 | 显示全部楼层
mark

出0入0汤圆

发表于 2008-10-31 17:55:43 | 显示全部楼层
好贴啊!&nbsp;&nbsp;顶了在详细的研究研究

出0入0汤圆

发表于 2009-4-25 16:08:43 | 显示全部楼层
MARK!

出0入0汤圆

发表于 2009-5-6 09:35:42 | 显示全部楼层
做个记号,下半年开始学操作系统

出0入0汤圆

发表于 2009-10-3 15:51:18 | 显示全部楼层
又让我找到了,这次要好好研究下了

出0入0汤圆

发表于 2009-10-3 23:41:58 | 显示全部楼层
马老师出山,一定要收藏

出0入0汤圆

发表于 2009-10-4 22:53:24 | 显示全部楼层
深刻

出0入0汤圆

发表于 2009-10-6 10:20:40 | 显示全部楼层
记号

出0入0汤圆

发表于 2009-10-7 11:50:47 | 显示全部楼层
记号

出0入0汤圆

发表于 2009-10-17 17:02:33 | 显示全部楼层
最近,我在弄ds18b20及点阵动态扫描显示时也遇到了很多相似的问题,很想请教啊,真是学习了!以后常来,还希望高手赐教

出200入0汤圆

发表于 2009-10-18 01:00:19 | 显示全部楼层
专门登上来感谢 45楼Gorgon Meducer 傻孩子 “动态定时器的轻量级状态机” ,最近想到这个问题,不知道咋搞,这确实是个好办法。

谢谢!

出200入0汤圆

发表于 2009-10-18 01:01:04 | 显示全部楼层
再顶一次,为100楼做贡献。

出0入0汤圆

发表于 2009-10-18 11:36:39 | 显示全部楼层
顶!

出0入0汤圆

发表于 2009-10-18 11:38:11 | 显示全部楼层
再顶下,100楼了…呵呵

出0入0汤圆

发表于 2009-10-18 12:17:35 | 显示全部楼层
记号,很精彩
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-24 21:45

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

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