You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UART AT指令4G模块状态机设计及状态切换问题咨询

4G模块UART AT指令状态机实现指导:解决接收/发送/状态切换问题

Let's break down how to fix your state machine issues with the 4G module, focusing on reliable message parsing, controlled command sequencing, and clean state transitions. Your current code has several gaps (like unhandled responses, uncoordinated command sending, and overly granular states) that we'll address step by step.


1. Simplify State Definitions (Remove Redundancy)

Your current state list mixes core module states with sub-states (like SIM/registration status). These sub-states should be tracked as separate variables, not full state machine states. Here's a streamlined state enum:

typedef enum {
    STATE_INIT,          // 初始化:配置套接字上下文、认证参数
    STATE_CHECK_SIM,     // 验证SIM卡状态
    STATE_CHECK_REG,     // 检查网络注册状态
    STATE_CONNECTING,    // 建立数据附着/连接
    STATE_CONNECTED,     // 已就绪,可执行HTTP操作
    STATE_HTTP_PENDING,  // 等待HTTP请求响应
    STATE_RETRY_ATTEMPT, // 连接失败后重试流程
    STATE_FATAL_FAILURE  // 重试耗尽,进入错误状态
} ModuleState;

// Track sub-states as variables, not state machine states
uint8_t sim_status;       // 0: unknown, 1: ready, 2: locked/error
uint8_t network_reg_status; // 0: unregistered, 1: home, 5: roaming, etc.

ModuleState current_state;

2. Fix Command Sending & Response Synchronization

AT commands require a request-response handshake—you can't blast multiple commands without waiting for each to succeed/fail. Add these helper variables and functions to manage command flow:

typedef enum {
    CMD_NONE,
    CMD_CGSOCKCONT,
    CMD_CSOCKAUTH,
    CMD_CHTTPSOPSE,
    CMD_CPIN,          // SIM status check
    CMD_CREG,          // Network registration check
    CMD_CGATT,         // Attach to GPRS
    CMD_HTTP_POST,
    CMD_HTTP_GET
} ActiveCmd;

ActiveCmd current_cmd;
uint8_t cmd_in_flight;   // Flag: 1 = waiting for response, 0 = ready to send
uint16_t retry_count;    // Track retry attempts per command
#define MAX_RETRIES 3
#define CMD_TIMEOUT_MS 3000 // 3-second timeout for responses

// Helper to get command string by type
const char* get_cmd_string(ActiveCmd cmd) {
    switch(cmd) {
        case CMD_CGSOCKCONT: return "AT+CGSOCKCONT=1,\"IP\",\"A1.net\"\r\n";
        case CMD_CSOCKAUTH: return "AT+CSOCKAUTH=1,1,\"ppp\",\"ppp@a1plus.at\"\r\n";
        case CMD_CHTTPSOPSE: return "AT+CHTTPSOPSE=\"ipdb-eu1.com\",443\r\n";
        case CMD_CPIN: return "AT+CPIN?\r\n";
        case CMD_CREG: return "AT+CREG?\r\n";
        case CMD_CGATT: return "AT+CGATT=1\r\n";
        // Add HTTP command strings as needed
        default: return "";
    }
}

// Safe command sender (only sends if no pending command)
void send_at_cmd(ActiveCmd cmd) {
    if (!cmd_in_flight) {
        const char* cmd_str = get_cmd_string(cmd);
        strcpy(UART_Out_Buffer, cmd_str);
        UART_Out_Cnt = strlen(cmd_str);
        current_cmd = cmd;
        cmd_in_flight = 1;
        retry_count = 0;
    }
}

3. Implement Robust Message Parsing

Your empty processMessage function is the biggest gap—you need to parse AT responses to drive state transitions. Here's how to handle both generic (OK/ERROR) and command-specific responses:

void processMessage(char *msg) {
    // Trim whitespace/newlines from the response line
    char *trimmed = strtok(msg, "\r\n");
    if (!trimmed) return;

    // Handle generic success/error
    if (strcmp(trimmed, "OK") == 0) {
        cmd_in_flight = 0;
        handle_cmd_success(current_cmd);
        return;
    }
    if (strcmp(trimmed, "ERROR") == 0) {
        cmd_in_flight = 0;
        handle_cmd_failure(current_cmd);
        return;
    }

    // Parse command-specific responses
    if (strstr(trimmed, "+CPIN:") != NULL) {
        // Parse SIM status: "+CPIN: READY" or "+CPIN: SIM PIN"
        if (strstr(trimmed, "READY")) sim_status = 1;
        else if (strstr(trimmed, "SIM PIN")) sim_status = 2;
        else sim_status = 0;
        cmd_in_flight = 0;
        handle_cmd_success(current_cmd);
    }
    else if (strstr(trimmed, "+CREG:") != NULL) {
        // Parse network registration status: "+CREG: 0,1" (home) or "+CREG:0,5" (roaming)
        int reg_code;
        sscanf(trimmed, "+CREG: %*d,%d", &reg_code);
        network_reg_status = reg_code;
        cmd_in_flight = 0;
        handle_cmd_success(current_cmd);
    }
    // Add parsing for HTTP responses (e.g., "+CHTTPSPOST: 200" for success)
}

// Handle successful command completion (drive state transitions)
void handle_cmd_success(ActiveCmd cmd) {
    switch(cmd) {
        case CMD_CGSOCKCONT: send_at_cmd(CMD_CSOCKAUTH); break;
        case CMD_CSOCKAUTH: send_at_cmd(CMD_CHTTPSOPSE); break;
        case CMD_CHTTPSOPSE: current_state = STATE_CHECK_SIM; break;
        case CMD_CPIN: 
            if (sim_status == 1) current_state = STATE_CHECK_REG;
            else current_state = STATE_FATAL_FAILURE; // SIM locked/error
            break;
        case CMD_CREG:
            // 1 = home network, 5 = roaming (both valid)
            if (network_reg_status == 1 || network_reg_status ==5) current_state = STATE_CONNECTING;
            else current_state = STATE_RETRY_ATTEMPT;
            break;
        case CMD_CGATT: current_state = STATE_CONNECTED; break;
        case CMD_HTTP_POST: 
            current_state = STATE_CONNECTED; // Return to ready state after success
            // Trigger post-success logic here (e.g., log, reset counters)
            break;
        // Add other command success handlers
    }
}

// Handle command failure (retry or fail)
void handle_cmd_failure(ActiveCmd cmd) {
    retry_count++;
    if (retry_count >= MAX_RETRIES) {
        current_state = STATE_FATAL_FAILURE;
    } else {
        send_at_cmd(cmd); // Retry the same command
    }
}

4. Refine State Machine Logic & Timeout Handling

Update your state machine to work with the synchronized command flow, and add timeout checks to handle unresponsive modules:

void process_state_machine() {
    static uint32_t timeout_start_ms;

    // First, check for command timeouts
    if (cmd_in_flight) {
        if (HAL_GetTick() - timeout_start_ms > CMD_TIMEOUT_MS) {
            cmd_in_flight = 0;
            handle_cmd_failure(current_cmd);
        }
        return; // Don't process state transitions while waiting for a response
    }

    // Reset timeout when starting a new state/command
    timeout_start_ms = HAL_GetTick();

    switch(current_state) {
        case STATE_INIT:
            send_at_cmd(CMD_CGSOCKCONT);
            break;
        case STATE_CHECK_SIM:
            send_at_cmd(CMD_CPIN);
            break;
        case STATE_CHECK_REG:
            send_at_cmd(CMD_CREG);
            break;
        case STATE_CONNECTING:
            send_at_cmd(CMD_CGATT);
            break;
        case STATE_CONNECTED:
            // Example: Trigger an HTTP POST when connected
            send_at_cmd(CMD_HTTP_POST);
            current_state = STATE_HTTP_PENDING;
            break;
        case STATE_HTTP_PENDING:
            // Wait for processMessage to handle the HTTP response
            break;
        case STATE_RETRY_ATTEMPT:
            retry_count++;
            if (retry_count >= MAX_RETRIES) current_state = STATE_FATAL_FAILURE;
            else current_state = STATE_CHECK_SIM; // Restart connection flow
            break;
        case STATE_FATAL_FAILURE:
            // Handle permanent failure (e.g., reset module, trigger alert)
            break;
    }
}

5. Fix UART/DMA Reception Logic

Your current UART processing has buffer overflow risks and incorrect DMA handling. Use a DMA completion callback to safely process incoming data:

uint8_t uart_data_ready = 0;

// DMA receive completion callback (triggered when buffer is full or newline is received)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == huart2.Instance) {
        // Copy DMA data to processing buffer and null-terminate
        memcpy(UART_Buffer, DMA_RX_Buffer, DMA_RX_BUFFER_SIZE);
        UART_Buffer[DMA_RX_BUFFER_SIZE] = '\0';
        // Restart DMA reception for next batch
        HAL_UART_Receive_DMA(&huart2, DMA_RX_Buffer, DMA_RX_BUFFER_SIZE);
        uart_data_ready = 1;
    }
}

// Process UART data in your main loop
void main_loop() {
    if (uart_data_ready) {
        process_uart();
        uart_data_ready = 0;
    }
    if (start_processing) {
        process_state_machine();
    }
}

// Updated UART line parser
void process_uart(void) {
    char *line = strtok(UART_Buffer, "\n");
    while (line != NULL) {
        processMessage(line);
        line = strtok(NULL, "\n");
    }
    memset(UART_Buffer, 0, sizeof(UART_Buffer)); // Clear buffer after processing
}

Key Takeaways

  • One command at a time: Never send multiple AT commands without waiting for a response—this is the #1 cause of unreliable module behavior.
  • Simplify states: Use variables for sub-states (SIM/registration) instead of cluttering the state machine.
  • Parse all responses: Don't ignore non-OK/ERROR lines—many AT commands return structured data (like +CREG) that drives state transitions.
  • Timeout everything: Always handle unresponsive modules with timeouts to avoid hanging the state machine.

内容的提问来源于stack exchange,提问作者Andre Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:52