qpc之qk调度学习笔记

1.qk和qv调度的区别

qv是以轮询调度的方式派发事件;
qk是以抢占的方式调度,优先级高的活动对象关联的事件经系统调度后会优先得到派发

2.qk调度的过程

2.1中断级调度流程

2.2中断级调度过程
①系统中断
中断级调度是有SystickHandler发起的,在中断任务中查询是否有优先级更高的活动对象关联的时间需要处理,有则触发pendsv中断,没有则正常退出。
void SysTick_Handler(void)
{
   uint32_t tmp;
    (void)tmp;
    QK_ISR_ENTRY(); 
    systick_ticks += 1;
    tmp = SysTick->CTRL;            /* clear SysTick_CTRL_COUNTFLAG /
    qs_tick_time += qs_tick_period; /
 account for the clock rollover */
    QF_TICK_X(0, &obj_systick); // 处理所有的事件定时
    QK_ISR_EXIT();  // 查询是否有更高优先级的活动对象有事件要处理,触发pendsv中断
}

②pendsv中断
void PendSV_Handler(void) {
__asm volatile (
    ; 为清除pendsv悬起状态准备,r1是写入的数据,r3是ICSR寄存器地址
    "  LDR     r3,=" STRINGIFY(NVIC_ICSR) “\n” 
    "  MOVS    r1,#1            \n"
    "  LSLS    r1,r1,#27        \n" 
   ; 进入临界区
#if (__ARM_ARCH == 6)               
    "  CPSID   i                \n" ; 关闭总中断
#else                               
#if (ARM_FP != 0)                 ; 如果内核支持浮点运算
    "  PUSH    {r0,lr}          \n" 
#endif                              
    "  MOVS    r0,#" STRINGIFY(QF_BASEPRI) “\n”
    "  CPSID   i                \n" 
    "  MSR     BASEPRI,r0       \n" ; 设置中断屏蔽寄存器,屏蔽部分中断
    "  CPSIE   i                \n" 
#endif                              /* M3/M4/M7 /
    "  STR     r1,[r3]          \n" ; 设置ICSR寄存器,清除pendsv悬起状态
    /
 The QK activator must be called in a Thread mode, while this code
    * executes in the Handler mode of the PendSV exception. The switch
    * to the Thread mode is accomplished by returning from PendSV using
    * a fabricated exception stack frame, where the return address is
    * QK_activate
().
    *
    * NOTE: the QK activator is called with interrupts DISABLED and also
    * returns with interrupts DISABLED.
    */
    "  LSRS    r3,r1,#3         \n" ; 左移3位(1<<24), 设置EPSR的T位,使用thumb指令集
    "  LDR     r2,=QK_activate
 \n" ; 加载QK_activate_到r2
    "  SUBS    r2,r2,#1         \n" ; 按照半字对齐thumb地址(新的pc指针)
    "  LDR     r1,=Thread_ret   \n" ; 设置lr寄存器,QK_activate_执行完后返回Thread_ret执行
    "  SUB     sp,sp,#84       \n" ; 将当前的栈帧(xpsr,pc,lr,r12,r3-r0)压入栈中
    "  ADD     r0,sp,#5
4       \n" ; r0指向栈中lr地址处
    "  STM     r0!,{r1-r3}      \n" ; 将xpsr,pc,lr写入栈中
    "  MOVS    r0,#6            \n" ; r0 = 6
    "  MVNS    r0,r0            \n" ; 取反r0 = 0xFFFFFFF9, 设置中断特殊返回值
    "  BX      r0               \n" ; 退出中断返回到线程模式,自动出栈,sp每次自增4按照顺序依次出栈
    );
}

③QK_activate_
从pendsv中断退出后进入到QK_activate_(),此函数主要作用是派发优先级最高的活动对象的事件。如果在派发期间产生了更高优先级的活动对象事件,则连续派发。在systick中断退出时执行QK_sched_()计算了当前所有活动对象中优先级最高的,在次函数中利用此变量(QK_attr.nextPrio)进行切换
void QK_activate_(void) {
    uint_fast8_t const pin = (uint_fast8_t)QK_attr_.actPrio;  /* save /
    uint_fast8_t p = (uint_fast8_t)QK_attr_.nextPrio; /
 next prio to run */
    QActive a;
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
    uint_fast8_t pprev;
#endif /
 QK_ON_CONTEXT_SW || Q_SPY */

/* QK_attr_.actPrio and QK_attr_.nextPrio must be in ragne /
    Q_REQUIRE_ID(500, (pin < Q_DIM(QF_active_))
                      && (0U < p)
                      && (p < Q_DIM(QF_active_)));
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
    pprev = pin;
#endif /
 QK_ON_CONTEXT_SW || Q_SPY /
    QK_attr_.nextPrio = 0U; /
 clear for the next time /
    /
 loop until no more ready-to-run AOs of higher prio than the initial /
    do  {
        QEvt const e;
        a = QF_active_[p];  // 根据优先级从活动对象列表中取出活动对象
        QK_attr_.actPrio = (uint8_t)p; // p作为当前活动对象的优先级
        QS_BEGIN_NOCRIT_PRE_(QS_SCHED_NEXT, QS_priv_.locFilter[AO_OBJ], a)
            QS_TIME_PRE_();     /
 timestamp /
            QS_2U8_PRE_(p,      /
 priority of the scheduled AO /
                        pprev); /
 previous priority /
        QS_END_NOCRIT_PRE_()
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
        if (p != pprev) {   // 发生了切换?执行上下文切换回调函数
#ifdef QK_ON_CONTEXT_SW
            /
 context-switch callback
/
            QK_onContextSw(((pprev != 0U)
                            ? QF_active_[pprev]
                            : (QActive )0), a);
#endif /
 QK_ON_CONTEXT_SW /
            pprev = p;     /
 update previous priority /
        }
#endif /
 QK_ON_CONTEXT_SW || Q_SPY /
        QF_INT_ENABLE();  // 关闭中断
        e = QActive_get_(a); // 从活动对象a的事件队列中取出一个事件e
        QHSM_DISPATCH(&a->super, e); // 派发事件e到状态机函数并同步执行
        QF_gc(e); // 事件回收(如果是动态分配的事件,需要将内存回收至内存池)
        /
 determine the next highest-priority AO ready to run… */
        QF_INT_DISABLE();  // 打开中断
        if (a->eQueue.frontEvt == (QEvt )0) {  // 检查活动对象a的时间队列是否为空?
            QPSet_remove(&QK_attr_.readySet, p); // 从活动对象就绪表中移除相应的优先级位
        }
        QPSet_findMax(&QK_attr_.readySet, p); // 计算最高的活动对象优先级p
        if (p <= pin) { // p比当前活动对象优先级更低,清0退出
            p = 0U;
        }
        else if (p <= (uint_fast8_t)QK_attr_.lockPrio) { // 低于锁定优先级,同样退出
            p = 0U; /
 active object not eligible /
        }
        else {
            Q_ASSERT_ID(510, p <= QF_MAX_ACTIVE);
        }
    } while (p != 0U); // 优先级有效,继续重复上述操作
    QK_attr_.actPrio = (uint8_t)pin;  // 恢复当前活动状态优先级
#if (defined QK_ON_CONTEXT_SW) || (defined Q_SPY)
    if (pin != 0U) { /
 resuming an active object? /
        a = QF_active_[pin]; /
 the pointer to the preempted AO /
        QS_BEGIN_NOCRIT_PRE_(QS_SCHED_RESUME, QS_priv_.locFilter[AO_OBJ], a)
            QS_TIME_PRE_();     /
 timestamp /
            QS_2U8_PRE_(pin,    /
 priority of the resumed AO /
                        pprev); /
 previous priority /
        QS_END_NOCRIT_PRE_()
    }
    else {  /
 resuming priority==0 --> idle */
        a = (QActive )0; / QK idle loop */
        QS_BEGIN_NOCRIT_PRE_(QS_SCHED_IDLE, (void )0, (void )0)
            QS_TIME_PRE_();     /
 timestamp /
            QS_U8_PRE_(pprev);  /
 previous priority /
        QS_END_NOCRIT_PRE_()
    }
#ifdef QK_ON_CONTEXT_SW
    QK_onContextSw(QF_active_[pprev], a); /
 context-switch callback /
#endif /
 QK_ON_CONTEXT_SW /
#endif /
 QK_ON_CONTEXT_SW || Q_SPY /
}
④Thread_ret
从QK_active_退出后,执行Thread_ret。为什么执行Thread_ret函数? 请回看步骤2关于pendsv中断的分析。Thread_ret的内容非常简单,写ICSR寄存器触发NMI中断。
attribute ((naked))
void Thread_ret(void) {
__asm volatile (
#if (__ARM_FP != 0)                 /
 if VFP available… /
    "  MRS     r0,CONTROL       \n" /
 r0 := CONTROL /
    "  BICS    r0,r0,#4         \n" /
 r0 := r0 & ~4 (FPCA bit) /
    "  MSR     CONTROL,r0       \n" /
 CONTROL := r0 (clear CONTROL[2] FPCA bit) /
    "  ISB                      \n" /
 ISB after MSR CONTROL (ARM AN321,Sect.4.16) /
#endif                              /
 VFP available /
    /
 trigger NMI to return to preempted task…
    * NOTE: The NMI exception is triggered with nterrupts DISABLED
    /
    "  LDR     r0,=0xE000ED04   \n" ; 加载ICSR寄存器地址到r0
    "  MOVS    r1,#1            \n" ; r1 = 1
    "  LSLS    r1,r1,#31        \n" ; r1 = 1 << 31
    "  STR     r1,[r0]          \n" ; 写ICSR寄存器的bit31悬起NMI中断
    "  B       .                \n" ; 原地等待进入NMI中断,相当于while(1);
    );
}
⑤NMI_Handler
NMI中断是MCU内部复位中断之外优先级最高的中断。在此处的作用是恢复现场。从线程模式(可理解为主循环)中被pendsv中断打断时,有一个自动入栈的操作。执行完调度后需要恢复现场,将进入pendsv中断时压入的栈帧恢复,这样退出NMI中断后会继续从被pendsv打断处运行。
attribute ((naked))
void NMI_Handler(void) {
__asm volatile (
    "  ADD     sp,sp,#(8
4)     \n" ; 移除8个寄存器的栈帧
#if (__ARM_ARCH == 6)               ; Cortex-M0/M0+/M1 (v6-M, v6S-M)内核无中断BASEPRI寄存器
    "  CPSIE   i                \n" ; 打开中断
    "  BX      lr               \n" ; 退出中断, 返回到被pendsv打断处继续执行
#else                               ; M3/M4/M7内核有BASEPRI寄存器
    "  MOVS    r0,#0            \n" ; r0 = 0
    "  MSR     BASEPRI,r0       \n" ; 清除中断屏蔽寄存器
#if (__ARM_FP != 0)                 ; 如果是带浮点运算的内核
    "  POP     {r0,pc}          \n" ; 将r0, pc先弹出
#else                               ; 无浮点运算的内核
    "  BX      lr               \n" ; 退出中断,返回到被pendsv打断处继续执行
#endif                              /
 no VFP /
#endif                              /
 M3/M4/M7 /
    );
}
思考一个问题:
1). 进入NMI中断后为什么首先执行ADD sp,sp,#(8
4)这个出栈的操作?
答:程序是从Thread_ret函数进入NMI中断的,Thread_ret是在线程模式下运行的,进入NMI中断内核会自动压入8个寄存器的栈帧,所以进入NMI中断,首先要弹出栈帧,否则退出NMI中断时,程序会回到Thread_ret函数被打断处继续执行。

2.3任务级调度
任务级调度在线程中发送事件时调用,相关的函数有QActive_post_,QActive_postLIFO和QTicker_post_。该调度的触发函数是QACTIVE_EQUEUE_SIGNAL_

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
qp官网下载qpc,移植到stm32f103单片机,在正点原子战舰V3开发板上实验成功 qp搭建步骤重要提示: #define RED_QUEUE_LEN 3 #define BLUE_QUEUE_LEN 3 #define TACKER_EVENT_POOL_LEN (RED_QUEUE_LEN + BLUE_QUEUE_LEN) static QEvt const * l_redQueueSto[RED_QUEUE_LEN]; //事件队列 static QEvt const * l_blueQueueSto[BLUE_QUEUE_LEN]; //事件队列 static LedEvt LedEvtPoolSto[TACKER_EVENT_POOL_LEN]; //事件池 static QSubscrList SubSrcSto[MAX_PUB_SIG]; //订阅列表 typedef struct LedEvtTag LedEvt; //定义事件结构 struct LedEvtTag{ QEvt super_; uint16_t uiParaH; uint16_t uiParaL; }; //定义信号枚举 enum LedSignals{ START_SIG = Q_USER_SIG, KEY0_SIG, KEY1_SIG, KEY2_SIG, KEYUP_SIG, ALL_OFF_SIG, ONLY_BULE_SIG, ONLY_RED_SIG, ALL_ON_SIG, MAX_PUB_SIG }; void PublishLedEvt(uint16_t uiSig, uint16_t uiParaH, uint16_t uiParaL) //发布事件函数 { LedEvt* peTacker; peTacker = Q_NEW(LedEvt, uiSig); peTacker->uiParaH = uiParaH; peTacker->uiParaL = uiParaL; QF_publish_((QEvt*)peTacker); } QF_init(); //时间事件列表、活动对象查找表、优先级集合 QF_psInit(SubSrcSto, Q_DIM(SubSrcSto)); //初始化事件池 QF_poolInit(LedEvtPoolSto,sizeof(LedEvtPoolSto),sizeof(LedEvtPoolSto[0])); RedLed_Start(uiPrio++, l_redQueueSto, Q_DIM(l_redQueueSto), 0, 0); //建立活动对象 BlueLed_Start(uiPrio++, l_blueQueueSto, Q_DIM(l_blueQueueSto), 0, 0); /////////////////////////////////////////////////// typedef struct RedActiveTag RedActive; //构建一个活动对象活动类型 struct RedActiveTag{ QActive super_; volatile uint16_t RedLedStateNow; uint16_t a; uint16_t b; }; extern RedActive RedLed; RedActive RedLed; void RedLed_Start(uint_fast8_t prio, QEvt const *qSto[], uint_fast16_t qLen, void *stkSto, uint_fast16_t stkSize) { RedLed_Ctor(&RedLed;); QActive_start_((QActive*)&RedLed;, prio, qSto, qLen, stkSto, stkSize, (QEvt const *)0); //创立活动对象的线程并提醒 QF 开始管理活动对象 } void RedLed_Ctor(RedActive* me) { QActive_ctor(&me;->super_, (QStateHandler)RedLed_Initial); //初始化状态机基础类(成员 super) me->RedLedStateNow = 0; }
STM32 HAL QPC 是一种针对STM32系列微控制器的硬件抽象层(Hardware Abstraction Layer),它最初是由Quantum Leaps公司开发的。它的目的是提供一种方便使用的编程接口,使开发者能够更容易地配置和控制STM32微控制器的各种功能。 HAL是STM32全新的软件平台,整合了STM32CubMX和STM32CubeF4,为开发人员提供了一种基于HAL库的新的软件开发方式。它提供了一组高级API函数,用于配置和控制STM32的内部模块,如GPIO、USART、SPI、I2C等。这使得开发者可以更加方便地编写和维护代码。 而QPC(Qt Positioning Control)是一个由Quantum Leaps开发的嵌入式实时操作系统(RTOS)和事件驱动的框架。它为开发者提供了一种易于使用的方法,用于编写高效、可靠的嵌入式软件。QPC提供了一种事件驱动的编程模型,开发者可以通过定义事件和事件处理函数的方式来编写应用程序。在QPC中,每个事件都有一个优先级,这样可以确保高优先级事件在低优先级事件之前得到处理。 STM32 HAL QPC的结合使用使得开发者能够更轻松地开发出高性能、可靠的嵌入式应用程序。HAL提供了方便的编程接口,使得配置和控制STM32的各种外设变得更加容易。而QPC提供了一种高效的开发框架,通过事件驱动的方式,使得程序的执行更加可控和可靠。这种结合使用的方式,可以大大提高开发效率和软件质量,使得开发者能够更快地将产品推向市场。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值