XiNian_dada's Blog - 单片机
type: channel
link: https://www.leeinx.com/tag/%E5%8D%95%E7%89%87%E6%9C%BA/
items: ESP32 + PulseSensor 全踩坑记录

ESP32 + PulseSensor 全踩坑记录
type: item
date: 2026-04-01T20:21:00+08:00
description: 哥们玩的就是心跳起因是哥们有兴趣做一个项目, 其中的一个核心功能就是检测用户的生理状态, 包括心率, 并推算出 HRV 判断情绪波动于是乎为了节约Money就先搞了一套 ESP32-S3-N16R8 和一个PulseSensor传感器(一个三线模拟脉搏传感器)三个线还是很好接的罢传感器 + -> ESP32 3V3 传感器 - -> ESP32 GND 传感器 S -> GPIO4接完线以后链接ArduinoIDE, 烧录了一个简单的代码先测一下东西的好坏int val = analogRead(4); Serial.println(val);直接连接串口, 笔者用的是MAC, 于是直接cu连接, 把手放上去看到有信号输出bernard@BernarddeMacBook-Pro ~ % ls /dev/cu.* /dev/cu.Bluetooth-Incoming-Port /dev/cu.debug-console /dev/cu.usbmodem5B8F0730691 bernard@BernarddeMacBook-Pro ~ % sudo cu -l /dev/cu.usbmodem5B8F0730691 -s 115200 3400 3395 3410 3388我以为最开始就能看到那种心率的波形图, 结果很惨, 看到的全是高频输出的数值 3000 多的数据, 甚至手在不在上面也在乱动去查一下才发现这个心率传感器输出的不是心率信息, 而是光强模拟电压, 而心率需要我们从其中的大电压上的微小变化提取出来, 这才是真正的心跳然后我实现了一个简单的心率波形图的实现, 然后用Python做上位机用来读数据画图我懵掉了, 乱七八糟的, 我尝试用一个这个电压减去一个固定的阈值来分离出心跳数据, 结果结果只是如上图但问题是, 这个基础阈值是可能因为各种环境变化, 比如压力 光线等变化的于是乎去查了一下知乎的一篇文章 玩的就是心跳 —— 使用 PulseSensor 脉搏传感器测量心率 看到他们用的是一种动态阈值和上升沿检测之后我把它的算法核心分为了三步第一步是一个滑动窗口, 采集最近 50 个采样点第二步找出其中的最大值最小值取平均, 作为一个动态阈值第三步检测穿过中线的瞬间, 即只在穿过中线的那一刻算一次心跳这点确实是我没想到的, 我一直想的是, 在一段时间中的最大值, 作为一个检测心跳的事件, 结果跨国阈值才是记录心跳的效果远远好于这个一旦能检测到了心率, 后面的事情就简单了, 根据 HRV 的常用算法补了一些关键数据, 这个demo就实现力分享下我使用的代码:import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from scipy import signal # ========================= # 串口配置 上位机 # ========================= PORT = "/dev/cu.usbmodem5B8F0730691" BAUD = 115200 # ========================= # 缓冲区长度 # ========================= MAX_WAVE_POINTS = 300 MAX_IBI_POINTS = 180 # 最近 180 个 IBI，够做短时 HRV MIN_IBI_MS = 300 MAX_IBI_MS = 2000 # ========================= # 全局数据 # ========================= wave_data = deque([0] * MAX_WAVE_POINTS, maxlen=MAX_WAVE_POINTS) ibi_history = deque(maxlen=MAX_IBI_POINTS) current_bpm = 0 current_ibi = 0 last_serial_time = 0 lock = threading.Lock() # ========================= # HRV 计算函数 # ========================= def compute_time_domain_metrics(ibi_list_ms): """ ibi_list_ms: list of IBI in milliseconds """ if len(ibi_list_ms) < 3: return None ibi = np.array(ibi_list_ms, dtype=float) mean_i…