心率数据加入平滑处理,加入模拟活体检测机制
author1624952103 <pl@DESKTOP-EN9LKR5.localdomain>
Tue, 9 Jun 2026 10:53:54 +0000 (18:53 +0800)
committer1624952103 <pl@DESKTOP-EN9LKR5.localdomain>
Tue, 9 Jun 2026 10:53:54 +0000 (18:53 +0800)
code/sdk/application/wearable/n9/nativeapp/nativeui/heartrate/src/HeartModel.cpp
code/sdk/zhihui/drivers/ppg/hal/ppg_hal.c
code/sdk/zhihui/drivers/ppg/hal/ppg_hal.h
code/sdk/zhihui/drivers/ppg/pah831x/pah_sensor/pah831x_main.c
code/sdk/zhihui/drivers/ppg/pah831x/pah_sensor/pah831x_main.h

index 60f18f1..8362fb7 100644 (file)
@@ -575,7 +575,7 @@ void HeartModel::Heart_Result() {
     thresholdValue = ConfigModel::GetInstance()->get_configData_int(
         ConfigModel::CONFIGMODEL_ITEM_HEARTRATE_THRESHOLD_VALUE);
 
-    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "心率提醒状态:%d,阈值:%d,當前值:%d",
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "心率提醒状态:%d,阈值:%d,当前值:%d",
                   heartRemindState, thresholdValue, currentheart);
 
     if (heartRemindState) {
index a294eec..ce80a1c 100644 (file)
@@ -159,6 +159,17 @@ bool is_ppg_bp_need_cali(void)
     return 1;
 }
 
+uint32_t ppg_read_ecg_raw(int32_t *buf, uint32_t buf_len)
+{
+#ifdef PPG_PAH831X
+    return pah831x_read_ecg_raw(buf, buf_len);
+#else
+    (void)buf;
+    (void)buf_len;
+    return 0;
+#endif
+}
+
 uint8_t ppg_get_wear_status(void)
 {
     uint8_t wear_status = 0;
index c221c31..1370435 100644 (file)
@@ -77,6 +77,13 @@ void ppg_set_bp_cali_data(int *sbp,int *dbp);
 //是否需要血压校准 1是 0否
 bool is_ppg_bp_need_cali(void);
 
+/* 读取 ECG 原始 ADC(int32 样本)。
+ * buf: 输出缓冲,建议 int32_t buf[128];
+ * buf_len: buf 可容纳的样本个数,建议传 PPG_ECG_RAW_READ_BUF_SIZE;
+ * 返回: 本次读到的样本数(<= buf_len),0 表示暂无新数据。
+ * 开始/停止 ecgbp、ecgafib、ecgraw 时驱动内部自动清缓冲。 */
+uint32_t ppg_read_ecg_raw(int32_t *buf, uint32_t buf_len);
+
 #ifdef __cplusplus
 #if __cplusplus
 }
index 957d5e7..4022989 100644 (file)
@@ -12,6 +12,7 @@
 #include "pah831x_main.h"\r
 #include "pah831x_comm.h"\r
 #include "cmsis_os2.h"\r
+#include "osal_mutex.h"\r
 // c\r
 #include <stdarg.h>\r
 #include <stdio.h>\r
@@ -138,15 +139,12 @@ static uint16_t acc_num = 50;
 \r
 static int32_t factory_data = 0;\r
 static uint8_t work_mode = 0;\r
-static uint8_t wear_status = 0;\r
 \r
 static osTimerId_t g_pah831x_timer = NULL;\r
 \r
 static FILE *logFp = NULL;\r
 static uint8_t *log_swtich = 0;\r
 static int time_out = 200;\r
-static uint8_t time_cnt = 0;\r
-static uint8_t wear_cnt = 0;\r
 \r
 static int history_wear_status = -1;\r
 static uint8_t ppg_work_status = 0 ;\r
@@ -157,6 +155,87 @@ static uint8_t ret_wear = 0;
 static ecg_data_out _ecg_data_out_alg;\r
 \r
 static uint8_t bp_cali_cnt = 0;\r
+\r
+#ifdef ENABLE_PAH831X_ECG_FUNCTION\r
+#define ECG_RAW_RING_CAPACITY 8192\r
+static int32_t g_ecg_raw_ring[ECG_RAW_RING_CAPACITY];\r
+static uint32_t g_ecg_raw_wr = 0;\r
+static uint32_t g_ecg_raw_count = 0;\r
+static osal_mutex g_ecg_raw_mutex;\r
+static uint8_t g_ecg_raw_mutex_inited = 0;\r
+\r
+static uint8_t living_status = 0;\r
+\r
+\r
+#define HRS_WINDOW_SIZE 7\r
+static uint8_t hrs_num = 0;\r
+static int32_t sum_hrs = 0;\r
+static int32_t hrs_data[7] = {0};\r
+static uint8_t left = 0;\r
+static uint8_t right = 0;\r
+static uint8_t time_cnt = 0;\r
+static uint8_t living_wear_cnt = 0;\r
+\r
+static void ecg_raw_mutex_init(void)\r
+{\r
+    if (g_ecg_raw_mutex_inited == 0) {\r
+        if (osal_mutex_init(&g_ecg_raw_mutex) == 0) {\r
+            g_ecg_raw_mutex_inited = 1;\r
+        }\r
+    }\r
+}\r
+\r
+static void ecg_raw_mutex_lock(void)\r
+{\r
+    ecg_raw_mutex_init();\r
+    if (g_ecg_raw_mutex_inited) {\r
+        osal_mutex_lock(&g_ecg_raw_mutex);\r
+    }\r
+}\r
+\r
+static void ecg_raw_mutex_unlock(void)\r
+{\r
+    if (g_ecg_raw_mutex_inited) {\r
+        osal_mutex_unlock(&g_ecg_raw_mutex);\r
+    }\r
+}\r
+\r
+static uint32_t ecg_raw_ring_rd_pos(void)\r
+{\r
+    return (g_ecg_raw_wr + ECG_RAW_RING_CAPACITY - g_ecg_raw_count) % ECG_RAW_RING_CAPACITY;\r
+}\r
+\r
+static void ecg_raw_ring_reset(void)\r
+{\r
+    ecg_raw_mutex_lock();\r
+    g_ecg_raw_wr = 0;\r
+    g_ecg_raw_count = 0;\r
+    ecg_raw_mutex_unlock();\r
+}\r
+\r
+static void ecg_raw_ring_push(const int32_t *data, uint32_t num)\r
+{\r
+    uint32_t i;\r
+\r
+    if (data == NULL || num == 0) {\r
+        return;\r
+    }\r
+\r
+    ecg_raw_mutex_lock();\r
+    for (i = 0; i < num; i++) {\r
+        g_ecg_raw_ring[g_ecg_raw_wr] = data[i];\r
+        g_ecg_raw_wr = (g_ecg_raw_wr + 1) % ECG_RAW_RING_CAPACITY;\r
+        if (g_ecg_raw_count < ECG_RAW_RING_CAPACITY) {\r
+            g_ecg_raw_count++;\r
+        }\r
+    }\r
+    ecg_raw_mutex_unlock();\r
+}\r
+#else\r
+static void ecg_raw_ring_reset(void)\r
+{\r
+}\r
+#endif\r
 //void demo_pah831x(void);\r
 \r
 static bool sensor_basic_verification(main_state *this, pah_plat *plat);\r
@@ -815,6 +894,55 @@ ecg_data_out get_ecg_data_out(void)
     return _ecg_data_out ;\r
 }\r
 \r
+#ifdef ENABLE_PAH831X_ECG_FUNCTION\r
+uint32_t pah831x_read_ecg_raw(int32_t *buf, uint32_t buf_len)\r
+{\r
+    uint32_t to_read = 0;\r
+    uint32_t rd = 0;\r
+    uint32_t i = 0;\r
+\r
+    if (buf == NULL || buf_len == 0) {\r
+        return 0;\r
+    }\r
+\r
+    ecg_raw_mutex_lock();\r
+    to_read = buf_len;\r
+    if (to_read > g_ecg_raw_count) {\r
+        to_read = g_ecg_raw_count;\r
+    }\r
+\r
+    rd = ecg_raw_ring_rd_pos();\r
+    for (i = 0; i < to_read; i++) {\r
+        buf[i] = g_ecg_raw_ring[(rd + i) % ECG_RAW_RING_CAPACITY];\r
+    }\r
+    g_ecg_raw_count -= to_read;\r
+    ecg_raw_mutex_unlock();\r
+\r
+    return to_read;\r
+}\r
+\r
+static void pah831x_notify_ecg_raw(const ecg_data_out *out)\r
+{\r
+    if (out == NULL || out->ecg_raw_data == NULL || out->sample_num_per_ch == 0) {\r
+        return;\r
+    }\r
+\r
+    ecg_raw_ring_push((const int32_t *)out->ecg_raw_data, out->sample_num_per_ch);\r
+}\r
+#else\r
+uint32_t pah831x_read_ecg_raw(int32_t *buf, uint32_t buf_len)\r
+{\r
+    (void)buf;\r
+    (void)buf_len;\r
+    return 0;\r
+}\r
+\r
+static void pah831x_notify_ecg_raw(const ecg_data_out *out)\r
+{\r
+    (void)out;\r
+}\r
+#endif\r
+\r
 uint8_t get_ecg_enable(void)\r
 {\r
     return _state.config.ecg_is_enabled ;\r
@@ -898,6 +1026,61 @@ uint16_t get_pah831x_touch_value()
 }\r
 \r
 \r
+static void get_avg_hrs(uint8_t hrs)\r
+{\r
+    if (hrs_num >= HRS_WINDOW_SIZE) {\r
+        sum_hrs -= hrs_data[left];\r
+        left = (left + 1) % HRS_WINDOW_SIZE;\r
+        hrs_num--;\r
+    }\r
+\r
+    hrs_data[right] = hrs;\r
+    sum_hrs += hrs;\r
+    right = (right + 1) % HRS_WINDOW_SIZE;\r
+    hrs_num++;\r
+\r
+\r
+    if (hrs_num > 2) \r
+    {\r
+\r
+        uint8_t first_idx = left;\r
+        int32_t min_hrs = hrs_data[first_idx];\r
+        int32_t max_hrs = hrs_data[first_idx];\r
+\r
+\r
+        for (uint8_t i = 0; i < hrs_num; i++) {\r
+            uint8_t current_idx = (left + i) % HRS_WINDOW_SIZE;\r
+            uint8_t current_hrs = hrs_data[current_idx];\r
+\r
+            if (current_hrs < min_hrs) {\r
+                min_hrs = current_hrs;\r
+            }\r
+            if (current_hrs > max_hrs) {\r
+                max_hrs = current_hrs;\r
+            }\r
+        }\r
+\r
+        uint8_t valid_count = hrs_num - 2;\r
+        ret_hrs = (sum_hrs - max_hrs - min_hrs) / valid_count;\r
+\r
+\r
+        // if (last_hrs >= 30 && last_hrs <= 210) \r
+        // {\r
+        //     int16_t diff = avg_hrs - last_hrs;\r
+        //     if (abs(diff) > 10) \r
+        //     {\r
+        //         if (diff > 0) \r
+        //             avg_hrs = last_hrs + 5;\r
+        //         else \r
+        //             avg_hrs = last_hrs - 5;\r
+        //     }\r
+        // }\r
+        // last_hrs = avg_hrs;\r
+        // printf("max_hrs[%d],min_hrs[%d],avg_hrs[%d]\n",max_hrs,min_hrs,avg_hrs);\r
+    }\r
+}\r
+\r
+\r
 static void pah831x_hr_timer_handler(void)\r
 {\r
     pah831x_sensor_task();\r
@@ -946,11 +1129,71 @@ static void pah831x_hr_timer_handler(void)
         );\r
         LOG_PRINT("_ppg_data_out_alg.is_touched  %d\n",  _ppg_data_out_alg.is_touched );\r
         LOG_PRINT("hr_out:%d,hr_trust_level_out:%d,acc_num:%d\n",  hr_out,hr_trust_level_out,acc_num);\r
+        LOG_PRINT("living_status:%d\n",living_status);\r
 \r
-//    \r
-        ret_hrs = hr_out;\r
         ret_wear = _ppg_data_out_alg.is_touched;\r
 \r
+        if(hr_out)\r
+        {\r
+            if(hr_trust_level_out < 2)\r
+                living_status = 2;\r
+            else\r
+                living_status = 1;\r
+        }\r
+        else\r
+            living_status = 0;\r
+\r
+//    \r
+        time_cnt++;\r
+\r
+        if(time_cnt >= 5)\r
+        {\r
+            time_cnt = 0;\r
+            if(uapi_dev_runtime_get_train_status())\r
+            {\r
+                if(hr_out >= 30 && hr_out <= 210)\r
+                    get_avg_hrs(hr_out);\r
+            }\r
+            else\r
+            {\r
+                if(living_status == 1)\r
+                {\r
+                    living_wear_cnt = 0;\r
+\r
+                    if(hr_out >= 30 && hr_out <= 210)\r
+                        get_avg_hrs(hr_out);\r
+\r
+                    living_status = 1;\r
+                    #ifdef SUPPORT_CYWEE_SERVICE\r
+                    cywee_wear_status_update(&living_status);\r
+                    #endif\r
+                }\r
+                else if(living_status == 2)\r
+                {\r
+                    living_wear_cnt++;\r
+                    if(living_wear_cnt >= 5)\r
+                    {\r
+                        ret_hrs = 0;\r
+                        living_status = 2;\r
+                        ret_wear = 0;\r
+                        history_wear_status = 0;\r
+                        // hx3695_unwearstatus_update();\r
+\r
+                        #ifdef SUPPORT_CYWEE_SERVICE\r
+                        cywee_wear_status_update(&living_status);\r
+                        #endif\r
+                        living_wear_cnt = 0;\r
+                    }\r
+                }\r
+                else\r
+                {\r
+                    living_wear_cnt = 0;\r
+                    living_status = 0;\r
+                }\r
+            }\r
+        }\r
+\r
+\r
         clear_ppg_data_ready();\r
     }\r
     if((get_ppg_enable())!=(ppg_work_status)){\r
@@ -983,11 +1226,36 @@ static void pah831x_wear_timer_handler(void)
         // printf("history_wear_status:%d,ret_wear:%d\n",history_wear_status,ret_wear);\r
         if(history_wear_status != ret_wear)\r
         {\r
+            if(uapi_dev_runtime_get_train_status() && !ret_wear)\r
+                ret_hrs = 0;\r
+\r
             history_wear_status = ret_wear;\r
-        #ifdef SUPPORT_CYWEE_SERVICE\r
-                // do_cywee_service_check_msg(CYWEE_SERVICE_CMD_WEAR_STATUS_CHANGE,&wear_status);\r
-                cywee_wear_status_update(&ret_wear);\r
-        #endif\r
+\r
+            if(uapi_dev_runtime_get_sleep_monitoring_status())\r
+            {\r
+                if(!ret_wear)\r
+                {\r
+                    #ifdef SUPPORT_CYWEE_SERVICE\r
+                    cywee_wear_status_update(&ret_wear);\r
+                    #endif\r
+                }\r
+                else if(living_status == 1 && ret_wear)\r
+                {\r
+                    #ifdef SUPPORT_CYWEE_SERVICE\r
+                    cywee_wear_status_update(&ret_wear);\r
+                    #endif\r
+                }\r
+            }\r
+            else\r
+            {\r
+                if(living_status != 2)\r
+                {\r
+\r
+    #ifdef SUPPORT_CYWEE_SERVICE\r
+                    cywee_wear_status_update(&ret_wear);\r
+    #endif\r
+                }\r
+            }\r
         }\r
 \r
         clear_ppg_data_ready();\r
@@ -1385,6 +1653,8 @@ static void pah831x_ecg_raw_timer_handler(void)
     {\r
         _ecg_data_out_alg = get_ecg_data_out() ;\r
 \r
+        pah831x_notify_ecg_raw(&_ecg_data_out_alg);\r
+\r
         report_ecg_fifo_data(\r
             (uint32_t)_ecg_data_out_alg.end_timestamp,     \r
             _ecg_data_out_alg.duration, \r
@@ -1534,6 +1804,7 @@ void pah831x_ecg_afib_start(void)
 \r
 void pah831x_ecg_raw_start(void)\r
 {\r
+    ecg_raw_ring_reset();\r
     pah831x_sensor_init();\r
     pah831x_sensor_start(PAH831X_WORK_POLLING, PAH831X_INIT_ALWAYS_RUN, PAH831X_PPG_REG_SETTING_IDLE, PAH831X_ECG_REG_SETTING_1, 250, 2);\r
     ecg_filter_init(); \r
@@ -1550,6 +1821,16 @@ void pah831x_hr_stop(void)
 {\r
     pah_sensor_stop();\r
     hr_algorithm_close();\r
+\r
+    hrs_num = 0;\r
+    sum_hrs = 0;\r
+    memset(hrs_data,0,7);\r
+\r
+    left = 0;\r
+    right = 0;\r
+    ret_hrs = 0;\r
+    living_wear_cnt = 0;\r
+    living_status = 0;\r
 }\r
 \r
 void pah831x_hrv_stop(void)\r
@@ -1589,6 +1870,7 @@ void pah831x_rr_stop(void)
 \r
 void pah831x_ecg_raw_stop(void)\r
 {\r
+    ecg_raw_ring_reset();\r
     pah_sensor_stop();\r
     ecg_filter_close();\r
 }\r
@@ -1675,10 +1957,10 @@ void pah831x_sensor_stop(void)
     else if(work_mode == 9)\r
         pah831x_ecg_raw_stop();\r
 \r
-    ret_hrs = 0;\r
     ret_spo2 = 0;\r
     ret_hrv = 0;\r
     ret_wear = 0;\r
+    history_wear_status = -1;\r
 \r
     memset(acc_data, 0, sizeof(acc_data));\r
 }\r
index 3a62944..6d4c424 100644 (file)
@@ -378,4 +378,6 @@ void pah831x_save_calidata(void);
 \r
 void pah831x_set_bp_cali_data(int *sbp,int *dbp);\r
 \r
-bool is_pah831x_need_cali(void);
\ No newline at end of file
+bool is_pah831x_need_cali(void);\r
+\r
+uint32_t pah831x_read_ecg_raw(int32_t *buf, uint32_t buf_len);
\ No newline at end of file