Menu

How does the QP framework handle simple blocking?

tuzik
2024-08-21
2024-09-04
  • tuzik

    tuzik - 2024-08-21

    HI.

    I am using the QP framework for project development with the STM32F446 chip and the Arduino framework. I am utilizing the SysTick, an encoder (using the encoder mode of the TIM2 timer), and an Ethernet module (SPI Ethernet module with the W5500 chip).

    I am currently encountering an issue. I found that the w5500 library does not use SPI interrupts but instead uses traditional while loop polling, such as the following code:

    /**
      * @brief This function is implemented by user to send/receive data over
      *         SPI interface
      * @param  obj : pointer to spi_t structure
      * @param  tx_buffer : tx data to send before reception
      * @param  rx_buffer : rx data to receive if not numm
      * @param  len : length in byte of the data to send and receive
      * @retval status of the send operation (0) in case of error
      */
    spi_status_e spi_transfer(spi_t *obj, const uint8_t *tx_buffer, uint8_t *rx_buffer,
                              uint16_t len)
    {
      spi_status_e ret = SPI_OK;
      uint32_t tickstart, size = len;
      SPI_TypeDef *_SPI = obj->handle.Instance;
      uint8_t *tx_buf = (uint8_t *)tx_buffer;
    
      if (len == 0) {
        ret = SPI_ERROR;
      } else {
        tickstart = HAL_GetTick();
    
    #if defined(SPI_CR2_TSIZE)
        /* Start transfer */
        LL_SPI_SetTransferSize(_SPI, size);
        LL_SPI_Enable(_SPI);
        LL_SPI_StartMasterTransfer(_SPI);
    #endif
    
        while (size--) {
    #if defined(SPI_SR_TXP)
          while (!LL_SPI_IsActiveFlag_TXP(_SPI));
    #else
          while (!LL_SPI_IsActiveFlag_TXE(_SPI));
    #endif
          LL_SPI_TransmitData8(_SPI, tx_buf ? *tx_buf++ : 0XFF);
    
    #if defined(SPI_SR_RXP)
          while (!LL_SPI_IsActiveFlag_RXP(_SPI));
    #else
          while (!LL_SPI_IsActiveFlag_RXNE(_SPI));
    #endif
          if (rx_buffer) {
            *rx_buffer++ = LL_SPI_ReceiveData8(_SPI);
          } else {
            LL_SPI_ReceiveData8(_SPI);
          }
          if ((SPI_TRANSFER_TIMEOUT != HAL_MAX_DELAY) &&
              (HAL_GetTick() - tickstart >= SPI_TRANSFER_TIMEOUT)) {
            ret = SPI_TIMEOUT;
            break;
          }
        }
    
    #if defined(SPI_IFCR_EOTC)
        // Add a delay before disabling SPI otherwise last-bit/last-clock may be truncated
        // See https://github.com/stm32duino/Arduino_Core_STM32/issues/1294
        // Computed delay is half SPI clock
        delayMicroseconds(obj->disable_delay);
    
        /* Close transfer */
        /* Clear flags */
        LL_SPI_ClearFlag_EOT(_SPI);
        LL_SPI_ClearFlag_TXTF(_SPI);
        /* Disable SPI peripheral */
        LL_SPI_Disable(_SPI);
    #else
        /* Wait for end of transfer */
        while (LL_SPI_IsActiveFlag_BSY(_SPI));
    #endif
      }
      return ret;
    }
    

    This function uses many while loops. My encoder uses interrupts, and when a pulse from the encoder arrives, the encoder interrupt calls a callback function and then sends the event to the active object. My systick works the same way, sending systick events to the active object. The problem arises when I rotate the encoder quickly, which means a large number of events are published to the active object, and then the QP framework asserts.

    bool QActive_post_(QActive * const me,
                       QEvt const * const e,
                       uint_fast16_t const margin,
                       void const * const sender)
    {
        #ifndef Q_SPY
        Q_UNUSED_PAR(sender); 
        #endif
    
        #ifdef Q_UTEST
        #if Q_UTEST != 0
        if (me->super.temp.fun == Q_STATE_CAST(0)) {
            return QActiveDummy_fakePost_(me, e, margin, sender); 
        }
        #endif
        #endif
    
        QF_CRIT_STAT 
        QF_CRIT_ENTRY(); 
        QF_MEM_SYS(); 
    
        #ifndef Q_UNSAFE
        uint8_t const pcopy = (uint8_t)(~me->prio_dis); 
        Q_REQUIRE_INCRIT(102, (QEvt_verify_(e)) && (me->prio == pcopy)); 
        #endif
    
    
        QEQueueCtr nFree = me->eQueue.nFree; 
    
        QS_TEST_PROBE_DEF(&QActive_post_)
        QS_TEST_PROBE_ID(1,
            nFree = 0U; 
        )
    
        bool status;
        if (margin == QF_NO_MARGIN) 
        {
            if (nFree > 0U) 
            {
                status = true; 
            }
            else 
            { 
                status = false; 
                Q_ERROR_INCRIT(190); // Here comes the assertion!!!!!!!!!!!!!!!!!!!
            }
        }
    

    I feel that under the dual pressure of the encoder and systick (triggered every 1ms), the qp state machine has crashed. QP probably doesn't have time to execute QF_run(). Is my guess correct? Is there any way to solve this problem? Can the qp+FreeRTOS method solve this problem? Can qp allow some simple blocking?

    best greetings.

     
  • tuzik

    tuzik - 2024-08-23

    The issue has been resolved. I have four levels of nested states, and the innermost fourth-level state handles TIME_TICK. After processing, it returns to the third level. The third, second, and first levels do not handle TIME_TICK. Therefore, I added a TIME_TICK event in the third level to consume TIME_TICK as quickly as possible, preventing it from reaching the top level and wasting CPU resources. Since the fourth level handles MPG_TICK, and the arrival speed of MPG_TICK is irregular, the QP state machine will keep propagating TIME_TICK from the fourth level upwards while also frequently handling MPG_TICK in the fourth level. This may result in delayed event processing, causing event accumulation and eventually leading to event pool overflow and assertion failure.

     
  • Quantum Leaps

    Quantum Leaps - 2024-08-28

    This seems to me a very common problem of mixing the heavy sequential programming with polling/blocking and lightweight event-driven programming that should not block (or poll). Mixing the two paradigms gives you the worst of both worlds. On the one hand, you have very high CPU utlization caused by polling, but you also have event-driven state machines that must react quickly.

    My recommendation is always to avoid such situations and never mix blocking/polling with the event-driven paradigm in the same thread of execution. Just make up your mind and use one or the other.

    And also, several posts to this forum show that people publish the frequent events. Again, publishing is more expensive than direct posting of events. So, don't do this for events that are so frequently produced.

    I hope that these comments makes sense to you.

    --MMS

     
  • Harry Rostovtsev

    Miro, correct me if I'm wrong but one way to mix blocking and non-blocking "safely" seems to be to use the QXK kernel instead of QV or QK.

     
    • Quantum Leaps

      Quantum Leaps - 2024-09-04

      Hi Harry,
      Yes, the very reason why the "dual-mode" QXK has been designed is to enable hybrid approach, where part of the system is event-driven, and the other part is traditional sequential blocking code.

      However, and this is crucial, even with a "dual-mode" kernel like QXK, event-driven Active Objects should never block internally. Blocking must be confined to the traditional threads only (QXThreads in QXK).

      An alternative to QXK is any traditional RTOS kernel, such as ThreadX, embOS, Zephyr, or FreeRTOS. QP ports to these 3rd-party RTOS are provided, but are not as efficient as QXK, which has been specifically designed to reuse as much of QP as possible. But again, with 3rd-party RTOS also blocking must be strictly avoided inside Active Objects.

      Thanks for the comment and an opportunity to clarify this point.

      --MMS

       
      • Harry Rostovtsev

        However, and this is crucial, even with a "dual-mode" kernel like QXK, event-driven Active Objects should never block internally. Blocking must be confined to the traditional threads only (QXThreads in QXK).

        Yeah, I should have clarified that with QXK, you just stick the blocking 3rd party code into a thread with an interface to handle events and call the appropriate blocking functions only from that thread.

         

Log in to post a comment.