技术标签: 蓝牙
下面将逐一介绍,从max30102驱动开始,最终实现心率数据向安卓手机上传,完成一个BLE应用。
FR801xH SDK 的架构如下图所示。 SDK 包含了完整的 BLE 5.0 协议栈, 包括完整的 controller, host, profile, SIG Mesh部分。 其中蓝牙协议栈的 controller 和 host 部分以及操作系统抽象层 OSAL 都是以库的形式提供, 图中为灰色部分。 MCU 外设驱动和 profile, 以及应用层的例程代码, 都是以源码的形式提供, 图中为绿色部分。
MAX30102是一个完整的脉搏血氧饱和度和心率传感器系统解决方案模块,专为可穿戴设备的苛刻要求而设计。该设备在不牺牲光学或电学性能的情况下保持非常小的解决方案尺寸。集成到可穿戴系统中需要最少的外部硬件组件。
MAX30102通过软件寄存器完全可调,数字输出数据可存储在IC内的32深FIFO中。FIFO允许MAX30102连接到共享总线上的微控制器或处理器,在共享总线上数据不是从MAX30102的寄存器连续读取的。
MAX30102的SpO2子系统包含环境光消除(ALC)、连续时间sigma-delta ADC和专有的离散时间滤波器。自动高度控制有一个内部跟踪/保持电路,以消除环境光并增加有效动态范围。SpO2 ADC的可编程满标度范围为2µA至16µA。ALC可抵消高达200µA的环境电流。
内部ADC是一个具有18位分辨率的连续时间过采样sigma-delta转换器。ADC采样率为10.24MHz。ADC输出数据速率可从50sps(每秒采样数)编程到3200sps。
MAX30102有一个片上温度传感器,用于校准SpO2子系统的温度依赖性。温度传感器的固有分辨率为0.0625°C。
设备输出数据对红外LED的波长相对不敏感,其中红色LED的波长对于正确解释数据至关重要。与MAX30102输出信号一起使用的SpO2算法可以补偿与环境温度变化相关的SpO2误差。
MAX30102集成了红色和红外LED驱动器,用于调制用于SpO2和HR测量的LED脉冲。在适当的电源电压下,LED电流可编程设定在0到50mA之间。LED脉冲宽度可从69µs编程到411µs,以允许算法根据用例优化SpO2和HR精度以及功耗。
当用户的手指不在传感器上时,该装置包括接近功能以节省功率并减少可见光发射。当SpO2或HR功能启动时(通过写入模式寄存器),IR LED在接近模式下激活,驱动电流由PILOT PA寄存器设置。当通过超过IR ADC计数阈值(在PROX\u INT\u THRESH寄存器中设置)检测到物体时,部件自动转换到正常SpO2/HR模式。要重新进入接近模式,必须重写模式寄存器(即使值相同)。
可通过将接近功能重置为0来禁用接近功能。在这种情况下,血氧饱和度或心率模式立即开始。
通常,从I2C接口读取寄存器会自动递增寄存器地址指针,这样所有寄存器都可以在无I2C启动事件的突发读取中读取。在MAX30102中,这适用于除FIFO数据寄存器(寄存器0x07)以外的所有寄存器。
读取FIFO\数据寄存器不会自动增加寄存器地址。突发读取该寄存器从同一地址反复读取数据。每个样本都包含多个字节的数据,因此应从该寄存器(在同一事务中)读取多个字节以获得一个完整的样本。
另一个例外是0xFF。在0xFF寄存器之后读取更多字节不会使地址指针返回0x00,读取的数据也没有意义。
数据FIFO由一个32采样内存组组成,它可以存储IR和Red ADC数据。由于每个样本由两个数据通道组成,因此每个样本有6个字节的数据,因此FIFO中可以存储总共192个字节的数据。
FIFO数据左对齐,如表1所示;换句话说,无论ADC分辨率设置如何,MSB位始终位于位17数据位置。FIFO数据结构的直观表示见表2。
表1。FIFO数据左对齐
FIFO数据左对齐,这意味着无论ADC分辨率设置如何,MSB始终位于同一位置。未使用FIFO数据[18]–[23]。表2显示了每个三元组字节的结构(包含每个通道的18位ADC数据输出)。
SpO2模式下的每个数据样本包含两个数据三元组(每个三元组3个字节),要读取一个样本,每个字节需要一个I2C读取命令。因此,要在SpO2模式下读取一个样本,需要读取6个I2C字节。读取每个样本的第一个字节后,FIFO读取指针将自动递增。
写/读指针用于控制FIFO中的数据流。每次向FIFO添加新样本时,写入指针都会递增。每次从FIFO读取样本时,读取指针都会递增。要从FIFO中重新读取样本,将其值减一,然后再次读取数据寄存器。
在进入SpO2模式或HR模式时,应清除FIFO写/读指针(返回0x00),以便在FIFO中没有表示的旧数据。如果VDD通电或VDD低于其UVLO电压,指针将自动清除。
表2。FIFO数据(每个通道3字节)
第一个事务:获取FIFO_WR_PTR:
START;
Send device address + write mode
Send address of FIFO_WR_PTR;
REPEATED_START;
Send device address + read mode
Read FIFO_WR_PTR;
STOP;
中央处理器评估要从FIFO读取的样本数:
NUM_AVAILABLE_SAMPLES = FIFO_WR_PTR – FIFO_RD_PTR
(Note: pointer wrap around should be taken into account)
NUM_SAMPLES_TO_READ = < less than or equal to NUM_AVAILABLE_SAMPLES >
第二个事务:从FIFO读取样本数:
START;
Send device address + write mode
Send address of FIFO_DATA;
REPEATED_START;
Send device address + read mode
for (i = 0; i < NUM_SAMPLES_TO_READ; i++) {
Read FIFO_DATA;
Save LED1[23:16];
Read FIFO_DATA;
Save LED1[15:8];
Read FIFO_DATA;
Save LED1[7:0];
Read FIFO_DATA;
Save LED2[23:16];
Read FIFO_DATA;
Save LED2[15:8];
Read FIFO_DATA;
Save LED2[7:0];
Read FIFO_DATA;
}
STOP;
START;
Send device address + write mode
Send address of FIFO_RD_PTR;
Write FIFO_RD_PTR;
STOP;
第三个事务:写入FIFO_RD_PTR寄存器。如果第二个事务成功,则FIFO_RD_PTR指向FIFO中的下一个样本,而第三个事务不是必需的。否则,处理器会适当地更新FIFO_RD_PTR,以便重新读取样本。
这里以读取数据的FIFO为例子,介绍fr8016读取max30102的fifo。
int8_t maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
/**
* \brief Read a set of samples from the MAX30102 FIFO register
* \par Details
* This function reads a set of samples from the MAX30102 FIFO register
*
* \param[out] *pun_red_led - pointer that stores the red LED reading data
* \param[out] *pun_ir_led - pointer that stores the IR LED reading data
*
* \retval true on success
*/
{
uint32_t un_temp;
unsigned char uch_temp;
char ach_i2c_data[6];
uint8_t Ack1,Ack2,Ack3;//,Ack4;
*pun_red_led=0;
*pun_ir_led=0;
//read and clear status register
maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);
ach_i2c_data[0]=REG_FIFO_DATA;
sensirion_i2c_start();
Ack1 = sensirion_i2c_write_byte(I2C_WRITE_ADDR); //发送设备写地址
Ack2 = sensirion_i2c_write_byte(ach_i2c_data[0]); //发送寄存器地址
sensirion_i2c_start();
Ack3 = sensirion_i2c_write_byte(I2C_READ_ADDR); //发送设备读地址
//un_temp=(unsigned char) ach_i2c_data[0];
un_temp = sensirion_i2c_read_byte(1);//读取
un_temp<<=16;
*pun_red_led+=un_temp;
//un_temp=(unsigned char) ach_i2c_data[1];
un_temp = sensirion_i2c_read_byte(1);//读取
un_temp<<=8;
*pun_red_led+=un_temp;
//un_temp=(unsigned char) ach_i2c_data[2];
un_temp = sensirion_i2c_read_byte(1);//读取
*pun_red_led+=un_temp;
// un_temp=(unsigned char) ach_i2c_data[3];
un_temp = sensirion_i2c_read_byte(1);//读取
un_temp<<=16;
*pun_ir_led+=un_temp;
//un_temp=(unsigned char) ach_i2c_data[4];
un_temp = sensirion_i2c_read_byte(1);//读取
un_temp<<=8;
*pun_ir_led+=un_temp;
//un_temp=(unsigned char) ach_i2c_data[5];
un_temp = sensirion_i2c_read_byte(0);//读取
sensirion_i2c_stop();//产生停止
*pun_ir_led+=un_temp;
*pun_red_led&=0x03FFFF; //Mask MSB [23:18]
*pun_ir_led&=0x03FFFF; //Mask MSB [23:18]
if(Ack1 || Ack2 || Ack3)//如果
return -1; //发送失败
else
return 0; //发送成功
}
上面需要使用到FR8016的SDK的模拟I2C函数,比如i2c起始,i2c读写一个字节。
#define DELAY_USECC (SENSIRION_I2C_CLOCK_PERIOD_USEC / 2)
static uint8_t sensirion_wait_while_clock_stretching(void) {
uint8_t timeout = 100;
while (--timeout) {
//co_printf("timeout= %d\r\n",timeout);
if (sensirion_SCL_read())
return STATUS_OK;
sensirion_sleep_usec(DELAY_USECC);
}
return STATUS_FAIL;
}
static int8_t sensirion_i2c_write_byte(uint8_t data) {
int8_t nack, i;
for (i = 7; i >= 0; i--) {
sensirion_SCL_out();
if ((data >> i) & 0x01)
sensirion_SDA_in();
else
sensirion_SDA_out();
sensirion_sleep_usec(DELAY_USECC);
sensirion_SCL_in();
sensirion_sleep_usec(DELAY_USECC);
if (sensirion_wait_while_clock_stretching())
return STATUS_FAIL;
}
sensirion_SCL_out();
sensirion_SDA_in();
sensirion_sleep_usec(DELAY_USECC);
sensirion_SCL_in();
if (sensirion_wait_while_clock_stretching())
return STATUS_FAIL;
// printf("sensirion_i2c_write_byte\r\n************");
nack = (sensirion_SDA_read() != 0);//判断ACK
sensirion_SCL_out();
//printf("nack =%d\r\n",nack);
return nack;
}
static uint8_t sensirion_i2c_read_byte(uint8_t ack) {
int8_t i;
uint8_t data = 0;
sensirion_SDA_in();
for (i = 7; i >= 0; i--) {
sensirion_sleep_usec(DELAY_USECC);
sensirion_SCL_in();
if (sensirion_wait_while_clock_stretching())
return STATUS_FAIL;
data |= (sensirion_SDA_read() != 0) << i;
sensirion_SCL_out();
}
if (ack)
sensirion_SDA_out();
else
sensirion_SDA_in();
sensirion_sleep_usec(DELAY_USECC);
sensirion_SCL_in();
sensirion_sleep_usec(DELAY_USECC);
if (sensirion_wait_while_clock_stretching())
return STATUS_FAIL;
sensirion_SCL_out();
sensirion_SDA_in();
return data;
}
static uint8_t sensirion_i2c_start(void) {
sensirion_SCL_in();
if (sensirion_wait_while_clock_stretching())
return STATUS_FAIL;
sensirion_SDA_out();
sensirion_sleep_usec(DELAY_USECC);
sensirion_SCL_out();
sensirion_sleep_usec(DELAY_USECC);
return STATUS_OK;
}
static void sensirion_i2c_stop(void) {
sensirion_SDA_out();
sensirion_sleep_usec(DELAY_USECC);
sensirion_SCL_in();
sensirion_sleep_usec(DELAY_USECC);
sensirion_SDA_in();
sensirion_sleep_usec(DELAY_USECC);
}
SDK 里面包含了完整的协议栈, 虽然 controller 和 host 部分是以库的形式提供, 但给出了接口丰富的 API 提供给上层应用开发调用。 Profile 则是以源码的形式提供。
GAP操作如下:
/*********************************************************************
* @fn simple_peripheral_init
*
* @brief Initialize simple peripheral profile, BLE related parameters.
*
* @param None.
*
*
* @return None.
*/
void simple_peripheral_init(void)
{
// set local device name
uint8_t local_name[] = "Simple Peripheral";
gap_set_dev_name(local_name, sizeof(local_name));
// Initialize security related settings.
gap_security_param_t param =
{
.mitm = false,
.ble_secure_conn = false,
.io_cap = GAP_IO_CAP_NO_INPUT_NO_OUTPUT,
.pair_init_mode = GAP_PAIRING_MODE_WAIT_FOR_REQ,
.bond_auth = true,
.password = 0,
};
gap_security_param_init(¶m);
gap_set_cb_func(app_gap_evt_cb);
gap_bond_manager_init(BLE_BONDING_INFO_SAVE_ADDR, BLE_REMOTE_SERVICE_SAVE_ADDR, 8, true);
gap_bond_manager_delete_all();
mac_addr_t addr;
gap_address_get(&addr);
co_printf("Local BDADDR: 0x%2X%2X%2X%2X%2X%2X\r\n", addr.addr[0], addr.addr[1], addr.addr[2], addr.addr[3], addr.addr[4], addr.addr[5]);
// Adding services to database
sp_gatt_add_service();
speaker_gatt_add_service(); //创建Speaker profile,
//按键初始化 PD6 PC5
pmu_set_pin_pull(GPIO_PORT_D, (1<<GPIO_BIT_6), true);
pmu_set_pin_pull(GPIO_PORT_C, (1<<GPIO_BIT_5), true);
pmu_port_wakeup_func_set(GPIO_PD6|GPIO_PC5);
button_init(GPIO_PD6|GPIO_PC5);
demo_LCD_APP(); //显示屏
// demo_CAPB18_APP(); //气压计
demo_SHT3x_APP(); //温湿度
// gyro_dev_init(); //加速度传感器
//
// //OS Timer
os_timer_init(&timer_refresh,timer_refresh_fun,NULL);//创建一个周期性1s定时的系统定时器
os_timer_start(&timer_refresh,1000,1);
}
GATT操作主要是在回调函数sp_gatt_read_cb和sp_gatt_write_cb里进行:
/*********************************************************************
* @fn sp_gatt_msg_handler
*
* @brief Simple Profile callback funtion for GATT messages. GATT read/write
* operations are handeled here.
*
* @param p_msg - GATT messages from GATT layer.
*
* @return uint16_t - Length of handled message.
*/
static uint16_t sp_gatt_msg_handler(gatt_msg_t *p_msg)
{
switch(p_msg->msg_evt)
{
case GATTC_MSG_READ_REQ:
sp_gatt_read_cb((uint8_t *)(p_msg->param.msg.p_msg_data), &(p_msg->param.msg.msg_len), p_msg->att_idx);
break;
case GATTC_MSG_WRITE_REQ:
sp_gatt_write_cb((uint8_t*)(p_msg->param.msg.p_msg_data), (p_msg->param.msg.msg_len), p_msg->att_idx,p_msg->conn_idx);
break;
default:
break;
}
return p_msg->param.msg.msg_len;
}
本次使用os_user_loop_event_set创建了循环任务读取max30102的心率数据。OSAL有以下部分组成:
软件定时器用来创建一个定时任务,比如10秒钟处理一次任务A,20秒处理一次任务B。
(FR8016) PC6 ->SCL(MAX30102)
(FR8016) PC7 ->SDA(MAX30102)
(FR8016) PA5 ->INT(MAX30102)
手机在应用商店下载一个 蓝牙调试器。可见我的心率在80~90之间,完成了心率数据通过BLE上传到我的安卓手机。
通过USB dongle抓包,可见fr8016与我的安卓手机建立了加密通信。
由下图可见,fr8016与max30102能正常的进行i2c通信。
修改后上传
拍摄后上传
文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...
文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档
文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子
文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud
文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换
文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装
文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者
文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be and th_normalized plane coordinates
文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取
文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面
文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思
文章浏览阅读240次。conan简单使用。_apt install conan