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);

}