diff --git a/.vscode/settings.json b/.vscode/settings.json index 82c7cc1..dfdff98 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -184,7 +184,8 @@ "optional": "c", "so_chen_detect.h": "c", "adaptive_algorithm.h": "c", - "pan_tompkins_detect.h": "c" + "pan_tompkins_detect.h": "c", + "qrs_time_domain_zh.h": "c" }, "files.encoding": "gbk" } \ No newline at end of file diff --git a/app/app.uvoptx b/app/app.uvoptx index 9ec7873..49b65de 100644 --- a/app/app.uvoptx +++ b/app/app.uvoptx @@ -715,6 +715,18 @@ 0 0 + + 1 + 30 + 1 + 0 + 0 + 0 + .\src\basic\qrs_time_domain_zh.c + qrs_time_domain_zh.c + 0 + 0 + @@ -725,7 +737,7 @@ 0 2 - 30 + 31 1 0 0 @@ -745,7 +757,7 @@ 0 3 - 31 + 32 1 0 0 @@ -757,7 +769,7 @@ 3 - 32 + 33 1 0 0 @@ -777,7 +789,7 @@ 0 4 - 33 + 34 1 0 0 @@ -797,7 +809,7 @@ 0 5 - 34 + 35 1 0 0 @@ -809,7 +821,7 @@ 5 - 35 + 36 1 0 0 @@ -821,7 +833,7 @@ 5 - 36 + 37 1 0 0 @@ -833,7 +845,7 @@ 5 - 37 + 38 1 0 0 @@ -845,7 +857,7 @@ 5 - 38 + 39 1 0 0 @@ -857,7 +869,7 @@ 5 - 39 + 40 1 0 0 @@ -869,7 +881,7 @@ 5 - 40 + 41 1 0 0 @@ -881,7 +893,7 @@ 5 - 41 + 42 1 0 0 @@ -901,7 +913,7 @@ 0 6 - 42 + 43 1 0 0 @@ -921,7 +933,7 @@ 0 7 - 43 + 44 1 0 0 @@ -933,7 +945,7 @@ 7 - 44 + 45 1 0 0 @@ -945,7 +957,7 @@ 7 - 45 + 46 1 0 0 @@ -957,7 +969,7 @@ 7 - 46 + 47 1 0 0 @@ -969,7 +981,7 @@ 7 - 47 + 48 1 0 0 @@ -981,7 +993,7 @@ 7 - 48 + 49 1 0 0 @@ -993,7 +1005,7 @@ 7 - 49 + 50 1 0 0 @@ -1005,7 +1017,7 @@ 7 - 50 + 51 1 0 0 @@ -1017,7 +1029,7 @@ 7 - 51 + 52 1 0 0 @@ -1029,7 +1041,7 @@ 7 - 52 + 53 1 0 0 @@ -1041,7 +1053,7 @@ 7 - 53 + 54 1 0 0 @@ -1053,7 +1065,7 @@ 7 - 54 + 55 1 0 0 @@ -1065,7 +1077,7 @@ 7 - 55 + 56 1 0 0 @@ -1077,7 +1089,7 @@ 7 - 56 + 57 1 0 0 @@ -1089,7 +1101,7 @@ 7 - 57 + 58 1 0 0 @@ -1101,7 +1113,7 @@ 7 - 58 + 59 1 0 0 @@ -1113,7 +1125,7 @@ 7 - 59 + 60 1 0 0 @@ -1125,7 +1137,7 @@ 7 - 60 + 61 1 0 0 @@ -1137,7 +1149,7 @@ 7 - 61 + 62 1 0 0 @@ -1149,7 +1161,7 @@ 7 - 62 + 63 1 0 0 @@ -1169,7 +1181,7 @@ 0 8 - 63 + 64 1 0 0 @@ -1181,7 +1193,7 @@ 8 - 64 + 65 1 0 0 @@ -1193,7 +1205,7 @@ 8 - 65 + 66 1 0 0 @@ -1205,7 +1217,7 @@ 8 - 66 + 67 1 0 0 @@ -1217,7 +1229,7 @@ 8 - 67 + 68 1 0 0 @@ -1229,7 +1241,7 @@ 8 - 68 + 69 1 0 0 @@ -1241,7 +1253,7 @@ 8 - 69 + 70 1 0 0 @@ -1253,7 +1265,7 @@ 8 - 70 + 71 1 0 0 @@ -1265,7 +1277,7 @@ 8 - 71 + 72 1 0 0 @@ -1277,7 +1289,7 @@ 8 - 72 + 73 1 0 0 @@ -1289,7 +1301,7 @@ 8 - 73 + 74 1 0 0 @@ -1301,7 +1313,7 @@ 8 - 74 + 75 1 0 0 @@ -1313,7 +1325,7 @@ 8 - 75 + 76 1 0 0 @@ -1325,7 +1337,7 @@ 8 - 76 + 77 1 0 0 @@ -1337,7 +1349,7 @@ 8 - 77 + 78 1 0 0 @@ -1349,7 +1361,7 @@ 8 - 78 + 79 1 0 0 @@ -1361,7 +1373,7 @@ 8 - 79 + 80 1 0 0 @@ -1373,7 +1385,7 @@ 8 - 80 + 81 1 0 0 @@ -1385,7 +1397,7 @@ 8 - 81 + 82 1 0 0 @@ -1397,7 +1409,7 @@ 8 - 82 + 83 1 0 0 @@ -1409,7 +1421,7 @@ 8 - 83 + 84 1 0 0 @@ -1421,7 +1433,7 @@ 8 - 84 + 85 1 0 0 @@ -1433,7 +1445,7 @@ 8 - 85 + 86 1 0 0 @@ -1445,7 +1457,7 @@ 8 - 86 + 87 1 0 0 @@ -1457,7 +1469,7 @@ 8 - 87 + 88 1 0 0 @@ -1469,7 +1481,7 @@ 8 - 88 + 89 1 0 0 @@ -1481,7 +1493,7 @@ 8 - 89 + 90 1 0 0 @@ -1501,7 +1513,7 @@ 0 9 - 90 + 91 1 0 0 @@ -1513,7 +1525,7 @@ 9 - 91 + 92 1 0 0 @@ -1525,7 +1537,7 @@ 9 - 92 + 93 1 0 0 @@ -1537,7 +1549,7 @@ 9 - 93 + 94 1 0 0 @@ -1549,7 +1561,7 @@ 9 - 94 + 95 1 0 0 @@ -1561,7 +1573,7 @@ 9 - 95 + 96 1 0 0 @@ -1581,7 +1593,7 @@ 0 10 - 96 + 97 1 0 0 @@ -1593,7 +1605,7 @@ 10 - 97 + 98 1 0 0 @@ -1605,7 +1617,7 @@ 10 - 98 + 99 1 0 0 @@ -1625,7 +1637,7 @@ 0 11 - 99 + 100 1 0 0 @@ -1637,7 +1649,7 @@ 11 - 100 + 101 1 0 0 @@ -1649,7 +1661,7 @@ 11 - 101 + 102 1 0 0 @@ -1669,7 +1681,7 @@ 0 12 - 102 + 103 1 0 0 @@ -1681,7 +1693,7 @@ 12 - 103 + 104 1 0 0 @@ -1693,7 +1705,7 @@ 12 - 104 + 105 1 0 0 @@ -1713,7 +1725,7 @@ 0 13 - 105 + 106 1 0 0 @@ -1725,7 +1737,7 @@ 13 - 106 + 107 1 0 0 @@ -1745,7 +1757,7 @@ 0 14 - 107 + 108 1 0 0 @@ -1757,7 +1769,7 @@ 14 - 108 + 109 1 0 0 @@ -1769,7 +1781,7 @@ 14 - 109 + 110 1 0 0 @@ -1781,7 +1793,7 @@ 14 - 110 + 111 1 0 0 diff --git a/app/app.uvprojx b/app/app.uvprojx index 472a262..7aada8c 100644 --- a/app/app.uvprojx +++ b/app/app.uvprojx @@ -528,6 +528,11 @@ 1 .\src\basic\Pan_Tompkins_detect.c + + qrs_time_domain_zh.c + 1 + .\src\basic\qrs_time_domain_zh.c + @@ -4437,6 +4442,11 @@ 1 .\src\basic\Pan_Tompkins_detect.c + + qrs_time_domain_zh.c + 1 + .\src\basic\qrs_time_domain_zh.c + diff --git a/app/src/basic/qrs_time_domain_zh.c b/app/src/basic/qrs_time_domain_zh.c new file mode 100644 index 0000000..01d45e7 --- /dev/null +++ b/app/src/basic/qrs_time_domain_zh.c @@ -0,0 +1,218 @@ +#include "qrs_time_domain_zh.h" + +#include +#include +#include + +#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; } \ No newline at end of file diff --git a/app/src/basic/qrs_time_domain_zh.h b/app/src/basic/qrs_time_domain_zh.h new file mode 100644 index 0000000..37f6f43 --- /dev/null +++ b/app/src/basic/qrs_time_domain_zh.h @@ -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 +#define TABLE_SIZE 500 + +void QRS_resetBuf(); +void QRS_processData(uint16_t data); +uint16_t QRS_getHeartRate(); diff --git a/app/src/heart_wave_sample_service.c b/app/src/heart_wave_sample_service.c index eaf7518..21a95b0 100644 --- a/app/src/heart_wave_sample_service.c +++ b/app/src/heart_wave_sample_service.c @@ -2,11 +2,7 @@ #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 "basic/qrs_time_domain_zh.h" #include "board/board_ecg_sensor.h" #include "nrfx_timer.h" #include "one_conduction_board.h" @@ -23,117 +19,9 @@ 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; @@ -207,19 +95,13 @@ static inline void prvf_light_block_trigger_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); - } + uint16_t val = BoardEcgSensor_plod_get_ecg_val(); // 12bit /******************************************************************************* * 显示数据计算并赋值 * *******************************************************************************/ - + QRS_processData(val); + float val_af100 = (float)val / 4096.0f * 100; m_frame_index++; val_af100 = amp_val(val_af100, 45, 3.5f); val_af100 = Filter(&m_filter, val_af100); @@ -281,19 +163,16 @@ void hwss_init(void) { } void hwss_uninit(void) { nrfx_timer_disable(&m_timer); } -void hwss_pre_start_capture(void) { +void hwss_start_capture(void) { m_start_capture_tp = znordic_getpower_on_s(); swap_buffer(); m_frame_index = 0; - QRS_reset(); + QRS_resetBuf(); 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; @@ -307,7 +186,7 @@ float hwss_read_val(void) { return val; } float hwss_read_heart_rate(void) { // - return m_qrs.heart_rate; + return QRS_getHeartRate(); } int hwss_has_captured_time_ms() { return (znordic_getpower_on_s() - m_start_capture_tp) * 1000; } diff --git a/app/src/heart_wave_sample_service.c.bak b/app/src/heart_wave_sample_service.c.bak new file mode 100644 index 0000000..eaf7518 --- /dev/null +++ b/app/src/heart_wave_sample_service.c.bak @@ -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; } diff --git a/app/src/heart_wave_sample_service.h b/app/src/heart_wave_sample_service.h index 177feed..65e18dc 100644 --- a/app/src/heart_wave_sample_service.h +++ b/app/src/heart_wave_sample_service.h @@ -7,7 +7,6 @@ typedef void (*heart_wave_sample_service_callback_t)(uint16_t *p_data, uint16_t void hwss_init(void); void hwss_uninit(void); -void hwss_pre_start_capture(void); void hwss_start_capture(void); void hwss_stop_capture(void); diff --git a/app/src/one_conduction_main.c b/app/src/one_conduction_main.c index 21d3205..b42a0dc 100644 --- a/app/src/one_conduction_main.c +++ b/app/src/one_conduction_main.c @@ -98,7 +98,7 @@ void ENTER_DEEP_SLEEP() { NVIC_SystemReset(); } -static void check_battery_level() {} + /******************************************************************************* * 状态切换方法 * *******************************************************************************/ @@ -229,7 +229,7 @@ static void app_event_listener(void* p_event_data, uint16_t event_size) { if (!BoardBattery_get_charging_state()) { ds_change_to_state(kdevice_state_keep_still); dsp_mgr_change_to_preparePage(); - hwss_pre_start_capture(); + // hwss_pre_start_capture(); } } @@ -252,7 +252,7 @@ static void app_event_listener(void* p_event_data, uint16_t event_size) { if (!BoardEcgSensor_plod_get_connected_state_after_filter()) { // 如果用户未保持静止,切换到首页 state_machine__change_to_home_state(); - hwss_stop_capture(); + // hwss_stop_capture(); } else { /******************************************************************************* * 页面加载中 *