How to Build an AT Command Parsing Module for Modem, Wi‑Fi, and Bluetooth
This guide explains the design, architecture, and step‑by‑step usage of an AT command parsing library for bare‑metal and OS‑based systems, covering job queue management, core APIs, example code, and integration with modem, Wi‑Fi, and Bluetooth modules.
Overview
The AT command communication module provides two implementations: a bare‑metal version ( at_chat) and an OS‑based version ( at). Both are suitable for modem, Wi‑Fi, and Bluetooth modules.
Software Architecture
at_chat.c, at_chat.h, list.h – no‑OS implementation. Uses chained queues and asynchronous callbacks to transmit and receive AT commands, supports URC handling and custom command jobs.
at.c, at.h, at_util.h, comdef.h – OS implementation. Requires porting of OS primitives (semaphores, task delays, etc.) defined in at_util.h.
at_chat (no‑OS) API
Job Item Structure
typedef struct {
unsigned int state : 3;
unsigned int type : 3; /* job type */
unsigned int abort : 1;
void *param; /* generic parameter */
void *info; /* generic info pointer */
struct list_head node; /* list node */
} at_item_t;Ten job items are statically allocated; no dynamic memory allocation is required.
Core Functions
at_send_singlline(at_obj_t *obj, at_response_cb cb, const char *cmd)– send a single‑line command, wait for “OK”, timeout 3000 ms.
at_send_multiline(at_obj_t *obj, at_response_cb cb, const char *cmd)– send a multi‑line command, same timeout.
at_do_cmd(at_obj_t *obj, at_respond_t *resp, const char *fmt, ...)– custom format and response matching. at_do_work(at_obj_t *obj, at_work work, void *ctx) – custom send/receive parsing for composite commands.
Typical Initialization
Define an at_obj_t controller and an at_adapter_t that implements write and read for the UART (or other transport).
static at_obj_t at;
const at_adapter_t adap = {
.write = uart_write,
.read = uart_read,
/* other fields as needed */
};Initialize the controller and start a periodic poll (recommended interval ≤20 ms).
void wifi_init(void) {
at_obj_init(&at, &adap);
/* additional hardware init */
}
driver_init("wifi", wifi_init);
void wifi_task(void) {
at_poll_task(&at);
}
task_register("wifi", wifi_task, 10); /* 10 ms interval */Example: Single‑Line Command
// Send AT+GPIO_TEST_EN=1 and handle result
static void gpio_cb(at_response_t *r) {
if (r->ret == AT_RET_OK) {
printf("Execute successfully
");
} else {
printf("Execute failure
");
}
}
at_send_singlline(&at, gpio_cb, "AT+GPIO_TEST_EN=1");at (OS) API
Key Functions
at_do_cmd– execute an AT command; can be wrapped for common single‑line or multi‑line usage. at_split_respond_lines – split a raw response into separate lines. at_do_work – state‑machine helper for commands that require intermediate prompts such as “CONNECT”.
Initialization for an RTOS
Define controller, URC buffer, and URC table.
static at_obj_t at;
static char urc_buf[128];
utc_item_t utc_tbl[] = {
{ "+CSQ: ", csq_updated_handler },
/* add more URC patterns as needed */
};
const at_adapter_t adap = {
.urc_buf = urc_buf,
.urc_bufsize = sizeof(urc_buf),
.utc_tbl = utc_tbl,
.urc_tbl_count = sizeof(utc_tbl) / sizeof(utc_item_t),
.debug = at_debug,
.write = uart_write,
.read = uart_read,
};Create the controller and run a dedicated polling thread.
void at_thread(void) {
at_obj_create(&at, &adap);
while (1) {
at_process(&at);
}
}Example 1 – Query Signal Quality
bool read_csq_value(at_obj_t *at, int *rssi, int *error_rate) {
unsigned char recvbuf[32];
at_respond_t resp = { "OK", recvbuf, sizeof(recvbuf), 3000 };
if (at_do_cmd(at, &resp, "AT+CSQ") != AT_RET_OK)
return false;
return (sscanf((char *)recvbuf, "%*[^+]+CSQ: %d,%d", rssi, error_rate) == 2);
}Example 2 – Send TCP/UDP Data (HL8518 socket)
static bool socket_send_handler(at_work_ctx_t *e) {
struct socket_info *i = (struct socket_info *)e->params;
struct ril_sock *s = i->s;
if (s->type == SOCK_TYPE_TCP)
e->printf(e, "AT+KTCPSND=%d,%d", s->session, i->bufsize);
else
e->printf(e, "AT+KUDPSND=%d,%s,%d,%d",
s->session, s->host, s->port, i->bufsize);
if (e->wait_resp(e, "CONNECT", 5000) != AT_RET_OK)
goto error;
e->write(i->buf, i->bufsize);
e->write("--EOF--Pattern--", strlen("--EOF--Pattern--"));
return (e->wait_resp(e, "OK", 5000) == AT_RET_OK);
error:
e->write("--EOF--Pattern--", strlen("--EOF--Pattern--"));
return false;
}
static bool hl8518_sock_send(ril_obj_t *r, struct ril_sock *s,
const void *buf, unsigned int len) {
struct socket_info info = { s, (unsigned char *)buf, len, 0 };
if (len == 0) return false;
return at_do_work(&r->at, (at_work)socket_send_handler, &info);
}Repository
Source code: https://toscode.gitee.com/smtian/AT-Command
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
