You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
8.7 KiB
313 lines
8.7 KiB
#include "heart_wave_sample_service.h"
|
|
|
|
#include "app_event.h"
|
|
#include "app_event_distribute.h"
|
|
#include "basic/FIR.h"
|
|
#include "basic/HC_Chen_detect.h"
|
|
#include "basic/So_Chen_detect.h"
|
|
#include "basic/adaptive_algorithm.h"
|
|
#include "basic/Pan_Tompkins_detect.h"
|
|
#include "board/board_ecg_sensor.h"
|
|
#include "nrfx_timer.h"
|
|
|
|
|
|
static uint16_t m_capture_buffer_a[128];
|
|
static uint16_t m_capture_buffer_b[128];
|
|
|
|
static uint16_t* m_capture_buffer;
|
|
static uint16_t m_capture_buffer_index = 0;
|
|
|
|
volatile static float m_sensor_display_data = 0; // 0->100
|
|
static uint32_t m_start_capture_tp;
|
|
static uint32_t m_frame_index = 0;
|
|
|
|
static one_frame_data_t m_sensor_little_frame_cache[LITTLE_DATA_BLOCK_FRAME_NUM];
|
|
static uint32_t m_little_frame_index;
|
|
static bool m_prestart_flag;
|
|
|
|
static nrfx_timer_t m_timer = NRFX_TIMER_INSTANCE(HEART_WAVE_SAMPLE_TMR_INSTANCE);
|
|
|
|
typedef struct {
|
|
int heartSignalCnt;
|
|
int heart_rate;
|
|
uint32_t last_qrs_point;
|
|
uint32_t time_cnt;
|
|
uint32_t origin_time_cnt;
|
|
|
|
uint32_t index;
|
|
float index_f;
|
|
} QRS_t;
|
|
|
|
QRS_t m_qrs;
|
|
|
|
void QRS_reset() {
|
|
m_qrs.heartSignalCnt = 0;
|
|
m_qrs.heart_rate = 0;
|
|
m_qrs.time_cnt = 0;
|
|
m_qrs.index = 0;
|
|
m_qrs.index_f = 0;
|
|
FIR_reset_buffer();
|
|
}
|
|
|
|
void QRS_process(float value) {
|
|
bool isPeak = false;
|
|
#if 1
|
|
float result = value;
|
|
SignalPoint sp;
|
|
sp.value = result;
|
|
sp.index = m_qrs.time_cnt;
|
|
SignalPoint peak = So_Chen_detect(sp, SAMPLING_RATE * 0.25f, 4, 16);
|
|
if (peak.index != -1) {
|
|
isPeak = true;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
isPeak = HC_Chen_detect(value);
|
|
#endif
|
|
|
|
#if 0
|
|
static const float CV_LIMIT = 50.0f;
|
|
static const float THRESHOLD_FACTOR = 3.0f;
|
|
double mean = CalculateMean(value);
|
|
double rms = CalculateRootMeanSquare(value);
|
|
double cv = CalculateCoefficientOfVariation(value);
|
|
double threshold;
|
|
if (cv > CV_LIMIT) {
|
|
threshold = rms;
|
|
} else {
|
|
threshold = rms * (cv / 100.0f) * THRESHOLD_FACTOR;
|
|
}
|
|
bool is_peak;
|
|
SignalPoint result;
|
|
result = PeakDetect(value, m_qrs.time_cnt, threshold, &is_peak);
|
|
if (result.index != -1) {
|
|
if (is_peak) {
|
|
isPeak = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
|
|
double result = value;
|
|
double bandpass;
|
|
double integral;
|
|
double square;
|
|
|
|
bandpass = result;
|
|
result = Derivative(result);
|
|
result = Squar(result);
|
|
square = result;
|
|
result = MovingWindowIntegral(result);
|
|
integral = result;
|
|
|
|
SignalPoint peak = ThresholdCalculate(m_qrs.time_cnt,value,bandpass,square,integral);
|
|
|
|
if(peak.index != -1){
|
|
isPeak = true;
|
|
}
|
|
|
|
#endif
|
|
|
|
// if (isPeak) {
|
|
// uint32_t time_diff = m_qrs.time_cnt - m_qrs.last_qrs_point;
|
|
// m_qrs.last_qrs_point = m_qrs.time_cnt;
|
|
// m_qrs.heartSignalCnt++;
|
|
// // m_qrs.heart_rate = m_qrs.heartSignalCnt;
|
|
// if (m_qrs.last_qrs_point != 0) {
|
|
// m_qrs.heart_rate = 60 * (1 / (time_diff * 1.0 / SAMPLING_RATE));
|
|
// }
|
|
// }
|
|
|
|
m_qrs.time_cnt++;
|
|
}
|
|
|
|
static const void compute_heart_rate(float sample_data) {
|
|
ZASSERT(SAMPLE_RATE == 500);
|
|
m_qrs.index_f = m_frame_index / 500.0 * 360;
|
|
if ((m_qrs.index_f - m_qrs.index) > 1) {
|
|
m_qrs.index = m_qrs.index_f;
|
|
float val = sample_data;
|
|
QRS_process(FIR_filter(val));
|
|
}
|
|
}
|
|
|
|
static void swap_buffer() {
|
|
if (m_capture_buffer == NULL) {
|
|
m_capture_buffer = m_capture_buffer_a;
|
|
m_capture_buffer_index = 0;
|
|
return;
|
|
}
|
|
|
|
if (m_capture_buffer == m_capture_buffer_a) {
|
|
m_capture_buffer = m_capture_buffer_b;
|
|
} else {
|
|
m_capture_buffer = m_capture_buffer_a;
|
|
}
|
|
m_capture_buffer_index = 0;
|
|
return;
|
|
}
|
|
static float amp_val(uint16_t val, uint16_t valcener, float amp) {
|
|
float valf = (float)val - valcener;
|
|
valf = valf * amp;
|
|
valf += valcener;
|
|
|
|
if (valf >= 100) {
|
|
valf = 100;
|
|
}
|
|
|
|
if (valf <= 0) {
|
|
valf = 0;
|
|
}
|
|
return valf;
|
|
}
|
|
|
|
typedef struct {
|
|
float value;
|
|
float efectiveFactor;
|
|
} filter_t;
|
|
|
|
filter_t m_filter = {0, 0.8};
|
|
|
|
static float Filter(filter_t* filter, float newInput) {
|
|
float newv = ((float)filter->value * (1.0f - filter->efectiveFactor)) + ((float)newInput * filter->efectiveFactor);
|
|
filter->value = newv;
|
|
return newv;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* 小包数据上报 *
|
|
*******************************************************************************/
|
|
static inline void prvf_little_block_cache_push_one_frame(uint16_t data) {
|
|
if (m_little_frame_index >= LITTLE_DATA_BLOCK_FRAME_NUM) {
|
|
return;
|
|
}
|
|
m_sensor_little_frame_cache[m_little_frame_index].data = data;
|
|
m_little_frame_index++;
|
|
}
|
|
|
|
static inline bool prvf_light_block_cache_is_full(void) {
|
|
if (m_little_frame_index >= LITTLE_DATA_BLOCK_FRAME_NUM) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
static inline void prvf_light_block_cache_clear(void) { m_little_frame_index = 0; }
|
|
static inline void prvf_light_block_trigger_event() {
|
|
static app_event_t event;
|
|
event.eventType = kevent_capture_little_data_block_event;
|
|
for (uint32_t i = 0; i < LITTLE_DATA_BLOCK_FRAME_NUM; i++) {
|
|
event.val.little_data_block.data[i].data = m_sensor_little_frame_cache[i].data;
|
|
}
|
|
event.val.little_data_block.frameIndex = m_frame_index - LITTLE_DATA_BLOCK_FRAME_NUM;
|
|
// ZLOGI("%d", event.val.little_data_block.frameIndex);
|
|
AppEvent_pushEvent(&event);
|
|
}
|
|
|
|
void nrfx_timer_event_handler(nrf_timer_event_t event_type, void* p_context) { //
|
|
uint16_t val = BoardEcgSensor_plod_get_ecg_val(); // 12bit
|
|
float val_af100 = (float)val / 4096.0f * 100;
|
|
if (m_prestart_flag) {
|
|
compute_heart_rate(val_af100);
|
|
return;
|
|
} else {
|
|
compute_heart_rate(val_af100);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* 显示数据计算并赋值 *
|
|
*******************************************************************************/
|
|
|
|
m_frame_index++;
|
|
val_af100 = amp_val(val_af100, 45, 3.5f);
|
|
val_af100 = Filter(&m_filter, val_af100);
|
|
m_sensor_display_data = val_af100;
|
|
|
|
/*******************************************************************************
|
|
* 采样数据缓存 *
|
|
*******************************************************************************/
|
|
if (m_capture_buffer == NULL) {
|
|
swap_buffer();
|
|
}
|
|
|
|
if (m_capture_buffer_index < 128) {
|
|
m_capture_buffer[m_capture_buffer_index++] = val;
|
|
}
|
|
|
|
if (m_capture_buffer_index == 128) {
|
|
app_event_t evt;
|
|
evt.eventType = kevent_capture_256data_event;
|
|
evt.val.capture_data_cache = (uint8_t*)m_capture_buffer;
|
|
swap_buffer();
|
|
AppEvent_pushEvent(&evt);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* 实时采样数据事件上报 *
|
|
*******************************************************************************/
|
|
/**
|
|
* @brief 缓存数据,并触发小数据块事件
|
|
*/
|
|
prvf_little_block_cache_push_one_frame(val);
|
|
if (prvf_light_block_cache_is_full()) {
|
|
prvf_light_block_trigger_event();
|
|
prvf_light_block_cache_clear();
|
|
}
|
|
}
|
|
|
|
void hwss_init(void) {
|
|
static bool m_timer_inited = false;
|
|
if (!m_timer_inited) {
|
|
/**
|
|
* @brief 初始化定时器
|
|
*/
|
|
static nrfx_timer_config_t timer_cfg = {
|
|
.frequency = NRF_TIMER_FREQ_500kHz,
|
|
.mode = NRF_TIMER_MODE_TIMER,
|
|
.bit_width = NRF_TIMER_BIT_WIDTH_24,
|
|
.p_context = NULL,
|
|
.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY,
|
|
};
|
|
|
|
// nrfx_timer_init(&m_timer, &timer_cfg, nrfx_timer_event_handler);
|
|
ZERROR_CHECK(nrfx_timer_init(&m_timer, &timer_cfg, nrfx_timer_event_handler));
|
|
uint32_t timer_ticks = nrfx_timer_ms_to_ticks(&m_timer, 2); //
|
|
ZASSERT(SAMPLE_RATE == 500);
|
|
nrfx_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, timer_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
|
|
m_timer_inited = true;
|
|
}
|
|
}
|
|
void hwss_uninit(void) { nrfx_timer_disable(&m_timer); }
|
|
|
|
void hwss_pre_start_capture(void) {
|
|
m_start_capture_tp = znordic_getpower_on_s();
|
|
swap_buffer();
|
|
m_frame_index = 0;
|
|
|
|
QRS_reset();
|
|
|
|
prvf_light_block_cache_clear();
|
|
nrfx_timer_enable(&m_timer);
|
|
m_prestart_flag = true;
|
|
}
|
|
|
|
void hwss_start_capture(void) { m_prestart_flag = false; }
|
|
void hwss_stop_capture(void) {
|
|
nrfx_timer_disable(&m_timer);
|
|
m_frame_index = 0;
|
|
prvf_light_block_cache_clear();
|
|
}
|
|
|
|
float hwss_read_val(void) {
|
|
__disable_irq();
|
|
float val = m_sensor_display_data;
|
|
__enable_irq();
|
|
return val;
|
|
}
|
|
float hwss_read_heart_rate(void) { //
|
|
return m_qrs.heart_rate;
|
|
}
|
|
|
|
int hwss_has_captured_time_ms() { return (znordic_getpower_on_s() - m_start_capture_tp) * 1000; }
|