RSL10-SENSE-DB-GEVK环境监测节点设计
[复制链接]
本帖最后由 dql2016 于 2021-7-5 21:02 编辑
我的本次作品有一个环境监测节点,正好充分利用 RSL10-SENSE-DB-GEVK的板载传感器:LV0104CS(环境光)、BME680(集成式高精度气体、压力、湿度和温度传感器)、INMP522(超低噪声数字麦克风)。原来调通了微信小程序与RSL10双向通信,RSL10端基于peripheral _server例程,后来在将传感器相关驱动、BDK相关组件添加到peripheral _server中后,无法同时采集数据和进行蓝牙通信了,原因在于RSL10-SENSE-DB-GEVK板卡的SDK和RSL10-002GEVB板卡的SDK相差太大,暂时没有精力去深究这个问题了,详见帖子:RSL10蓝牙特征值读写https://bbs.eeworld.com.cn/thread-1170300-1-1.html。因此只好另辟蹊径,周末抽空深入学习了RSL10-SENSE-DB-GEVK的SDK,在sense_ics_firmware例程的基础上实现了本次项目中环境监测节点的功能。环境监测节点的功能是将采集到的室内环境光、空气质量、温度、湿度、气压、声音通过蓝牙发送到微信小程序,并接受微信小程序下发的控制指令,当触发预设的规则后可以开启风扇等进行通风以改善室内空气质量,类似HAVC系统的功效,如图1所示。微信小程序拿到采集数据后,可以进行存储、分析,手机强大的性能或者是微信云平台的AI能力带来了无限可能。
图1 环境监测节点与微信小程序通信示意图
sense_ics_firmware例程通过自定义服务协议与安森美的官方手机app RSL10 Sense and Control通信 ,实现在手机app端显示板载传感器各项数据。在这个例程中,RSL10-SENSE-DB-GEVK板卡作为服务端,每种传感器、板载按键都被注册为一个服务节点,传感器采集的数据和按键状态则作为节点属性,客户端(手机app)向服务端请求读写属性值,这是一个典型的客户端-服务器模型。在这个协议中节点和属性用于访问具体的数据值,节点是属性的逻辑分组,它包含了一个具体设备的所有属性。属性是指可以被读取或者被写入的数据端点,每个节点具有的属性都是具体的。例如一个BLDC节点用于控制电动机,电动机具有可读可写的转速属性RPM,可写的方向属性DIR。请求令牌用于同步,客户端发出的每个请求包以‘0’---‘~’之间的随机数开头,服务端响应相同的请求令牌,因此两个不同的请求必须具有不同的请求令牌。分隔符'/'用于分割请求令牌、节点名、属性名、属性值。数据包长度可变,最大为20字节,一共有3中数据包:
读请求包:用于索取给定属性的当前值。格式是请求令牌/节点名/属性名,例如s/ALS/LUX用于请求ALS节点的光照LUX属性值。
写请求包:用于设置给定属性的新值。格式是请求令牌/节点名/属性名/属性值 ,例如d/BLDC/TGT/2560用于请求改变BLDC节点的TGT属性为整数值2560。客户端需要知道给定属性的数据类型,规范定义了标准节点和节点属性发现协议。
响应包:一旦客户端请求到达服务端立即发送,格式是请求令牌/数据类型/值。
响应包数据类型有:i有符号整数;f浮点数;h十六进制数;s字符串;t多个连续字符串,读请求时是页数,写请求时是给定页数的内容,可用于保存节点类型、节点描述、设备名、配置文件等;e错误字符串;n节点定义;p属性定义;c复合值,需要在应用中解析,在响应包中不包含数据类型。数据包是ascill字符串形式。
CMSIS-Pack文件夹下面的文档SL10 BLE Custom Service communication protocol.pdf详细介绍了这个通信协议。如图2、3、4展示了蓝牙调试app请求服务的过程:
图2 蓝牙app请求数据
图3 服务端响应数据1
图4 服务端响应数据2
本次项目不需要用到这个协议,只需要在这个例子的基础上进行一些修改即可。由于板卡硬件版本不支持光照传感器NOA1305取而代之的是LV0104CS,论坛大佬已分享相关帖子,这里我就直接把驱动拿来用,在此表示感谢。另外sense_ics_firmware例程也没有采集麦克风数据,因此对sense_ics_firmware例程的主要有2点:main.c函数中加入光照传感器LV0104CS和数字麦克风INMP522的初始化
//-----------------------------------------------------------------------------
// Copyright (c) 2018 Semiconductor Components Industries LLC
// (d/b/a "ON Semiconductor"). All rights reserved.
// This software and/or documentation is licensed by ON Semiconductor under
// limited terms and conditions. The terms and conditions pertaining to the
// software and/or documentation are available at
// http://www.onsemi.com/site/pdf/ONSEMI_T&C.pdf ("ON Semiconductor Standard
// Terms and Conditions of Sale, Section 8 Software") and if applicable the
// software license agreement. Do not use this software and/or documentation
// unless you have carefully read and you agree to the limited terms and
// conditions. By using this software and/or documentation, you agree to the
// limited terms and conditions.
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <BDK.h>
#include <BSP_Components.h>
#include <BLE_Components.h>
#include <ics/CS.h>
#include <ics/CS_Nodes.h>
#include <CSN_PB.h> // PushButton custom node creation example
#include <lv0104cs.h>
#include <lv0104cs_lux.h>
#define AUDIO_DMIC0_GAIN 0x800
#define AUDIO_DMIC1_GAIN 0x800
#define AUDIO_OD_GAIN 0x800
#define AUDIO_CONFIG (OD_AUDIOSLOWCLK | \
DMIC_AUDIOCLK | \
DECIMATE_BY_64 | \
OD_UNDERRUN_PROTECT_ENABLE | \
OD_DATA_MSB_ALIGNED | \
DMIC0_DATA_LSB_ALIGNED | \
DMIC1_DATA_LSB_ALIGNED | \
OD_DMA_REQ_DISABLE | \
DMIC0_DMA_REQ_DISABLE | \
DMIC1_DMA_REQ_DISABLE | \
OD_INT_GEN_DISABLE | \
DMIC0_INT_GEN_ENABLE | \
DMIC1_INT_GEN_DISABLE | \
OD_DISABLE | \
DMIC0_ENABLE | \
DMIC1_DISABLE)
int32_t dmic_value = 0;
int32_t dmic_max = 0;
int32_t dmic_min = INT32_MAX;
int32_t lv0104cs_status = -1;
int main(void)
{
int32_t retval = 0;
/* Initialize all needed BDK components. */
BDK_Initialize();
/* Initialize all LEDs. */
LED_Initialize(LED_RED);
LED_Initialize(LED_GREEN);
LED_Initialize(LED_BLUE);
//Initialize LV0104CS.
retval = LV0104CS_LUX_Initialize();
if (retval == LV0104CS_OK)
{
retval = LV0104CS_LUX_StartContinuous(0, NULL);
if (retval == LV0104CS_OK)
{
lv0104cs_status = 0;
}
else
{
lv0104cs_status = 2;
}
}
else
{
lv0104cs_status = 1;
}
printf("LV0104CS initialization: %s\n", lv0104cs_status == 0 ? "OK":"ERROR");
//Configure DMIC input to test INMP522 microphone.
// Configure AUDIOCLK to 2 MHz and AUDIOSLOWCLK to 1 MHz.
CLK->DIV_CFG1 &= ~(AUDIOCLK_PRESCALE_64 | AUDIOSLOWCLK_PRESCALE_4);
CLK->DIV_CFG1 |= AUDIOCLK_PRESCALE_4 | AUDIOSLOWCLK_PRESCALE_2;
//Configure OD, DMIC0 and DMIC1
Sys_Audio_Set_Config(AUDIO_CONFIG);
Sys_Audio_Set_DMICConfig(DMIC0_DCRM_CUTOFF_20HZ | DMIC1_DCRM_CUTOFF_20HZ |
DMIC1_DELAY_DISABLE | DMIC0_FALLING_EDGE |
DMIC1_RISING_EDGE, 0);
Sys_Audio_DMICDIOConfig(DIO_6X_DRIVE | DIO_LPF_DISABLE | DIO_NO_PULL,
10, 6, DIO_MODE_AUDIOCLK);
//Configure Gains for DMIC0, DMIC1 and OD
AUDIO->DMIC0_GAIN = AUDIO_DMIC0_GAIN;
NVIC_EnableIRQ(DMIC_OUT_OD_IN_IRQn);
/* Indication - Initialization started. */
LED_On(LED_BLUE);
/* Initialize CS protocol, start Peripheral Server, Add Custom Service
* Profile to it.
*/
CS_Init();
/* Optionally set advertising interval to be 200 to 250 ms long. */
BDK_BLE_SetAdvertisementInterval(320, 400);
/* Optionally set custom device name.
* The name needs to contain one of 'IDK', 'BDK', 'RSL10' or 'BLE_Terminal'
* patterns to be recognized by RSL10 Sense & control mobile application.
* Default: 'HB_BLE_Terminal'
*/
BDK_BLE_SetLocalName("环境监测");
/* Also add battery service if its RTE component is enabled. */
#if defined (RTE_BDK_BLE_PERIPHERAL_SERVER_BASS)
BLE_BASS_Initialize(1000, 16);
BLE_BASS_SetVoltageRange(CALC_VBAT_MEASURED(2.4f), CALC_VBAT_MEASURED(3.0f));
// BLE_BASS_SetBattLevelInd(BattLevelChangeCallback);
#endif /* RTE_BDK_BLE_BASS_PRESENT */
// Change default bus speed to 400kHz
HAL_I2C_SetBusSpeed(HAL_I2C_BUS_SPEED_FAST);
/* Add all enabled Custom Service Nodes. */
// ASSERT_ALWAYS(CSN_ALS_CheckAvailability() == true);
// ASSERT_ALWAYS(CS_RegisterNode(CSN_ALS_Create()) == CS_OK);
ASSERT_ALWAYS(CSN_ENV_CheckAvailability() == true);
ASSERT_ALWAYS(CS_RegisterNode(CSN_ENV_Create()) == CS_OK);
ASSERT_ALWAYS(CSN_AO_CheckAvailability() == true);
ASSERT_ALWAYS(CS_RegisterNode(CSN_AO_Create()) == CS_OK);
ASSERT_ALWAYS(CS_RegisterNode(CSN_PB_Create()) == CS_OK);
/* Indication - Initialization complete. */
LED_Off(LED_BLUE);
LED_On(LED_GREEN);
HAL_Delay(250);
LED_Off(LED_GREEN);
CS_SYS_Info("Entering main loop.");
while (1)
{
/* Execute any events that have occurred and refresh Watchdog. */
BDK_Schedule();
/* Enter sleep mode until an interrupt occurs. */
SYS_WAIT_FOR_INTERRUPT;
}
return 0;
}
void DMIC_OUT_OD_IN_IRQHandler(void)
{
dmic_value = (int32_t)AUDIO->DMIC0_DATA;
if (dmic_max < dmic_value)
{
dmic_max = dmic_value;
}
else
{
if (dmic_min > dmic_value)
{
dmic_min = dmic_value;
}
}
}
在BSEC_ENV.c中BSEC_ENV_ReadData函数是一个定时调用的函数,在其中读取到数据后调用int CS_PlatformWrite(const char* tx_data, int tx_data_len)将数据以通知的方式发送出去。
这里的通信协议比较简单,将温度、湿度、光照、压力、空气质量、声音依次发送,先发高位字节,一共10个字节。
extern int32_t dmic_value;
extern int32_t dmic_max;
extern int32_t dmic_min;
extern lv0104cs_status;
extern struct BLE_ICS_Resources cs_res;
uint32_t lux = 0;
static void BSEC_ENV_ReadData(int64_t time_stamp_trigger, bsec_input_t *inputs,
uint8_t *num_bsec_inputs, int32_t bsec_process_data)
{
static struct bme680_field_data data;
int8_t bme680_status = BME680_OK;
int8_t buffer[10]={0};
/* We only have to read data if the previous call the bsec_sensor_control() actually asked for it */
if (bsec_process_data)
{
bme680_status = bme680_get_sensor_data(&data, &bme680_g);
ASSERT_DEBUG(bme680_status == BME680_OK);
if (data.status & BME680_NEW_DATA_MSK)
{
/* Pressure to be processed by BSEC */
if (bsec_process_data & BSEC_PROCESS_PRESSURE)
{
/* Place presssure sample into input struct */
inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_PRESSURE;
inputs[*num_bsec_inputs].signal = data.pressure;
inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger;
(*num_bsec_inputs)++;
}
/* Temperature to be processed by BSEC */
if (bsec_process_data & BSEC_PROCESS_TEMPERATURE)
{
/* Place temperature sample into input struct */
inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
#ifdef BME680_FLOAT_POINT_COMPENSATION
inputs[*num_bsec_inputs].signal = data.temperature;
#else
inputs[*num_bsec_inputs].signal = data.temperature / 100.0f;
#endif
inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger;
(*num_bsec_inputs)++;
/* Also add optional heatsource input which will be subtracted from the temperature reading to
* compensate for device-specific self-heating (supported in BSEC IAQ solution)*/
inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
inputs[*num_bsec_inputs].signal = bme680_temperature_offset_g;
inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger;
(*num_bsec_inputs)++;
}
/* Humidity to be processed by BSEC */
if (bsec_process_data & BSEC_PROCESS_HUMIDITY)
{
/* Place humidity sample into input struct */
inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
#ifdef BME680_FLOAT_POINT_COMPENSATION
inputs[*num_bsec_inputs].signal = data.humidity;
#else
inputs[*num_bsec_inputs].signal = data.humidity / 1000.0f;
#endif
inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger;
(*num_bsec_inputs)++;
}
/* Gas to be processed by BSEC */
if (bsec_process_data & BSEC_PROCESS_GAS)
{
/* Check whether gas_valid flag is set */
if (data.status & BME680_GASM_VALID_MSK)
{
/* Place sample into input struct */
inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[*num_bsec_inputs].signal = data.gas_resistance;
inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger;
(*num_bsec_inputs)++;
}
}
}
if (lv0104cs_status == 0)
{
LV0104CS_LUX_ReadLux(&lux);
printf("LV0104CS measured value:lux=%lu\n", lux);
}
CS_SYS_Info("INMP522 measured value: dmic_min=%ld dmic_value=%ld dmic_max=%ld\n", dmic_min, dmic_value,dmic_max);
CS_SYS_Info("BME680 measured value: temperature=%d humidity=%d pressure=%d gas_resistance=%d\n",data.temperature/100,data.humidity/1000,data.pressure,data.gas_resistance);
//通过通知发送给微信小程序
buffer[0]=data.temperature/100;//温度
buffer[1]=data.humidity/1000;//湿度
buffer[2]=((data.pressure & 0xff00) >> 8 );//气压高8位
buffer[3]=(data.pressure & 0x00ff);//气压低8位
buffer[4]=((data.gas_resistance & 0xff00) >> 8 );//AQI高8位
buffer[5]=(data.gas_resistance & 0x00ff);//AQI低8位
buffer[6]=((lux & 0x0000ff00) >> 8 );//光照高8位
buffer[7]=(lux & 0x000000ff);//光照低8位
buffer[8]=((dmic_value & 0x0000ff00) >> 8 );//噪声高8位
buffer[9]=(dmic_value & 0x000000ff);//噪声低8位
if(cs_res.tx_cccd_value==0x0001)
{
if(CS_PlatformWrite(buffer, sizeof(buffer))!=CS_OK)
{
CS_SYS_Info("=== CS_PlatformWrite Error ===");
}
}
}
}
微信小程序发送给RSL10数据后会调用BLE_ICS.c中的BLE_ICS_GATTC_WriteReqInd回调,在这里可以得到通知是否开启/关闭
static int BLE_ICS_GATTC_WriteReqInd(ke_msg_id_t const msg_id,
struct gattc_write_req_ind const *param, ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
uint8_t status = GAP_ERR_NO_ERROR;
uint16_t att_num = 0;
int conidx = BDK_BLE_GetConIdx();
struct gattc_write_cfm *cfm;
/* Check if connection is valid. */
if (conidx == INVALID_DEV_IDX)
{
return KE_MSG_CONSUMED;
}
/* Check that offset is valid */
if (param->offset != 0)
{
status = ATT_ERR_INVALID_OFFSET;
}
/* Get index of characteristic which was requested. */
if (param->handle > cs_res.start_hdl)
{
att_num = param->handle - cs_res.start_hdl - 1;
}
else
{
status = ATT_ERR_INVALID_HANDLE;
}
printf("[%d %s] get data from phone,att_num=%d\n",__LINE__,__FUNCTION__,att_num);
if (status == GAP_ERR_NO_ERROR)
{
switch (att_num)
{
case ICS_IDX_TX_VALUE_CCC:
if (param->length == 2)
{
//手机APP端开启通知就是设置tx_cccd_value为0x0001关闭通知就是设置它的值为0x0000,
//这是原来的值
printf("[%d %s]old tx_cccd_value:0x%04x\n",__LINE__,__FUNCTION__,cs_res.tx_cccd_value);
//要写入的新值
printf("[%d %s]will write 0x",__LINE__,__FUNCTION__);
//大端模式传输
for(int i=param->length-1;i>=0;i--)
{
printf("%02x",param->value);
}
printf(" to it\n");
memcpy(&cs_res.tx_cccd_value, param->value, 2);
}
else
{
status = ATT_ERR_INVALID_ATTRIBUTE_VAL_LEN;
}
break;
/* New command was written. */
case ICS_IDX_RX_VALUE_VAL:
if (param->length <= ICS_CHARACTERISTIC_VALUE_LENGTH)
{
printf("[%d %s] get data from phone,rx_value:",__LINE__,__FUNCTION__);
for(int i=0;i<param->length;i++)
{
printf("0x%02x ",cs_res.tx_cccd_value);
}
printf("\n");
memcpy(&cs_res.rx_value, param->value, param->length);
cs_res.rx_value_length = param->length;
}
else
{
status = ATT_ERR_INVALID_ATTRIBUTE_VAL_LEN;
}
break;
case ICS_IDX_RX_VALUE_CCC:
if (param->length == 2)
{
printf("[%d %s] get data from phone,rx_cccd_value=0x%04x\n",__LINE__,__FUNCTION__,cs_res.rx_cccd_value);
memcpy(&cs_res.rx_cccd_value, param->value, 2);
}
else
{
status = ATT_ERR_INVALID_ATTRIBUTE_VAL_LEN;
}
break;
default:
status = ATT_ERR_WRITE_NOT_PERMITTED;
break;
}
}
cfm = KE_MSG_ALLOC(GATTC_WRITE_CFM, KE_BUILD_ID(TASK_GATTC, conidx),
TASK_APP, gattc_write_cfm);
cfm->handle = param->handle;
cfm->status = status;
ke_msg_send(cfm);
/* Write indication handler */
if (att_num == ICS_IDX_RX_VALUE_VAL && cs_res.rx_write_handler != NULL
&& status == GAP_ERR_NO_ERROR)
{
struct BLE_ICS_RxIndData ind;
memcpy(ind.data, cs_res.rx_value, cs_res.rx_value_length);
ind.data_len = cs_res.rx_value_length;
cs_res.rx_write_handler(&ind);
}
return KE_MSG_CONSUMED;
}
在CS.c中CS_Loop函数可取得接收到的数据
int CS_Loop(int timeout)
{
int errcode, bytes, i;
uint32_t timestamp;
struct CS_Request_Struct request;
// Wait for BLE data with 10 ms timeout.
errcode = CS_PlatformSleep(timeout);
if (errcode != CS_OK)
{
return errcode;
}
timestamp = CS_PlatformTime();
// Read available BLE packet.
memset(cs_rx_buffer, 0, 21);
//接收微信小程序发的数据
errcode = CS_PlatformRead(cs_rx_buffer, 21, &bytes);
if (errcode != CS_OK || bytes <= 0)
{
CS_SYS_Error("Platform read failed. (errcode=%d)", errcode);
return CS_ERROR;
}
#if CS_LOG_WITH_ANSI_COLORS != 0 && defined RTE_DEVICE_BDK_OUTPUT_REDIRECTION
CS_SYS_Info("Received request packet: '" COLORIZE("%s", CYAN, BOLD) "'",
cs_rx_buffer);
#else
CS_SYS_Info("Received request packet: '%s'", cs_rx_buffer);
#endif
// Parse header information
request.token = strtok(cs_rx_buffer, "/");
if (request.token == NULL)
{
CS_SYS_Error("Failed to parse request token.");
return CS_ERROR;
}
if (strlen(request.token) != 1)
{
CS_SYS_Error("Invalid request token length.");
return CS_ERROR;
}
if (request.token[0] < '0' || request.token[0] > '~')
{
CS_SYS_Error("Invalid request token value.");
return CS_ERROR;
}
request.node = strtok(NULL, "/");
if (request.node == NULL)
{
CS_SYS_Error("Failed to parse request node name.");
return CS_ERROR;
}
request.property = strtok(NULL, "/");
if (request.property == NULL)
{
CS_SYS_Error("Failed to parse request node property.");
return CS_ERROR;
}
// NULL for read requests
request.property_value = strtok(NULL, "/");
// Iterate all available nodes to find a match.
for (i = 0; i < cs.node_cnt; ++i)
{
if (strcmp(request.node, cs.node->name) == 0)
{
// Matching node was found -> pass request
errcode = cs.node->request_handler(&request, cs_node_response);
if (errcode == CS_OK &&
strlen(cs_node_response) <= 18) // 2b for token + response = 20b
{
// Compose response packet from token + node response
sprintf(cs_tx_buffer, "%s/%s", request.token, cs_node_response);
#if CS_LOG_WITH_ANSI_COLORS != 0 && defined RTE_DEVICE_BDK_OUTPUT_REDIRECTION
CS_SYS_Info(
"Composed response packet '" COLORIZE("%s", MAGENTA, BOLD) "'",
cs_tx_buffer);
#else
CS_SYS_Info("Composed response packet '%s'", cs_tx_buffer);
#endif
// Send response to platform
int res_len = strlen(cs_tx_buffer);
if (CS_PlatformWrite(cs_tx_buffer, res_len) == CS_OK)
{
timestamp = CS_PlatformTime() - timestamp;
CS_SYS_Verbose("Request completed in %lu ms.", timestamp);
return CS_OK;
}
else
{
CS_SYS_Error("Platform send failed. (errcode=%d)", errcode);
return CS_ERROR;
}
}
else
{
CS_SYS_Error("Node request processing error. (errcode=%d)", errcode);
sprintf(cs_tx_buffer, "%s/e/UNK_ERROR", request.token);
errcode = CS_PlatformWrite(cs_tx_buffer, strlen(cs_tx_buffer));
if (errcode != CS_OK)
{
CS_SYS_Error("Platform send failed. (errcode=%d)", errcode);
return CS_ERROR;
}
// Node failed to process request
return CS_ERROR;
}
}
}
CS_SYS_Error("No matching node found for '%s'", request.node);
sprintf(cs_tx_buffer, "%s/e/UNK_NODE", request.token);
errcode = CS_PlatformWrite(cs_tx_buffer, strlen(cs_tx_buffer));
if (errcode != CS_OK)
{
CS_SYS_Error("Platform send failed. (errcode=%d)", errcode);
return CS_ERROR;
}
return CS_ERROR;
}
下面来看看数据交互过程吧!
微信小程序端接收到数据:
至此 RSL10-SENSE-DB-GEVK板卡的功能就基于sense_ics_firmware例程实现,RSL10-002GEVB板卡的功能就基于peripheral _server例程实现,后续需要实现的节点功能就变的简单了。
|