嵌入式状态机的几种骚操作

2023-05-16

已剪辑自: https://mp.weixin.qq.com/s/tulMJ7S7oOqV01J2E_-9Xg

1. 状态机基本术语

  • 现态:是指当前所处的状态。
  • 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

图片

2. 传统有限状态机FSM

如下图所示,这是一个定时计数器,计数器存在两种状态,一种为设置状态,一种为计时状态。

设置状态:

  • “+” “-” 按键对初始倒计时进行设置
  • 当计数值设置完成,点击确认键启动计时 ,即切换到计时状态

计时状态:

  • 按下“+” “-” 会进行密码的输入“+”表示1 ,“-”表示输入0 ,密码共有4位
  • 确认键:只有输入的密码等于默认密码,按确认键才能停止计时,否则计时直接到零,并执行相关操作

图片

3. 嵌套switch

/***************************************
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;
  }
}

图片

优点:简单,代码阅读连贯,容易理解

缺点:

  1. 当状态或事件增多时,代码状态函数需要经常改动,状态事件处理函数会代码量会不断增加
  2. 状态机没有进行封装,移植性差。
  3. 没有实现状态的进入和退出的操作。进入和退出在状态机中尤为重要。进入事件:只会在刚进入时触发一次,主要作用是对状态进行必要的初始化。退出事件:只会在状态切换时触发一次 ,主要的作用是清除状态产生的中间参数,为下次进入提供干净环境

4. 状态表

二维状态转换表

状态机可以分为状态和事件 ,状态的跃迁都是受事件驱动的,因此可以通过一个二维表格来表示状态的跃迁。

图片

仅当(code == defuse_code) 时才发生到setting 的转换。

/*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);
}

缺点:函数粒度太小是最明显的一个缺点,一个状态和一个事件就会产生一个函数,当状态和事件较多时,处理函数将增加很快,在阅读代码时,逻辑分散。没有实现进入退出动作。

一维状态转换表

图片

实现原理:图片

 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 状态相关*/

优点:

  • 各个状态面向用户相对独立,增加事件和状态不需要去修改先前已存在的状态事件函数。
  • 实现了状态的进入和退出
  • 容易根据状态跃迁图来设计 (状态跃迁图列出了每个状态的跃迁可能,也就是这里的转换表)
  • 实现灵活,可实现复杂逻辑,如上一次状态,增加监护条件来减少事件的数量。可实现非完全事件驱动

缺点:

  • 函数粒度较小(比二维小且增长慢),可以看到,每一个状态需要至少3个函数,还需要列出所有的转换关系。

5. QP嵌入式实时框架

事件驱动型编程

好莱坞原则:和传统的顺序式编程方法例如“超级循环”,或传统的RTOS 的任务不同。绝大多数的现代事件驱动型系统根据好莱坞原则被构造(Don’t call me; I’ll call you.)

面向对象

类和单一继承:

工具

QM :一个通过UML类图来描述状态机的软件,并且可以自动生成C代码

QS软件追踪工具:

6. QEP实现有限状态机Fsm

/* 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();
  }

优点:

  • 采用面向对象的设计方法,很好的移植性
  • 实现了进入退出动作
  • 合适的粒度,且事件的粒度可控
  • 状态切换时通过改变指针,效率高
  • 可扩展成为层次状态机

缺点:

  • 对事件的定义以及事件粒度的控制是设计的最大难点,如串口接收到一帧数据,这些变量的更新单独作为某个事件,还是串口收到数据作为一个事件。再或者显示屏,如果使用此种编程方式,如何设计事件。

7. QP实现层次状态机

初始化层次状态机的实现:在初始化时,用户所选取的状态永远是最底层的状态,如上图,我们在计算器开机后,应该进入的是开始状态,这就涉及到一个问题,由最初top(顶状态)到begin 是有一条状态切换路径的,当我们设置状态为begin如何搜索这条路径成为关键(知道了路径才能正确的进入begin,要执行路径中过渡状态的进入和退出事件)。

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;
    }

状态切换:

/*.................................................................*/
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 */
}
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 */
                    ....

8. QP实时框架的组成

内存管理

使用内存池,对于低性能mcu,内存极为有限,引入内存管理主要是整个架构中,是以事件作为主要的任务通信手段,且事件是带参数的,可能相同类型的事件会多次触发,而事件处理完成后,需要清除事件,无法使用静态的事件,因此是有必要为不同事件创建内存池的。对于不同块大小的内存池,需要考虑的是每个块的起始地址对齐问题。在进行内存池初始化时,我们是根据blocksize+header大小来进行划分内存池的。假设一个2字节的结构,如果以2来进行划分,假设mcu 4字节对齐,那么将有一半的结构起始地址无法对齐,这时需要为每个块预留空间,保证每个块的对齐。

事件队列

每一个活动对象维护一个事件队列,事件都是由基础事件派生的,不同类型的事件只需要将其基础事件成员添加到活动对象的队列中即可,最终在取出的时候通过一个强制转换便能获得附加的参数。

事件派发

  • 直接事件发送 QActive_postLIFO()
  • 发行订阅事件发送 竖轴表示信号(为事件的基类) 活动对象支持64个优先级,每一个活动对象要求拥有唯一优先级 通过优先级的bit位来表示某个事件被哪些活动对象订阅,并在事件触发后根据优先级为活动对象派发事件。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式状态机的几种骚操作 的相关文章

  • 嵌入式环形队列、消息队列的实现原理及异同

    已剪辑自 https mp weixin qq com s opF5FCDRarYwGhrG4gGCWQ 环形队列 和 消息队列 在嵌入式领域有应用非常广泛 xff0c 相信有经验的嵌入式软件工程师对它们都不陌生 但经常看到一些初学者问一些
  • 华为专家自述:一个成功码农要经历四个阶段

    已剪辑自 https mp weixin qq com s RyykrGlpxVM1z24bFdJyog 无论是在T W公司还是在华为 xff0c 我有幸得以一直从事OS xff08 操作系统 xff09 行业 xff0c 但坦率来讲 xf
  • 陈吉宁经典演讲:平庸与卓越的差别

    来 源 xff1a 清华大学研究生教育 xff08 ID tsinghua grad edu xff09 亲爱的同学们 xff1a 今天 xff0c 共有1318名同学获得博士 硕士学位 首先 xff0c 我代表学校 xff0c 向同学们奋
  • 谈谈汽车软件中间件(Autosar为例)

    文章目录 操作系统 xff0c 中间件 xff0c 应用软件 各司其职分工不同什么是汽车软件中间件 xff1f 汽车软件中间件有什么好处 xff1f 中间件的明星方案 AUTOSARAUTOSAR Adaptive拯救AUTOSAR技术细节
  • 深入浅出讲解低功耗蓝牙(BLE)协议栈

    已剪辑自 https www cnblogs com bluestorm p 12031957 html 详解BLE 空中包格式 兼BLE Link layer协议解析 https www cnblogs com iini p 897780
  • 常见通信协议

    文章目录 1 通信 与 通讯 傻傻分得清2 通讯协议2 1 HTTP HTTPS2 2 WebService REST2 3 CoAP 协议2 4 MQTT 协议 低带宽 2 5 DDS 协议 高可靠性 实时 2 6 AMQP 协议 互操作
  • 渲染业务领域全景图

    最近图形学应用领域愈发广泛 xff0c 根据我的理解 xff0c 制作了一张渲染相关业务全景图 xff0c 希望对大家的职业规划有一定帮助
  • 谈一谈AI对人工的取代

    文章目录 AI绘画现在达到了什么水平 xff1f 易用性怎么样 xff1f 缘起 xff1a 2015年 用文字画画 2021年 Dalle 与 开源社区的程序员们 openAI与它并不open的Dalle AI开源社区 Dream by
  • 推荐几个代码自动生成器

    文章目录 老的代码生成器的地址 xff1a https www cnblogs com skyme archive 2011 12 22 2297592 html https link zhihu com target 61 https 3
  • 开始做公众号的一些方法技巧总结

    文章目录 封面正文预览公众号文章排版公众号运营全攻略 xff08 理论篇 xff09 公众号运营全攻略 xff08 工具技巧篇 xff09 封面 因为公众号的封面是分两个尺寸的 在头条的封面会长一些 xff0c 比例为 xff08 2 35
  • 程序员需要建立的对技术、业务、行业、管理、投资的认知

    文章目录 作为 IT 行业的过来人 xff0c 你有什么话想对后辈说的 xff1f 谈谈程序员转型的事儿 xff08 1 程序员应该重视技术吗 xff09 到底什么是IT技术 xff1f 怎么找到自己的学习方向 xff1f 献给新手程序员最
  • 虚拟化技术在机载软件中的应用

    虚拟化技术在航空计算领域的应用 基于软件虚拟化技术的新一代航空机载软件设计
  • 如何判断一段程序是否是裸机程序?

    在嵌入式MCU领域 xff0c 一般将不移植操作系统直接烧录运行的程序称为裸机程序 一般来说 xff0c 非易失性存储 xff0c 时钟 xff0c 图形显示 xff0c 网络通讯 xff0c 用户I O设备 都需要硬件依赖 基于硬件基础
  • 单片机STM32有什么推荐的裸机编程架构

    作者 xff1a DBinary 链接 xff1a https www zhihu com question 438340661 answer 2735154401 来源 xff1a 知乎 著作权归作者所有 商业转载请联系作者获得授权 xf
  • 一文讲清微服务架构、分布式架构、微服务、SOA

    文章目录 四种软件架构 xff1a 单体架构 分布式架构 微服务架构 Serverless架构一 单体架构二 分布式应用三 微服务架构四 Serverless架构 微服务是什么 xff1f 一 单体软件二 面向服务架构三 微服务 SOA架构
  • 敏捷开发,持续集成/交付/部署, DevOps总结

    文章目录 敏捷开发入门教程一 迭代开发二 增量开发三 敏捷开发的好处3 1 早期交付3 2 降低风险 四 如何进行每一次迭代五 敏捷开发的价值观六 十二条原则七 参考链接 持续集成 交付 部署一 概念二 持续交付三 持续部署四 流程4 1
  • IC集成电路 测试与验证的区别?

    在数字IC中 xff0c 验证与测试完全是两个概念 验证是在pre silicon 阶段 xff0c 也就是流片之前 xff0c 随着设计一起进行的 验证的主要目的是保证芯片逻辑功能的正确性和功能的完备性 验证的一般流程如下 xff1a 测
  • EGL综述

    参考 xff1a https www khronos org registry EGL specs eglspec 1 5 pdf 什么是EGL EGL是支持多平台 多操作系统的 xff0c 比如安卓 Unix Windows等 为了扩展性
  • pcie的rc模式和ep模式有什么区别?

    pcie的rc模式和ep模式有什么区别 xff1f RC PCI Express root complex 在RC模式时 xff0c 使用PCIE类型1配置头 xff1b EP endpoint device 工作方式 在EP模式时 xff
  • Android程序员一年没上班该如何找工作

    前言 Android程序员老王在21年7月份向公司提出了离职 离职后老王觉得在上家工作那么久 xff0c 就想趁着这个机会好好放松一下 由于让自己休息了两个月在加上他自己存了一点积蓄 xff0c 导致后面半年时间都没有找工作面试 到了22年

随机推荐

  • 为什么C语言执行效率高,运行快?

    已剪辑自 https mp weixin qq com s JUucTzACS IFO3iTO77DhQ 简述 都说C语言编写的程序执行效率比较高 xff0c 那么到底高在哪里 xff0c 我们一块来学习学习 C语言由来 C语言源自于BCP
  • 嵌入式5个RTOS程序设计建议

    已剪辑自 https mp weixin qq com s cCgQ5nfGiQckyqkXKxWtLQ 今天聊一下RTOS应用程序设计的五个实践技巧 我在编写RTOS应用程序的过程中 xff0c 经常会遇到这些困难 xff0c 包括正确确
  • 详解C语言二级指针三种内存模型

    已剪辑自 https mp weixin qq com s EBoKOgoVFl751jPe QEAlg 整理 xff1a 李肖遥 二级指针相对于一级指针 xff0c 显得更难 xff0c 难在于指针和数组的混合 xff0c 定义不同类型的
  • 软件架构设计与需求分析方法论

    文章目录 1 软件架构体系1 1 系统与子系统1 2 模块 组件 服务1 3 软件架构体系 2 架构原则2 1 解耦2 2 分层2 3 封装 3 架构的方法3 1 业务架构3 2 功能架构3 3 系统架构3 4 技术架构3 5 数据架构3
  • 马斯洛人类需求五层次理论(Maslow‘s Hierarchy of Needs)

    已剪辑自 https wiki mbalib com wiki E9 A9 AC E6 96 AF E6 B4 9B E4 BA BA E7 B1 BB E9 9C 80 E6 B1 82 E4 BA 94 E5 B1 82 E6 AC A
  • 从需求收集到需求落地,需求分析如何才能更全面?

    从需求收集到需求落地 xff0c 需求分析如何才能更全面 xff1f 已剪辑自 http www moonpm com 503 html 一 什么是需求 心里学上定义 xff1a 需求是由个体在生理上或者心理上感到某种欠缺而力求获得满足的一
  • 什么是云原生?

    已剪辑自 https juejin cn post 6844904197859590151 伴随云计算的滚滚浪潮 xff0c 云原生 CloudNative 的概念应运而生 xff0c 云原生很火 xff0c 火得一塌糊涂 xff0c 都0
  • 三年!我完成了自己的一次蜕变

    已剪辑自 https mp weixin qq com s r9Qv4XkLQ 3QClOeb5f19g 大家好 xff0c 我是txp xff0c 今天分享一篇我个人的一个成长经历 xff01 希望对大家有帮助 xff0c 文字可能会稍微
  • 真正的模块化编程原来是这样的!

    已剪辑自 https mp weixin qq com s uo4tnsEnpULAruayZHcKAw 随着我们工程化经验的增加 xff0c 不知不觉的我们就会关心到这个问题 xff0c 模块化 xff0c 模块设计就显现出来 xff0c
  • 分享嵌入式软件调试方法和几个工具

    已剪辑自 https mp weixin qq com s dbYmBOISjd7tzniVT2l eg 我们常常说 xff0c 软件三分写七分调 实际开发中 xff0c 确实也是这样子的 我工作这几年了 xff0c 对这体会也越来越深 每
  • Ubuntu安装指定版本clang-format

    执行以下命令即可 xff1a wget O https apt llvm org llvm snapshot gpg key sudo apt key add sudo vim etc apt sources list 插入从https a
  • 一种简洁、可拓展的RTOS任务初始化设计

    已剪辑自 https mp weixin qq com s 9IN3AZsqnvgvYLukqvlEPQ 随着写代码功力的提升 xff0c 个人对于代码的整洁 优雅 可维护 易拓展等就有了一定的要求 xff0c 虽然自己曾经就属于那种全局变
  • 谁在滋养你,谁在消耗你

    我微信里有4000多个朋友 xff0c 或者准确来说应该是4000多个联系人 但其中只有极少数人的朋友圈给我留下非常深刻的印象 xff0c 这些人无疑是在滋养我 虽然人数我没有具体统计 xff0c 但是整体而言 xff0c 所占比例应该在1
  • 代码插桩技术

    百度百科 程序插桩 xff0c 最早是由J C Huang 教授提出的 xff0c 它是在保证被测程序原有逻辑完整性的基础上在程序中插入一些探针 xff08 又称为 探测仪 xff0c 本质上就是进行信息采集的代码段 xff0c 可以是赋值
  • 全数字仿真测试平台V-Sim TP

    已剪辑自 http www softtest cn show 37 html 全数字仿真测试平台V Sim TP 产品概述 V SimTP虚拟仿真测试平台是一套可对嵌入式系统进行虚拟仿真测试 快速原型验证的自动化测试平台 xff0c 适用于
  • 消息队列这么多,用哪个哟?

    已剪辑自 https mp weixin qq com s GJaZ00T2Ee5z3hF639XJnQ 提到消息队列 xff0c 大家一定不陌生 xff0c 无论是在校 面试还是工作 xff0c 我们涉及得都很多 有需求就有供应 xff0
  • 嵌入式软件分层隔离的典范

    已剪辑自 https mp weixin qq com s 9gVBZL0sTYIIcvQ bKn8gw 引言 xff1a 嵌入式软件开发分层 模块化是理想状态 xff0c 实际开发中因各种限制而有所取舍 xff0c 但这不妨碍学习参考优秀
  • 单片机ADC常见的几种滤波方法

    已剪辑自 https mp weixin qq com s ObtCPcxnBmpr3mR7NPkB7Q 如今传感器的种类越来越多 xff0c 数量也越来越多 xff0c 而这些传感器很多都会用到模拟量 xff0c 模拟量就离不开ADC 然
  • 浅谈民机软件适航宝典-DO-178

    已剪辑自 https mp weixin qq com s cyx9fSwpX35nDBkHqtO9lQ 序言 DO 178有一个不起眼的标题 机载系统和设备合格审定中的软件考虑 xff0c 但最好不要光看表面 实际上 xff0c 在业界中
  • 嵌入式状态机的几种骚操作

    已剪辑自 https mp weixin qq com s tulMJ7S7oOqV01J2E 9Xg 1 状态机基本术语 现态 xff1a 是指当前所处的状态 条件 xff1a 又称为 事件 xff0c 当一个条件被满足 xff0c 将会