SYD8801是一款低功耗高性能蓝牙低功耗SOC,集成了高性能2.4GHz射频收发机、32位ARM Cortex-M0处理器、128kB Flash存储器、以及丰富的数字接口。SYD8801片上集成了Balun无需阻抗匹配网络、高效率DCDC降压转换器,适合用于可穿戴、物联网设备等。具体可咨询:http://www.sydtek.com/
复位等系统行为要等待硬件响应
软件调用复位命令想让mcu复位的时候,软件虽然向硬件发送了复位的请求,但是硬件并没有马上能够复位,会顺延执行下面几条命令后硬件才能够反应过来。为了调用复位命令之后错误的执行到了相应的代码,所以这里在复位命令之后建议调用软件延时函数。正确的调用复位的软件操作如下:
注意:不单单是复位行为要等待硬件响应,进入深度睡眠的命令也要等待硬件响应,相关资讯请看如下博客:
SYD8801低功耗【深度睡眠模式】【浅度睡眠模式】【进入睡眠模式后要等待硬件进入睡眠】:http://blog.csdn.net/chengdong1314/article/details/70233652
协议栈上报事件处理
协议栈如果监听到底层蓝牙的状态发生了变化,将通过回调ble_evt_callback函数通知应用层,传回的变量为gap_ble_evt结构体,其结构如下:
struct gap_ble_evt {
uint8_t evt_type;
uint16_t evt_code;
union
{
struct gap_disconnected_evt disconn_evt;
struct gap_ble_addr bond_dev_evt;
struct gap_key_params enc_key_evt;
struct gap_att_read_evt att_read_evt;
struct gap_att_write_evt att_write_evt;
struct gap_att_handle_configure_evt att_handle_config_evt;
struct gap_connection_update_rsp_evt connection_update_rsp_evt;
} evt;
};
其中 evt_type代表的是上报事件的类型,这里有可能是GAP层上报的事件,也有可能是LL层上报的事件,应用程序不需要区分该值!
evt_type代表的是上报事件的代码,也就是蓝牙底层监听到的具体状态,比如蓝牙连接和断线等,蓝牙能够上报的状态在如下枚举中列出:
enum _GAP_EVT_{
GAP_EVT_ADV_END = 0x0001, //蓝牙发送一次广播数据包宝成
GAP_EVT_CONNECTED = 0x0002, //蓝牙连接完成
GAP_EVT_DISCONNECTED = 0x0004, //蓝牙断开连接
GAP_EVT_ENC_KEY = 0x0008, //蓝牙交互秘钥结束事件,只有在第一次加密的时候会上报该事件
GAP_EVT_PASSKEY_REQ = 0x0010, //在加密过程中,如果使用了密码加密,蓝牙上报该事件显示正在获取默认的蓝牙密码
GAP_EVT_SHOW_PASSKEY_REQ = 0x0020, //在加密过程中,如果使用了密码加密,蓝牙上报该事件请求输入蓝牙密码
GAP_EVT_CONNECTION_INTERVAL = 0x0040, //在蓝牙底层连接间隔到来时蓝牙上报该事件,底层唤醒射频监听空中蓝牙信号
GAP_EVT_CONNECTION_SLEEP = 0x0080, //在蓝牙底层连接间隔结束时蓝牙上报该事件,底层关闭射频进入低功耗
GAP_EVT_ATT_READ = 0x0100, //主机对SYD8801进行读操作(包括读、按类型读等各种读操作)蓝牙上报该事件
GAP_EVT_ATT_WRITE = 0x0200, //主机对SYD8801进行写操作蓝牙上报该事件
GAP_EVT_ATT_PREPARE_WRITE = 0x0400, //主机发送Prepare Write Request请求时蓝牙蓝牙上报该事件, 属于蓝牙中Queued Writes操作的一部分
GAP_EVT_ATT_EXECUTE_WRITE = 0x0800, //主机发送Execute Write Request请求时蓝牙蓝牙上报该事件, 标志着蓝牙中Queued Writes操作的结束
GAP_EVT_ATT_HANDLE_CONFIRMATION = 0x1000, //蓝牙在收到指示确认时上报该事件,标志着指示操作完成
GAP_EVT_ATT_HANDLE_CONFIGURE = 0x2000, //蓝牙在收到配置CCCD也就是主机使能notify或者指示功能的时候上报该事件,代表主机要开启相应功能
GAP_EVT_ENC_START = 0x4000, //蓝牙在加密开始的时候上报该事件,代表蓝牙配对工作已完成,进入加密流程,重连的时候只会上报该事件
GAP_EVT_CONNECTION_UPDATE_RSP =0x8000, //蓝牙在接收到主机发送光来的更新链接参数更新请求的时候进入该事件
};
注意:只有在第一次加密的时候会上报GAP_EVT_ENC_KEY事件,如果加密后进行重连,只是会上报GAP_EVT_ENC_START,这属于蓝牙规范的内容
evt 联合体包含了与evt_type对应事件的具体信息,比如蓝牙写操作时主机写过来的具体内容以及UUID等,如下:
struct gap_att_write_evt {
uint16_t primary;
uint16_t uuid;
uint16_t hdl;
uint8_t sz;
uint8_t data[MAX_ATT_DATA_SZ];
};
ble_evt_callback函数只要鉴别不同的命令代码 evt_code进行相应的处理即可,如:
时钟源准确度的优化
SYD8801原来提供的官方例程时钟选择的相关工作是放在ble_init函数中的,但是每次开启广播的时候都会去调用ble_init函数,而SYD8801的广播机制又是由定时器来控制的,造成ble_init函数被频繁的调用,也就是说会频繁的切换时钟,造成设置不准以及和时钟相关的部件工作不正常(比如PWM),解决办法就是把时钟选择的相关工作拿到主函数初始化来,这样在开始广播的时候就不会进行时钟的选择了。
开启广播的流程如下左图,原来的ble_init函数部分如下右图:
SYD8801广播机制相关说明请看:http://blog.csdn.net/chengdong1314/article/details/60871037
提高时钟精确度的办法是把ble_init函数中和选择时钟相关的语句拿到main函数下面,修改后main函数如下左图,ble_init函数如下右图:
注意:上面左图中的“delay_ms(500);”语句在内部晶振首次校准的时候必须使用等待硬件复位完成
注意:因为4K_setting文件中对晶振有着默认的配置,所以这里要去掉4K_setting文件对晶振的默认配置。所以按照上面的方法修改了时钟的选择后,内部晶振和外部晶振的程序所烧录的4K_setting文件就不一样了,在下面提供的工程资料会提供这两个4K_setting文件,如下所示:
关于4K文件对于晶振的配置请看: http://blog.csdn.net/chengdong1314/article/details/77019222
下面提供本节博客提到的源代码:http://download.csdn.net/download/chengdong1314/10103621
修改蓝牙名称
SYD8801修改蓝牙名称只要修改两个部分,一个部分是蓝牙广播中的蓝牙名称,另外一个部分是蓝牙ble_gatt_read函数的DEVICE_NAME_UUID分支的名称,广播数据中的名称如下:
注意:上面的‘S’上面的"0x09"是固定的,但是"0x09"上面的“0x08”却是根据蓝牙名称的长度来变化的,"0x08"(length)代表的是从"0x09"开始到蓝牙名称结束的长度,比如如果蓝牙名称是“UART1”那么这里的length就是0x06,而不是0x08!
ble_gatt_read函数的DEVICE_NAME_UUID分支的名称如下:
下面修改蓝牙名称为“UART1”,广播数据中的名称修改如下:
ble_gatt_read函数的DEVICE_NAME_UUID分支的名称修改如下:
下面是测试实物图,左图是蓝牙名称为“SYD8801”,右图是蓝牙名称为“UART1”:
注意:如果是使用IOS的lightblue软件来测试的话,因为该软件有记录存储,所有当把名字改成"UART1"的时候软件上显示的依旧是原来的"SYD8801",这时候要先连接"SYD8801"然后lightblue会更新蓝牙名称为"UART1",然后断开连接再次搜索看到的广播名字就是"UART1"了。
设置中断优先级
SYD8801使用的内核是ARM M0,中断优先级机制使用ARM M0的机制(ARM M0的优先级说明请看:http://blog.csdn.net/chengdong1314/article/details/75095512),下面列举出一个设置中断优先级的例子,其中优先级高的中断能够打断优先级低的中断,实现一个简单的中断嵌套:
void nvic_priority(void){
NVIC_SetPriority(LLC_IRQn , 1);//蓝牙协议栈中断优先级为1
NVIC_SetPriority(I2C0_IRQn , 2);//配置中断优先级 普通中断优先级为2
NVIC_SetPriority(I2C1_IRQn , 2);
NVIC_SetPriority(UART0_IRQn , 2);
NVIC_SetPriority(UART1_IRQn , 2);
NVIC_SetPriority(TIMER0_IRQn, 0);//定时器优先级为0 最高
NVIC_SetPriority(TIMER1_IRQn, 2);
NVIC_SetPriority(TIMER2_IRQn, 2);
NVIC_SetPriority(TIMER3_IRQn, 2);
NVIC_SetPriority(GPIO_IRQn , 2);
}
其中NVIC_SetPriority函数是ARM 官方提供的函数,源代码如下:
/**
\brief Set Interrupt Priority
\details Sets the priority of an interrupt.
\note The priority cannot be set for every core interrupt.
\param [in] IRQn Interrupt number.
\param [in] priority Priority to set.
*/
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if ((int32_t)(IRQn) < 0)
{
SCB->SHP[_SHP_IDX(IRQn)] = ((uint32_t)(SCB->SHP[_SHP_IDX(IRQn)] & ~(0xFFUL << _BIT_SHIFT(IRQn))) |
(((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL) << _BIT_SHIFT(IRQn)));
}
else
{
NVIC->IP[_IP_IDX(IRQn)] = ((uint32_t)(NVIC->IP[_IP_IDX(IRQn)] & ~(0xFFUL << _BIT_SHIFT(IRQn))) |
(((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL) << _BIT_SHIFT(IRQn)));
}
}
本节博客的源码请看:http://download.csdn.net/download/chengdong1314/10147350