目前市面上能够测量心率的设备很多。有腕带腕表式的,也有夹在耳朵或者手指末端的。从准确性上来说,腕带式的容易松动因此没有胸带式的准确。同时,胸带式的对于运动统计来说也是最好的选择。
前一段入手了三根心率带和一个接收模块。其中的心率带是带有编码的,因此在接收端可以很容易的区分数据来源。当然,与之对应的还有不带编码的心率带,无法在多个的情况下使用。
先说说心率带模块:
- 内部使用 CR2032 纽扣电池,据说每天工作1小时可以撑9个月;
- 发送频率为 2.4Ghz;
- 平时处于睡眠模式,戴上之后才开始发送数据
- 发射数据每次 4 Bytes,3 Bytes ID+ 1 Byte 心率。
接收模块:
- 模块三个引脚,GND VCC (特别强调是 3.3V供电) TX 。使用串口通讯。波特率 9600 bps。比如:DD 20 03 04 50(个人感觉有点低,设想如果很多根在同一个空间内使用不知道会有什么问题);
- 上面有一个LED,当收到有效数据后会闪动一次;
- 板子上的孔间距是 2.0mm 不是 2.54,使用杜邦线会很别扭,我直接焊接上导线来使用;

下面是一个完整的例子,使用 Arduino Leonardo 接收数据,将收到的数据总结为 A + 每分钟心跳数(bpm),或者 B +每分钟心跳数上传到USB串口,然后在上位机上显示出来。试验中使用的心率带心率带 A ID :22 05 28 心率带 B ID :22 06 58
- Arduino代码
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
}
boolean status1=false;
boolean status2=false;
boolean status3=false;
byte addr[3];
byte pos=0;
char outadd;
void loop() {
byte c;
while (Serial1.available())
{
c=Serial1.read()&0xFF;
Serial.write(c);
if ((status1==false)&&(c==0xdd)) {
status1=true;
status2=true;
// Serial.print("st1");
}
else if (status2) {
addr[pos]=c;
pos++;
if (pos==3) {
status2=false;
status3=true;
}
// Serial.print("st2");
}
else if (status3) {
//Serial.print("st3");
if ((addr[0]==0x22)&&(addr[1]==0x05)&&(addr[2]==0x28)) {
Serial.write('A');
Serial.write(c);
Serial.println("");
}
else
if ((addr[0]==0x22)&&(addr[1]==0x06)&&(addr[2]==0x58)) {
Serial.write('B');
Serial.write(c);
Serial.println("");
}
status1=false;
status2=false;
status3=false;
pos=0;
}
} //while
- C# 编写上位机程序,在界面上绘制当前心率曲线
完整代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace WindowsFormsApplication9
{
public partial class Form1 : Form
{
private Queue<double> dataQueue = new Queue<double>(100);
private Queue<double> dataQueue2 = new Queue<double>(100);
private int num = 5;//每次删除增加几个点
private int heartA;
private int heartB;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//textBox1.AppendText("start");
Form2 f2 = new Form2();
f2.ShowDialog();
serialPort1.PortName = "COM" + f2.SelectedPort.ToString();
// MessageBox.Show(f2.SelectedPort.ToString());
button3.Enabled = true;
button4.Enabled = true;
}
private void Form1_Load(object sender, EventArgs e)
{
//设置窗体为无边框样式
this.FormBorderStyle = FormBorderStyle.None;
//最大化窗体
this.WindowState = FormWindowState.Maximized;
InitChart();
chart1.Width = this.Width - 20;
chart1.Height = this.Height - 100;
button3.Enabled = false;
button4.Enabled = false;
//如果没有这句话,串口收到的只有0-128 的 ASCII
serialPort1.Encoding = System.Text.Encoding.GetEncoding(28591);
serialPort1.DtrEnable = true;
serialPort1.RtsEnable = true;
}
/// <summary>
/// 初始化图表
/// </summary>
private void InitChart()
{
//定义图表区域
this.chart1.ChartAreas.Clear();
ChartArea chartArea1 = new ChartArea("C1");
this.chart1.ChartAreas.Add(chartArea1);
//定义存储和显示点的容器
this.chart1.Series.Clear();
Series series1 = new Series("S1");
series1.ChartArea = "C1";
this.chart1.Series.Add(series1);
Series series2 = new Series("S2");
this.chart1.Series.Add(series2);
//设置图表显示样式
this.chart1.ChartAreas[0].AxisY.Minimum = 50;
this.chart1.ChartAreas[0].AxisY.Maximum = 170;
this.chart1.ChartAreas[0].AxisY.Interval = 10;
this.chart1.ChartAreas[0].AxisX.Interval = 5;
this.chart1.ChartAreas[0].AxisX.MajorGrid.LineColor = System.Drawing.Color.Silver;
this.chart1.ChartAreas[0].AxisY.MajorGrid.LineColor = System.Drawing.Color.Silver;
//设置标题
this.chart1.Titles.Clear();
this.chart1.Titles.Add("Red");
this.chart1.Titles[0].Text = "Heart";
this.chart1.Titles[0].Font = new System.Drawing.Font("Microsoft Sans Serif", 12F);
this.chart1.Titles[0].ForeColor = Color.Red;
//设置图表显示样式
this.chart1.Series[0].Color = Color.Red;
this.chart1.Series[0].ChartType = SeriesChartType.Line;
this.chart1.Series[0].Points.Clear();
this.chart1.Series[1].Color = Color.Blue;
this.chart1.Series[1].ChartType = SeriesChartType.Line;
}
private void button2_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void timer1_Tick(object sender, EventArgs e)
{
UpdateQueueValue();
this.chart1.Series[0].Points.Clear();
this.chart1.Series[1].Points.Clear();
for (int i = 0; i < dataQueue.Count; i++)
{
this.chart1.Series[0].Points.AddXY((i + 1), dataQueue.ElementAt(i));
this.chart1.Series[1].Points.AddXY((i + 1), dataQueue2.ElementAt(i));
}
}
//更新队列中的值
private void UpdateQueueValue()
{
int heart=0;
if (dataQueue.Count >= 100)
{
//先出列
for (int i = 0; i < num; i++)
{
dataQueue.Dequeue();
dataQueue2.Dequeue();
}
}
//Random r = new Random();
for (int i = 0; i < num; i++)
{
//heart = r.Next(50, 170);
dataQueue.Enqueue(heartA);
//heart = r.Next(50, 170);
dataQueue2.Enqueue(heartB);
}
chart1.Titles[0].Text = "Heart"+ heart.ToString();
}
private void button3_Click(object sender, EventArgs e)
{
this.serialPort1.Open();
this.timer1.Enabled = true;
this.timer2.Enabled = true;
}
private void button4_Click(object sender, EventArgs e)
{
this.timer1.Enabled = false;
this.timer2.Enabled = false;
serialPort1.Close();
this.timer1.Stop();
this.timer2.Stop();
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
}
private void timer2_Tick(object sender, EventArgs e)
{
if (serialPort1.BytesToRead!=0) {
String s=serialPort1.ReadLine();
if (s.Length>=2) {
if (s[0] == 'A') {
heartA = s[1]; // Convert.ToInt32(s[1]);
textBox1.AppendText(heartA.ToString());
}
else
if (s[0] == 'B')
{
heartB = s[1];
//textBox1.AppendText(heartB.ToString("X2"));
}
}
}
}
}
}
运行结果:

工作的视频在 https://zhuanlan.zhihu.com/p/56291800