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:
xxxxxxxxxxy = 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):
xxxxxxxxxx136 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:
xxxxxxxxxxSensor ainda não posicionado!83 *103Sensor não posicionado!65 36 125 125 88 --> 88130 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 2024Sensor ainda não posicionado!83 *103Sensor não posicionado!65 36 125 125 88 --> 88130 100 120 120 143 --> 122150 120 136 125 125 --> 131136 136 107 111 88 --> 116125Sensor não posicionado!43 46 120 136 120 --> 93125Sensor não posicionado!73 34 125 136 143 --> 102111 115 73 130 125 --> 111125 63 107 97 125 --> 103136 130 136 120 103 --> 125130 150 120 86 150 --> 127120 143 125 125 115 --> 125130 120 150 100 150 --> 130150 136 115 120 136 --> 131111 125 150 120 150 --> 131150 150 103 107 103 --> 123125 150Sensor não posicionado!
Boa sorte com seu projeto!
🌊 Fernando Passold 📬 | Página originalmente criada em 04/10/2024 | atualizada em 03/09/2025.