MCU

MM32W0/3提供模组和开发板方式供客户使用,支持UART\SPI\IIC接口的AT指令,用户通过发送相关固定格式的指令方式可以实现对应功能。开发板上电后,模块会自动进行广播,移动设备的APP 会对其进行扫描和连接,连接成功之后可以通过BLE 在模块和移动设备之间进行数据传输。用户MCU 可通过模块的串口和移动设备进行双向通信,移动设备也可以通过APP 对模块进行写操作,写入的数据将通过串口发送给用户的MCU,模块收到来自用户MCU 串口的数据,将自动转发给移动设备。

AT 指令主要用于配置模块参数,比如广播间隔、设备名、等,也用于发送透传数据和断开BLE 连接。而对于AT指令,客户可以很方便的进行修改,添加自己需要的功能。

通信流程


图1 通信流程

UART AT指令集


表1 UART AT指令集

在官方提供的程序中已经支持大部分的蓝牙设置等操作,可以实现透传、修改蓝牙参数等操作,如果用户需要单独开发AT指令集可以通过以下方式进行开发。

接收指令

首先是接收指令时的数据处理流程:


图2 接收指令流程图

在每次蓝牙服务调用UsrProcCallback()函数时,使用CheckAtCmdInfo()函数检查是否收到数据,如果有,在进入休眠之前加入一个可以接收20个字节的延时,在接收中断中接收剩下的数据,通过判断最后一位是否是0x0d或是0x0a来获得一条完整的指令,调用AtCmdPreParser()函数处理数据。检查数组开始的“AT+”和后面的指令名称,在at_func_list[]中查找并调用对应的函数对数据中后续的参数进行处理。

从流程中可以看到,如果只是简单的加减指令的话,只需要修改at_func_list数组就可以了,结构体AT_CMD_FUNC的两个成员变量分别是函数名称和对应的字符串。

注:处理时间不宜太长,更不能阻塞

typedef void (*ATCMDFUNC)(u8* cmd,u8 len);    

typedef struct _tagATCMD

{

ATCMDFUNC func;

u8 name[MAX_AT_CMD_NAME_SIZE]; //max len is 11 bytes

}AT_CMD_FUNC;

在例程中,收到AT指令要通过蓝牙发送数据时,使用的是sconn_notifydata()接口函数,这是一种不需要应答的蓝牙特征值,预设句柄为0x12,可以在发送前用set_notifyhandle()函数修改对应的句柄,或者直接修改变量u16 cur_notifyhandle。

发送数据

在例程中,通过UART发送数据都是通过moduleOutData()函数,往一个特定的缓存数组中写入数据。这个函数可以加在任何位置,可以加在AT指令处理函数中发送应答数据,也可以加到BLE服务中实现数据透传功能。

在每次蓝牙服务调用UsrProcCallback()函数时检查缓存数组,若不为空,在休眠之前加入一个延时,开启发送缓冲空中断,并在中断中发送剩下的数据。


图3 发送数据流程图
void moduleOutData(u8*data, u8 len) //api

{

unsigned char i;

if ((txLen+len)

以上例程使用的是UART接口的自定义AT指令实现方式,用户可以根据需要自行修改为其他接口,如SPI、IIC、CAN、USB等。

来源: 灵动MM32MCU

浏览 1 次

MM32W0x2xxB 蓝牙功能协议栈目前以Lib 形式提供,用户通过调用相关接口的方式实现对应功能。例程中,用户如需调整BLE 数据交互的特征值、服务及数据的收发,可按照如下的几个步骤进行调整,大部分的配置都在..\SRC_LIB\app.c文件中。

蓝牙之间通信是以参数来进行数据传输,即服务端定好一个参数,客户端可以对这个参数进行读,写,通知等操作,这个东西我们称之为特征值(characteristic),但一个参数不够我们用,比如我们这个特征值是电量的值,另一个特征值是设备读取的温度值。那这时候会有多个特征值,并且我们还会对它们分类,分出来的类我们称之为服务(service)。一个设备可以有多个服务,每一个服务可以包含多个特征值,本章节将介绍如何在例程中调整服务及自定义特征值。
在收发数据的时候,对于协议的处理基本都在lib中完成了,我们只需要在对应的接口函数中进一步处理就好。

声明与定义

首先是服务及特特征值的定义,用户可以自己分配,参考结构定义如下所示:

typedef struct ble_character16{

u16 type16; //type2

u16 handle_rec; //handle

u8 characterInfo[5];//property1 - handle2 - uuid2

u8 uuid128_idx; //0xff means uuid16,other is idx of uuid128

}BLE_CHAR;

 

typedef struct ble_UUID128{

u8 uuid128[16];//uuid128 string: little endian

}BLE_UUID128;

分别修改const BLE_CHAR AttCharList[]和const BLE_UUID128 AttUuid128List[]中的数据,自行分配句柄(递增,不得重复)

1、type16 为database 每个记录的类型,具体取值根据蓝牙规范定义;

常用的三个宏定义:

#define TYPE_CHAR 0x2803 //特征值的声明

#define TYPE_CFG 0x2902 //客户端特征值配置描述符

#define TYPE_INFO 0x2901 //特征值用户描述符

2、handle_rec 为对应记录的句柄,用户可以自定义;

3、characterInfo 保存了对应特征值的属性(property1)、句柄(handle2)及uuid(uuid2),其中handle2 及uuid2 为16 bit 小端格式;

常用的属性的宏定义如下:

#define ATT_CHAR_PROP_RD   0x02   //可读

#define ATT_CHAR_PROP_W_NORSP   0x04   //可写,无需应答

#define ATT_CHAR_PROP_W  0x08    //可写

#define ATT_CHAR_PROP_NTF  0x10    //notify

#define ATT_CHAR_PROP_IND   0x20    //indicate

4、uuid128_idx 表示uuid2 的格式,如该值为UUID16_FORMAT(0xFF) 则表示uuid2 为16bit 格式,反之则表示uuid2 为128bit 的uuid 信息对应的索引值,该索引值对应于AttUuid128List 的内容索引。uuid128 为小端格式保存。UUID就是通用唯一识别码。在蓝牙协议栈中可能会有多个服务,每个服务会有多个特征值,而这些服务或者特征值都有一个唯一的ID,这样就可以区分了。这个UUID是其他设备设置蓝牙服务和特征值的唯一方法。

应答Primary Service 的查询

下一步要修改的是att_server_rdByGrType函数。

在函数中缺省情况下如果客户对Device Info 不做特别修改,可直接调用缺省函数att_server_rdByGrTypeRspDeviceInfo(pdu_type)即可。

而下面的att_server_rdByGrTypeRspPrimaryService()需要按照上面的定义填充对应的数据,其中start_hd 与end_hd 为对应Service handle 取值范围,uuid 为字符串,对应的长度由uuidlen给出。

写操作

当外界发来相关数据时,ser_write_rsp()函数将被调用。

void ser_write_rsp( u8 pdu_type/*reserved*/,

u8 attOpcode/*reserved*/,

u16 att_hd, //对应特征值句柄

u8* attValue, //数据内容指针

u8 valueLen_w) //数据长度

通过判断特征值句柄att_hd,就可以进一步处理收到的数据。

若特征值属性为ATT_CHAR_PROP_W,需要调用ser_write_rsp_pkt()函数对这次写操作进行应答,不应答会导致连接断开。

若特征值无效或未定义,则使用att_notFd()函数进行应答,参数直接引用回调函数对应参数即可。

其中att_hd 为从手机BLE 传(写)过来数据对应的特征值的句柄,数据内容保存在变量attValue 中,数据长度为valueLen_w。

读操作

类似写操作,收到读取特征值请求时,ser_write_rsp()函数将被调用。

void server_rd_rsp(u8 attOpcode, u16 attHandle, u8 pdu_type)

通过判断attHandle来执行对应操作,使用att_server_rd()函数进行应答。

void att_server_rd(unsigned char pdu_type,

unsigned char attOpcode

unsigned short att_hd, //对应特征值句柄

unsigned char* attValue, //应答数据指针

unsigned char datalen ); //数据长度

其中pdu_type和attOpcode直接引用回调函数中对应参数,每次调用发送的数据长度不得超过20字节。

同写操作,若特征值无效或未定义,则使用att_notFd()函数进行应答。

Notify 数据发送操作

在模块出厂时烧录的例程中,可以通过UART的AT指令,调用Notify数据透传,对应的接口函数是

u8 sconn_notifydata(u8* data, u8 len);

原则上数据长度可以超过20 字节,协议会自动拆包发送,每个分包最大20字节,推荐一次发送的数据尽量不超过3 个分包,该函数返回实际发送的数据长度。这一函数没有指定对应的句柄,如果用户定义了多个Notify特征值,需要在发送前使用set_notifyhandle()函数指定对应的句柄,或者直接修改变量u16 cur_notifyhandle。

下面我们以在例程中添加一个可读可写的特征值为例,最后通过手机app与BLE之间进行通信:

1、在const BLE_CHAR AttCharList[]数组最后添加

{TYPE_CHAR,0x1A,ATT_CHAR_PROP_RD|ATT_CHAR_PROP_W, 0x1B,0,0,0,4},

//User defined

即在原数组最后句柄0x19后添加新的特征值,对应特征值设置可读可写,句柄0x001B为用户自定义特征值,128位UUID,索引值为4;

2、在const BLE_UUID128 AttUuid128List[]数组最后添加对应的UUID

{0x9e,0xca,0x0dc,0x24,0x0e,0xe5,0xa9,0xe0,0x93,0xf3,0xa3,0xb5,5,0,0x40,0x6e}, //idx4,little endian, Test

3、修改att_server_rdByGrType()函数:

att_server_rdByGrTypeRspPrimaryService(pdu_type,0x10,0x1B,(u8*)(AttUuid128List[0].uuid128),16);

修改最后的句柄值。

4、修改ser_write_rsp()函数

在switch(att_hd)分支中加入

case 0x1B:

moduleOutData("Write_Server_1B\r\n",17);

ser_write_rsp_pkt(pdu_type);

break;

5、修改server_rd_rsp()函数

在switch(attHandle)分支中加入

case 0x1B:

moduleOutData("Read_Server_1B\r\n",17);

att_server_rd( pdu_type, attOpcode, attHandle, "RD_SERVE_1B", 11);

break;

如下图,程序下载运行后,我们用手机连接模块,可以看到在服务列表最后多出了一项Unknown Characteristic ,可读可写,点击读按钮,可以收到字符串” RD_SERVE_1B”,UART串口输出”Read_Server_1B”,点击写按钮发送任意值会UART串口输出”Write_Server_1B”。


图1 手机端截图


图2 UART输出

来源:灵动MM32MCU

浏览 1 次


上表是lib中的接口函数,在编程指导手册中都有详细的说明,大部分函数的调用很简单,其中与服务(service)及特征值定义相关的函数将在后续章节详细介绍。

1、radio_initBle

函数原型: void radio_initBle(unsigned char txpwr, unsigned char**addr/*Output*/);

函数功能:用于初始化蓝牙芯片及蓝牙协议栈。需要在协议一开始调用。

输入参数:txpwr用于配置发射功率,可取的值有TXPWR_0DBM,TXPWR_3DBM 等。

输出参数: addr 该参数返回蓝牙 MAC 地址信息, 6 个字节长度。

2、ble_run

函数原型:void ble_run(unsigned short adv_interval);

函数功能:运行蓝牙协议

输入参数:adv_interval,参数的单位为0.625us,如果160 表示100ms 的广播间隔。

注:阻塞调用。中断方式运行时,在IRQ中断处理函数中调用,参数为0

3、sconn_notifydata

函数原型: unsigned char sconn_notifydata(unsigned char* data, unsigned char len);

函数功能:通过蓝牙发送数据输入。

参数: data 需要发送的数据指针 len 数据长度。

注意事项:本接口函数会根据系统缓存情况自动拆包发送数据,但不得在原地阻塞等待反复调用本接口。

4、radio_standby

函数原型:void radio_standby(void);

函数功能:在通过该函数可以使射频模块进入standby模式。

注意事项:射频模块进入standby后不能定时唤醒(射频模块进入STOP 模式可以定时唤醒自身以及控制模块),此时需要外界给IRQ 提供上升沿电平信号才能唤醒射频模块,给PA0 提供下降沿电平才能唤醒控制模块。

5、att_notFd

函数原型: void att_notFd(unsigned char pdu_type, unsigned char attOpcode, unsigned short attHd );

函数功能:对无效特征值(或没有定义的特征值)进行操作的应答函数

注意事项:凡是无效特征值的操作需要应答本函数,可将本函数作为缺省调用。

6、ser_write_rsp_pkt

函数原型: void ser_write_rsp_pkt(unsigned char pdu_type);

函数功能:对具有 Write With Response 属性特征值写操作后的应答函数。

注意事项:对需要写应答的特征值,如果不应答会导致连接的断开。

7、att_server_rdByGrTypeRspDeviceInfo

函数原型: void att_server_rdByGrTypeRspDeviceInfo(unsigned char pdu_type);

函数功能:对缺省 Device Info 内容的应答可调用本接口函数。

输入参数: pdu 类型参数,直接引用回调函数 att_server_rdByGrType 中对应参数。

注意事项:如果用户直接使用发布包代码,可直接调用本接口函数。

8、att_server_rdByGrTypeRspPrimaryService

函数原型: void att_server_rdByGrTypeRspPrimaryService(unsigned char pdu_type,

unsigned short start_hd,

unsigned short end_hd,

unsigned char*uuid,

unsigned char uuidlen);

函数功能:应答 Primary Service 的查询,用户需按特征值实际定义的句柄及 UUID 填充对应数据。

输入参数: pdu_type PDU 类型参数,直接引用回调函数 att_server_rdByGrType中对应参 start_hd, 某个Service 对应的起始句柄值 end_hd,某个 Service 对应的结束句柄值 uuid,某个 Service 对应的 UUID 字串(Hex 值),如 0x180A 表示为 0x0a, 0x18。 uuidlen,某个 Service 对应 UUID 字串的长度。

注意事项:需要严格按照特征值定义规划填充对应参数。

9、att_server_rd

函数原型: void att_server_rd(unsigned char pdu_type,

unsigned char attOpcode

unsigned short att_hd,

unsigned char* attValue,

unsigned char datalen );

函数功能:读取某特征值的值。

输入参数:pdu_type PDU 类型参数,直接引用回调函数server_rd_rsp 中对应参数 attOpcode操作对应的值,直接引用回调函数server_rd_rsp 中对应参数att_hd 特征值对应的句柄值,直接引用回调函数server_rd_rsp 中对应参数 attValue特征值对应的值字串指针 datalen特征值字串长度。

注意事项:需要按需将对应特征值内容作为应答内容,如果对应特征值内容无效可应答 att_notFd()。

回调函数

为便于蓝牙差异化功能的灵活实现,蓝牙协议内设置了若干接口并以回调函数的方式由应用层porting实现,所有回调函数不得阻塞调用,具体函数的实现可参考SDK 发布包中对应代码的实现示例。回调函数主要包括如下的一些:

1、void gatt_user_send_notify_data_callback(void);

蓝牙连接成功后协议在空闲的时侯会调用本回调函数;

2、void UsrProcCallback(void);

蓝牙协议会周期性回调本函数;

3、void ser_prepare_write(unsigned short handle,unsigned char* attValue, unsigned short attValueLen, unsigned short att_offset);

4、void ser_execute_write(void);

以上两个函数为队列写数据回调函数。

5、unsigned char* getDeviceInfoData(unsigned char* len);

本函数GATT 中设备名称获取的回调函数;

6、void att_server_rdByGrType( unsigned char pdu_type, unsigned char attOpcode,

unsigned short st_hd, unsigned short end_hd, unsigned short att_type );

蓝牙GATT 查询服务的回调函数;

7、void ser_write_rsp(unsigned char pdu_type/*reserved*/, unsigned char attOpcode/*reserved*/,unsigned short att_hd, unsigned char* attValue/*app data pointer*/,

unsigned char valueLen_w/*app data size*/);

蓝牙GATT 写操作回调函数;

8、void server_rd_rsp(unsigned char attOpcode, unsigned short attHandle, unsigned char pdu_type);

蓝牙GATT 读操作回调函数;

9、void ConnectStausUpdate(unsigned char IsConnectedFlag);

蓝牙连接状态更新回调函数;

在使用接口函数时需要注意事项:

1、所有接口函数不得阻塞调用。

2、函数att_server_rd(。。。)每次调用发送的数据长度不得超过20 字节。

3、函数sconn_notifydata(...)只能在协议主循环体内调用,函数不可重入,可以发送多于20 字节的数据,协议会自动分包发送,且每个分包长度最大为20 字节。推荐一次发送的数据尽量不超过3 个分包。

4、在参考例程提供了支持配对/加密的AES加密方式:unsigned char aes_encrypt_HW(unsigned char *painText128bitBE,unsigned char *key128bitBE); //是否支持硬件AES。如果不支持请返回0,蓝牙库支持软件AES。

5、UUID 支持16bit 和128bit 两种。

来源:

围观 5