SYD8801是一款低功耗高性能蓝牙低功耗SOC,集成了高性能2.4GHz射频收发机、32位ARM Cortex-M0处理器、128kB Flash存储器、以及丰富的数字接口。SYD8801片上集成了Balun无需阻抗匹配网络、高效率DCDC降压转换器,适合用于可穿戴、物联网设备等。具体可咨询:http://www.syd-tek.com/
BLE_SendData函数gap_att_report结构体设置
BLE_SendData函数具体代码如下:
void BLE_SendData(uint8_t *buf, uint8_t len)
{
if(start_tx == 1)
{
struct gap_att_report report;
report.primary = BLE_SERVICE_UUID_UART;
report.uuid = BLE_SERVICE_UUID_UART_NOTIFICATION;
report.hdl = 0x0001F;
report.value = BLE_GATT_NOTIFICATION;
GATTDataSend(BLE_GATT_NOTIFICATION, &report, len, buf) ;
}
}
其中最关键的设置就是gap_att_report 结构体,该结构体定义如下(请看:http://blog.csdn.net/chengdong1314/article/details/60870526):
struct gap_att_report {
uint16_t primary; //主要服务的uuid
uint16_t uuid; //上报信息的特性的uuid
uint16_t hdl; //上报信息的特性的handle
uint16_t config;
uint16_t value;
};
蓝牙广播的实现
在ble_init函数中有调用:setup_adv_data();设置广播,其具体实现如下:
static void setup_adv_data()
{
struct gap_adv_params adv_params;
adv_params.type = ADV_IND;
adv_params.channel = 0x07; // advertising channel : 37 & 38 & 39
adv_params.interval = 0x6b; // advertising interval : 66.8ms (107 * 0.625ms)
adv_params.timeout = 0x1e; // timeout : 30s
SetAdvParams(&adv_params);
ADV_DATA[0x02] = 0x05; // LE Limited Discoverable Mode & BR/EDR Not Supported
SetAdvData(ADV_DATA, ADV_DATA_SZ, SCAN_DATA, SCAN_DATA_SZ);
}
注意:这里设置了广播结构体:gap_adv_params,该结构体定义了广播的间隔与超时时间‘’但是在SYD8801中这些设置是没有使用的,主要原因是在广播发送数据包出去的时候底层硬件没有关掉RF,所以要应用层来关掉RF,但是连接的时候底层硬件已经有做了在发送数据包之后立即关掉RF的功能,所以在连接的时候不需要应用层去关掉R,所以SYD8801的广播机制如下:
1.在程序初始化的时候开始广播,并且设置一个0.5s的定时器:
ble_init();
timer_0_enable(19200, timer0_adv_callback);
StartAdv();
2.当rf把广播包发送出去的时候协议栈回调GAP_EVT_ADV_END事件,这时候MCU设置停止RF模块:
void ble_evt_callback(struct gap_ble_evt *p_evt)
{
if(p_evt->evt_code == GAP_EVT_ADV_END)
{
RFSleep();
}
RFSleep();函数使射频模块进入休眠状态!
3.当0.5s定时器中断发生的时候唤醒RF再次初始化协议栈并且开始广播:
static void timer0_adv_callback()
{
RFWakeup();
ble_init();
StartAdv();
}
其中RFWakeup();函数将唤醒射频模块!
这样一来只要设置定时器的周期就能够设置广播的间隔!当然因为定时器回调函数是用户自己写的代码,什么时候广播就由用户决定!
协议栈回调事件
enum _GAP_EVT_{
GAP_EVT_ADV_END = 0x0001,
GAP_EVT_CONNECTED = 0x0002,
GAP_EVT_DISCONNECTED = 0x0004,
GAP_EVT_ENC_KEY = 0x0008, // 如果device需要配對,配對完後會收到此事件,協議棧會將配對相關key傳給上層知道 (可不處理)
GAP_EVT_PASSKEY_REQ = 0x0010,
GAP_EVT_SHOW_PASSKEY_REQ = 0x0020,
GAP_EVT_CONNECTION_INTERVAL = 0x0040, //每次 connection interval 前的事件 (可不處理)
GAP_EVT_CONNECTION_SLEEP = 0x0080, //每次 connection interval 後的事件,代表RF可進入睡眠模式 (由於RF是Auto-Sleep,所以可不處理)
GAP_EVT_ATT_READ = 0x0100,
GAP_EVT_ATT_WRITE = 0x0200,
GAP_EVT_ATT_PREPARE_WRITE = 0x0400,
GAP_EVT_ATT_EXECUTE_WRITE = 0x0800,
GAP_EVT_ATT_HANDLE_CONFIRMATION = 0x1000,
GAP_EVT_ATT_HANDLE_CONFIGURE = 0x2000,
GAP_EVT_ENC_START = 0x4000,
GAP_EVT_CONNECTION_UPDATE_RSP =0x8000,
};
自动睡眠
在SYD8801的串口打印中有如下代码:
void dbg_printf(char *format,...)
{
uint8_t iWriteNum = 0;
va_list ap;
if(!format)
return;
*((uint8_t*)(0x50001000 + 0x24)) = 0x00;
va_start(ap,format);
iWriteNum = vsprintf((char *)s_formatBuffer,format,ap);
va_end(ap);
if(iWriteNum > MAX_FORMAT_BUFFER_SIZE)
iWriteNum = MAX_FORMAT_BUFFER_SIZE;
_uart_0_write(s_formatBuffer, iWriteNum);
*((uint8_t*)(0x50001000 + 0x24)) = 0x01;
}
其中*((uint8_t*)(0x50001000 + 0x24))寄存器置1的时候打开自动睡眠,清零的时候关闭自动睡眠,在串口发送数据之前关掉自动睡眠,发送数据之后打开自动睡眠,如果在自动睡眠的时候调用串口模块这时候串口打印将会出现乱码!
使用内部晶振
SYD8801的方案中,去掉外部的32KHz晶振转而使用芯片内部的32KHz的RC晶振可以减少产品成本,下面就是使用内部32KHz晶振的方法:
首先在《config.h》文件中打开“USER_32K_CLOCK_RCOSC”这个宏,如下图:
然后在“ble_init”函数的时钟选择中选择32KHz内部RC晶振:
注意这里必须调用“LPOCalibration”函数来进行内部32KHz RC晶振的校准,否则定时器将不能正常工作,蓝牙连接也会存在问题!
接下来在主函数中每3分钟(最多3分钟)必须进行内部32KHz RC晶振的校准,如下:
这样内部32KHz RC晶振就能够正常使用了!
这里上传本小节的使用代码:http://download.csdn.net/detail/chengdong1314/9906419
蓝牙状态机的说明
所谓的蓝牙状态机就是在蓝牙的状态发生变化的时候(比如由广播态变成了连接态)设置相应的标志位并且保存下这时候蓝牙的数据(比如接收到的数据),在主函数的主循环体中把这些状态通过串口或者OLED显示出来,这就是状态机制!
状态机的意义:
通过串口打印状态是一件十分耗时的事情,如果把十分耗时的事情放在蓝牙回调事件的处理函数中很有可能阻塞了蓝牙协议栈的运行,造成蓝牙出现了异常!所以这里要通过状态机制把该在蓝牙协议栈回调事件中的串口打印工作转移到主函数中来运行!还有一种更糟糕的情况串口会造成死机,请看:http://write.blog.csdn.net/postlist?ticket=ST-238386-cKTLPedZte23nc2Fg6Zw-passport.csdn.net博客中串口会造成死机的章节
状态机的使能:
打开《config.h》文件中的“USER_MARCHE_STATE”宏将使能状态机,如下:
状态机状态的定义:
这里状态机其实就是一个结构体,该结构体具体如下:
typedef struct{
uint8_t state;
union
{
uint8_t datau8;
uint16_t uuid;
uint16_t hdl;
gap_att_array att_evt;
}data;
} MARCHE_STATE;
其中的“state”代表着当前程序所处的状态,状态的具体定义如下:
注意该字节的最高位代表着蓝牙的状态是否有变化!
状态机数据的定义:
蓝牙状态机中的数据在每一个状态下都有可能不同,如下写状态下数据就代表着对等设备对该蓝牙写的内容,但是断线状态下,数据却是代表着断线的原因,当然有一些状态是没有数据的,所以状态机的数据其实是一个联合体:
状态机的声明(设置状态标志位和数据的方法):
在蓝牙的状态发生变化的时候只要正确的设置状态机结构体“march_state”中的状态标志位“state”即可,比如在蓝牙连接上的时候这样设置标志位:
在对等设备对该蓝牙进行写的时候这样设置标志位和数据:
蓝牙状态机的显示和打印:
这里在主函数的主循环中判断状态机标志位的最高位是否为1就知道了状态机的状态是否改变了,再根据标志位的剩下七位来判断当前蓝牙的状态,这里在主循环中调用“march_state_process”函数来处理状态机,如下:
该函数的源码如下:
#ifdef USER_MARCHE_STATE
void march_state_process(void){
if(march_state.state & 0x80){
march_state.state &=0x7f;
led_turn(LED1); // ·×ªLED1 ָʾÀ¶ÑÀ״̬·¢ÉúÁ˱仯
switch(march_state.state){
case 1:
#ifdef _DEBUG_
DBGPRINTF(("GAP_EVT_CONNECTED"));
#endif
oled_8x16str(0,2,"CONNECTED ");
break;
case 2:
#ifdef _DEBUG_
DBGPRINTF(("GAP_EVT_DISCONNECTED(%02x)\r\n",march_state.data.datau8));
#endif
oled_8x16str(0,2,"DISCONNECTED ");
break;
// case 3:
// #ifdef _DEBUG_
// DBGPRINTF(("GAP_EVT_ATT_HANDLE_CONFIGURE uuid:(%02x)\r\n",march_state.data.uuid));
// #endif
// break;
// case 4:
// #ifdef _DEBUG_
// DBGPRINTF(("GAP_EVT_ATT_WRITE uuid:(%02x)\r\n",march_state.data.uuid));
// #endif
// break;
// case 5:
// #ifdef _DEBUG_
// DBGPRINTF(("GAP_EVT_ATT_READ uuid:(%02x)\r\n",march_state.data.uuid));
// #endif
// break;
// case 6:
// #ifdef _DEBUG_
// DBGPRINTF(("GAP_EVT_ATT_HANDLE_CONFIRMATION uuid:(%02x)\r\n",march_state.data.uuid));
// #endif
// break;
case 7:
#ifdef _DEBUG_
DBGPRINTF(("ancs_find_ervice\r\n"));
#endif
oled_8x16str(0,2,"Security end ");
break;
// case 8:
// #ifdef _DEBUG_
// DBGPRINTF(("GAP_EVT_ENC_START\r\n"));
// #endif
// break;
case 9:
#ifdef _DEBUG_
DBGPRINTF(("update rsp:%04x\r\n",march_state.data.datau8));
#endif
break;
case 10:
#ifdef _DEBUG_
dbg_printf("ancs end\r\n");
#endif
oled_8x16str(0,2,"ancs end ");
break;
case 11:
#ifdef _DEBUG_
dbg_printf("not ancs\r\n");
#endif
oled_8x16str(0,2,"not ancs ");
break;
case 12:
#ifdef _DEBUG_
dbg_printf("not band\r\n");
#endif
oled_8x16str(0,2,"not band ");
break;
case 13:
#ifdef _DEBUG_
dbg_printf("Security start\r\n");
#endif
oled_8x16str(0,2,"Security start ");
break;
case 14:
#ifdef _DEBUG_
dbg_hexdump("msg:\r\n",march_state.data.att_evt.data,march_state.data.att_evt.sz);
// dbg_printf("c:%x l:%x\r\n",march_state.data.att_evt.data[0],march_state.data.att_evt.data[1]);
#endif
oled_8x16str(0,2,"Writing ");
oled_hexdump(0,4,"msg:",march_state.data.att_evt.data,march_state.data.att_evt.sz);
break;
default:
oled_8x16str(0,2,"advertising ");
break;
}
}
}
#endif
这里就根据状态机中的状态标志位来进行不一样的显示!
这里上传本小节的使用代码:http://download.csdn.net/detail/chengdong1314/9865161
断线时重新配置IO口
SYD8801的程序在蓝牙断线的时候协议栈底层硬件会把所有的GPIO的配置设置成默认的配置(gpio输出配置),但是这个配置可能不是程序所需要的,所以这里在断线的时候代码必须调用gpio_init函数再次进行所有gpio口的配置。如果代码中还有其他的地方重新配置了GPIO口,那么在断线的时候必须要重新进行配置,比如gsensor模块中会把I2C模块相关的gpio口配置成I2C模式,那么在断线的时候也必须要把该IO口配置成GPIO模式,如下示意代码: