/** * @file at_driver.c * @brief 4G Cat.1 AT command driver (Air724UG / EC200U) * * State machine based AT command handler. * UART1: 115200 8N1, full duplex with ring buffer. */ #include "at_driver.h" #include #include #include #include "stm32g4xx.h" /* ==================== Configuration ==================== */ #define AT_UART_TX_PORT GPIOA #define AT_UART_TX_PIN 9 #define AT_UART_RX_PORT GPIOA #define AT_UART_RX_PIN 10 #define AT_PWRKEY_PORT GPIOB #define AT_PWRKEY_PIN 0 #define AT_STATUS_PORT GPIOB #define AT_STATUS_PIN 1 #define AT_RX_BUF_SIZE 2048 #define AT_TX_BUF_SIZE 512 #define AT_RESP_LINE_MAX 64 #define AT_RESP_BUF_SIZE 4096 #define AT_TIMEOUT_DEFAULT 5000 /* 5 seconds default */ #define AT_TIMEOUT_LONG 30000 /* 30 seconds for network */ #define AT_BAUDRATE 115200 /* ==================== Ring buffer for UART RX ==================== */ typedef struct { uint8_t buf[AT_RX_BUF_SIZE]; volatile uint16_t head; volatile uint16_t tail; } ringbuf_t; static ringbuf_t g_rx_rb; static bool rb_put(ringbuf_t *rb, uint8_t byte) { uint16_t next = (rb->head + 1) % AT_RX_BUF_SIZE; if (next == rb->tail) return false; /* Full */ rb->buf[rb->head] = byte; rb->head = next; return true; } static int rb_get(ringbuf_t *rb) { if (rb->tail == rb->head) return -1; /* Empty */ uint8_t byte = rb->buf[rb->tail]; rb->tail = (rb->tail + 1) % AT_RX_BUF_SIZE; return byte; } static void rb_flush(ringbuf_t *rb) { rb->head = 0; rb->tail = 0; } /* ==================== Response buffer ==================== */ static char g_resp_buf[AT_RESP_BUF_SIZE]; static uint16_t g_resp_len = 0; static bool g_resp_has_data = false; /* ==================== Module state ==================== */ static AT_ModuleState_t g_module_state = AT_MODULE_OFF; static AT_NetRegState_t g_net_state = AT_NET_NOT_REGISTERED; static AT_SIMState_t g_sim_state = AT_SIM_ABSENT; /* ==================== Low-level UART ==================== */ static void uart_init(void) { /* Enable clocks */ RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; /* PA9 = USART1_TX (AF7), PA10 = USART1_RX (AF7) */ GPIOA->MODER &= ~(GPIO_MODER_MODE9_Msk | GPIO_MODER_MODE10_Msk); GPIOA->MODER |= (2 << GPIO_MODER_MODE9_Pos) | (2 << GPIO_MODER_MODE10_Pos); GPIOA->AFR[1] &= ~((0xF << (9 - 8) * 4) | (0xF << (10 - 8) * 4)); GPIOA->AFR[1] |= (7 << (9 - 8) * 4) | (7 << (10 - 8) * 4); GPIOA->OSPEEDR |= (3 << GPIO_OSPEEDR_OSPEED9_Pos) | (3 << GPIO_OSPEEDR_OSPEED10_Pos); /* PWRKEY pin as output, STATUS as input */ GPIOB->MODER &= ~(GPIO_MODER_MODE0_Msk | GPIO_MODER_MODE1_Msk); GPIOB->MODER |= (1 << GPIO_MODER_MODE0_Pos); /* Output */ GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPD0_Msk); GPIOB->BSRR = GPIO_BSRR_BR0; /* PWRKEY low initially */ /* Configure UART: 115200 8N1 */ uint32_t pclk = 72000000; /* APB2 = 72MHz */ USART1->BRR = pclk / AT_BAUDRATE; USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE; USART1->CR2 = 0; USART1->CR3 = 0; /* Enable UART and RX interrupt */ USART1->CR1 |= USART_CR1_UE; NVIC_EnableIRQ(USART1_IRQn); NVIC_SetPriority(USART1_IRQn, 3); } void USART1_IRQHandler(void) { if (USART1->SR & USART_SR_RXNE) { uint8_t byte = (uint8_t)(USART1->DR & 0xFF); rb_put(&g_rx_rb, byte); } /* Clear overrun if any */ if (USART1->SR & USART_SR_ORE) { (void)USART1->DR; } } static void uart_send_byte(uint8_t byte) { while (!(USART1->SR & USART_SR_TXE)); USART1->DR = byte; } static void uart_send_str(const char *str) { while (*str) { uart_send_byte((uint8_t)*str); str++; } } static void uart_send_buf(const uint8_t *data, uint16_t len) { for (uint16_t i = 0; i < len; i++) { uart_send_byte(data[i]); } } /* ==================== AT command execution ==================== */ static uint32_t g_timeout_ms = AT_TIMEOUT_DEFAULT; static void set_timeout(uint32_t ms) { g_timeout_ms = ms; } static uint32_t get_tick(void) { return HAL_GetTick(); /* Provided by STM32 HAL, or use SysTick directly */ } /* Wait for specific response in RX buffer, with timeout */ static bool wait_response(const char *expect, uint32_t timeout_ms) { uint32_t start = get_tick(); uint16_t match_idx = 0; g_resp_has_data = false; g_resp_len = 0; while ((get_tick() - start) < timeout_ms) { int c = rb_get(&g_rx_rb); if (c >= 0) { /* Store in response buffer */ if (g_resp_len < AT_RESP_BUF_SIZE - 1) { g_resp_buf[g_resp_len++] = (char)c; g_resp_buf[g_resp_len] = '\0'; } /* Check for expected response */ if (expect) { if ((char)c == expect[match_idx]) { match_idx++; if (expect[match_idx] == '\0') { return true; } } else { /* Partial match check */ match_idx = ((char)c == expect[0]) ? 1 : 0; } } /* Also check for ERROR */ if (g_resp_len >= 5) { if (strstr(g_resp_buf + g_resp_len - 5, "ERROR") != NULL) { return false; } } } } return false; /* Timeout */ } /* Send AT command and wait for expected response */ static bool at_cmd(const char *cmd, const char *expect, uint32_t timeout_ms) { rb_flush(&g_rx_rb); uart_send_str(cmd); uart_send_str("\r\n"); return wait_response(expect, timeout_ms); } /* ==================== Public API ==================== */ void AT_Init(void) { uart_init(); rb_flush(&g_rx_rb); g_module_state = AT_MODULE_OFF; } bool AT_PowerOn(void) { /* PWRKEY pulse: pull low >= 1 second, then release */ g_module_state = AT_MODULE_POWERING_ON; GPIOB->BRR = GPIO_BRR_BR0; /* PWRKEY low */ for (volatile uint32_t i = 0; i < 72000000; i++) __NOP(); /* ~1s @ 72MHz */ GPIOB->BSRR = GPIO_BSRR_BS0; /* PWRKEY high */ return true; } void AT_PowerOff(void) { /* AT+CPOWD=0 (graceful power down) */ at_cmd("AT+CPOWD=0", "POWERED DOWN", 10000); g_module_state = AT_MODULE_OFF; } bool AT_Reset(void) { bool ok = at_cmd("AT+CRESET", "RDY", 10000); if (ok) { g_module_state = AT_MODULE_READY; } return ok; } bool AT_WaitReady(uint32_t timeout_ms) { bool ok = at_cmd("AT", "OK", timeout_ms); if (ok) { g_module_state = AT_MODULE_READY; } return ok; } AT_ModuleState_t AT_GetState(void) { return g_module_state; } AT_SIMState_t AT_CheckSIM(void) { if (at_cmd("AT+CPIN?", "READY", 5000)) { g_sim_state = AT_SIM_READY; } else if (strstr(g_resp_buf, "SIM PIN") != NULL) { g_sim_state = AT_SIM_PIN_REQUIRED; } else if (strstr(g_resp_buf, "ERROR") != NULL) { g_sim_state = AT_SIM_ABSENT; } return g_sim_state; } AT_NetRegState_t AT_CheckNetwork(void) { /* AT+CREG? returns +CREG: ,[,,] */ if (at_cmd("AT+CREG?", "+CREG:", 5000)) { /* Parse: +CREG: 0,1 or +CREG: 0,5 */ char *p = strstr(g_resp_buf, "+CREG:"); if (p) { p += 7; /* Skip first number */ while (*p && *p != ',') p++; if (*p == ',') p++; int reg = (*p - '0'); if (reg >= 1 && reg <= 5) { g_net_state = (AT_NetRegState_t)reg; } } } return g_net_state; } uint8_t AT_GetSignalQuality(void) { if (at_cmd("AT+CSQ", "+CSQ:", 5000)) { char *p = strstr(g_resp_buf, "+CSQ:"); if (p) { p += 6; int rssi = atoi(p); if (rssi >= 0 && rssi <= 31) return (uint8_t)rssi; } } return 99; } bool AT_SetupAPN(const char *apn, const char *user, const char *pass) { char cmd[128]; /* Define PDP context */ snprintf(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"", apn); if (!at_cmd(cmd, "OK", 5000)) return false; /* Activate PDP context */ if (!at_cmd("AT+CGACT=1,1", "OK", 30000)) return false; /* Wait for IP address */ if (!at_cmd("AT+CGPADDR=1", "+CGPADDR:", 10000)) return false; return true; } bool AT_GetIP(char *ip_buf) { if (!at_cmd("AT+CGPADDR=1", "+CGPADDR:", 5000)) return false; char *p = strstr(g_resp_buf, "+CGPADDR:"); if (p) { p = strchr(p, '"'); if (p) { p++; char *end = strchr(p, '"'); if (end) { uint16_t len = (uint16_t)(end - p); if (len > 15) len = 15; memcpy(ip_buf, p, len); ip_buf[len] = '\0'; return true; } } } return false; } void AT_DisconnectNetwork(void) { at_cmd("AT+CGACT=0,1", "OK", 10000); } bool AT_HTTP_GET(const char *url, uint32_t timeout_s) { char cmd[512]; /* Configure HTTP parameters */ at_cmd("AT+QHTTPCFG=\"contextid\",1", "OK", 2000); at_cmd("AT+QHTTPCFG=\"responseheader\",1", "OK", 2000); /* Set URL */ snprintf(cmd, sizeof(cmd), "AT+QHTTPURL=%u", (uint16_t)strlen(url)); if (!at_cmd(cmd, "CONNECT", 5000)) return false; uart_send_str(url); if (!wait_response("OK", 10000)) return false; /* Start GET */ snprintf(cmd, sizeof(cmd), "AT+QHTTPGET=%u", timeout_s); if (!at_cmd(cmd, "+QHTTPGET:", 5000)) return false; /* Wait for download complete */ if (!wait_response("+QHTTPGET: 0", timeout_s * 1000)) return false; return true; } uint32_t AT_HTTP_GetDownloadSize(void) { /* AT+QHTTPDL? returns size */ if (at_cmd("AT+QHTTPDL?", "+QHTTPDL:", 3000)) { char *p = strstr(g_resp_buf, "+QHTTPDL:"); if (p) { p = strchr(p, ','); if (p) { p++; /* Skip comma */ return (uint32_t)atol(p); } } } return 0; } uint32_t AT_HTTP_ReadData(uint32_t offset, uint8_t *buffer, uint32_t len) { char cmd[64]; snprintf(cmd, sizeof(cmd), "AT+QHTTPREAD=%u,%u", offset, len); if (!at_cmd(cmd, "CONNECT", 10000)) return 0; /* Read data payload (between AT response lines) */ uint32_t read_count = 0; for (uint32_t i = 0; i < len; i++) { int c = rb_get(&g_rx_rb); if (c < 0) break; buffer[read_count++] = (uint8_t)c; } /* Wait for final OK */ if (!wait_response("OK", 5000)) return read_count; return read_count; } bool AT_MQTT_Configure(const char *client_id, const char *username, const char *password) { char cmd[256]; /* Configure MQTT version */ at_cmd("AT+QMTCFG=\"version\",0,4", "OK", 2000); /* Set client ID */ snprintf(cmd, sizeof(cmd), "AT+QMTCFG=\"clientid\",0,\"%s\"", client_id); if (!at_cmd(cmd, "OK", 2000)) return false; if (username && password) { snprintf(cmd, sizeof(cmd), "AT+QMTCFG=\"userna\",0,\"%s\",\"%s\"", username, password); if (!at_cmd(cmd, "OK", 2000)) return false; } return true; } bool AT_MQTT_Connect(const char *host, uint16_t port) { char cmd[128]; snprintf(cmd, sizeof(cmd), "AT+QMTOPEN=0,\"%s\",%u", host, port); if (!at_cmd(cmd, "+QMTOPEN: 0,0", 30000)) return false; if (!at_cmd("AT+QMTCONN=0", "+QMTCONN: 0,0,0", 30000)) return false; return true; } bool AT_MQTT_Subscribe(const char *topic, uint8_t qos) { char cmd[256]; snprintf(cmd, sizeof(cmd), "AT+QMTSUB=0,1,\"%s\",%u", topic, qos); return at_cmd(cmd, "+QMTSUB: 0,0,", 10000); } bool AT_MQTT_Publish(const char *topic, const uint8_t *payload, uint16_t len, uint8_t qos) { char cmd[256]; /* First command to set topic and length */ snprintf(cmd, sizeof(cmd), "AT+QMTPUB=0,\"%s\",%u,0,%u", topic, qos, len); if (!at_cmd(cmd, ">", 5000)) return false; /* Send payload */ uart_send_buf(payload, len); uart_send_byte(0x1A); /* Ctrl+Z to end */ return wait_response("+QMTPUB: 0,0,0", 15000); } void AT_MQTT_Disconnect(void) { at_cmd("AT+QMTDISC=0", "+QMTDISC: 0,0", 5000); } bool AT_MQTT_IsConnected(void) { /* Check MQTT connection state */ if (at_cmd("AT+QMTSTAT?", "+QMTSTAT:", 2000)) { if (strstr(g_resp_buf, "0,0") != NULL || strstr(g_resp_buf, "0,1") != NULL) { return true; } } return false; } void AT_EnterSleep(void) { at_cmd("AT+CSCLK=1", "OK", 2000); } void AT_WakeUp(void) { /* Send break to wake module */ uart_send_str("AT\r\n"); wait_response("OK", 3000); } void AT_UART_RxCallback(uint8_t byte) { rb_put(&g_rx_rb, byte); } bool AT_CheckResponse(const char *tag, char *buf, uint16_t len) { if (!g_resp_has_data) return false; char *p = strstr(g_resp_buf, tag); if (p) { uint16_t copy_len = (uint16_t)strlen(p); if (copy_len >= len) copy_len = len - 1; memcpy(buf, p, copy_len); buf[copy_len] = '\0'; return true; } return false; } void AT_ClearResponse(void) { g_resp_len = 0; g_resp_buf[0] = '\0'; g_resp_has_data = false; }