9 changed files with 663 additions and 214 deletions
-
3.vscode/settings.json
-
174app/app.uvoptx
-
10app/app.uvprojx
-
218app/src/basic/qrs_time_domain_zh.c
-
17app/src/basic/qrs_time_domain_zh.h
-
135app/src/heart_wave_sample_service.c
-
313app/src/heart_wave_sample_service.c.bak
-
1app/src/heart_wave_sample_service.h
-
6app/src/one_conduction_main.c
@ -0,0 +1,218 @@ |
|||
#include "qrs_time_domain_zh.h" |
|||
|
|||
#include <stdbool.h> |
|||
#include <stdint.h> |
|||
#include <string.h> |
|||
|
|||
#define HEART_RATE_FILTER_SIZE 5 |
|||
|
|||
typedef struct { |
|||
uint16_t data[HEART_RATE_FILTER_SIZE]; |
|||
uint16_t data_process_buf[HEART_RATE_FILTER_SIZE]; |
|||
uint32_t cnt; |
|||
uint32_t index; |
|||
} HeartRateMedianFilter_t; // 中值滤波器 |
|||
|
|||
typedef struct { |
|||
uint16_t data[HEART_RATE_FILTER_SIZE]; |
|||
uint32_t cnt; |
|||
uint32_t index; |
|||
uint32_t sum; |
|||
} HeartRateMeanFilter_t; // 均值滤波器 |
|||
|
|||
HeartRateMedianFilter_t m_heart_rate_median_filter; |
|||
HeartRateMeanFilter_t m_heart_rate_mean_filter; |
|||
|
|||
static void HeartRateMedianFilter_reset() { |
|||
memset(m_heart_rate_median_filter.data, 0, sizeof(m_heart_rate_median_filter.data)); |
|||
m_heart_rate_median_filter.cnt = 0; |
|||
m_heart_rate_median_filter.index = 0; |
|||
} |
|||
static uint16_t HeartRateMedianFilter_process(uint16_t data) { |
|||
HeartRateMedianFilter_t* pfilter = &m_heart_rate_median_filter; |
|||
|
|||
pfilter->data[pfilter->index] = data; |
|||
pfilter->index++; |
|||
pfilter->cnt++; |
|||
if (pfilter->index >= 5) { |
|||
pfilter->index = 0; |
|||
} |
|||
|
|||
if (pfilter->cnt < 5) { |
|||
return data; |
|||
} |
|||
|
|||
memcpy(pfilter->data_process_buf, pfilter->data, HEART_RATE_FILTER_SIZE * sizeof(uint16_t)); |
|||
for (uint8_t i = 0; i < HEART_RATE_FILTER_SIZE; i++) { |
|||
for (uint8_t j = i + 1; j < HEART_RATE_FILTER_SIZE; j++) { |
|||
if (pfilter->data_process_buf[i] > pfilter->data_process_buf[j]) { |
|||
uint16_t temp = pfilter->data_process_buf[i]; |
|||
pfilter->data_process_buf[i] = pfilter->data_process_buf[j]; |
|||
pfilter->data_process_buf[j] = temp; |
|||
} |
|||
} |
|||
} |
|||
return pfilter->data_process_buf[2]; |
|||
} |
|||
static void HeartRateMeanFilter_reset() { |
|||
memset(m_heart_rate_mean_filter.data, 0, sizeof(m_heart_rate_mean_filter.data)); |
|||
m_heart_rate_mean_filter.cnt = 0; |
|||
m_heart_rate_mean_filter.index = 0; |
|||
m_heart_rate_mean_filter.sum = 0; |
|||
} |
|||
static uint16_t HeartRateMeanFilter_process(uint16_t data) { |
|||
HeartRateMeanFilter_t* pfilter = &m_heart_rate_mean_filter; |
|||
|
|||
pfilter->sum -= pfilter->data[pfilter->index]; |
|||
pfilter->data[pfilter->index] = data; |
|||
pfilter->sum += data; |
|||
|
|||
pfilter->index++; |
|||
pfilter->cnt++; |
|||
|
|||
if (pfilter->index >= 5) { |
|||
pfilter->index = 0; |
|||
} |
|||
|
|||
if (pfilter->cnt < 5) { |
|||
return data; |
|||
} |
|||
|
|||
return pfilter->sum / 5; |
|||
} |
|||
|
|||
static uint16_t m_data[TABLE_SIZE]; |
|||
static uint32_t m_ndata = 0; |
|||
static uint32_t m_dataindex = 0; |
|||
static uint32_t m_data_cnt = 0; |
|||
static uint16_t m_heartrate = 0; |
|||
|
|||
static uint32_t m_datasum = 0; |
|||
static float m_avg = 0; |
|||
static uint32_t m_max_val_in_m_data; |
|||
|
|||
static bool m_findpeak = false; |
|||
|
|||
static uint16_t pQRS_median_filter_cache[5]; |
|||
static uint16_t pQRS_median_filter_cache_index = 0; |
|||
static uint16_t pQRS_median_filter_cache_cnt = 0; |
|||
|
|||
static uint32_t m_last_peak_pos = 0; |
|||
static uint32_t m_peakcnt = 0; |
|||
|
|||
static uint16_t pQRS_median_filter(uint16_t indata) { |
|||
// memcpy(pQRS_median_filter_cache + 1, pQRS_median_filter_cache, 4 * sizeof(uint16_t)); |
|||
pQRS_median_filter_cache[pQRS_median_filter_cache_index] = indata; |
|||
pQRS_median_filter_cache_index++; |
|||
pQRS_median_filter_cache_cnt++; |
|||
if (pQRS_median_filter_cache_index >= 5) { |
|||
pQRS_median_filter_cache_index = 0; |
|||
} |
|||
|
|||
if (pQRS_median_filter_cache_cnt < 5) { |
|||
return indata; |
|||
} |
|||
|
|||
static uint16_t process_cache[5]; |
|||
memcpy(process_cache, pQRS_median_filter_cache, 5 * sizeof(uint16_t)); |
|||
for (uint8_t i = 0; i < 5; i++) { |
|||
for (uint8_t j = i + 1; j < 5; j++) { |
|||
if (process_cache[i] > process_cache[j]) { |
|||
uint16_t temp = process_cache[i]; |
|||
process_cache[i] = process_cache[j]; |
|||
process_cache[j] = temp; |
|||
} |
|||
} |
|||
} |
|||
return process_cache[2]; |
|||
} |
|||
|
|||
static uint32_t pQRS_findMaxValue() { |
|||
uint32_t max_val = 0; |
|||
for (uint32_t i = 0; i < TABLE_SIZE; i++) { |
|||
if (m_data[i] > max_val) { |
|||
max_val = m_data[i]; |
|||
} |
|||
} |
|||
return max_val; |
|||
} |
|||
|
|||
void QRS_resetBuf() { // |
|||
m_ndata = 0; |
|||
m_dataindex = 0; |
|||
m_heartrate = 0; |
|||
m_data_cnt = 0; |
|||
memset(m_data, 0, sizeof(m_data)); |
|||
m_datasum = 0; |
|||
m_findpeak = false; |
|||
pQRS_median_filter_cache_index = 0; |
|||
pQRS_median_filter_cache_cnt = 0; |
|||
m_peakcnt = 0; |
|||
|
|||
HeartRateMedianFilter_reset(); |
|||
HeartRateMeanFilter_reset(); |
|||
} |
|||
|
|||
|
|||
void QRS_processData(uint16_t _data) { |
|||
uint16_t data = pQRS_median_filter(_data); |
|||
/******************************************************************************* |
|||
* 填充BUF * |
|||
*******************************************************************************/ |
|||
m_datasum -= m_data[m_dataindex]; |
|||
m_data[m_dataindex] = data; |
|||
m_datasum += data; |
|||
|
|||
m_data_cnt++; |
|||
|
|||
if (m_dataindex < TABLE_SIZE) { |
|||
m_dataindex++; |
|||
} else { |
|||
m_dataindex = 0; |
|||
} |
|||
|
|||
m_ndata++; |
|||
if (m_ndata > TABLE_SIZE) { |
|||
m_ndata = TABLE_SIZE; |
|||
} |
|||
|
|||
/******************************************************************************* |
|||
* 求BUF的平均值和最大值 * |
|||
*******************************************************************************/ |
|||
if (m_ndata == TABLE_SIZE) { |
|||
m_avg = (float)m_datasum / m_ndata; |
|||
m_max_val_in_m_data = pQRS_findMaxValue(); |
|||
} |
|||
|
|||
/******************************************************************************* |
|||
* 寻找QRS波峰和波谷 * |
|||
*******************************************************************************/ |
|||
|
|||
if (!m_findpeak) { |
|||
uint16_t thresholdValue = (m_max_val_in_m_data - m_avg) * 0.666 + m_avg; |
|||
if (data > thresholdValue) { |
|||
m_findpeak = true; |
|||
m_peakcnt++; |
|||
|
|||
if (m_last_peak_pos != 0) { |
|||
uint16_t diff_peak_pos = m_data_cnt - m_last_peak_pos; |
|||
if (diff_peak_pos > 0) { |
|||
// |
|||
// m_heartrate = 60 * 500 / diff_peak_pos; |
|||
|
|||
uint16_t diff_peak_ms = diff_peak_pos * 2; // 500Hz |
|||
uint16_t heart_rate = 60 * 1000 / diff_peak_ms; |
|||
|
|||
m_heartrate = HeartRateMeanFilter_process(HeartRateMedianFilter_process(heart_rate)); |
|||
} |
|||
} |
|||
|
|||
m_last_peak_pos = m_data_cnt; |
|||
} |
|||
} else { |
|||
if (data < m_avg) { |
|||
m_findpeak = false; |
|||
} |
|||
} |
|||
} |
|||
uint16_t QRS_getHeartRate() { return m_heartrate; } |
@ -0,0 +1,17 @@ |
|||
/** |
|||
* @file qrs_time_domain_zh.h |
|||
* @author zhaohe (zhaohe@domain.com) |
|||
* @brief |
|||
* @version 0.1 |
|||
* @date 2024-02-10 |
|||
* |
|||
* @copyright Copyright (c) 2024 |
|||
* |
|||
*/ |
|||
#pragma once |
|||
#include <stdint.h> |
|||
#define TABLE_SIZE 500 |
|||
|
|||
void QRS_resetBuf(); |
|||
void QRS_processData(uint16_t data); |
|||
uint16_t QRS_getHeartRate(); |
@ -0,0 +1,313 @@ |
|||
#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" |
|||
#include "one_conduction_board.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; } |
Write
Preview
Loading…
Cancel
Save
Reference in new issue