O objetivo é tentar realizar um sistema para medição da pulsação ou batimentos cardíacos por minuto (BPM) usando abordagem fotométrica.
Etapas:
Projeto de um Sensor de PulsaçãoEmbasamento TeóricoTeste de SensoresPós-processamentoResultados (esperados)DicasO que funcionouDetalhesComentários sobre código desenvolvido
Sensor de Pulso (o que é um sensor de pulso, métodos)
Exemplo de sistema usando sensor comercial KS0015, Raspberry Pi e Python: Monitor de Frequência do Pulso. Acompanha considerações e etapas necessárias envolvidas com processamento do sinal.
Outros projetos usando outros sensores comerciais prontos:
Envolve:
Sinal bruto capturado na saída do sensor:
Sinal filtrado:
Ou, segue gráfico de um bag file com resultados do sinal bruto sinal filtrado:
Note que falta destacar os "vales" e detectar os picos nos vales. Ou seja, são necessários mais processamentos.
Resultado final desejado:
Obviamente, leia o material piublicado anteriormente (não há necessidade de testar todos os códigos apresentados, apenas entenda a "dimensão" do problema);
Se concentre no item: teste_pulse_monitor_3, para começar os trabalhos depois de eleger um sensor.
Obs.: é útil você conseguir gerar seus próprios "bag files", caso contrário pode baixar os arquivos disponibilizados no item anterior.
Siga para o item: Testes de Rotinas de Detecção de Picos. Aqui que começa a "brincadeira" com compor um algoritmo que aplique uma sequência de processamento sobre o sinal bruto à fim de obter o objetivo final desejado: estimar BPM.
Este item demonstra o teste de sequencia de processamentos sendo propostos usando bastante o Matlab para testes sobre dados reais (usando bag files, sem necessidade de uploads de firmwares para o Arduíno) ❗️
A idéia aqui é: primeiro teste sua sequência de algoritmos de processamento de sinal usando o Matlab e somente depois que alcançar resultados animadores, passe o algoritmo para o Arduíno.
O que funcionou para mim:
Vesão 5.2 de pulse_monitor_5_2.ino
, 294 linhas (incluindo comentários), bloco void loop(){}
vazio:
Sketch uses 6342 bytes (19%) of program storage space. Maximum is 32256 bytes.
Global variables use 415 bytes (20%) of dynamic memory, leaving 1633 bytes for local variables. Maximum is 2048 bytes.
Seguem alguns detalhes:
Seguem comentários sobre código desenvolvido:
ISR(TIMER1_COMPA_vect)
:
"Coração do código": roda uma sequencia de operações e funções à taxa de 50 Hz, alternando ainda nível lógico de um pino I/O de "controle" (útil para verificar "carga de processamento" no Arduino). Faz:
Executa o método media_x.adicionarValor(x)
para acrescentar valor bruto a um buffer circular;
Processa do filtro passa-faixa Hz sobre o dado A/D de entrada. Algo como:
xxxxxxxxxx
y = b0 * (float)x + b2 * (float)x2 + b4 * (float)x4 - a1 * y1 - a2 * y2 - a3 * y3 - a4 * y4;
Obs.: Parâmetros (variáveis) determinadas previamente com auxílio do Matlab.
Calcula derivada primeira sobre o sinal y
.
Calcula derivada segunda sobre o sinal y
.
Executa método media_x.mediaAtual
, testando: if (media_x.mediaAtual > 900)
para descobrir se dedo está (ou não) posicionado sobre o sensor (ativa variável DedoPosicionado = true
). Caso contrário não importa realizar outros processamentos sobre dados capturados.
Verifica se valor da derivada segunda < 0 e se derivada primeira > 0 e se y < threshold
para tentar identificar um mínimo local. Caso positivo, testa se está dentro um "vale" permitido (mínimo): if (k_atual > vale_min)
e então determina a distância entre as amostras dentro do intervalo válido: distancia = k_atual - 1
e calcula BPM: batidas = 3000.0 / distancia
. Depois disto acrescenta este valor ao buffer circular (objeto) usado para calcular média móvel dos BPM's: media_batidas.adicionarValor(batidas)
.
A rotina ISR ainda verifica o valor de k_atual
. Se k_atual > 100
significa que o intervalo para medição foi "perdido" (a variável é zerada). Isto implicaria na prática: 30 BPM's, considerado neste caso, como o menor valor admissível. Isto significa que a rotina ISR mostra novos valores de BPM, no máximo, à cada 2 segundos. No mínimo, a rotina ISR atualiza novos valores em 0,4 segudos (ver mais abaixo).
Algumas variáveis "interessantes":
float threshold = -0.2
: valor mínimo para considerar como pico negativo (valor determinado empiricamente com sensor adotado)int vale_min = 20
: distânica mínima à ser considerada entre picos. Note que 0,4 segundos, o que equivale à 150 BPM (valor máximo estimado de captura). Esta rotina ainda possui "aprimoramentos": a variável boolena ShowSerialPlot
que serve para indicar quando a placa deve mostrar texto na porta serial ou quando deve mostrar um gráfico. É uma variável global que deve ser ajustada no início do código antes da compilação para surtir efeito.
E esta rotina ainda alterna o nível lógico de um pino (monitor_isr
) da placa para indicar (via osciloscópio) tempo de processamento requerido para a rotina ISR.
Classe pública MediaMovel
criada para realizar média móvel sobre dados de entrada composta dos seguintes métodos:
void adicionarValor(int valor)
: adiciona valor ao buffer e calcula média continuamente, chamando método calcularMedia()
.
float getMediaAtual()
: retorna média atual.
void ShowMedia()
: mostra na porta serial valores armazenados no buffer (serve apenas para "debug").
void reiniciar()
: reinicia a classe, zerando buffer e média.
Classe instanciada como: MediaMovel media_batidas(NumPassosMediaBatidas)
, onde: int NumPassosMediaBatidas = 5
, o que implica que rotina ISR atualiza/trabalha com um buffer circular para os 5 valores estimados para o BPM, o que faz a rotina gerar a mensagem (no terminal serial):
xxxxxxxxxx
136 130 136 120 103 --> 125
Classe instanciada uma segunda vez com: MediaMovel media_x(10)
para realizar média móvel sobre os dados brutos x
, o que permite a rotina ISR identificar dedo posicionado sobre o cursor ou não. Neste caso, o buffer trata os últimos 10 valores adicionados ao mesmo. Na prática como a ISR é executa à 50 Hz, isto significa que em no máximo 0,2 segundos detecta dedo não posicionado sobre sensor e assim se consegue gerar mensagens como:
xxxxxxxxxx
Sensor ainda não posicionado!
83 *103
Sensor não posicionado!
65 36 125 125 88 --> 88
130 100 120 120 143 --> 122
O código final desenvolvido gera as seguintes mensagem no terminal (porta) serial:
x:: Pulse_monitor_5.2 ::........
© Fernando Passold, Nov 2024
Sensor ainda não posicionado!
83 *103
Sensor não posicionado!
65 36 125 125 88 --> 88
130 100 120 120 143 --> 122
150 120 136 125 125 --> 131
136 136 107 111 88 --> 116
125
Sensor não posicionado!
43 46 120 136 120 --> 93
125
Sensor não posicionado!
73 34 125 136 143 --> 102
111 115 73 130 125 --> 111
125 63 107 97 125 --> 103
136 130 136 120 103 --> 125
130 150 120 86 150 --> 127
120 143 125 125 115 --> 125
130 120 150 100 150 --> 130
150 136 115 120 136 --> 131
111 125 150 120 150 --> 131
150 150 103 107 103 --> 123
125 150
Sensor não posicionado!
Boa sorte com seu projeto!
🌊 Fernando Passold 📬 | Página originalmente criada em 04/10/2024 | atualizada em 03/09/2025.