/* ble_at.c - HLK-B40 蓝牙透传模块 AT 配置 * * 接线:模块 PC5(功能按键输入) 接单片机 PC4。 * 进 AT 模式:PC4 拉低 1s 后释放(手册要求 0.5~3s); * 回透传模式:AT 模式下发送 AT+TS=1。 * 上电默认透传模式。 */ #include "ble_at.h" #include "uart.h" #include "log.h" #include "main.h" #include "system.h" #include "24c02.h" #include "stdio.h" #include "string.h" #define BLE_PC5_PORT GPIOC #define BLE_PC5_PIN GPIO_PIN_4 /* BT_RST(PA7):高电平复位模块,默认低电平工作(system.c 已初始化为推挽低) */ #define BLE_RST_PORT GPIOA #define BLE_RST_PIN GPIO_PIN_7 #define BLE_RST_HIGH() HAL_GPIO_WritePin(BLE_RST_PORT, BLE_RST_PIN, GPIO_PIN_SET) #define BLE_RST_LOW() HAL_GPIO_WritePin(BLE_RST_PORT, BLE_RST_PIN, GPIO_PIN_RESET) #define BLE_PC5_LOW() HAL_GPIO_WritePin(BLE_PC5_PORT, BLE_PC5_PIN, GPIO_PIN_RESET) #define BLE_PC5_HIGH() HAL_GPIO_WritePin(BLE_PC5_PORT, BLE_PC5_PIN, GPIO_PIN_SET) /* EEPROM 缓存区:已配置好的蓝牙名(132~157,26B,避开其它占用) */ #define BLE_CACHE_ADDR 132 #define BLE_POWER_ADDR 158 /* EEPROM:已配置的发射功率(132~157 名称缓存, 160 日志等级) */ #define BLE_CACHE_LEN 26 /* PC4 开漏输出:释放时由模块 PC5 内部上拉保持高电平 */ void ble_at_init(void) { GPIO_InitTypeDef g = {0}; __HAL_RCC_GPIOC_CLK_ENABLE(); BLE_PC5_HIGH(); g.Pin = BLE_PC5_PIN; g.Mode = GPIO_MODE_OUTPUT_OD; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(BLE_PC5_PORT, &g); BLE_PC5_HIGH(); } static void ble_delay(uint32_t ms) { while (ms >= 100) { HAL_Delay(100); IWDG_Feed(); ms -= 100; } if (ms) HAL_Delay(ms); } /* 清空 USART1 待处理行 */ static void ble_flush_rx(void) { while (USART1_GetCommand() != NULL) { } } /* 发送 AT 命令并等待期望应答(按行匹配子串),0=成功 */ static int ble_at_cmd(const char *cmd, const char *expect, int timeout_ms) { char line[64]; int n = snprintf(line, sizeof(line), "%s\r\n", cmd); ble_flush_rx(); HAL_UART_Transmit(&huart1, (uint8_t *)line, n, 500); int t = timeout_ms / 50; while (t-- > 0) { ble_delay(50); const char *resp = USART1_GetCommand(); if (resp) { if (strstr(resp, expect)) { log_info("> BLE AT: %s", resp); return 0; } if (strstr(resp, "ERROR")) { log_warn("> BLE AT 应答错误: %s", resp); return -1; } } } log_warn("> BLE AT 超时: %s", cmd); return -1; } /* 上电执行一次:进 AT 模式 → 蓝牙名称查询/设置为 BLE_NAME → 需要时重启模块生效。 * 先查询后设置:名字已正确则整个流程跳过(不留 AT 窗口,避免 APP 数据撞进 AT 命令) */ void ble_setup_name(void) { char cmd[48]; /* EEPROM 缓存命中(名称+功率都对)则跳过整个 AT 流程(零窗口,蓝牙 APP 不断联) */ char cache[BLE_CACHE_LEN] = {0}; eepromReadData(BLE_CACHE_ADDR, cache, BLE_CACHE_LEN); cache[BLE_CACHE_LEN - 1] = 0; uint8_t pwr_cache = 0xFF; eepromReadData(BLE_POWER_ADDR, &pwr_cache, 1); if (strcmp(cache, BLE_NAME) == 0 && pwr_cache == BLE_TX_POWER) { log_info("> BLE: 配置已缓存(%s/功率%d),跳过设置", BLE_NAME, BLE_TX_POWER); return; } /* 先硬复位一次模块(PA7 拉高 100ms),防止模块状态异常时 PC5 进不了 AT 模式。 * 只有缓存未命中(要改配置)才会走到这里,正常开机零窗口不受影响 */ BLE_RST_HIGH(); ble_delay(100); BLE_RST_LOW(); ble_delay(1500); /* 等模块重启完成 */ /* AT 窗口内静音日志:UART1 与模块共用,日志字节会污染模块的 AT 命令解析 */ log_mute(1); /* 进 AT 模式并查询当前名称:无应答则重新拉 PC5 再试,最多 3 轮 */ const char *cur = NULL; for (int round = 0; round < 3 && !cur; round++) { BLE_PC5_LOW(); ble_delay(1100); /* 拉低 1s 进 AT 模式 */ BLE_PC5_HIGH(); ble_delay(600); /* 等模块切换到 AT 模式 */ ble_flush_rx(); HAL_UART_Transmit(&huart1, (uint8_t *)"AT+NAME=?\r\n", 12, 500); int t = 30; while (t-- > 0 && !cur) { ble_delay(50); cur = USART1_GetCommand(); } } if (!cur) { log_mute(0); log_warn("> BLE: 3 轮 AT 均无应答(模块未接/被手机占用/PC5 未拉低)"); return; } if (strstr(cur, BLE_NAME) && pwr_cache == BLE_TX_POWER) { /* 名称与功率都正确:直接回透传,不动任何设置 */ ble_at_cmd("AT+TS=1", "OK", 1500); log_mute(0); log_info("> BLE: 名称已是 %s,无需设置", BLE_NAME); return; } if (strstr(cur, BLE_NAME)) { /* 名称对但功率变了:只重设发射功率 */ snprintf(cmd, sizeof(cmd), "AT+RFPOWER=%d", BLE_TX_POWER); ble_at_cmd(cmd, "OK", 1500); ble_at_cmd("AT+REBOOT=1", "OK", 1500); uint8_t p = BLE_TX_POWER; eepromWriteData(BLE_POWER_ADDR, &p, 1); log_mute(0); log_info("> BLE: 发射功率已设置为 %d", BLE_TX_POWER); return; } /* 需要设置:发 AT+NAME(最多重试 3 次)。 * 注意:本模块设置类参数重启后才生效,当场回查查到的仍是旧名, * 不能用作校验——改完直接重启,下次开机的查询才是真正的校验 */ int ok = -1; for (int i = 0; i < 3 && ok != 0; i++) { snprintf(cmd, sizeof(cmd), "AT+NAME=%s", BLE_NAME); ok = ble_at_cmd(cmd, "OK", 1500); } if (ok == 0) { #if BLE_UUID_CUSTOM /* 顺带设置透传 GATT UUID(FFE0/FFE1),通用 BLE 串口 APP 可连 */ snprintf(cmd, sizeof(cmd), "AT+UUIDS=%s", BLE_UUID_SERVICE); ble_at_cmd(cmd, "OK", 1500); snprintf(cmd, sizeof(cmd), "AT+UUIDR=%s", BLE_UUID_TX); ble_at_cmd(cmd, "OK", 1500); snprintf(cmd, sizeof(cmd), "AT+UUIDW=%s", BLE_UUID_RX); ble_at_cmd(cmd, "OK", 1500); #endif /* 顺带设置发射功率 */ snprintf(cmd, sizeof(cmd), "AT+RFPOWER=%d", BLE_TX_POWER); ble_at_cmd(cmd, "OK", 1500); /* 手册说明:设置类参数重启后才生效,重启模块使新名称生效; * 模块重启后默认就是透传模式,无需再发 AT+TS=1 */ for (int i = 0; i < 2; i++) { if (ble_at_cmd("AT+REBOOT=1", "OK", 1500) == 0) break; } eepromWriteData(BLE_CACHE_ADDR, (void *)BLE_NAME, strlen(BLE_NAME) + 1); { uint8_t p = BLE_TX_POWER; eepromWriteData(BLE_POWER_ADDR, &p, 1); } log_mute(0); log_info("> BLE: 名称/UUID/功率已设置完成"); } else { ble_at_cmd("AT+TS=1", "OK", 1500); /* 确保回到透传 */ log_mute(0); log_warn("> BLE: 设置名称失败(模块未接或应答异常)"); } } /* 强制重新执行蓝牙设置(蓝牙 BLESET 命令触发):清缓存后重跑 */ void ble_force_setup(void) { eepromWriteData(BLE_CACHE_ADDR, "", 1); ble_setup_name(); }