pixhw 发表于 2023-5-26 07:08

单片机非阻塞式框架

1.设置1ms定时器中断,设置变量在定时器中断函数中++/–,以此计数,while(1)中对改变量进行值判断就能得到指定时间。
2.跑裸机都可参考此框架的非阻塞式延时。
3.注意MCU的工作频率和机器周期。

unsigned int Cnt1 = 0;
unsigned int Cnt1 = 0;
void main()
{
        while(1)
        {
                if(Cnt1)              //1ms执行一次
                {
                        Cnt1 = 0;
                        task_1ms();//在此任务函数中,可以继续计时
                }
               
                if(Cnt2 >= 10)//确定时间,10ms执行一次
                {
                        Cnt2 = 0;
                        task_10ms();
                }
        }
}

void timer0() interrupt 1
{       
        TL0        = 0x1C;        //与计算值会有一定偏差,需做补偿
        TH0        = 0xFC;        //定时器重赋值
        Cnt1 = 1;   //1ms定时器
        Cnt2++;   //不定       
}


xiaokunkun 发表于 2023-5-27 14:31

这个不就是时间片轮询吗?

nomomy 发表于 2023-6-6 17:36

之前都是使用延时来实现。            

zerorobert 发表于 2023-6-7 12:52

单片机非阻塞式框架是一种常用的编程框架

macpherson 发表于 2023-6-7 12:57

代码的延时函数如何实现呢            

maqianqu 发表于 2023-6-7 13:46

其他的延时函数可以实现吗?            

tpgf 发表于 2023-6-7 15:40

void KeyScan(void)      //1ms定时器中
{
        static uint Su16TouchKeyCnt = 0;        //计时
        static bit SbTouchKeyLock = 0;                //长按锁
        static bit SbKeyShortPressFlag = 0; //确定短按
        if(!KeyPin)//没有按下时
        {
                SbTouchKeyLock = 0;       
                Su16TouchKeyCnt = 0; //按下时间清零
                if(SbKeyShortPressFlag)//短按任务
                {
                        SbKeyShortPressFlag = 0;
                        //短按标志位
                }
        }
        else if(!SbTouchKeyLock)                //避免多次触发长按功能
        {
                Su16TouchKeyCnt++;                        //按下计时
               
                if(Su16TouchKeyCnt >= 20)        //大于20ms才算按下,以此来延时消抖,并且不会阻塞系统
                {
                        SbKeyShortPressFlag = 1; //已有短按
                }
                if(Su16TouchKeyCnt >= 2000)        //长按2s后,取消短按/锁住长按
                {
                        SbTouchKeyLock = 1;
                        SbKeyShortPressFlag = 0;//取消短按
                        //长按标志/长按任务
                }
        }
}

//任务函数
void Key_Task(void)
{
        switch或者if判断是哪种类型的按键按下
}

kxsi 发表于 2023-6-7 15:52

void DisplayScan(void)   //1ms定时器中扫描
{
        static uchar Su8ScanStep = 0;//动态扫描步骤
       
        if(GbWorkStatus)
        {
                if(GbLedEnableFlag )
                {
                        //消隐
                        P2 = (unsigned char)0xFF;        //共阳,给高电平不亮
                        LED_COM1_CLOSE;                                //第一个8片选关闭
                        LED_COM2_CLOSE;                                //第二个8片选关闭

                        switch(Su8ScanStep)                        //扫描步骤
                        {
                                case 0:   //first
                                        P2 = Gu8LedDigTableBuf; //在外部确定Gu8LedFirstShowData的值是多少,将0-9和部分字母的编码列举在Gu8LedDigTableBuf[]数组中
                                        LED_COM1_OPEN;        //选中第一个数码管
                                        LED_COM2_CLOSE;
                                        Su8ScanStep++;        //下一次显示第二个数码管
                                break;
                               
                                case 1:   //second
                                        P2 = Gu8LedDigTableBuf;//第二位数据
                                        P27 = !GbLedThirdShowEnable;                               //是否带小数点
                                        LED_COM1_CLOSE;
                                        LED_COM2_OPEN;
                               
                                        //=============================以下为闪烁部分程序,不需要闪烁时,删除下面case 1程序,添加Su8ScanStep = 0;即可
                                        if(!GbLedFlickerFlag)        //闪烁标志位,没有要求闪烁的话跳回到第一个数码管的显示
                                        {
                                                Gu16LedOffFlickerCnt = 0;
                                                Su8ScanStep = 0;
                                        }
                                        else if(++Gu16LedOffFlickerCnt >= LED_ON_FLICKER_TIME)//要求闪烁时,闪烁时期亮的时间
                                        {
                                                Gu16LedOffFlickerCnt = 0;
                                                Su8ScanStep++;
                                        }
                                        else
                                                Su8ScanStep = 0;       
                                        break;
                                case 2://
                                        LED_COM1_CLOSE;
                                        LED_COM2_CLOSE;
                                        if(++Gu16LedOnFlickerCnt >= LED_OFF_FLICKER_TIME)        //闪烁时期灭的时间
                                        {
                                                Gu16LedOnFlickerCnt = 0;
                                                Su8ScanStep = 0;
                                        }
                                        break;
                        }
                }
                else
                {
                        LED_COM1_CLOSE;
                        LED_COM2_CLOSE;
                }
        }
}

wiba 发表于 2023-6-7 16:17

unsigned int xdata Gu16BuztimerCnt = 0;
#define BUZ_OPEN (Gu16BuztimerCnt = 200)//一次响200ms
void BeepTask(void)
{
        static bit Su8Buzlock = 0;
        static uchar TempCnt = 0;
        static uint IntervalCnt = 0;                //间隔时间
        static SbVoiceIntervalFlag = 0;                //间隔响
       
        if(SbVoiceIntervalFlag == 0 && Gu16BuztimerCnt > 0)                
        {
                if(Su8Buzlock == 0)
                {
                        Su8Buzlock = 1;
                        TempCnt = Gu16BuztimerCnt;        //重新赋值不受Gu16BuztimerCnt影响
                        PWM_Set(Enable,x);//PWM开启
                }
                else
                {
                        TempCnt--;
                        if(TempCnt == 0)
                        {
                                Gu16BuztimerCnt = 0;                                       
                                SbVoiceIntervalFlag = 1;
                                PWM_Set(Disable,x);   //PWM关闭
                                Su8Buzlock = 0;
                        }
                }
        }
        else if(SbVoiceIntervalFlag == 1)    //若需要均匀响
        {
                IntervalCnt++;
               
                if(IntervalCnt >= 50)                //间隔50ms使能
                {
                        IntervalCnt = 0;
                        SbVoiceIntervalFlag = 0;
                        Gu16BuztimerCnt = 0;               
                }       
        }
}

drer 发表于 2023-6-7 16:40

void IoToPwmScan(void)        //放在1ms定时器中
{
        if(Enable)       
        {
                if(++Cnt <= 100)        //100ms低电平
                        PIN = 0;
                else if(Cnt <= 200) //100ms高电平
                        PIN = 1;
                else
                        Cnt = 0;
        }
        else
                Cnt = 0;
}

磨砂 发表于 2023-6-7 17:04

//处理延时在中断中卡死的情况
static u8 delaying_times = 0;//叠加执行延时的次数
static u16 delaying_finish = 0;//记录最多16个的递归溢出事件中,每一个是否都已经记数溢出
void delay_ms(u16 nms)
{
        u32 last_systick_val;
        if(delaying_times != 0)//如果主程序在跑delay函数的过程中,发生中断并在中断中又进入了delay函数
        {
                last_systick_val = SysTick->VAL;//将上次的计数器的值保存下来以便退出中断后回去时可以从该值继续递减
                //如果上次记数已经溢出,代表着上次的delay已经记数完成,将该次溢出事件记录下来,以便出了中断回到原delay函数时,可以直接跳出while
                //delaying_finish是16位的,最多可以记录16次溢出事件,即16层的递归
                        if(SysTick->CTRL & (1 << 16))delaying_finish |= (1 << (delaying_times - 1));
        }
        delaying_times ++;
        SysTick->LOAD = (u32)fac_ms * nms;//自动重装载值
        SysTick->VAL = 0x00;//清除计时器的值
        SysTick->CTRL |= (1 << 0);//SysTick使能,使能后定时器开始倒数
        while(!(SysTick->CTRL & (1 << 16)))//判断是否减到0,减到0时CTRL的第16位会置1,读取后会自动置0
        {
                //如果在中断中计数器已经溢出,就退出while,并且对应中断位清零
                if(delaying_finish & (1 << (delaying_times- 1)))
                {
                        delaying_finish &= ~(1 << (delaying_times- 1));
                        break;
                }
        }
    delaying_times --;
        if(delaying_times == 0)
        {
                SysTick->CTRL &= ~(1 << 0);//关闭SysTick,关闭后记数器将不再倒数
                SysTick->VAL = 0x00;//清除计时器的值(执行关闭SysTick程序时,记数器又开始了新一轮的倒数,所以关闭后记数器的值不为0)
        }
        else
        {
                /* 读取CTRL寄存器的同时,CTRL的第16位会变为0,关闭SysTick后给VAL寄存器赋值再使能的原因
               * 1.若未关闭SysTick,且先将CTRL的第16位清零后再给VAL寄存器赋值,则在赋值的过程中计数器可能会记数到0,从而导致CTRL的第16位又被置1
               * 2.若未关闭SysTick,且先给VAL寄存器赋值后再将CTRL的第16位清零,则在清零的过程中计数器会继续递减并且可能在CTRL的第16位完成清零前就溢出
               * 所以必须关闭SysTick,且赋值完需要再使能使得递归回原函数的while中计数器会继续递减
               */
                SysTick->CTRL &= ~(1 << 0);//关闭SysTick,关闭后记数器将不再倒数
                SysTick->LOAD = last_systick_val;
                SysTick->VAL = 0x00;//清除计时器的值
                SysTick->CTRL |= (1 << 0);//SysTick使能,使能后定时器开始倒数
        }
}

晓伍 发表于 2023-6-7 17:17

非阻塞式延时可以在等待的时间进行其他函数处理,可节省单片机效率

caigang13 发表于 2023-6-7 18:56

可以用此方法来实现软件定时器的功能。

uytyu 发表于 2023-6-7 21:27

利用时间差来解决这个问题。            

everyrobin 发表于 2023-6-8 22:46

需要注意系统资源的消耗、任务之间的调度和同步问题等细节,以保证程序的正确性和稳定性。

plsbackup 发表于 2023-6-8 22:52

通过轮询方式实现任务调度和处理,避免了阻塞等待的情况,提高了程序的响应速度和可靠性。

hearstnorman323 发表于 2023-6-8 23:08

定时器的实现的效果如何            

elsaflower 发表于 2023-6-8 23:14

缺点是效率较低,当任务数量增加时容易出现调度问题。

primojones 发表于 2023-6-8 23:22

这个可以,执行的效率提高很多。            

primojones 发表于 2023-6-10 10:30

需要频繁切换和调度,这个怎么解决的
页: [1] 2 3
查看完整版本: 单片机非阻塞式框架