4.3.9. APP - Bluetooth Dual-Mode AT Moudle (char)
4.3.9.1. 概述
主要功能是在普通数传BLE和EDR的基础上增加了由上位机或其他MCU可以通过UART对接蓝牙芯片进行基本配置、状态获取、控制扫描、连接断开以及数据收发等操作。
AT控制透传主从多机模式。
定义一套串口的控制协议,具体请查看协议文档《蓝牙AT_CHAR协议》。
支持的板级: br25、br23、bd19、br34
支持的芯片: AC636N、AC635N、AC632N、AC638N
Note
注意不同芯片可以使用RAM的空间有差异,有可能会影响性能。
4.3.9.2. 工程配置
代码工程:apps/spp_and_le/board/bd19/AC632N_spp_and_le.cbp
先配置板级board_config.h和对应配置文件中蓝牙双模使能
*/\** * 板级配置选择 */ #define CONFIG_BOARD_AC632N_DEMO \ *// CONFIG_APP_KEYBOARD,CONFIG_APP_PAGE_TURNER* #include "board_ac632n_demo_cfg.h"
配置对应的board_acxxx_demo_cfg.h文件使能BLE 以board_ac632n_demo_cfg.h为例
#define TCFG_USER_BLE_ENABLE 1 //BLE功能使能 #define TCFG_USER_EDR_ENABLE 0 //EDR功能使能
配置app_config.h, 选择CONFIG_APP_AT_CHAR_COM
#define CONFIG_APP_AT_CHAR_COM 1//AT com 字符串格式命令
4.3.9.3. 主要说明代码<at_char_cmds.c>
命令包头
static const char at_head_at_cmd[] = "AT+"; static const char at_head_at_chl[] = "AT>"; static const str_info_t at_head_str_table[] = { INPUT_STR_INFO(STR_ID_HEAD_AT_CMD, at_head_at_cmd), INPUT_STR_INFO(STR_ID_HEAD_AT_CHL, at_head_at_chl), };
命令类型:
static const char at_str_gver[] = "GVER"; static const char at_str_gcfgver[] = "GCFGVER"; static const char at_str_name[] = "NAME"; static const char at_str_lbdaddr[] = "LBDADDR"; static const char at_str_baud[] = "BAUD"; static const char at_str_adv[] = "ADV"; static const char at_str_advparam[] = "ADVPARAM"; static const char at_str_advdata[] = "ADVDATA"; static const char at_str_srdata[] = "SRDATA"; static const char at_str_connparam[] = "CONNPARAM"; static const char at_str_scan[] = "SCAN"; static const char at_str_targetuuid[] = "TARGETUUID"; static const char at_str_conn[] = "CONN"; static const char at_str_disc[] = "DISC"; static const char at_str_ota[] = "OTA"; static const char at_str_conn_cannel[] = "CONN_CANNEL"; static const char at_str_power_off[] = "POWEROFF"; static const char at_str_lowpower[] = "LOWPOWER"; static const str_info_t at_cmd_str_table[] = { INPUT_STR_INFO(STR_ID_GVER, at_str_gver), INPUT_STR_INFO(STR_ID_GCFGVER, at_str_gcfgver), INPUT_STR_INFO(STR_ID_NAME, at_str_name), INPUT_STR_INFO(STR_ID_LBDADDR, at_str_lbdaddr), INPUT_STR_INFO(STR_ID_BAUD, at_str_baud), INPUT_STR_INFO(STR_ID_ADV, at_str_adv), INPUT_STR_INFO(STR_ID_ADVPARAM, at_str_advparam), INPUT_STR_INFO(STR_ID_ADVDATA, at_str_advdata), INPUT_STR_INFO(STR_ID_SRDATA, at_str_srdata), INPUT_STR_INFO(STR_ID_CONNPARAM, at_str_connparam), INPUT_STR_INFO(STR_ID_SCAN, at_str_scan), INPUT_STR_INFO(STR_ID_TARGETUUID, at_str_targetuuid), INPUT_STR_INFO(STR_ID_CONN, at_str_conn), INPUT_STR_INFO(STR_ID_DISC, at_str_disc), INPUT_STR_INFO(STR_ID_OTA, at_str_ota), INPUT_STR_INFO(STR_ID_CONN_CANNEL, at_str_conn_cannel), INPUT_STR_INFO(STR_ID_POWER_OFF, at_str_power_off), INPUT_STR_INFO(STR_ID_LOW_POWER, at_str_lowpower), // INPUT_STR_INFO(, ), // INPUT_STR_INFO(, ), };
串口收数中断,
进入收数中断之前会进行初步校验, 数据结尾不是’r’, 则无法唤醒中断
static void at_packet_handler(u8 \*packet, int size)
AT命令解析
/*比较数据包头*/ str_p = at_check_match_string(parse_pt, parse_size, at_head_str_table, sizeof(at_head_str_table)); if (!str_p) { log_info("###1unknow at_head:%s", packet); at_respond_send_err(ERR_AT_CMD); return; } parse_pt += str_p->str_len; parse_size -= str_p->str_len; /*普通命令*/ if (str_p->str_id == STR_ID_HEAD_AT_CMD) { /*比较命令*/ str_p = at_check_match_string(parse_pt, parse_size, at_cmd_str_table, sizeof(at_cmd_str_table)); if (!str_p) { log_info("###2unknow at_cmd:%s", packet); at_respond_send_err(ERR_AT_CMD); return; } parse_pt += str_p->str_len; parse_size -= str_p->str_len; /*判断当前是命令类型,查询或设置命令*/ if (parse_pt[0] == '=') { operator_type = AT_CMD_OPT_SET; } else if (parse_pt[0] == '?') { operator_type = AT_CMD_OPT_GET; } parse_pt++; /*通道切换命令*/ } else if (str_p->str_id == STR_ID_HEAD_AT_CMD) { operator_type = AT_CMD_OPT_SET; } // if(operator_type == AT_CMD_OPT_NULL) // { // AT_STRING_SEND(at_str_err); // log_info("###3unknow operator_type:%s", packet); // return; // } log_info("str_id:%d", str_p->str_id); /*解析并返回命令参数par*/ par = parse_param_split(parse_pt, ',', '\r'); log_info("\n par->data: %s", par->data); /*命令处理与响应*/ switch (str_p->str_id) { case STR_ID_HEAD_AT_CHL: { u8 tmp_cid = func_char_to_dec(par->data, '\0'); if (tmp_cid == 9) { black_list_check(0, NULL); } log_info("STR_ID_HEAD_AT_CHL:%d\n", tmp_cid); AT_STRING_SEND("OK"); //响应 cur_atcom_cid = tmp_cid; } break;
命令的参数获取与遍历
par = parse_param_split(parse_pt,',','\r'); /*parameter \*packet: 参数指针 split_char: 参数之间的间隔符, 一般是’,’ end_char: 参数的结束符, 一般是’\r’ \*/ static at_param_t *parse_param_split(const u8 *packet, u8 split_char, u8 end_char) { u8 char1; int i = 0; at_param_t *par = parse_buffer; if (*packet == end_char) { return NULL; } log_info("%s:%s", __FUNCTION__, packet); par->len = 0; while (1) { char1 = packet[i++]; if (char1 == end_char) { par->data[par->len] = 0; par->next_offset = 0; break; } else if (char1 == split_char) { par->data[par->len] = 0; par->len++; par->next_offset = &par->data[par->len] - parse_buffer; //init next par par = &par->data[par->len]; par->len = 0; } else { par->data[par->len++] = char1; } if (&par->data[par->len] - parse_buffer >= PARSE_BUFFER_SIZE) { log_error("parse_buffer over"); par->next_offset = 0; break; } } #if 0 par = parse_buffer; log_info("param_split:"); while (par) { log_info("len=%d,par:%s", par->len, par->data); if (par->next_offset) { par = AT_PARAM_NEXT_P(par); } else { break; } } #endif return (void *)parse_buffer; }
当有多个参数是, 需要遍历获取, 以连接参数为例
while (par) { //遍历所有参数 log_info("len=%d,par:%s", par->len, par->data); conn_param[i] = func_char_to_dec(par->data, '\0'); //获取参数 if (par->next_offset) { par = AT_PARAM_NEXT_P(par); } else { break; } i++; }
默认信息配置
#define G_VERSION "BR30_2021_01_31" #define CONFIG_VERSION "2021_02_04" /*版本信息*/ u32 uart_baud = 115200; /*默认波特率*/ char dev_name_default[] = "jl_test"; /*默认dev name*/
在代码中添加新的命令(以查询、设置波特率为例)
添加波特率枚举成员
enum { STR_ID_NULL = 0, STR_ID_HEAD_AT_CMD, STR_ID_HEAD_AT_CHL, STR_ID_OK = 0x10, STR_ID_ERROR, STR_ID_GVER = 0x20, STR_ID_GCFGVER, STR_ID_NAME, STR_ID_LBDADDR, STR_ID_BAUD, STR_ID_ADV, STR_ID_ADVPARAM, STR_ID_ADVDATA, STR_ID_SRDATA, STR_ID_CONNPARAM, STR_ID_SCAN, STR_ID_TARGETUUID, STR_ID_CONN, STR_ID_DISC, STR_ID_OTA, STR_ID_CONN_CANNEL, STR_ID_POWER_OFF, STR_ID_LOW_POWER, // STR_ID_, // STR_ID_, };
添加波特率命令类型
static const char at_str_gver[] = "GVER"; static const char at_str_gcfgver[] = "GCFGVER"; ...
添加命令到命令列表
static const str_info_t at_cmd_str_table[] = { INPUT_STR_INFO(STR_ID_GVER, at_str_gver), INPUT_STR_INFO(STR_ID_GCFGVER, at_str_gcfgver), INPUT_STR_INFO(STR_ID_NAME, at_str_name), INPUT_STR_INFO(STR_ID_LBDADDR, at_str_lbdaddr), INPUT_STR_INFO(STR_ID_BAUD, at_str_baud), ...
在at_packet_handler函数中添加命令的处理与响应
case STR_ID_BAUD: log_info("STR_ID_BAUD\n"); { if (operator_type == AT_CMD_OPT_SET) { //2.7 uart_baud = func_char_to_dec(par->data, '\0'); log_info("set baud= %d", uart_baud); if (uart_baud == 9600 || uart_baud == 19200 || uart_baud == 38400 || uart_baud == 115200 || uart_baud == 230400 || uart_baud == 460800 || uart_baud == 921600) { AT_STRING_SEND("OK"); ct_uart_change_baud(uart_baud); } else { //TODO返回错误码 at_respond_send_err(ERR_AT_CMD); ...
串口发数api, 用于发送响应信息
/\* parameter packet: 数据包 size: 数据长度 \*/ void at_cmd_send(const u8 *packet, int size) { log_info("###at_cmd_send(%d):", size); // put_buf(packet, size); at_send_uart_data(at_str_enter, 2, 0); at_send_uart_data(packet, size, 0); at_send_uart_data(at_str_enter, 2, 1); }