Classic state machine QP framework and principle

Follow + star official account , don't miss exciting content

8420d5010e175720223d1d33b03ba158.gif

Source | CSDN

Author |

State machine is a common method in embedded development, but there are many forms of state machine. Here I will share with you the principle of the classic QP framework.

State Machine Basic Terminology

Current Status: Refers to the current state. Condition: Also known as "event", when a condition is met, an action will be triggered, or a state transition will be performed. Action: The action to be executed after the condition is met. After the action is executed, it can migrate to a new state, or it can remain in the original state.

Actions are not required. When the conditions are met, you can directly migrate to the new state without performing any actions. Next state: The new state to move to after the condition is met. The "secondary state" is relative to the "present state". Once the "secondary state" is activated, it will transform into a new "present state".

9c28d8ce2b4e3bb3b122f89afeb27ccb.png

Implementation method of traditional finite state machine Fsm

4e3dc1ef0702b0224e2161cad1fa1abc.png

As shown in the figure, it is a timing counter. The counter has two states, one is the setting state, and the other is the timing state.

set state

"+" and "-" buttons are used to set the initial countdown. When the counting value is set, click the OK button to start the timing, that is, switch to the timing state

timing status

Press "+" "-" to enter the password. "+" means 1, "-" means input 0, the password has 4 digits 

Confirmation key: Only when the entered password is equal to the default password, press the confirmation key to stop the timing, otherwise the timing will go to zero directly, and related operations will be performed

Nested switches

/***************************************
      1.列出所有的状态
      ***************************************/
      typedef enum{
          SETTING,
          TIMING
      }STATE_TYPE;
      /***************************************
      2.列出所有的事件
      ***************************************/
      typedef enum{
         UP_EVT,
          DOWN_EVT,
          ARM_EVT,
          TICK_EVT
      }EVENT_TYPE;
      /***************************************
      3.定义和状态机相关结构
      ***************************************/
      struct  bomb
      {
          uint8_t state;
          uint8_t timeout;
          uint8_t code;
          uint8_t defuse_code;
      }bomb1;
      /***************************************
      4.初始化状态机
      ***************************************/
      void bomb1_init(void)
      {
          bomb1.state = SETTING;
          bomb1.defuse_code = 6;    //0110 
      }
      /***************************************
      5. 状态机事件派发
      ***************************************/
      void bomb1_fsm_dispatch(EVENT_TYPE evt ,void* param)
      {
          switch(bomb1.state)
          {
              case SETTING:
              {
                  switch(evt)
                  {
                      case UP_EVT:    // "+"   按键按下事件
                        if(bomb1.timeout< 60)  ++bomb1.timeout;
                          bsp_display(bomb1.timeout);
                      break;
                      case DOWN_EVT:  // "-"   按键按下事件
                          if(bomb1.timeout > 0)  --bomb1.timeout;
                          bsp_display(bomb1.timeout);
                      break;
                      case ARM_EVT:   // "确认" 按键按下事件
                          bomb1.state = TIMING;
                          bomb1.code  = 0;
                      break;
                  }
              } break; 
              case TIMING:
              {
                  switch(evt)
                  {
                      case UP_EVT:    // "+"   按键按下事件
                         bomb1.code = (bomb1.code <<1) |0x01;
                      break;
                      case DOWN_EVT:  // "-"   按键按下事件
                          bomb1.code = (bomb1.code <<1); 
                      break;
                      case ARM_EVT:   // "确认" 按键按下事件
                          if(bomb1.code == bomb1.defuse_code){
                              bomb1.state = SETTING;
                          }
                          else{
                           bsp_display("bomb!")
                          }
                      break;
                      case TICK_EVT:
                          if(bomb1.timeout)
                          {
                              --bomb1.timeout;
                              bsp_display(bomb1.timeout);
                          }
                          if(bomb1.timeout == 0)
                          {
                              bsp_display("bomb!")
                          }
                      break;
                  }   
              }break;
          }
      }
b11ed0ce1bcb972a52bf038674bc29f5.png

advantage:

  • Simple, coherent code reading, easy to understand

shortcoming

  • When the number of states or events increases, the code state function needs to be changed frequently, and the code amount of the state event processing function will continue to increase

  • The state machine is not encapsulated, and the portability is poor.

  • No state entry and exit operations are implemented. Entry and exit are especially important in state machines

  • Entry event: it will only be triggered once when it is just entered, and its main function is to initialize the state necessary

  • Exit event: It will only be triggered once when the state is switched. The main function is to clear the intermediate parameters generated by the state and provide a clean environment for the next entry

state table

Two-dimensional state transition table

The state machine can be divided into states and events. State transitions are driven by events, so a two-dimensional table can be used to represent state transitions.

50e69a706fc223e712957aa76f707099.png

(*) Transition to setting only happens if (code == defuse_code).

/*1.列出所有的状态*/
      enum
      {
          SETTING,
          TIMING,
          MAX_STATE
      };
      /*2.列出所有的事件*/
      enum
      {
          UP_EVT,
          DOWN_EVT,
          ARM_EVT,
          TICK_EVT,
          MAX_EVT
      };
      
      /*3.定义状态表*/
      typedef void (*fp_state)(EVT_TYPE evt , void* param);
      static  const fp_state  bomb2_table[MAX_STATE][MAX_EVENT] =
      {
          {setting_UP , setting_DOWN , setting_ARM , null},
          {setting_UP , setting_DOWN , setting_ARM , timing_TICK}
      };
      
      struct bomb_t
      {
          const fp_state const *state_table; /* the State-Table */
          uint8_t state; /* the current active state */
          
          uint8_t timeout;
          uint8_t code;
          uint8_t defuse_code;
      };
      struct bomb bomb2=
      {
          .state_table = bomb2_table;
      }
      void bomb2_init(void)
      {
          bomb2.defuse_code = 6; // 0110
          bomb2.state = SETTING;
      }
      
      void bomb2_dispatch(EVT_TYPE evt , void* param)
      {
          fp_state  s = NULL;
          if(evt > MAX_EVT)
          {
              LOG("EVT type error!");
              return;
          }
          s = bomb2.state_table[bomb2.state * MAX_EVT + evt];
          if(s != NULL)
          {
              s(evt , param);
          }
      }
      /*列出所有的状态对应的事件处理函数*/
      void setting_UP(EVT_TYPE evt, void* param)
      {
          if(bomb1.timeout< 60)  ++bomb1.timeout;
          bsp_display(bomb1.timeout);
      }

advantage

  • Each state is relatively independent for users, adding events and states does not need to modify the pre-existing state event functions.

  • The state machine can be encapsulated, which has better portability. Safe conversion of function pointers. Using the following features, users can expand state machines and events with private attributes and use a unified basic state machine interface

    typedef void (*Tran)(struct StateTableTag *me, Event const *e);
    void Bomb2_setting_ARM (Bomb2 *me, Event const *e);
    typedef struct Bomb
    {
       struct StateTableTag *me;  //必须为第一个成员
        uint8_t private;
    }

shortcoming

  • The most obvious disadvantage is that the function granularity is too small. One state and one event will generate a function. When there are many states and events, the processing function will increase rapidly. When reading the code, the logic is scattered.

  • Entry and exit actions are not implemented.

One-dimensional state transition table

683151b7f460bb52a278c3878cd77c1b.png

Implementation principle:

943ac5b21aad9b374462c30ef12771d7.png
typedef void (*fp_action)(EVT_TYPE evt,void* param);
    
    /*转换表基础结构*/
    struct tran_evt_t
    {
       EVT_TYPE evt;
        uint8_t next_state;
    };
    /*状态的描述*/
    struct  fsm_state_t
    {
        fp_action  enter_action;      //进入动作
        fp_action  exit_action;   //退出动作
        fp_action  action;           
        
        tran_evt_t* tran;    //转换表
        uint8_t     tran_nb; //转换表的大小
        const char* name;
    }
    /*状态表本体*/
    #define  ARRAY(x)   x,sizeof(x)/sizeof(x[0])
    const struct  fsm_state_t  state_table[]=
    {
        {setting_enter , setting_exit , setting_action , ARRAY(set_tran_evt),"setting" },
        {timing_enter , timing_exit , timing_action , ARRAY(time_tran_evt),"timing" }
    };
    
    /*构建一个状态机*/
    struct fsm
    {
        const struct state_t * state_table; /* the State-Table */
        uint8_t cur_state;                      /* the current active state */
        
        uint8_t timeout;
        uint8_t code;
        uint8_t defuse_code;
    }bomb3;
    
    /*初始化状态机*/
    void  bomb3_init(void)
    {
        bomb3.state_table = state_table;  //指向状态表
        bomb3.cur_state = setting;
        bomb3.defuse_code = 8; //1000
    }
    /*状态机事件派发*/
    void  fsm_dispatch(EVT_TYPE evt , void* param)
    {
        tran_evt_t* p_tran = NULL;
        
        /*获取当前状态的转换表*/
        p_tran = bomb3.state_table[bomb3.cur_state]->tran;
        
        /*判断所有可能的转换是否与当前触发的事件匹配*/
        for(uint8_t i=0;i<x;i++)
        {
            if(p_tran[i]->evt == evt)//事件会触发转换
            {
                if(NULL != bomb3.state_table[bomb3.cur_state].exit_action){
              bomb3.state_table[bomb3.cur_state].exit_action(NULL);  //执行退出动作
             }
                if(bomb3.state_table[_tran[i]->next_state].enter_action){
                   bomb3.state_table[_tran[i]->next_state].enter_action(NULL);//执行进入动作
                }
                /*更新当前状态*/
                bomb3.cur_state = p_tran[i]->next_state;
            }
            else
            {
                 bomb3.state_table[bomb3.cur_state].action(evt,param);
            }
        }
    }
    /*************************************************************************
    setting状态相关
    ************************************************************************/
    void setting_enter(EVT_TYPE evt , void* param)
    {
        
    }
    void setting_exit(EVT_TYPE evt , void* param)
    {
        
    }
    void setting_action(EVT_TYPE evt , void* param)
    {
        
    }
    tran_evt_t set_tran_evt[]=
    {
        {ARM , timing},
    }
    /*timing 状态相关*/

advantage

  • Each state is relatively independent for users, adding events and states does not need to modify the pre-existing state event functions.

  • Implemented state entry and exit

  • It is easy to design according to the state transition diagram (the state transition diagram lists the transition possibilities of each state, which is the transition table here)

  • The implementation is flexible, and complex logic can be realized, such as the last state, and the number of events can be reduced by increasing monitoring conditions. Not fully event-driven

shortcoming

  • The function granularity is small (smaller than two-dimensional and slower to grow). It can be seen that each state requires at least 3 functions, and all conversion relationships need to be listed.

QP Embedded Real-Time Framework

features

event-driven programming

Hollywood principles: Different from traditional sequential programming methods such as "hyperloop", or traditional RTOS tasks. Most modern event-driven systems are structured according to Hollywood principles, (Don't call me; I'll call you.)

object oriented

Classes and single inheritance.

d9b187fadebcc5607631875e9d6d8087.png

tool

QM, a software that describes state machines through UML class diagrams, and can automatically generate C code:

7e1b898386255bfad81ead34cb8c6824.png

QS Software Tracking Tool:

79301725806c935393c2ae56876d0ee6.png eba748ec03e22cb8249e5d6ceac85c96.png

QEP implements finite state machine Fsm

ad103ee5b3c31abb3676611cc217d8af.png
/* qevent.h ----------------------------------------------------------------*/
      typedef struct QEventTag 
      {  
        QSignal sig;     
       uint8_t dynamic_;  
      } QEvent;
      /* qep.h -------------------------------------------------------------------*/
      typedef uint8_t QState; /* status returned from a state-handler function */
      typedef QState (*QStateHandler) (void *me, QEvent const *e); /* argument list */
      typedef struct QFsmTag   /* Finite State Machine */
      { 
        QStateHandler state;     /* current active state */
      }QFsm;
      
      #define QFsm_ctor(me_, initial_) ((me_)->state = (initial_))
      void QFsm_init (QFsm *me, QEvent const *e);
      void QFsm_dispatch(QFsm *me, QEvent const *e);
      
      #define Q_RET_HANDLED ((QState)0)
      #define Q_RET_IGNORED ((QState)1)
      #define Q_RET_TRAN ((QState)2)
      #define Q_HANDLED() (Q_RET_HANDLED)
      #define Q_IGNORED() (Q_RET_IGNORED)
      
       #define Q_TRAN(target_) (((QFsm *)me)->state = (QStateHandler)   (target_),Q_RET_TRAN)
      
      enum QReservedSignals
      {
          Q_ENTRY_SIG = 1, 
        Q_EXIT_SIG, 
        Q_INIT_SIG, 
        Q_USER_SIG 
      };
      
      /* file qfsm_ini.c ---------------------------------------------------------*/
      #include "qep_port.h" /* the port of the QEP event processor */
      #include "qassert.h" /* embedded systems-friendly assertions */
      void QFsm_init(QFsm *me, QEvent const *e) 
      {
          (*me->state)(me, e); /* execute the top-most initial transition */
       /* enter the target */
        (void)(*me->state)(me , &QEP_reservedEvt_[Q_ENTRY_SIG]);
      }
      /* file qfsm_dis.c ---------------------------------------------------------*/
      void QFsm_dispatch(QFsm *me, QEvent const *e)
      {
          QStateHandler s = me->state; /* save the current state */
        QState r = (*s)(me, e); /* call the event handler */
        if (r == Q_RET_TRAN)  /* transition taken? */
          {
          (void)(*s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]); /* exit the source */
          (void)(*me->state)(me, &QEP_reservedEvt_[Q_ENTRY_SIG]);/*enter target*/
       }
      }
  实现上面定时器例子
      #include "qep_port.h" /* the port of the QEP event processor */
      #include "bsp.h" /* board support package */
      
      enum BombSignals /* all signals for the Bomb FSM */
      { 
          UP_SIG = Q_USER_SIG,
          DOWN_SIG,
          ARM_SIG,
          TICK_SIG
      };
      typedef struct TickEvtTag 
      {
        QEvent super;      /* derive from the QEvent structure */
        uint8_t fine_time; /* the fine 1/10 s counter */
      }TickEvt;
      
      typedef struct Bomb4Tag 
      {
        QFsm super;   /* derive from QFsm */
        uint8_t timeout; /* number of seconds till explosion */
         uint8_t code;    /* currently entered code to disarm the bomb */
         uint8_t defuse;  /* secret defuse code to disarm the bomb */
      } Bomb4;
      
      void Bomb4_ctor (Bomb4 *me, uint8_t defuse);
      QState Bomb4_initial(Bomb4 *me, QEvent const *e);
      QState Bomb4_setting(Bomb4 *me, QEvent const *e);
      QState Bomb4_timing (Bomb4 *me, QEvent const *e);
      /*--------------------------------------------------------------------------*/
      /* the initial value of the timeout */
      #define INIT_TIMEOUT 10
      /*..........................................................................*/
      void Bomb4_ctor(Bomb4 *me, uint8_t defuse) {
       QFsm_ctor_(&me->super, (QStateHandler)&Bomb4_initial);
        me->defuse = defuse; /* the defuse code is assigned at instantiation */
      }
      /*..........................................................................*/
      QState Bomb4_initial(Bomb4 *me, QEvent const *e) {
       (void)e;
       me->timeout = INIT_TIMEOUT;
       return Q_TRAN(&Bomb4_setting);
      }
      /*..........................................................................*/
      QState Bomb4_setting(Bomb4 *me, QEvent const *e) {
       switch (e->sig){
        case UP_SIG:{
         if (me->timeout < 60) {
          ++me->timeout;
          BSP_display(me->timeout);
         }
                  return Q_HANDLED();
        }
        case DOWN_SIG: {
         if (me->timeout > 1) {
          --me->timeout;
          BSP_display(me->timeout);
         }
         return Q_HANDLED();
        }
        case ARM_SIG: {
         return Q_TRAN(&Bomb4_timing); /* transition to "timing" */
        }
       }
       return Q_IGNORED();
      }
      /*..........................................................................*/
      void Bomb4_timing(Bomb4 *me, QEvent const *e) {
       switch (e->sig) {
        case Q_ENTRY_SIG: {
         me->code = 0; /* clear the defuse code */
         return Q_HANDLED();
              }
        case UP_SIG: {
         me->code <<= 1;
         me->code |= 1;
         return Q_HANDLED();
              }
        case DOWN_SIG: {
         me->code <<= 1;
         return Q_HANDLED();
        }
        case ARM_SIG: {
         if (me->code == me->defuse) {
          return Q_TRAN(&Bomb4_setting);
         }
         return Q_HANDLED();
        }
        case TICK_SIG: {
         if (((TickEvt const *)e)->fine_time == 0) {
          --me->timeout;
          BSP_display(me->timeout);
          if (me->timeout == 0) {
          BSP_boom(); /* destroy the bomb */
          }
         }
         return Q_HANDLED();
        }
       }
       return Q_IGNORED();
      }

advantage

  • Object-oriented design method, good portability

  • Implemented entry and exit actions

  • Appropriate granularity, and the granularity of events is controllable

  • By changing the pointer when the state is switched, the efficiency is high

  • Can be extended to become a hierarchical state machine

shortcoming

  • The definition of events and the control of event granularity are the biggest difficulties in design. For example, if the serial port receives a frame of data, the update of these variables is regarded as an event alone, or the data received by the serial port is regarded as an event. Or the display screen, if this programming method is used, how to design events.

QP implements a brief introduction to the hierarchical state machine Hsm

729ebb86c63c563968bb79c675f4a1af.png

initialization:

e0fc92e5d5b2fcbfa166662481065988.png

Implementation of the initialization level state machine: During initialization, the state selected by the user is always the bottom state. As shown in the figure above, after we turn on the calculator, we should enter the start state, which involves a problem. (Top state) There is a state switching path to begin. When we set the state to begin, how to search for this path becomes the key (knowing the path can enter begin correctly, and the entry and exit events of the transition state in the path must be executed)

void QHsm_init(QHsm *me, QEvent const *e) 
    {
     Q_ALLEGE((*me->state)(me, e) == Q_RET_TRAN);
        t = (QStateHandler)&QHsm_top; /* HSM starts in the top state */
      do { /* drill into the target... */
      QStateHandler path[QEP_MAX_NEST_DEPTH_];
       int8_t ip = (int8_t)0; /* transition entry path index */
       path[0] = me->state; /* 这里的状态为begin */
            
            /*通过执行空信号,从底层状态找到顶状态的路径*/
        (void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
        while (me->state != t) {
         path[++ip] = me->state;
       (void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
      }
            /*切换为begin*/
       me->state = path[0]; /* restore the target of the initial tran. */
      /* 钻到最底层的状态,执行路径中的所有进入事件 */
        Q_ASSERT(ip < (int8_t)QEP_MAX_NEST_DEPTH_);
      do { /* retrace the entry path in reverse (desired) order... */
          QEP_ENTER_(path[ip]); /* enter path[ip] */
       } while ((--ip) >= (int8_t)0);
            
        t = path[0]; /* current state becomes the new source */
       } while (QEP_TRIG_(t, Q_INIT_SIG) == Q_RET_TRAN);
      me->state = t;
    }

State switch:

d522419abb1a478c7a1a788edcbe2a4b.png
/*.................................................................*/
    QState result(Calc *me, QEvent const *e) 
    {
        switch (e->sig) 
        {you
            case ENTER_SIG:{
                break;
            }
            case EXIT_SIG:{
             break;
            }
         case C_SIG: 
            {
          printf("clear");    
                return Q_HANDLED();
            }
            case B_SIG:
            {  
                return Q_TRAN(&begin);
            }
     }
     return Q_SUPER(&reday);
    }
    /*.ready为result和begin的超状态................................................*/
    QState ready(Calc *me, QEvent const *e) 
    {
        switch (e->sig) 
        {
            case ENTER_SIG:{
                break;
            }
            case EXIT_SIG:{
             break;
            }
            case OPER_SIG:
            {  
                return Q_TRAN(&opEntered);
            }
     }
     return Q_SUPER(&on);
    }



    void QHsm_dispatch(QHsm *me, QEvent const *e) 
    {
        QStateHandler path[QEP_MAX_NEST_DEPTH_];
     QStateHandler s;
     QStateHandler t;
     QState r;
     t = me->state;     /* save the current state */
     do {       /* process the event hierarchically... */
      s = me->state;
      r = (*s)(me, e);   /* invoke state handler s */
     } while (r == Q_RET_SUPER); //当前状态不能处理事件 ,直到找到能处理事件的状态
        
     if (r == Q_RET_TRAN) {     /* transition taken? */
      int8_t ip = (int8_t)(-1);   /* transition entry path index */
      int8_t iq;       /* helper transition entry path index */
      path[0] = me->state;    /* save the target of the transition */
         path[1] = t;
      while (t != s) {   /* exit current state to transition source s... */
       if (QEP_TRIG_(t, Q_EXIT_SIG) == Q_RET_HANDLED) {/*exit handled? */
        (void)QEP_TRIG_(t, QEP_EMPTY_SIG_); /* find superstate of t */
       }
       t = me->state;   /* me->state holds the superstate */
      }
      . . .
     }
     me->state = t;     /* set new state or restore the current state */
    }
1b0c9756cc6c95cb26a7e4f2877c8410.png
img
t = path[0]; /* target of the transition */
        if (s == t) { /* (a) check source==target (transition to self) */
             QEP_EXIT_(s) /* exit the source */
             ip = (int8_t)0; /* enter the target */
         }
         else {
             (void)QEP_TRIG_(t, QEP_EMPTY_SIG_); /* superstate of target */
             t = me->state;
             if (s == t) { /* (b) check source==target->super */
                  ip = (int8_t)0; /* enter the target */
            }
             else {
                 (void)QEP_TRIG_(s, QEP_EMPTY_SIG_); /* superstate of src */
                 /* (c) check source->super==target->super */
                 if(me->state == t) {
                     QEP_EXIT_(s) /* exit the source */
                     ip = (int8_t)0; /* enter the target */
                  }
                  else {
                       /* (d) check source->super==target */
                       if (me->state == path[0]) {
                          QEP_EXIT_(s) /* exit the source */
                       }
                       else { /* (e) check rest of source==target->super->super..
                           * and store the entry path along the way */
                        ....

Composition of QP real-time framework

2bdcea5ecde657b2fbcdd6a7d8c19423.png 3e71ae5e928364532b99f7a90c7576ce.png

memory management

Using a memory pool, for low-performance MCUs, the memory is extremely limited. The introduction of memory management is mainly in the entire architecture. Events are used as the main means of task communication, and events have parameters. Events of the same type may be triggered multiple times. After the event processing is completed, the event needs to be cleared, and static events cannot be used, so it is necessary to create memory pools for different events.

For memory pools with different block sizes, what needs to be considered is the alignment of the start address of each block. When initializing the memory pool, we divide the memory pool according to the blocksize+header size. Assuming a 2-byte structure, if it is divided by 2, assuming that the mcu is 4-byte aligned, then half of the structure’s start address will not be aligned. At this time, it is necessary to reserve space for each block to ensure that each block align.5fe8d90c2592e3176b8cac09cec7a88f.png

event queue

Each active object maintains an event queue. Events are derived from basic events. Different types of events only need to add their basic event members to the active object queue, and finally pass a mandatory conversion when taking them out. Get additional parameters.

4aacc7cbc7de7294644f3b6d07a0335f.png

event dispatch

Direct event sending:

  • QActive_postLIFO()

Publish Subscribe Event Send:

  • The vertical axis represents the signal (the base class of the event)

  • Active objects support 64 priorities, and each active object requires a unique priority

  • The priority bit indicates which active objects are subscribed to an event, and dispatches events to active objects according to the priority after the event is triggered.

4d67c1bd0a6ab5b6a21f74cbfbbcc836.png

timed event

Non-ordered linked list:

438a5789b37f264a053f6c44b5e12b62.png

Cooperative scheduler QV:

1ed651d58fb5e9f258a27babe94d130a.png

Introduction to QP nano

  • Full support for hierarchical state nesting, including guaranteed entry/exit actions for any state transition topology up to 4 levels of state nesting

  • Active objects supporting up to 8 concurrently executing, deterministic, thread-safe event queues57

  • Supports signals that are one byte wide (255 signals), and a scalable parameter, which can be configured as 0 (no parameter), 1, 2 or 4 bytes

  • Direct event dispatch mechanism using first-in-first-out FIFO queuing strategy

  • Each active object has a one-shot time event (timer) with a configurable dynamic range of 0 (no time event), 1, 2 or 4 bytes

  • Built-in cooperative vanilla kernel

  • Built-in preemptible RTC kernel named QK-nano (see Chapter 6, Section 6.3.8)

  • A low-power architecture with idle callback functions for easy implementation of power-saving modes.

  • Prepared in code for non-standard extensions to C compilers for popular low-end CPU architectures (e.g. allocation of constant objects in code space, reentrant functions, etc.)

  • Assertion-Based Error Handling Strategies

Code style:

27e9906958761c0140d96b90fbc68a84.png 6ece219edb8d07606415b414bb1474d5.png 8e2d6eb33c4ffa43e7d3f99fe7aea466.png 1ffd7f83d338e6fb1e8d3a550ae7d5f4.png df2a80cb01e9927bc62bcfcdb7f9b521.png

This article is shared here, I hope it will be helpful to you.

Source: https://blog.csdn.net/qq_36969440/article/details/110387716

Disclaimer: The material of this article comes from the Internet, and the copyright belongs to the original author. If it involves copyright issues, please contact me to delete.

------------ END ------------

f934ed2d8b9075edbd53c9c1b7049523.gif

●Column "Embedded Tools "

●Column "Embedded Development"

●Column "Keil Tutorial"

●Selected tutorials in the embedded column

Pay attention to the official account and reply " Jiagroup " to join the technical exchange group according to the rules, and reply " 1024 " to view more content.

71233eecf2f54969c2a830791d69b133.jpeg

8f2f3b8e80eb924dea0bf214c32c0923.png

Click " Read the original text " to view more sharing.

Guess you like

Origin blog.csdn.net/ybhuangfugui/article/details/131862318