Framework Overview
When working with NB-IoT, BLE, or other communication modules that utilize AT command interfaces, a flexible and maintainable AT command framework becomes essential. The framework described here combines a state machine architecture with structured command arrays to handle complex communication sequences efficient.
Core Design Principles
- State Machine Architecture: Sequential command execution with timeout and retry capabilities
- Command Array Storage: Two-dimensional array structure storing command definitions, expected responses, timing parameters, and callback handlers
- Event-Driven Procesing: Non-blocking operation through mailbox-based task synchronization
- Extensible Design: Easy addition of new commands without modifying core logic
Command Definition Header
#ifndef __AT_FRAMEWORK_H
#define __AT_FRAMEWORK_H
#include "app_config.h"
// AT Task Events
typedef enum {
EVT_AT_IDLE = 1 << 0,
EVT_AT_SEND = 1 << 1,
EVT_AT_WAIT = 1 << 2,
EVT_AT_TIMEOUT = 1 << 3,
EVT_AT_COMPLETE = 1 << 4,
EVT_AT_FAILED = 1 << 5,
} at_event_t;
// Command Identifiers
typedef enum {
CMD_NONE = 0,
CMD_AT,
CMD_RESET,
CMD_CLOCK_CONFIG,
CMD_MANUFACTURER_INFO,
CMD_SERIAL_NUMBER,
CMD_ECHO_DISABLE,
CMD_BAND_CONFIG,
CMD_GET_IP_ADDRESS,
CMD_NOTIFICATION_ENABLE,
CMD_NETWORK_CONFIG,
CMD_CONNECTION_REPORT,
CMD_CONNECTION_STATUS,
CMD_REGISTRATION_STATUS,
CMD_SERVER_CONNECT,
CMD_SIGNAL_QUALITY,
CMD_NETWORK_STATUS,
CMD_SEND_DATA,
CMD_TCP_CONNECT,
CMD_PROTOCOL_CONFIG,
CMD_TCP_SEND,
CMD_TCP_CLOSE,
CMD_GET_TIME,
CMD_NEIGHBOR_CELL,
CMD_END_MARKER
} cmd_id_t;
// Command Descriptor Structure
typedef struct {
cmd_id_t cmd_id;
const char* tx_payload;
const char* expected_response;
uint32_t timeout_ms;
uint8_t max_retries;
uint8_t (*response_handler)(uint8_t* data, uint16_t len);
} cmd_descriptor_t;
// Runtime Command State
typedef struct {
cmd_id_t current_cmd;
const char* tx_buffer;
const char* expected_ack;
uint32_t wait_duration;
uint8_t retry_count;
uint8_t (*data_handler)(uint8_t* data, uint16_t len);
} cmd_runtime_t;
// Handler Function Prototypes
uint8_t handle_default_response(uint8_t* data, uint16_t len);
uint8_t handle_signal_strength(uint8_t* data, uint16_t len);
uint8_t handle_rtc_timestamp(uint8_t* data, uint16_t len);
uint8_t handle_cell_measurement(uint8_t* data, uint16_t len);
#endif
Command Sequence Definitions
#include "at_framework.h"
// External References
extern rt_mailbox_t comm_mailbox_at;
extern rt_mailbox_t comm_mailbox_ctrl;
extern rt_uint8_t recv_buffer[RECV_BUFF_SIZE];
extern rt_uint16_t recv_data_len;
extern rt_uint8_t send_buffer[SEND_BUFF_SIZE];
extern device_message_t dev_msg;
extern rt_timer_t timer_monitor;
// Execution Context
static uint8_t seq_index = 0;
static uint8_t seq_retry = 0;
static cmd_id_t* active_sequence = NULL;
static cmd_runtime_t current_state = {0};
// Command Sequences for Different Protocols
// LwM2M Protocol Initialization
static cmd_id_t seq_init_lwm2m[] = {
CMD_AT, CMD_AT, CMD_CLOCK_CONFIG, CMD_MANUFACTURER_INFO, CMD_SERIAL_NUMBER,
CMD_ECHO_DISABLE, CMD_BAND_CONFIG, CMD_GET_IP_ADDRESS, CMD_NOTIFICATION_ENABLE,
CMD_NETWORK_CONFIG, CMD_CONNECTION_REPORT, CMD_CONNECTION_STATUS, CMD_REGISTRATION_STATUS,
CMD_SERVER_CONNECT, CMD_SIGNAL_QUALITY, CMD_NETWORK_STATUS, CMD_END_MARKER
};
// LwM2M Data Transmission
static cmd_id_t seq_upload_lwm2m[] = {
CMD_SIGNAL_QUALITY, CMD_SEND_DATA, CMD_END_MARKER
};
// Signal Query Sequence
static cmd_id_t seq_signal_check[] = {
CMD_SIGNAL_QUALITY, CMD_GET_TIME, CMD_END_MARKER
};
// TCP/IP Protocol Initialization
static cmd_id_t seq_init_tcpip[] = {
CMD_AT, CMD_CLOCK_CONFIG, CMD_TCP_CLOSE, CMD_MANUFACTURER_INFO, CMD_SERIAL_NUMBER,
CMD_ECHO_DISABLE, CMD_BAND_CONFIG, CMD_CONNECTION_REPORT, CMD_CONNECTION_STATUS,
CMD_REGISTRATION_STATUS, CMD_GET_IP_ADDRESS, CMD_SIGNAL_QUALITY, CMD_GET_TIME,
CMD_NEIGHBOR_CELL, CMD_END_MARKER
};
// TCP/IP Data Transmission
static cmd_id_t seq_upload_tcpip[] = {
CMD_TCP_CONNECT, CMD_PROTOCOL_CONFIG, CMD_TCP_SEND, CMD_END_MARKER
};
// Command Table with Response Handlers
static cmd_descriptor_t cmd_table[] = {
{CMD_NONE, "NULL", "NULL", 0, 0, NULL},
{CMD_AT, "AT\r\n", "OK", 1000, 3, handle_default_response},
{CMD_RESET, "AT+QRST=1\r\n", "+IP", 15000, 3, handle_default_response},
{CMD_CLOCK_CONFIG, "AT+QSCLK=0\r\n", "OK", 1000, 3, handle_default_response},
{CMD_MANUFACTURER_INFO, "AT+CGMI\r\n", "OK", 1000, 3, handle_default_response},
{CMD_SERIAL_NUMBER, "AT+CGSN=1\r\n", "OK", 1000, 3, handle_default_response},
{CMD_ECHO_DISABLE, "ATE0\r\n", "OK", 1000, 3, handle_default_response},
{CMD_BAND_CONFIG, "AT+QBAND=0\r\n", "OK", 1000, 3, handle_default_response},
{CMD_GET_IP_ADDRESS, "AT+CGPADDR?\r\n", "OK", 1000, 3, handle_default_response},
{CMD_NOTIFICATION_ENABLE, "AT+NNMI=1\r\n", "OK", 1000, 3, handle_default_response},
{CMD_NETWORK_CONFIG, "AT+NCFG=0,300\r\n", "OK", 1000, 3, handle_default_response},
{CMD_CONNECTION_REPORT, "AT+CSCON=1\r\n", "OK", 1000, 3, handle_default_response},
{CMD_CONNECTION_STATUS, "AT+CSCON?\r\n", "OK", 1000, 3, handle_default_response},
{CMD_REGISTRATION_STATUS, "AT+CEREG?\r\n", "OK", 1000, 3, handle_default_response},
{CMD_SERVER_CONNECT, "AT+NCDPOPEN=\"221.229.214.202\",5683\r\n", "+QLWEVTIND: 3", 5000, 3, handle_default_response},
{CMD_SIGNAL_QUALITY, "AT+CSQ\r\n", "OK", 1000, 3, handle_signal_strength},
{CMD_NETWORK_STATUS, "AT+NMSTATUS?\r\n", "REGISTERED_AND_OBSERVED", 1000, 3, handle_default_response},
{CMD_SEND_DATA, send_buffer, "OK", 5000, 3, handle_default_response},
{CMD_TCP_CONNECT, "AT+QIOPEN=0,0,\"TCP\",\"47.94.169.135\",7191\r\n", "+QIOPEN: 0,0", 5000, 3, handle_default_response},
{CMD_PROTOCOL_CONFIG, "AT+QICFG=\"dataformat\",1,1\r\n", "OK", 5000, 3, handle_default_response},
{CMD_TCP_SEND, send_buffer, "OK", 5000, 3, handle_default_response},
{CMD_TCP_CLOSE, "AT+QICLOSE=0\r\n", "CLOSE OK", 30000, 3, handle_default_response},
{CMD_GET_TIME, "AT+CCLK?\r\n", "+CCLK", 3000, 3, handle_rtc_timestamp},
{CMD_NEIGHBOR_CELL, "AT+QENG=0\r\n", "OK", 3000, 3, handle_cell_measurement},
};
Response Handlers Implementation
/* Reset runtime state */
void reset_runtime_context(void) {
current_state.current_cmd = CMD_NONE;
current_state.tx_buffer = NULL;
current_state.expected_ack = NULL;
current_state.wait_duration = 0;
current_state.retry_count = 0;
current_state.data_handler = NULL;
seq_index = 0;
seq_retry = 0;
active_sequence = NULL;
}
/* Default no-op handler */
uint8_t handle_default_response(uint8_t* data, uint16_t len) {
return 1;
}
/* Signal strength extraction */
uint8_t handle_signal_strength(uint8_t* data, uint16_t len) {
uint8_t* ptr = data;
if (strstr(ptr, "+CSQ") != NULL) {
uint8_t csq = (*(ptr + 8) - '0') * 10 + (*(ptr + 9) - '0');
dev_msg.signal_strength = csq;
}
return 1;
}
/* RTC timestamp parsing */
uint8_t handle_rtc_timestamp(uint8_t* data, uint16_t len) {
uint8_t* ptr = data;
if (strstr(ptr, "+CCLK") != NULL) {
uint8_t year = (*(ptr + 12) - '0') * 10 + (*(ptr + 13) - '0');
uint8_t month = (*(ptr + 15) - '0') * 10 + (*(ptr + 16) - '0');
uint8_t day = (*(ptr + 18) - '0') * 10 + (*(ptr + 19) - '0');
uint8_t hour = (*(ptr + 21) - '0') * 10 + (*(ptr + 22) - '0');
uint8_t minute = (*(ptr + 24) - '0') * 10 + (*(ptr + 25) - '0');
uint8_t second = (*(ptr + 27) - '0') * 10 + (*(ptr + 28) - '0');
rtc_calibration(hour, minute, second, year, month, day);
uint32_t ts;
app_rtc_read(&ts);
dev_msg.timestamp[0] = (ts >> 24) & 0xFF;
dev_msg.timestamp[1] = (ts >> 16) & 0xFF;
dev_msg.timestamp[2] = (ts >> 8) & 0xFF;
dev_msg.timestamp[3] = ts & 0xFF;
}
return 1;
}
/* Neighbor cell measurement parsing */
uint8_t handle_cell_measurement(uint8_t* data, uint16_t len) {
uint8_t* ptr = data;
uint8_t comma_count = 0;
for (uint16_t i = 0; i < 60; i++) {
if (*(ptr + i) == ',') {
comma_count++;
if (comma_count == 3) {
uint16_t pci = 0;
if (*(ptr + i + 4) == ',') {
pci = (*(ptr + i + 1) - '0') * 100 + (*(ptr + i + 2) - '0') * 10 + (*(ptr + i + 3) - '0');
}
dev_msg.physical_cell_id[0] = pci >> 8;
dev_msg.physical_cell_id[1] = pci & 0xFF;
} else if (comma_count == 5 && *(ptr + i + 1) == '-') {
uint16_t rsrp = (*(ptr + i + 2) - '0') * 100 + (*(ptr + i + 3) - '0') * 10 + (*(ptr + i + 4) - '0');
rsrp = (rsrp >= 141) ? 0 : (141 - rsrp);
dev_msg.reference_power[0] = rsrp >> 8;
dev_msg.reference_power[1] = rsrp & 0xFF;
} else if (comma_count == 8 && *(ptr + i + 1) == '-') {
uint16_t snr = (*(ptr + i + 2) - '0') * 10 + (*(ptr + i + 3) - '0');
snr |= 0x8000;
dev_msg.sinr[0] = snr >> 8;
dev_msg.sinr[1] = snr & 0xFF;
}
}
}
return 1;
}
Timeout Monitoring Task
/* Timer callback for command timeout */
void timeout_monitor_callback(void* param) {
rt_mb_send_wait(comm_mailbox_at, EVT_AT_TIMEOUT, 1000);
}
Main AT Processing Thread
/* AT command processing thread */
void at_task_thread(void* parameter) {
rt_thread_delay(100);
uint32_t at_event = 0;
rt_err_t result;
while (1) {
result = rt_mb_recv(comm_mailbox_at, &at_event, RT_WAITING_FOREVER);
if (result == RT_EOK) {
switch (at_event) {
case EVT_AT_IDLE:
break;
case EVT_AT_SEND: {
cmd_descriptor_t desc;
for (int i = 0; i < sizeof(cmd_table) / sizeof(cmd_table[0]); i++) {
if (cmd_table[i].cmd_id == active_sequence[seq_index]) {
desc = cmd_table[i];
break;
}
}
current_state.current_cmd = desc.cmd_id;
current_state.tx_buffer = desc.tx_payload;
current_state.expected_ack = desc.expected_response;
current_state.wait_duration = desc.timeout_ms;
current_state.data_handler = desc.response_handler;
seq_retry = 0;
if (desc.cmd_id != CMD_END_MARKER) {
rt_device_write(uart_device, RT_NULL, desc.tx_payload, strlen(desc.tx_payload));
rt_timer_control(timer_monitor, RT_TIMER_CTRL_SET_TIME, &desc.timeout_ms);
rt_timer_start(timer_monitor);
} else {
rt_mb_send_wait(comm_mailbox_at, EVT_AT_COMPLETE, 1000);
}
} break;
case EVT_AT_WAIT: {
memset(recv_buffer, 0x00, sizeof(recv_buffer));
while ((recv_data_len = rt_device_read(uart_device, RT_NULL, recv_buffer, RT_NULL)) > 0) {
if (current_state.tx_buffer != NULL) {
if (strstr(recv_buffer, current_state.expected_ack) != NULL) {
if (current_state.data_handler(recv_buffer, recv_data_len) == 1) {
rt_timer_stop(timer_monitor);
seq_index++;
rt_mb_send_wait(comm_mailbox_at, EVT_AT_SEND, 1000);
}
} else if (current_state.expected_ack == NULL) {
rt_timer_stop(timer_monitor);
seq_index++;
rt_mb_send_wait(comm_mailbox_at, EVT_AT_SEND, 1000);
}
} else {
// URC (Unsolicited Response Code) handling
if (module_type == PROTOCOL_LWM2M) {
if (strstr(recv_buffer, "+NNMI") != NULL) {
urc_handler(recv_buffer, recv_data_len);
}
} else {
if (strstr(recv_buffer, "+QIURC: \"recv\"") != NULL) {
urc_handler(recv_buffer, recv_data_len);
} else if (strstr(recv_buffer, "+QIURC:\"closed\"") != NULL) {
comm_ctrl.pending_cmd = CMD_UPLOAD;
rt_mb_send_wait(comm_mailbox_at, EVT_AT_SEND, 1000);
}
}
}
}
} break;
case EVT_AT_TIMEOUT: {
if (seq_retry++ < desc.max_retries) {
rt_device_write(uart_device, RT_NULL, desc.tx_payload, strlen(desc.tx_payload));
rt_timer_control(timer_monitor, RT_TIMER_CTRL_SET_TIME, &desc.timeout_ms);
rt_timer_start(timer_monitor);
} else {
rt_mb_send_wait(comm_mailbox_at, EVT_AT_FAILED, 1000);
}
} break;
case EVT_AT_COMPLETE: {
reset_runtime_context();
rt_mb_send_wait(comm_mailbox_ctrl, EVT_CTRL_SUCCESS, 1000);
} break;
case EVT_AT_FAILED: {
reset_runtime_context();
rt_mb_send_wait(comm_mailbox_ctrl, EVT_CTRL_FAILURE, 1000);
} break;
}
}
}
}
Control Layer Implementation
/* Control event processing */
void ctrl_event_handler(uint32_t event) {
switch (event) {
case EVT_CTRL_IDLE:
break;
case EVT_CTRL_POWERON: {
memset(recv_buffer, 0x00, sizeof(recv_buffer));
while ((recv_data_len = rt_device_read(uart_device, RT_NULL, recv_buffer, RT_NULL)) > 0) {
if (strstr(recv_buffer, "+IP") != NULL) {
reset_runtime_context();
active_sequence = (module_type == PROTOCOL_LWM2M) ? seq_init_lwm2m : seq_init_tcpip;
comm_ctrl.pending_cmd = CMD_INIT;
rt_mb_send_wait(comm_mailbox_at, EVT_AT_SEND, 1000);
}
}
} break;
case EVT_CTRL_SUCCESS: {
switch (comm_ctrl.pending_cmd) {
case CMD_INIT:
if (comm_ctrl.upload_pending) {
comm_ctrl.upload_pending = false;
update_tx_payload();
reset_runtime_context();
active_sequence = (module_type == PROTOCOL_LWM2M) ? seq_upload_lwm2m : seq_upload_tcpip;
comm_ctrl.pending_cmd = CMD_UPLOAD;
rt_mb_send_wait(comm_mailbox_at, EVT_AT_SEND, 1000);
} else {
close_connection();
}
break;
case CMD_UPLOAD:
close_connection();
break;
}
} break;
case EVT_CTRL_FAILURE:
close_connection();
break;
}
}
Control State Definitions
#ifndef __COMM_CONTROL_H
#define __COMM_CONTROL_H
#include "app_config.h"
// Runtime States
typedef enum {
STATE_POWERON = 0,
STATE_WORKING,
STATE_SLEEP,
STATE_ALERT
} runtime_state_t;
// Command Types
typedef enum {
CMD_INIT = 0,
CMD_UPLOAD
} pending_cmd_t;
// Module Protocols
typedef enum {
PROTOCOL_LWM2M = 0,
PROTOCOL_TCPIP
} protocol_type_t;
// Control Block Structure
typedef struct {
runtime_state_t run_state;
pending_cmd_t pending_cmd;
uint8_t upload_pending;
protocol_type_t module_type;
} control_block_t;
// Control Events
typedef enum {
EVT_CTRL_IDLE = 1 << 0,
EVT_CTRL_POWERON = 1 << 1,
EVT_CTRL_SUCCESS = 1 << 2,
EVT_CTRL_FAILURE = 1 << 3,
EVT_CTRL_UPLOAD_OK = 1 << 4,
EVT_CTRL_UPLOAD_FAIL = 1 << 5,
EVT_CTRL_RTC_TRIGGER = 1 << 6,
EVT_CTRL_SENSOR_DATA = 1 << 7,
EVT_CTRL_SENSOR_CTRL = 1 << 8,
} ctrl_event_t;
extern control_block_t comm_ctrl;
void comm_init(void);
#endif
Framework Architecture Summary
The framework separates concerns into three layers:
- Command Definition Layer: Centralized command table with descriptor structures containing transmission data, expected responses, timing parameters, and callback handlers
- Processing Layer: State machine-based thread handling send, wait, timeout, complete, and failure states with automatic retry logic
- Control Layer: High-level coordinator managing protocol selection, command sequences, and result callbacks
This architecture enables rapid feature implementation through simple command table extensions while maintaining clean separation between protocol handling and business logic.