这段时间又参与了一个新的小项目,简单概括为
蓝牙、智能、家居
吧,虽然时间有点紧,还是希望能把这一些东西记录下来。
####BLE 什么是BLE?参考这篇
文章做如下总结。
中文名称为蓝牙低功耗。主要特点为低成本、超低功耗、短距离、标准接口和可互操作性强,并且工作在免许可的2.4GHz ISM射频段,需要支持蓝牙4.0(系统为Android4.3及以上)的主机设备才能与其连接。
目前生产BLE芯片的厂家主要有CSR、TI、Nodic和NXP(QN902x),各个厂家芯片对比如下图
从如上图对比可以看出,NXP的QN902x在功耗方面比CSR和TI更省电,在接收灵敏度和模式方面比Nodic的胜一筹,它的从设备相比其它几家可以连接的更多,共有8个,这也算是蓝牙4.0的一大特 {MOD}吧,并且NXP的芯片已经过了MFI认证,直接能与苹果设备相连接,因为这种认证也是挺贵的。
因为BLE的低功耗、低成本及强大的处理能力,并且随着iPhone的设备支持蓝牙4.0,BLE的终端设备在我们的生活当中将会越来越多,在未来将会有爆发式增长。
QN902X是一款内核为M0的蓝牙BLE SOC芯片,其SDK对蓝牙BLE的profile都有实现,并提供源码,SDK也提供很多工具以便使用芯片,比如引脚配置,NVDS读写,串口USB Dongle(配合上位机可以调试各profile,没有手机也可以调试),ISP下载等,但QN902X不提供数据手册,所有的外设操作都以库的方式提供,SDK说明比较全面但全是英文的。
与NODRDIC的51822和TI的CC2540不同QN902X的架构是M0+ROM+FLASH+SRAM的方式,其中ROM放的是蓝牙协议和内部一个小的调度核,FLASH放的是用户程序和数据,RAM用于跑程序。其中ROM:96K,SRAM:64K,FLASH:64K/128K。因为QN902X程序是跑到SRAM中,所以它的深度睡眠电流比较大些。
####环境搭建 老生长谈,开发一款产品,第一步当然是搭建开发环境咯。
#####wine安装 由
这里可知,keil不支持linux环境,所以必须自己想办法了。这是在linux环境下运行windows的程序,本身使用windows的请自动忽略这一步。具体请看
官网/
博文。
~ $ sudo add-apt-repository ppa:ubuntu-wine/ppa
~ $ sudo apt-get update
# => 我的是64为的系统,所有安装64位版
~ $ sudo apt-get install wine1.8-amd64
# => 配置
~ $ winecfg
# => 中文路径:~/ .wine/drive_c/windows/Fonts/
~ $ winetricks corefonts
#####keil安装 首先得下载keil,可以自己去
官网下载最新的,也可以直接点击
mdk5.17下载地址,参考这篇
博文并实践做如下记录。
# => 进入下载目录安装
~ $ wine mdk517.exe
提示
:卸载程序可以用wine
uninstaller
如编译有限制,下载破解注册机Keygen:http://pan.baidu.com/s/1hqGSRqs
安装完成后,会弹出来一个安装器件(pack installer)如下的界面,也就是说,你要用它来开发哪个芯片(此项目可以忽略,后面步骤导入DB)。
或者打开keil界面会看到如下图标
要开发哪一款芯片,点击install即可,或者先网页端下载好在导入,具体参考上面提供的博文地址。
#####MCU DB库安装 下载Quintic最新的SDK
QBlue1.3.7,安装:
~ $ wine QBlue-1.3.7.exe
会弹出窗口如下,点击安装即可,或者打开桌面QBlue里的QN9020DevDBforIDE工具安装。
#####keil使用 首先获取开
源代码
~ $ cd
~ $ git clone git@bitbucket.org:T-Firefly/fireble.git
桌面打开keil,假如我们希望开始proxr工程:
- 在keil的Project菜单中选择Open Project…
- 弹出文件选择框中,打开/home/xxx/fireble/BLE/prj_proxr/keil/proxr.uvproj工程文件(linux可能在z磁盘里)
-
配置DB如下,如果没发现库,请重启软件
-
编译代码,成功后如下,具体配置选项请参考这里
-
下载程序,下载的时候需要按复位键,如下载出错,可先下载到SRAM。
Tip
:在ubuntu
上串口识别为ttyS0或ttyUSB0之类,在wine上识别不到,可用:
# =>将其该为小写的com1,如果不行,将其改为大写的COM1
~ $ sudo ln -s /dev/ttyUSB0 ~/.wine/dosdevices/com1
~ $ sudo usermod -a -G dialout $USER
~ $ sudo chmod 777 ~/.wine/dosdevices/com1
即可在wine的应用程序使用串口
####项目实践 #####点亮LED 对于新的芯片与开发板,从LED实验开始。首先下载该开发板的
原理图,对该LED部分的电路进行分析。
本程序非常简单,复制gpio demo代码
实现让开发板D1灯闪烁如下:
/* Set pin D1/P2_7 */
gpio_set_direction_field(GPIO_P27, (uint32_t)GPIO_OUTPUT);
while(1)
{
gpio_write_pin_field(GPIO_P27, GPIO_LOW);
delay(100000);
gpio_write_pin_field(GPIO_P27, GPIO_HIGH);
delay(100000);
}
程序流程:系统初始化–>GPIO配置–>各驱动模块初始化–>主循环实现功能
效果如下:
#####UART实验 串口通信可以用来打印数据,调试程序,有必要实验一下。
同样复制uart demo代码
改程序如下:
//Print out "Hello NXP!
" thought uart.
uart_printf(QN_UART0, (uint8_t *)"Hello NXP!
");
uart_printf(QN_UART0, (uint8_t *)"This is nephen's test!
");
波特率设置为115200,现象如下:
#####PWM实验 由于这个项目会控制到电机什么的,所以pwm少不了,这个实验是通过pwm控制陶瓷蜂鸣器报警和呼吸灯。通过PWM方式调节脉冲频率和占空比,变换LED亮度,渐变亮度实现呼吸灯效果。FireBLE板载的贴片蜂鸣器是压电式陶瓷蜂鸣器,压电陶瓷蜂鸣器要想响起来,需要满足四个条件:多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱,压电蜂鸣片由锆钛酸铅或铌镁酸铅压电陶瓷材料制成,在陶瓷片的两面镀上银电极,经极化和老化处理后,再与黄铜片或不锈钢片粘在一起。板载的蜂鸣器缺少的只是多谢振荡器,这里我们用PWM来代替,输出1.5KHz-2.5KHz的方波信号推动压电蜂鸣片发声,频率在1.5KHz-2.5KHz才会响,太高太低都不响,直接加3.3V更不会响,直接加3.3V响的那是电磁式蜂鸣器,有源和无源。
首先看下电路连接
同样复制pwm代码,改写如下:
int main (void)
{
SystemInit();
pwm_init(PWM_CH0);
pwm_io_config();
//P2.7 will output pwm wave with period for 1000us and pulse for 400us
pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(1000, PWM_PSCAL_DIV), PWM_COUNT_US(500, PWM_PSCAL_DIV));
pwm_enable(PWM_CH0, MASK_ENABLE);
pwm_io_dis_config();
pwm_init(PWM_CH1);
pwm_io_config();
//P2.6 will output pwm wave with period for 1000us and pulse for 500us
pwm_config(PWM_CH1, 119, PWM_COUNT_US(1000, 119), PWM_COUNT_US(500, 119));
pwm_enable(PWM_CH1, MASK_ENABLE);
while (1) /* Loop forever */
{
int i;
for(i=0;i<=1000;i++)
{
pwm_config(PWM_CH1, 119, PWM_COUNT_US(1000-i, 119), PWM_COUNT_US(500, 119));
if(i%2)
pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(1000-i, PWM_PSCAL_DIV), PWM_COUNT_US(500, PWM_PSCAL_DIV));
else
pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(i, PWM_PSCAL_DIV), PWM_COUNT_US(500, PWM_PSCAL_DIV));
delay(2000);
}
}
}
呼吸灯效果:
#####按键广播 看到有些人对买的fireBLE按键广播不懂,其实刚开始我也是这样的,摸不着头脑,现在至少不会太迷糊,就给大家记录下吧。
首先按键按下属于gpio中断,找到中断初始化函数:SystemInit(); ——》
gpio_init(gpio_interrupt_callback); ——》
void gpio_interrupt_callback(enum gpio_pin pin) ——》
void usr_button1_cb(void) ——》
ke_evt_set(1UL « EVENT_BUTTON1_PRESS_ID); 找到EVENT_BUTTON1_PRESS_ID对应的事件,搜索 EVENT_BUTTON1_PRESS_ID,找到void usr_init(void)里的if(KE_EVENT_OK != ke_evt_callback_set(EVENT_BUTTON1_PRESS_ID, app_event_button1_press_handler))可知为app_event_button1_press_handler ——》
ke_timer_set(APP_KEY_SCAN_TIMER,TASK_APP,2); 同样通过搜索APP_KEY_SCAN_TIMER可知定时器事件为app_key_scan_timer_handler ——》
app_key_scan_timer_handler,这里进行adc采集,完成后由adc_read(&read_cfg, adc_key_value, KEY_SAMPLE_NUMBER, adc_test_cb);可知进入adc_test_cb,在这里设置了EVENT_ADC_KEY_SAMPLE_CMP_ID,搜索找到对应事件app_event_adc_key_sample_cmp_handler ——》
app_event_adc_key_sample_cmp_handler,判断按键朝哪个方向按,定时回调 ——》
app_key_process_timer_handler,如下:
int app_key_process_timer_handler(ke_msg_id_t const msgid, void const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
ke_evt_clear(1UL << EVENT_ADC_KEY_SAMPLE_CMP_ID);
switch (key_value0)
{
case key_up:
if (APP_IDLE == ke_state_get(TASK_APP))
{
// start adv
//QPRINTF("you press up key
!");
app_gap_adv_start_req(GAP_GEN_DISCOVERABLE | GAP_UND_CONNECTABLE,
app_env.adv_data, app_set_adv_data(GAP_GEN_DISCOVERABLE),
app_env.scanrsp_data, app_set_scan_rsp_data(app_get_local_service_flag()),
GAP_ADV_FAST_INTV1, GAP_ADV_FAST_INTV2);
这里一看就明白,向上按时,进入广播。其实在系统上电的时候,已经进行了一系列的gap建立连接的初始话,就差广播了,所以这里只要广播出去就能和别的蓝牙建立连接。
#####QTool使用及蓝牙了解 使用QBlueStudio中QTool工具进行蓝牙开发分析,可以方便的对各个蓝牙操作的过程进行细致的研究,并结合具体的源代码进行查看,能够更加深入的了解到蓝牙协议的实现过程,大部分的API接口在GAP和GATT。具体的使用文档请查看QBlueStudio里的Document。Linux里可以采取这种方法查看:地址见
/home/username/.wine/drive_c/QBlue/QN9020/QBlue-1.3.7/Documents
,然后使用evince命令打开。
在这之前,还需对蓝牙的一些术语做一个大概的了解,比如什么是master,slave,主机。客户端与服务器又是什么。GAP与GATT有什么区边。蓝牙各个协议层都有哪些分工。下面
参考做一些归纳:
-
BLE规范中定义了GAP(Generic Access Profile)和GATT(Generic Attribute)两个基本配置文件。
a.协议中的GAP层负责设备访问模式和进程,包括设备发现,建立连接,终止连接。初始化安全特性和设备配置。
b.GATT层用于已连接的蓝牙设备之间的数据通信。GATT通俗理解为用于主从机之间的客户端和服务器端的数据交互,以Attribute Table来体现。
- BLE低功耗蓝牙中有四种设备类型,Central主机,Peripheral从机,Observer观察者,Broadcaster广播者。通常Central主机,Peripheral从机一起使用,Observer观察者,Broadcaster广播者一起使用。Central和Peripheral连接交换数据,平时我们使用到的基本上是这种模式。而像多温度采集器,通常使用Observer和Broadcaster这种无连接形式。
主机和从机是这样开机工作的:从机开始广播,然后主机扫描广播的从机,当从机收到主机的扫描请求后,会向主机发送扫描回应数据。然后主机发起连接,然后开始通讯。所以从机需要设置广播内容和扫描回应内容。这方面的代码可以查看App_gap.c和App_gap_task.c。
- profile:可以理解为一种规范,一个标准的通信协议,profile存在于从机中。蓝牙组织规定了一系列的标准profile,如防丢计、心率计。每个profile中包含多个service,每个service代表从机的一种能力。
- Service:可以理解为一种服务,在BLE从机里,通过有多个服务,例如电量信息服务、系统信息服务等。每个service又包含多个characteristic特征值。每个具体的characteristic特征值才是BLE通讯的主体,比如当前电量是80%。所以会通过电量的characteristic特征值特征值保存在从机的profile里,这样主机就可以通过这个characteristic来读取80%这个数。
- characteristic:characteristic特征值,BLE主从机均是通过characteristic来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
- UUID:统一标识吗,我们刚提到的characteristic和service,都需要一个唯一的UUID来标识。
每个从机都会有个叫做profile的东西存在,不管自定义的还是标准的profile,他们都是由一系列的Service组成,然后每个service又包含多个characteristic,主机和从机之间的通信,均是通过characteristic来实现。
BLE协议栈中传输数据分为两方面,一个GATT的client主动向service发送数据。另一个是GATT的service主动向client发送数据。即主从之前相互传数据。
主机向从机发送数据,使用GATT_Write
从机向主机发送数据,使用GATT_Notification
- GATT有Service和Client,Service作为服务器端,对GATT Client提供read/write接口,一般情况下,Central作为Client,Peripheral作为Service。所以主机会调用read/write来和作为Service端的Peripheral从机通讯。而Peripheral则通过notify的方式即调用GATT_Notifycation发起和主机通讯。
- 特征值声明值可以有五种属性:Read(可读) Write(可靠的可写,带响应) Write without resp(不可靠的可写,不带响应) Indicate(可靠通知,带响应) Notify(不可靠通知,不带响应)
更多请查看
蓝牙设计问与答 /
Android
蓝牙4.0 BLE 理解
BLE中主从机建立连接,到配对和绑定的过程如下图。
#####QPPS工程 在此之前,建议先了解一下
Firefly的QPPS介绍/
对QPPS
profile中服务和特征实现的分析理解/
对profile QPPS的分析理解。关于蓝牙的数据收发部分可以看
Firefly的协议栈介绍。数据帧格式如下:
Tip
:技术案例中串口透传案例,实现的是将蓝牙模组串口所接收到的数据透传到app,并且把app的数据通过蓝牙透传到串口输出,案例本身的实现是针对串口通信和蓝牙透传的结合。
- 其实BLE完成初始化的条件并不是进入main函数的while(1)中(系统在调度了几个消息之后才完成的初始化,前几次进入while(1)时初始化都是没完成的),真正完成初始化并且可以运行是在打印BLE is Ready这句话的地方。那个地方你可以看到只要开启QN_DEMO_AUTO宏定义就可以上电广播了。
- 设备名默认从NVDS中写入,但是软件也可以修改,可以用app_gap_set_devname_req修改设备名。
-
如果你非要用软件指定,参考广播内容设定,取消NVDS读取,直接利用app_gap_set_devname_req函数指定设备名,然后将QN_LOACL_NAME广播出去,那么设备名和广播中的设备名都会是QN_LOCAL_NAME了。
具体实施如下
修改自动广播,无需向上拨动按键:
c
#=> App_config.h中,取消如下的注释即可 #define QN_DEMO_AUTO 1
在广播之前改变设备的名字,注意app_set_adv_data
函数
c
//Set remote device name app_gap_set_devname_req("nephen",6); // Created DB should has been finished by each profile service, // Start Adv mode automatically here app_gap_adv_start_req(GAP_GEN_DISCOVERABLE|GAP_UND_CONNECTABLE, app_env.adv_data, app_set_adv_data(GAP_GEN_DISCOVERABLE),
app_env.scanrsp_data, app_set_scan_rsp_data(app_get_local_service_flag()), GAP_ADV_FAST_INTV1, GAP_ADV_FAST_INTV2);
-
接收手机app中9600可写属性发送的数据。
由下面Qpps_task.c可知,当有数据到达蓝牙时,会触发gatt_write_cmd_ind_handler
,同理,发送数据触发qpps_data_send_req_handler
,得到通知触发gatt_notify_cmp_evt_handler
,app层对应的api是app_qpps_data_send_cfm_handler
、app_qpps_data_ind_handler
、app_qpps_cfg_indntf_ind_handler
,下文会提到,只会对这些做修改
c
/// Connected State handler definition. const struct ke_msg_handler qpps_connected[] = { {QPPS_DATA_SEND_REQ, (ke_msg_func_t) qpps_data_send_req_handler}, {GATT_WRITE_CMD_IND, (ke_msg_func_t) gatt_write_cmd_ind_handler}, {GATT_NOTIFY_CMP_EVT, (ke_msg_func_t)
gatt_notify_cmp_evt_handler}, };
而在gatt_write_cmd_ind_handler
函数里,会对QPPS_IDX_RX_DATA_VAL属性进行处理
```c else if (param->handle == (qpps_env.shdl + QPPS_IDX_RX_DATA_VAL)) { if (param->length <= QPP_DATA_MAX_LEN) { //inform APP of configuration change struct qpps_data_val_ind * ind = KE_MSG_ALLOC_DYN(QPPS_DAVA_VAL_IND,
qpps_env.appid, TASK_QPPS, qpps_data_val_ind, param->length);
memcpy(&ind->conhdl, &(qpps_env.conhdl), sizeof(uint16_t));
//Send received data to app value
ind->length = param->length;
memcpy(ind->data, param->value, param->length);
ke_msg_send(ind);
}
else
{
status = QPPS_ERR_RX_DATA_EXCEED_MAX_LENGTH;
}
} ```
再由上面的QPPS_DAVA_VAL_IND可知会跳到下面这个函数,在这里对接收到的数据进行处理
```c int app_qpps_data_ind_handler(ke_msg_id_t const msgid, struct qpps_data_val_ind *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { uint8_t i; if (param->length > 0) { QPRINTF(“len=%d, I%02X”,
param->length, param->data[0]); QPRINTF(“
”); QPRINTF(“the receive data is :”); for(i=0;ilength;i++) { QPRINTF("%x",param->data[i]); } } QPRINTF("
");
return (KE_MSG_CONSUMED); } ``` 当发送数据为29时,CuteCom蓝牙接收数据为
QN
BLE is ready. Set device name complete Advertising start. Connection with 1FDA7E9F8E30 result is 0x0. LTK request indication idx is 0, auth_req is 0. Start encryption complete, idx 0, status 0, key_size 0, sec_prop 1, bonded 0. Slave update success. Update
parameter complete, interval: 0xc, latency: 0x0, sup to: 0x12c. len=1, I29 the receive data is :29
-
App收取蓝牙通知。通过点击app上5个特征的通知,能获取蓝牙模块的实时通知。Gatt层与通知相关的函数是qpps_data_send_req_handler,但我们只需要修改app开头的api即可完成相应的开发,现象及分析如下:
当点击app上的通知时,会进入如下的函数,为了调试将其调整为如下:
```c int app_qpps_cfg_indntf_ind_handler(ke_msg_id_t const msgid, struct qpps_cfg_indntf_ind *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { if (app_qpps_env->conhdl == param->conhdl) { QPRINTF(“enter
app_qpps_cfg_indntf_ind_handler”); QPRINTF(“
”); if (param->cfg_val == PRF_CLI_START_NTF) { QPRINTF(“enter PRF_CLI_START_NTF”); QPRINTF(“
”); app_qpps_env->features |= (QPPS_VALUE_NTF_CFG « param->char_index); QPRINTF(“app_qpps_env->features is %d, param->char_index
is %d”,app_qpps_env->features,param->char_index); QPRINTF(“
”); // App send data if all of characteristic have been configured if (get_bit_num(app_qpps_env->features) == app_qpps_env->tx_char_num)//num is 5 { QPRINTF(“enter app_qpps_env->features”); QPRINTF(“
”);
app_qpps_env->char_status = app_qpps_env->features; app_test_send_data(app_qpps_env->tx_char_num - 1); } } else { app_qpps_env->features &= ~(QPPS_VALUE_NTF_CFG « param->char_index); app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG « param->char_index); }
}
return (KE_MSG_CONSUMED); } ``` 其中,param->char_index代表第几个可通知特征,app_qpps_env->tx_char_num为特征的数量5,只有当所有特征都进入通知状态时蓝牙模块才会发数据到手机app。
数据发送的过程见如下函数,这个函数中的数据val可以改为项目中的需求,如IO口状态
```c static void app_test_send_data(uint8_t max) { uint8_t cnt;
QPRINTF("enter app_test_send_data");
QPRINTF("
");
for (cnt = 0; (max != 0) && cnt < app_qpps_env->tx_char_num; cnt++)
{
if ((app_qpps_env->char_status >> cnt) & QPPS_VALUE_NTF_CFG)
{
static uint8_t val[] = {0, '0', '1', '2','3','4','5','6','7','8','9','8','7','6','5','4','3','2','1','0'};
// Increment the first byte for test
val[0]++;
max--;
// Allow next notify until confirmation received in this characteristic
app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG << cnt);
app_qpps_data_send(app_qpps_env->conhdl, cnt, sizeof(val), val);
}
} } ``` 数据发送后,有一个发送确认函数app_qpps_data_send_cfm_handler,发送成功后手机上看到的现象是这样的
至此完成的功能为,实时反应蓝牙通知信息,app发数据控制蓝牙模块。
####QTool使用 QTool是一个运行在PC端的应用软件,允许用户启动两个BLE设备之间的连接,它能帮助用户分析BLE蓝牙。它是通过串口与BLE设备进行通信,通过ACI命令扮演网络处理器的作用。
打开pdf使用文档
~ $ cd /home/nephne/.wine/drive_c/QBlue/QN9020/QBlue-1.3.7/Documents
~ $ evince QBlue ISP Studio Manual v1.0.pdf
插入USB连接蓝牙模块,下载np_controller_B2.bin,然后打开QTool进入设备连接。关于界面说明见系统文档。
例如:点击Setting-Server-QPPS里的Create DB,然后进入Setting-Mode里点击Advertising,即可实现如上QPPS工程的效果。而在旁边的Local Device Traces窗口可以看到程序的大概运行过程。
####参考文档 -
FireBLE-wiki -
BLE编程API参考手册 -
BLE软件开发手册 -
数据手册 -
QN9020快速入门 -
QN902x周立功版 -
防丢器项目 -
MDK-ARM
PRO SET -
总有“一道菜”适合你——BLE -
【FireBLE试用体验】体验报告汇总 -
FireBLE
开发板试用汇总贴(2015.11.29更新 共收录145篇) [板块置顶] [精华] -
FireBLE驱动第一篇:呼吸灯 -
FireBLE低功耗蓝牙开发板评测
> 可穿戴手环+蓝牙防丢器 -
esp8266