Em um hospital, mais especificamente em um leito de UTI, ou na enfermaria mesmo, temos vários displays, sensores e atuadores para monitorar o paciente e disponibilizar vários dados invasivos e não invasivos ao médico para um diagnóstico preciso.
Pela resolução 7 do ministério da saúde[1], são necessários mais de 39 equipamentos e materiais, no mínimo, para abertura de um leito de UTI em hospitais. Além disso, são obrigatórios equipamentos para monitoramento contínuo de:
- frequência respiratória;
- oximetria de pulso;
- frequência cardíaca;
- cardioscopia;
- temperatura;
- pressão arterial não invasiva.
Os monitores de sinais vitais, chamados de monitores multiparamétricos, são os responsáveis por realizar a leitura de sensores e mostrar os dados lidos em tempo real para médicos e enfermeiros. Também conseguem emitir alarmes sonoros e visuais para emergências detectadas.
Alguns modelos contém a opção de salvamento de dados do paciente por um determinado período de tempo, para análises futuras e para facilitar o diagnóstico por parte de médicos.
Por mais que pareça complicado, podemos fazer um monitor que leia alguns sinais vitais (ECG, oximetria e temperatura) e apresente os dados em um display OLED utilizando um Arduino e alguns módulos específicos que já falamos aqui no site:
O projeto proposto irá realizar as seguintes funções:
- medir o sinal de ECG proveniente do módulo AD8232 utilizando o ADC do Arduino e o pino A0;
- medir o sinal de oximetria proveniente do módulo MAX30102 através de comunicação I2C;
- calcular a frequência cardíaca utilizando o sinal de oximetria coletado;
- calcular a saturação do oxigênio no sangue (SpO2) utilizando o sinal de oximetria coletado;
- medir a temperatura através do sensor de temperatura interno do módulo MAX30102;
- medir a tensão de alimentação da bateria utilizando o ADC do Arduino e o pino A1, para detectarmos quando a bateria está fraca;
- apresentar as seguintes informações no display OLED:
- gráfico do sinal de oximetria;
- gráfico do sinal de ECG;
- frequência de aquisição dos sinais;
- frequência cardíaca;
- saturação de oxigênio (SpO2);
- temperatura corporal (medida no dedo através do sensor de oximetria);
- aviso de sensor de oximetria solto (dedo não detectado no sensor);
- tensão de alimentação.
Materiais
Materiais | Quantidade |
Arduino Uno | 1 |
Protoboard | 1 |
Módulo de oximetria MAX30102 | 1 |
Módulo ECG AD8232 | 1 |
Resistor 510kΩ | 2 |
Display OLED 0.96′ com comunicação I2C | 1 |
Jumpers | diversos |
Bateria 9V com conector P4 | 1 |
Montagem
As ligações dos módulos no Arduino são apresentadas a seguir. Como a tensão da bateria é de 9V, e a tensão máxima que podemos ligar nas portas analógicas do Arduino é de 5V, utilizamos um divisor de tensão para ler essa tensão, formado por 2 resistores iguais ligados em série, e lendo a tensão entre eles (usando o pino A1), de modo que a tensão efetivamente lida é a metade da tensão de alimentação, que pode ser lida diretamente da bateria, ou que pode ser obtida utilizando o pino Vin do Arduino, que ‘copia’ a tensão de alimentação, como mostrado a seguir.
Se você ainda não sabe como uma protoboard funciona, não deixe de consultar nosso post ‘Conheça o Arduino‘, onde explicamos como utilizar protoboards, resistores e muito mais!
***ATENÇÃO! Verifique corretamente as tensões dos módulos, tensões diferentes podem causar o mau funcionamento do módulo ou até danificá-lo***
Código
O código para esse projeto pode ser encontrado neste link. Ele é composto por 5 arquivos, para facilitar a organização e entendimento do código. Para fazer o download de cada arquivo, basta clicar no arquivo desejado e, na tela de visualização do código, clicar no botão ‘Raw’. Os arquivos são:
- monitor_multiparametrico_Uno.ino: arquivo com o programa principal;
- ecg.h: contém as funções relacionadas com a leitura do ECG através do módulo AD8232, utilizando o conversor AD do Arduino e o pino A0;
- oximetro.h: contém as funções relacionadas com o módulo de oximetria MAX30102, como tratamento dos dados e comunicação via I2C;
- tela.h: contém as funções utilizadas para desenhar os gráficos e apresentar outras informações na tela OLED;
- spo2.h: chamada internamente pela biblioteca oximetro.h, este arquivo contém uma função modificada para cálculo da saturação do oxigênio (SpO2), que precisa de menos memória que as funções originais da fabricante do sensor.
O código principal (monitor_multiparametrico_Uno.ino) é apresentado a seguir:
/*******************************************************************************
*
* Exemplo de monitor multiparamétrico usando Arduino Uno.
* Este exemplo utiliza:
* - display OLED com controlador SSD1306 para gráficos e informações;
* - módulo de oximetria MAX30102 (comunicação I2C (pinos A4 e A5));
* - módulo de ECG AD8232 (pino A0);
* - monitoramento da bateria (pino A1).
*
* Criado por: Erick León, 2023
*
********************************************************************************/
// Bibliotecas utilizadas:
#include <Wire.h> // comuicação I2C (OLED e Oxímetro)
#include <Adafruit_SSD1306.h> // comunicação com o display OLED
// Configuração do display OLED. Parâmetros:
// - largura_do_display: 128 pixels
// - altura_do_display: 64 pixels
// - biblioteca de comunicação I2C utilizada
// - pino de Reset do display (-1 em caso de não ter)
Adafruit_SSD1306 display(128, 64, &Wire, -1);
const byte NUM_PONTOS_GRAF = 55; // Número de pontos dos gráficos
unsigned long tempoAnterior = millis(); // para medir taxa de medição dos dados
float freqAquisicao; // taxa de medição dos dados
// Outras bibliotecas com as funções utilizadas:
#include "ecg.h" // funções do ECG AD8232
#include "oximetro.h" // funções do sensor MAX30102
#include "tela.h" // funções do display OLED
/////////////////////////////////////////////////////////////////////
// esta função só roda uma vez, no início
void setup() {
Wire.begin(); // inicia a comunicação I2C
delay(500); // aguarda 500ms
configuraAdcEcg();
inicializaDisplay();
inicializaOximetro();
}
/////////////////////////////////////////////////////////////////////
// esta função se repete indefinidamente
void loop() {
// mede a taxa de medição dos dados
freqAquisicao = 1000.0/(millis()-(float)tempoAnterior);
tempoAnterior = millis();
medeOximetro(); // dispara leitura do oxímetro e do ECG
desenhaTela(); // atualiza tela
}
Como podemos ver, inicialmente são chamadas as bibliotecas utilizadas (Wire.h para comunicação I2C com o módulo de oximetria, Adafruit_SSD1306.h para comunicação com o display OLED, ecg.h com as funções de coleta dos dados de ECG, oximetro.h com as funções de comunicação e tratamento dos dados do módulo MAX30102, e tela.h com as funções para desenhar os gráficos e apresentar os valores no display OLED). Em seguida, a função setup() realiza a configuração do ADC (para leitura dos dados do ECG), do display OLED e do oxímetro MAX30102.
Na função loop(), temos o cálculo da frequência de aquisição dos dados (que está sendo feita o mais rápido possível, já que não temos nenhum delay ao longo do programa), a aquisição dos dados do oxímetro e do ECG, e a atualização da tela.
O arquivo ecg.h contém as funções relacionadas ao ADC do Arduino:
/*******************************************************************************
* Funções usadas com o sensor AD8232 (ECG).
*
* Criado por: Erick León, 2023
********************************************************************************/
const byte NUM_PONTOS_ECG = NUM_PONTOS_GRAF; // Número de pontos do gráfico do ECG
int ecg_vec[NUM_PONTOS_ECG]; // vetor com as medidas atuais
bool lendo_bateria=false; // variável que verifica se é uma leitura de bateria
int tensao_bateria=0;
////////////////////////////////////////////////////////////////
// 'Anda' com o vetor de medidas do ADC, e guarda a nova medida
// na última posição.
// Esta interrupção roda assim que o ADC termina uma medida.
ISR (ADC_vect){
if(lendo_bateria){
tensao_bateria = ADC; // lê valor do ADC (tensão na bateria)
ADMUX = bit (REFS0) | (0 & 7); // volta para canal do ECG (A0)
lendo_bateria = false;
}
else{ // lendo ECG
// 'anda' com os dados do gráfico para a esquerda e guarda nova medida
for (int idx=0; idx<NUM_PONTOS_ECG-1;idx++) ecg_vec[idx] = ecg_vec[idx+1];
ecg_vec[NUM_PONTOS_ECG-1] = ADC; // lê valor do ADC (sinal de ECG)
}
}
////////////////////////////////////////////////////////////////
// Configura o ADC com prescaler de 128, leitura única no pino
// A0, disparando interrupção (ADC_vect) ao final da medição.
// Esta função só roda quando chamada.
void configuraAdcEcg(){
// configuração do ADC usado para medir o sinal de ECG
ADCSRA = bit (ADEN) | bit (ADIE) | bit (ADIF); // liga o ADC e interrupção (ADC_vect)
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // Prescaler de 128
ADMUX = bit (REFS0) | (0 & 7); // leitura do canal 0 (A0)
}
////////////////////////////////////////////////////////////////
// Inicia uma conversão (medida de tensão na bateria) do ADC.
// Esta função só roda quando chamada.
void disparaLeituraBateria(){
lendo_bateria=true; // avisa a interrupção do ADC que não é leitura de ECG
ADMUX = bit (REFS0) | (1 & 7); // leitura do canal 1 (A1)
bitSet (ADCSRA, ADSC); // inicia uma medida no ADC
}
////////////////////////////////////////////////////////////////
// Inicia uma conversão (medida de ECG) do ADC.
// Esta função só roda quando chamada.
void disparaLeituraEcg(){
bitSet (ADCSRA, ADSC); // inicia uma medida no ADC
}
Aqui, para melhor controle e rapidez, trabalhamos com os registradores do Arduino para operar seu conversor analógico-digital (ADC), ao invés de utilizar a função pronta analogRead(). Com isso, conseguimos um controle muito maior do ADC, como a possibilidade de utilizar uma interrupção ao término da medida (uma função que é chamada automaticamente toda vez que uma medida do ADC é finalizada). Outra vantagem de se utilizar os registradores é que, ao usar a função analogRead(), o programa fica ‘parado’ nessa função até o término da medição, enquanto ao usarmos um registrador para disparar a medida, o resto do programa continua rodando e podemos ir fazendo outras coisas enquanto a medição é feita pelo hardware.
A configuração do ADC é feita pela função configuraAdcEcg(), que liga o ADC, habilita a interrução, configura o prescaler e o canal utilizado para medição do sinal de ECG (canal A0). Para saber mais sobre a configuração do ADC, não deixe de ler nosso post sobre ADC!
Para disparar uma medida, temos que alterar o bit ADSC no registrador ADCSRA, o que é feito pela função disparaLeituraEcg(). Esse disparo também é feito pela função disparaLeituraBateria(), que lê a tensão da bateria no canal A1, mas nesse caso primeiro alteramos o registrador ADMUX, informando que iremos ler o canal A1, e depois disparamos a medição. Ao término da medição, o Arduino interrompe (pausa) o que estava fazendo e roda a interrupção ISR(ADC_vect). Nessa interrupção, temos duas possibilidades: se a leitura foi do ECG ou da bateria (o que é controlado pela variável lendo_bateria). Se foi uma leitura da bateria, guarda o valor lido e altera o registrador ADMUX para voltar a ler sinais do ECG no canal A0. Se foi uma leitura do ECG, guarda o valor lido na última posição de um vetor, ‘andando’ com o restante dos valores.
O arquivo oximetro.h possui as funções relacionadas com o módulo oxímetro MAX30102:
/*******************************************************************************
* Funções usadas com o sensor MAX30102 (oxímetro).
*
* Criado por: Erick León, 2023
********************************************************************************/
#include "MAX30105.h" // comunicação com sensor MAX30102
#define PONTOS_PICO_FREQ 5 // nro de pontos para verificar se ocorreu pico
const byte NUM_PONTOS_OX = NUM_PONTOS_GRAF; // Número de pontos do gráfico do Oxim
MAX30105 meuOximetro; // declaração do sensor MAX30102
const byte NUM_PONTOS_FREQ = 4; // Número de medidas usadas na média
byte freqs_vec[NUM_PONTOS_FREQ]; // Vetor de medidas de frequência
byte posFreq = 0; // guarda a posição atual do vetor
float batidasPorMinuto; // frequência (batidas por minuto)
int freqMedia; // média da frequência
long valorIR; // valor lido pelo sensor
int valorIR_vec[NUM_PONTOS_OX]; // vetor com valores lidos (IR)
long maximoOxim; // valor máximo do gráfico do oxímetro
long minimoOxim; // valor mínimo do gráfico do oxímetro
long amplitudeOxim; // amplitude do gráfico do oxímetro
unsigned long ultimaBatida = millis(); // instante da última batida
int contadorTemperatura = 0; // contador da última medida da temperatura
int contadorBatida = 100; // contador da última batida detectada
float temperatura; // valor da temperatura
long media = 0; // média dos valores lidos
// para o cálculo do SpO2:
int16_t spo2 = 0; // guarda o valor de SpO2
#include "spo2.h" // biblioteca modificada p/ cálculo do SpO2
/////////////////////////////////////////////////////////////////////
// Inicializa a comunicação com o módulo de oximetria MAX30102.
// Esta função só roda quando chamada.
void inicializaOximetro(){
display.clearDisplay(); // limpa o buffer da tela
display.setCursor(0,0); // coloca o cursor de texto no canto superior esquerdo do buffer da tela
// Initializa o sensor MAX30102, usando a porta I2C padrão, velocidade de 400kHz
if (!meuOximetro.begin(Wire, I2C_SPEED_FAST)) // se não encontrou
{
display.print(F("Sensor nao encontrado")); // coloca mensagem de erro no buffer
display.display(); // atualiza a tela com as informações do buffer
while (1); // Se não encontra o sensor, fica parado aqui em um loop infinito
}
display.println(F("Coloque o dedo no sensor")); // coloca mensagem no buffer
display.display(); // atualiza a tela com as informações do buffer
delay(1000); // aguarda 1 segundo
// Configura o sensor MAX30102
meuOximetro.setup();
temperatura = meuOximetro.readTemperature(); // mede temperatura inicial
}
/////////////////////////////////////////////////////////////////////
// Encontra os valores máximo e mínimo e a amplitude dos dados do
// oxímetro.
// Esta função só roda quando chamada.
void encontraMaxMinOxim(){
//encontra máximos e mínimos dos valores lidos
maximoOxim = -2000000;
minimoOxim = 2000000;
for (int idx=0; idx<NUM_PONTOS_OX;idx++){
if (valorIR_vec[idx] > maximoOxim) maximoOxim = valorIR_vec[idx];
if (valorIR_vec[idx] < minimoOxim) minimoOxim = valorIR_vec[idx];
}
amplitudeOxim = maximoOxim-minimoOxim;
}
/////////////////////////////////////////////////////////////////////
// Verifica se ocorreu uma batida cardíaca procurando por um pico
// no sinal do oxímetro.
// Esta função só roda quando chamada.
void verificaBatidaOximetro(){
encontraMaxMinOxim(); // encontra amplitude dos dados do oxímetro
//verifica se ocorreu um pico
int distancia = PONTOS_PICO_FREQ; // distância entre pontos para verificar um pico
long variacao = (float)amplitudeOxim/5.0; // variação mínima para considerar pico
unsigned long tempoAtual = millis();
// condição 1: ponto candidato acima do ponto à frente
bool condicao1 = valorIR_vec[NUM_PONTOS_OX-distancia-1]-valorIR_vec[NUM_PONTOS_OX-1]>variacao;
// condição 2: ponto candidato acima do ponto atrás
bool condicao2 = valorIR_vec[NUM_PONTOS_OX-distancia-1]-valorIR_vec[NUM_PONTOS_OX-2*distancia-1]>variacao;
// condição 3: frequencia < 200bpm (tempo entre batidas > 300ms)
bool condicao3 = tempoAtual-ultimaBatida > 300;
// condição 4: dedo no sensor
bool condicao4 = valorIR > 50000;
if ( condicao1 && condicao2 && condicao3 && condicao4 ){
contadorBatida = 0; // zera contador para mostrar indicador de batida na tela
// calcula a frequência em batidas por minuto
batidasPorMinuto = 60 / ((tempoAtual-ultimaBatida) / 1000.0);
ultimaBatida = tempoAtual;
if ( batidasPorMinuto>20 && batidasPorMinuto<200 ){ // se valor é válido
freqs_vec[posFreq++] = (byte)batidasPorMinuto; // Armazena a leitura no vetor
posFreq %= NUM_PONTOS_FREQ; // se chegou no final, volta para o início
// Calcula a média das leituras, eliminando maior e menor valores
freqMedia = 0;
int freqMax=0;
int freqMin=999;
for (byte x = 0 ; x < NUM_PONTOS_FREQ ; x++){
if (freqs_vec[x]>freqMax) freqMax = freqs_vec[x];
if (freqs_vec[x]<freqMin) freqMin = freqs_vec[x];
freqMedia += freqs_vec[x];
}
freqMedia = freqMedia - freqMax - freqMin;
freqMedia /= (NUM_PONTOS_FREQ-2);
}
}
}
/////////////////////////////////////////////////////////////////////
// Realiza leitura do módulo oxímetro MAX30102 e dispara a leitura
// do ECG, para as medidas ficarem sincronizadas.
// Esta função só roda quando chamada.
void medeOximetro(){
disparaLeituraEcg(); // para leituras ficarem sincronizadas...
valorIR = meuOximetro.getIR(); // mede valor do led IR do MAX30102
adicionaMedidaIR(valorIR); // para cálculo do SpO2
adicionaMedidaRed(meuOximetro.getRed()); // para cálculo do SpO2
// encontra a média exponencial dos últimos valores lidos
media = 0.8*media + 0.2*valorIR;
// 'anda' com os dados do gráfico para a esquerda
for (int idx=0; idx<NUM_PONTOS_OX-1;idx++) valorIR_vec[idx] = valorIR_vec[idx+1];
// acrescenta o novo valor no final, subtraindo o valor médio
valorIR_vec[NUM_PONTOS_OX-1] = valorIR - media;
verificaBatidaOximetro(); // verifica se ocorreu uma batida cardíaca
}
Nesse arquivo, primeiro temos a inclusão da biblioteca MAX30105.h, usada para a comunicação com o módulo MAX30102, e em seguida as definições das variáveis usadas pelo oxímetro (para mais informações, veja também o post sobre o módulo MAX30102). A função inicializaOximetro() faz a configuração do módulo, apresentando no display OLED uma mensagem de erro no caso do sensor não ser encontrado, ou pedindo para que se coloque o dedo no sensor, caso a comunicação seja bem sucedida.
A medida do sinal de oximetria pelo sensor é disparada pela função medeOximetro(). Nessa função, também disparamos a leitura do sinal de ECG. Desse modo, garantimos que as medidas são realizadas juntas, de modo síncrono, o que ajuda na interpretação das curvas no display. Para facilitar a apresentação da curva na tela e remover alguns artefatos de movimento, removemos o valor médio das medidas, calculado na linha 127, de modo que os novos valores oscilem em torno do valor ‘0’. Também verificamos se ocorreu uma batida, chamando a função verificaBatidaOximetro(). Essa verificação é feita procurando picos no sinal, e é baseada em 4 condições, que deve ser satisfeitas para que uma batida seja encontrada: 1) o ponto que estamos considerando como pico deve estar acima de um ponto medido à frente dele; 2) também deve estar acima de um ponto medido atrás dele; 3) deve ocorrer pelo menos 300ms após o último pico detectado; 4) o valor do sensor infravermelho (IR) do módulo deve estar acima de 50000, indicando que o dedo do paciente está no sensor. Caso essas condições sejam aceitas, a função calcula a frequência cardíaca correspondente à última batida detectada e guarda essa frequência em um vetor. A frequência cardíaca mostrada é uma média desse vetor, eliminando o menor e o maior valores, para diminuir erros de leitura.
As funções relacionadas à apresentação dos dados na tela estão no arquino tela.h:
/*******************************************************************************
* Funções usadas com o display OLED com controlador SSD1306, usado para
* gráficos e apresentação de informações diversas.
*
* Criado por: Erick León, 2023
********************************************************************************/
#include <Adafruit_GFX.h> // construção de gráficos
/////////////////////////////////////////////////////////////////////
// Iniciliza o display OLED.
// Esta função só roda quando chamada.
void inicializaDisplay(){
// inicializa a comunicação com o display, no endereço I2C 0x3C
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (1); // Se não conseguiu inicializar o display, fica aqui parado
}
display.setTextSize(1); // escolhe o tamanho da letra
display.setTextColor(WHITE); // escolhe a cor da letra
}
/////////////////////////////////////////////////////////////////////
// Desenha uma curva (gráfico) no buffer do display OLED,
// acrescentando um retângulo ao redor da curva.
// Esta função só roda quando chamada.
void desenhaCurva(int posicao_inferior, int altura, int n_pontos, int *vec){
long maximo = -99999;
long minimo = 99999;
long amplitude;
for (int idx=0; idx<n_pontos-1;idx++){ // encontra maximo e minimo
if (vec[idx] > maximo) maximo = vec[idx];
if (vec[idx] < minimo) minimo = vec[idx];
}
amplitude = maximo - minimo;
if (amplitude==0) amplitude = 1;
for (int idx=0; idx<n_pontos-1;idx++){ // desenha curva
// normaliza de 0 a 1
float valorNorm = ((float)vec[idx+1]-(float)minimo)/((float)amplitude);
float valorNorm_anterior = ((float)vec[idx]-(float)minimo)/((float)amplitude);
// encontra posição no display
int ponto = (float)posicao_inferior-(float)(altura-1)*valorNorm;
int ponto_anterior = (float)posicao_inferior-(float)(altura-1)*valorNorm_anterior;
display.drawLine(idx+1, ponto_anterior, idx+2, ponto, SSD1306_WHITE);
}
// desenha retângulo em volta do gráfico
display.drawRect(0, posicao_inferior-altura-1, n_pontos+2, altura+2, SSD1306_WHITE);
}
/////////////////////////////////////////////////////////////////////
// Desenha todos os elementos da tela.
// Esta função só roda quando chamada.
void desenhaTela(){
byte posXTexto = 70; // posição X do texto na tela
byte altura_linha = 11; // latura da linha de texto, em pixels
// soma 1 nos contadores
contadorTemperatura++;
contadorBatida++;
display.clearDisplay(); // limpa o buffer da tela
// Dados do oxímetro
display.setCursor(0,0); // posiciona cursor de texto
display.print(F("Oximetro:")); // mostra mensagem
display.setCursor(posXTexto,altura_linha*1); // posiciona cursor de texto
display.print(freqMedia); // mostra no buffer da tela frequência cardíaca medida
display.print(F(" bpm")); // mostra no buffer da tela a unidade de tempo do intervalo
if (valorIR < 50000){
display.setCursor(posXTexto,altura_linha*4); // posiciona cursor de texto
display.print(F("sem dedo?")); // mostra mensagem
spo2 = 0;
}
desenhaCurva(30,20,NUM_PONTOS_OX,valorIR_vec); // desenha curva do oxímetro no buffer
// se ocorreu uma batida a menos de 3 atualizações, mostra círculo
if (contadorBatida < 3) display.fillCircle(122, 5, 5, SSD1306_WHITE);
//a cada 20 atualizações (aprox. 2 segundos), mede temperatura e SpO2
if ( contadorTemperatura > 20 ) {
temperatura = meuOximetro.readTemperature();
contadorTemperatura = 0;
int16_t spo2_temp = medeSpO2();
if (spo2_temp>0) spo2 = spo2_temp;
}
display.setCursor(posXTexto,altura_linha*2); // posiciona cursor de texto
display.print(F("SpO2:")); // mostra mensagem no buffer
display.print(spo2); // mostra no buffer da tela SpO2
display.setCursor(posXTexto,altura_linha*3); // posiciona cursor de texto
display.print(temperatura); // mostra no buffer da tela frequência cardíaca medida
display.cp437(true); // usa código de caracteres 'Code page 437' para símbolo de grau
display.print((char)248); // símbolo de grau
display.print(F("C")); // mostra no buffer da tela a unidade da temperatura
// Dados do ECG
display.setCursor(0,33); // posiciona cursor de texto
display.print(F("ECG:")); // mostra mensagem
desenhaCurva(62,20,NUM_PONTOS_ECG,ecg_vec); // desenha curva do ECG no buffer
// mostra frequência de aquisição na tela:
display.setCursor(posXTexto,0); // posiciona cursor de texto
if (freqAquisicao<10.0) display.print(F(" ")); // para alinhar valores
display.print(freqAquisicao);
display.print(F("Hz"));
// atualiza valor da tensão da bateria:
display.setCursor(posXTexto,altura_linha*5); // posiciona cursor de texto
display.print(F("Vin:"));
disparaLeituraBateria();
display.print(tensao_bateria*5.0*2.0/1024.0);
display.print(F("V"));
display.display(); // atualiza a tela com as informações do buffer
}
Aqui primeiro chamamos a biblioteca Adafuit_GFX.h, que contém as funções para desenhar no display OLED (para mais informações, veja nosso post sobre displays). A inicialização do display é feito pela função inicializaDisplay(). Temos aqui também uma função auxiliar usada para desenhar um gráfico na tela: desenhaCurva(), onde passamos a posição inferior do gráfico, a altura do gráfico, quantos pontos o gráfico possui e o vetor com os dados a serem plotados.
A atualização da tela em si é feita pela função desenhaTela(). Esta função é responsável por incluir todos os textos e gráficos na tela. Além disso, ao ser detectada uma batida cardíaca, é desenhado um círculo no canto superior direito da tela, que, para facilitar a visualização, fica na tela por 3 atualizações da tela.
A cada 20 atualizações da tela (como a tela, neste exemplo, está sendo atualizada 10 vezes por segundo, isso equivale a 2 segundos), realizamos a leitura da temperatura, feita pelo módulo MAX30102, e o cálculo da saturação de oxigênio no sangue.
Por último, realizamos a medição da tensão da bateria. Aqui, como usamos uma bateria de 9V, e o Arduino Uno é capaz de medir sinais até 5V, usamos um divisor de tensão, formado por 2 resistores de valores iguais, ligados ao sinal da bateria, e medimos o valor entre os resistores. Como os resistores são iguais, estamos medindo a metade da tensão na ponta dos 2 resistores, ou seja, para uma bateria de 9V, medimos um sinal de até 4,5V. Então, antes de apresentar o valor medido, multiplicamos o valor por 2, e convertemos o valor medido para volts.
Ao montar o circuito, faça o upload para a placa Arduino e o código começará a rodar automaticamente. Entretanto, para ficarmos livres dos ruídos da rede elétrica, devemos desligar o cabo USB do computador e alimentar nossa placa Arduino utilizando uma bateria de 9V. O resultado pode ser visto a seguir:
Como podemos ver, esse exemplo é capaz de medir e apresentar diversos sinais, como as curvas de oximetria e do ECG, e os valores da frequência cardíaca, da saturação de oxigênio no sangue, da temperatura corporal e da tensão da bateria. Você pode alterar o código, usando o exemplo da medição da tensão na bateria, para ler outros sinais de outros sensores também!
Considerações importantes: 1) Como a apresentação dos dados no display OLED é lenta, e neste exemplo medimos os sinais na mesma frequência com que atualizamos a tela, a frequência de aquisição dos sinais fica em torno de 10 medições por segundo. Essa frequência é suficiente para apresentar o sinal de oximetria, mas é muito baixa para apresentar um sinal de ECG, fazendo com que a curva apresentada fique distorcida e que sinais de alta frequência, como o complexo QRS do ECG, sejam perdidos. Futuramente veremos como contornar esse tipo de problema. 2) Como a temperatura corporal é medida na ponta do dedo, essa temperatura é geralmente bem mais baixa que a temperatura média do corpo, principalmente em um dia frio, como vemos acima.
Por mais que tenhamos alguns dos componentes de um monitor multiparamétrico de hospital, tudo feito aqui é apenas para demonstração e exemplificação, com dados que não são precisos. Caso detecte algo fora do normal, procure um médico.
Fontes:
[1] https://bvsms.saude.gov.br/bvs/saudelegis/anvisa/2010/res0007_24_02_2010.html
Texto por: Lucas Jácome Cabral e Erick León