【问题标题】:Designing a State Machine for a UART Module为 UART 模块设计状态机
【发布时间】:2019-11-19 10:50:09
【问题描述】:

我正在尝试使用 UART AT COMMAND 4G 模块,并且我正在尝试设计一个工作流程图作为它的状态机。 我有一个问题

  1. 处理传入的消息并发送命令
  2. 如何移动和改变彼此之间的状态?

这是我的初始状态:

#define STATE_INIT 0 
#define STATE_CONNECTED 1 
#define STATE_DISCONNECTED 2 
#define STATE_RETRY_CONNECT 3 
#define STATE_FAILURE 4 
#define STATE_HTTP_POST 5
#define STATE_HTTP_GET 6 
#define STATE_HTTP_POST_RETRY 7
#define STATE_HTTP_POST_SUCCESS 8
#define STATE_HTTP_GET_RETRY 9
#define STATE_HTTP_GET_SUCCESS 10
#define STATE_CHECK_CONNECTIVITY 11
#define SIM_STATUS_ERROR 12 
#define SIM_STATUS_READY 13
#define SIM_STATUS_LOCKED 14
#define REG_STATUS_UNREGISTERED 15
#define REG_STATUS_SEARCHING 16
#define REG_STATUS_DENIED 17
#define REG_STATUS_OK 18
#define REG_STATUS_HOME 19
#define REG_STATUS_ROAMING 20
#define REG_STATUS_UNKNOWN 21
uint8_t current_state; 




void processMessage(char *msg) {

}

void sendCmd(char *cmd) {
    strcpy(UART_Out_Buffer, cmd);
    UART_Out_Cnt = strlen(cmd);
}

void Init_State(void) {

     current_state = STATE_INIT;
     sendCmd("AT+CGSOCKCONT=1,""\"IP""\",""\"A1.net""\"");
     sendCmd("AT+CSOCKAUTH=1,1,""\"ppp""\",""\"ppp@a1plus.at""\"");
     sendCmd("AT+CHTTPSOPSE=""\"ipdb-eu1.com""\",443""\"");


}

在这里,当我们发送命令时,应该处理响应。

void process_uart(void)
{
    uint16_t uartBufPos = 0;
    char line[UART_BUFFER_SIZE];
    line[0] = '\0';
    uint16_t linePos = 0;

    while (UART_Buffer[uartBufPos] != '\0')
    {
        if (UART_Buffer[uartBufPos] == '\n')
        {
            line[linePos] = '\0';
            processMessage(line);
            linePos = 0;
        }
        else
        {
            line[linePos] = UART_Buffer[uartBufPos];
            linePos++;
            if (linePos == UART_BUFFER_SIZE)
            {
                linePos = 0;
            }
        }
        uartBufPos++;
        if (uartBufPos == UART_BUFFER_SIZE)
        {
            uartBufPos = 0;
        }
    }

    if (UART_Out_Cnt > 0)
    {
        HAL_UART_Transmit(&huart2, (uint8_t *)UART_Out_Buffer, UART_Out_Cnt, 100);
        UART_Out_Cnt = 0;
    }
}

跟进答案:我已经这样做了:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2)
  {
      start_processing = true;
      isSynced = true;

      if (waitreply > 1)
      {
        waitreply--;

        HAL_UART_Receive_DMA(&huart2, DMA_RX_Buffer, DMA_RX_BUFFER_SIZE);

        uint16_t uartBufPos = 0;
        uint16_t linePos = 0;

        while (DMA_RX_Buffer[uartBufPos] != '\0')
        {
          if (DMA_RX_Buffer[uartBufPos] == '\n')
          {
            wait_reply = -1;
          }
          else
          {
            if (uartBufPos == DMA_RX_BUFFER_SIZE)
            {
              uartBufPos = 0;
            }

            uartBufPos++;
          }
        }
      }
  }
}

进程状态机:

void process_state_machine()
{

    uint8_t timeout = 0;
    switch (current_state)
    {
    case STATE_INIT:
        if (wait_reply == 0)
        {
            // just entered this state, send command
            HAL_UART_Transmit(&huart2, "AT+CGSOCKCONT=1,"
                                       "\"IP"
                                       "\","
                                       "\"A1.net"
                                       "\"\r\n",
                              strlen("AT+CGSOCKCONT=1,"
                                     "\"IP"
                                     "\","
                                     "\"A1.net"
                                     "\"\r\n"),
                              100);
            wait_reply = 2;
        }
        else
        {
            // reply, or timeout
            if (wait_reply == 1)
            {
                // timeout, retry
                timeout = 0; // this will re-enter this state
            }
            if (wait_reply == -1)
            {
                // analyze reply, may be change state
                wait_reply = 0;
                timeout = 1;
                current_state = STATE_CONNECTED;
            }
        }
        break;

【问题讨论】:

  • 作为关于样式的评论,我会使用枚举而不是一组#defines
  • @Skizz 感谢您的评论,您还有其他 cmet 或我想要使用的设计模式的答案吗?
  • 你的问题到底是什么?你收到一个角色,你决定你做什么。
  • 这不是一项简单的任务,我的实现超过 2k 行。许多调制解调器可能会乱序发送响应,即使之前没有完成,您也可以运行其他命令等。IMO RTOS 是此类项目的必备品
  • @P__J__ RTOS 不是一个选项,但我可以运行多个计时器......作为线程......你能否详细说明设计观点作为答案,可以给我见解?

标签: c stm32 uart at-command


【解决方案1】:

这并不容易,但是你可以用状态机来做;但该状态机必须至少有两个级别。这是因为当您向调制解调器发送命令时,需要时间;还需要更多的时间来等待响应。如果您有两个或至少一个用于调制解调器通信的循环缓冲区,则可以使用单个计时器(一个简单的变量)来完成。

状态机被及时调用,比如每 1/100 秒。计时器变量称为 waitreply。伪代码是这样的:

statemachine:
  if (waitreply > 1): 
    waitreply--;
  read characters from modem (from circular buffer)
  is the read message complete? (ends with CR-LF?)
    no:
      (fall to the rest of the routine)

    yes:
      is this out-of-band data?
        yes:
          put it aside and ignore
        no:
          waitreply = -1;

剩下的部分是一个 switch 语句,每个状态一个 case。每个状态都分为两部分:

case SEND_AT:
  if (waitreply == 0) {
    // just entered this state, send command
    send command
    waitreply = some_timeout
  } else {
    // reply, or timeout
    if (waitreply == 1) {
      // timeout, retry
      waitreply = 0;  // this will re-enter this state
    }
    if (waitreply == -1) {
      // analyze reply, may be change state
      waitreply = 0;
      STATE = SEND_ATI;
    }
  }
  break;

这只是一个想法,希望对您有所帮助。

===== 在 cmets 之后编辑 =====

从上面的代码可以看出,waitreply 变量实现了一个二级状态机。如果waitreply==0,没有事务在进行:可以发送命令;如果 ==-1,则来自调制解调器的回复已准备好被当前“状态”读取;否则,状态机只是在等待。因此,(waitreply > 0) 的测试可以移到函数的开头,如果满足,则简单地过早退出函数。但这似乎并没有很大的改进。

关于 OP 的问题:

  1. 是的,定时器每 1/100 秒调用一次状态机。变量 waitreply 初始化为零。
  2. 没有“线程”,这是一个从 main 调用的简单例程

一个C程序框架如下:

int waitreply;
enum blahblah state;
void statemachine(void);

void main(void) {
  waitreply = 0;    // already zeroed by C runtime
  state=ST_SENDAT;  // see if the modem is alive
  do {
    if (timer_expired) {
      // 1/100 sec elapsed
      statemachine();
      start_timer();
    }
  }
}


void statemachine(void) {
  // prologue... modem_replay will contain the reply from modem
  switch (state) {
    case ST_SENDAT:
      if (waitreply == 0) {
        // just entered this state, send command
        send_to_modem("AT" CR LF);
        waitreply = 200;   // 2 seconds
      } else {
        // reply, or timeout
        if (waitreply == 1) {
          // timeout, retry
          waitreply = 0;  // this will re-enter this state
        }
        if (waitreply == -1) {
          // analyze reply, may be change state
          waitreply = 0;
          if (0 == strcmp(modem_reply, "OK"))
              STATE = SEND_ATI;   // another state, to get info from modem
              // else will re-enter this same state, for ever
        }
      }
      break;
  }  // end of switch
} // func statemachine

我再说一遍,这只是一个可以提供良好控制的想法,仅此而已。

【讨论】:

  • 非常感谢,那么我将如何将您的想法应用到我的代码中呢?首先,我应该启动一个运行 1/100 秒的计时器,并将 wait_reply 启动到某个值吗?你能添加更多的伪代码吗,你的想法真的很好
  • 另外一个问题,switch部分,是跑在定时器线程部分还是主循环?
  • 我需要一个如何使用你的伪代码的例子
  • 你能检查一下帖子吗,我添加了一个你想法的例子......你能检查一下吗
  • 你也没有详细说明超时变量
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-20
  • 2011-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多