Nesses dois anos de pandemia, você provavelmente deve ter ouvido falar em oximetria nos jornais e programas de tv, mas afinal, você sabe o que é oximetria?
Um dos “combustíveis” do corpo humano é o oxigênio, entrando em nosso corpo a cada respiração e indo para os pulmões. Porém, a partir daí, temos um trabalho mais difícil, o de transporte. A logística do corpo é feita pelos glóbulos vermelhos ou hemácias, são carinhas que parecem mais minúsculos carteiros, que pegam o oxigênio nos pulmões e transportam pelo corpo até seu destino, que são os tecidos e células.
O problema está quando o oxigênio começa a faltar nesses glóbulos. Nos últimos dois anos você deve ter ouvido que um dos sintomas de covid seria ter a oximetria muito baixa, por isso alguns estabelecimentos mediam o seu índice de oxigênio no sangue logo na porta, e caso estivesse abaixo de 89% era bom você procurar um medico…
Esse mini aparelho se chama oxímetro, e parece um prendedor de roupas que é colocado no dedo e logo mede seu índice. Mas como ele funciona?
funcionamento
O oxímetro é um tipo de exame não invasivo, isto é, não envolve a penetração de algum tecido ou organismo, sendo assim, é como medir sua temperatura com um termômetro ou o medico olhar sua garganta com um palito, não dói em nada…
Existem vários tipos de oxímetro. O que vamos tratar aqui é sobre o tipo de pulso. Ele funciona com luz infravermelha, funciona assim:
- Primeiro dois LEDS emitem feixes de luz infravermelha e luz vermelha para o seu dedo;
- Dependendo da quantidade de glóbulos vermelhos oxigenados e não oxigenados, uma determinada quantidade de luz é absorvida e outra é refletida;
- Um sensor lê essa luz refletida e se baseando em testes vindos de fábrica, temos os nossos valores.
Como usar o módulo oxímetro
Neste artigo vamos mostrar como utilizar um módulo que faz tudo isso e ainda consegue fazer muito mais…
O MAX 30102 é um medidor de oximetria e ainda medidor de batimentos cardíacos, com um consumo ridiculamente baixo de energia e um tamanho muito pequeno, cabendo em quase qualquer projeto.
Leitura cardíaca
Um das funcionalidades do módulo é a de leitura cardíaca. A oxihemoglobina (hemoglobina oxigenada, ou seja, aquela que está carregando oxigênio), quando exposta a luz IR, a absorve. Quando temos os batimentos cardíacos, a proporção de oxihemoglobinas no sangue aumenta e diminui, alterando a leitura no gráfico, e assim conseguimos identificar os batimentos cardíacos.
Sensor
Medindo aproximadamente 1,5×1,5 cm, pode ser alimentado por 3.3V ou 5V, ideal para projetos móveis com consumo de energia muito baixo. O sensor em si é apenas esta parte preta do lado direito, constituído pelo sensor propriamente dito e um LED IR e outro da cor vermelha.
- VIN – alimentação positiva
- GND – alimentação negativa
- SDA – Comunicação
- SCL – Comunicação
- INT – não utilizado neste tutorial
Comunicação
Utiliza o protocolo I2C para comunicação com o microcontrolador. Como vimos em outros posts, este tipo de comunicação utiliza apenas dois fios para se comunicar (SDA e SCL). Tem endereços pré-definidos: 0xAEHEX para envio de informações e 0xAFHEX para recebimento de informações. Acesse I2C para mais informações sobre esse tipo de comunicação.
Conexão com arduino uno
Neste artigo iremos realizar leituras simples de oximetria e batimentos cardíacos, utilizando a placa de prototipagens Arduino UNO R3, mas o código é compatível com qualquer placa da família Arduino, desde que tenha embutido o protocolo de comunicação I2C.
Lista de materiais:
- Arduino UNO R3
- Módulo MAX 30102
- Display LCD 16X2
- Jumpers
- Protoboard
Cada sensor pode ter uma montagem e pinagem diferentes, porém se comportam de formas parecidas, apenas com características diferentes.
instalação da biblioteca
Para a parte da programação precisaremos de algo a mais, bibliotecas. Elas são recursos excelentes para deixar nosso trabalho muito mais fácil, são pacotes pré-programados com funções já feitas que nos exime do trabalho de programar coisas complexas de se ler/fazer.
Para instalar, vá em Ferramentas–>> Gerenciar Bibliotecas…
Clique, em seguida digite na barra de pesquisa: SparkFun MAX3010x Sensor Library, será o primeiro item, agora é só clicar em instalar e esperar terminar…
Leitura de batimentos
Este código mostra no display LCD a quantidade de batimentos cardíacos por minuto em seu corpo.
OBS: As leituras deste sensor podem sofrer alterações de ambiente e luz. Para medidas concretas e para fins médicos, recorra a um sensor calibrado e confiável!
Este código foi baseado no exemplo de utilização do módulo provido pela biblioteca, disponível em Arquivo–>> Exemplos–>>SparkFun MAX3010x Sensor Library —> Exemple 5: Hearthate
// Leitura de batimentos cardíacos utilizando Arduino Uno
// Programa baseado no Exemplo 5:HeartRate da biblioteca MAX30105
// Modificado por Lucas Jácome e Erick León, 2022.
#include <LiquidCrystal.h>
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // pinos usados no LCD: (rs, enable, d4, d5, d6, d7)
MAX30105 particleSensor;
const byte RATE_SIZE = 4; // Número de medidas usadas na média
byte rates[RATE_SIZE]; // Vetor de medidas de frequência
byte rateSpot = 0; // guarda a posição atual do vetor
long lastBeat = 0; // Instante em que ocorreu a última batida cardíaca
float beatsPerMinute; // frequência (batidas por minuto)
int beatAvg; // média da frequência
// esta função só roda uma vez, no início
void setup()
{
// Inicializa o display
lcd.begin(16, 2); // usando um display LCD de 16x2 caracteres
lcd.clear();
lcd.setCursor(0, 0);
// Initializa o sensor, usando a porta I2C padrão, velocidade de 400kHz
if (!particleSensor.begin(Wire, I2C_SPEED_FAST))
{
lcd.print("Sensor nao encontrado ");
while (1); // Se não encontra o sensor, fica parado aqui em um loop infinito
}
lcd.print("Coloque seu dedo");
delay(1000);
// Configura o sensor
particleSensor.setup();
// Liga o LED vermelho para inidicar que o sensor está ligado
particleSensor.setPulseAmplitudeRed(0x0A);
// desliga o LED verde
particleSensor.setPulseAmplitudeGreen(0);
}
// esta função se repete indefinidamente
void loop()
{
long irValue = particleSensor.getIR();
if (checkForBeat(irValue) == true) // verifica se ocorreu uma batida
{
// A batida ocorreu
long delta = millis() - lastBeat; // mede o intervalo desde a última batida
lastBeat = millis(); // guarda o instante desta batida
beatsPerMinute = 60 / (delta / 1000.0); // calcula a frequência em batidas por minuto
if (beatsPerMinute < 255 && beatsPerMinute > 20) // se o valor for válido...
{
rates[rateSpot++] = (byte)beatsPerMinute; // Armazena a leitura no vetor
rateSpot %= RATE_SIZE; // muda a posição da medida no vetor
// Calcula a média das leituras
beatAvg = 0;
for (byte x = 0 ; x < RATE_SIZE ; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;
}
}
// Mostra as informações no LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IR=");
lcd.print(irValue);
lcd.setCursor(0, 1);
if (irValue < 50000){
lcd.print("SEM DEDO? ");
} else{
lcd.print("BPM=");
lcd.print(beatsPerMinute);
}
delay(10);
}
A seguir vemos o módulo funcionando com este código:
Entendendo o código
Nas linhas 5 a 8, incluímos as bibliotecas que serão utilizadas: a biblioteca do LCD (LiquidCrystal.h), da comunicação I2C (Wire.h), do sensor MAX30105, que também funciona com o MAX30102 (MAX0105.h) e da detecção de batimentos cardíacos (heartRate.h).
Declaramos o uso o LCD na linha 10, e o sensor na linha 11.
Definimos as variáveis globais utilizadas nas linhas 13 a 18.
Nossa função setup() é definida a seguir. Aqui inicializamos o LCD e em seguida o sensor. Caso o sensor não seja encontrado, o programa fica parado aqui, na linha 32.
Nas linhas 46 a 85 temos nossa função loop(), que fica repetindo indefinidamente. Aqui fazemos a leitura do sensor MX3010x (linha 49). Em seguida, passamos esse valor para a função checkForBeat, responsável por verificar se foi detectada um batimento cardíaco. Em caso positivo, verificamos o intervalo, em milissegundos, entre o batimento atual e o último batimento, e guardamos o instante da batida atual usando a função millis(). Usando esse intervalo, é calculada a frequência e, caso o valor encontrado seja um valor válido (entre 20 e 255 batimentos por minuto), guardamos esse valor em um vetor. Então, com os valores do vetor, é calculado um valor médio. Finalmente, nas linhas 72 a 84, apresentamos o valor médio no display LCD.
Oximetria
Este código foi baseado no exemplo de utilização do módulo provido pela biblioteca, disponível em Arquivo–>> Exemplos–>>SparkFun MAX3010x Sensor Library –> Example 8: SPO2
Este código mostra no display LCD a porcentagem de oxihemoglobina em seu sangue.
OBS: As leituras deste sensor podem sofrer alterações de ambiente e luz, para medidas concretas e para fins médicos recorra a um sensor calibrado e preciso
// Leitura de oxigenação do sangue utilizando Arduino
// Programa baseado no Exemplo 8:SPO2 da biblioteca MAX30105
// Modificado por Lucas Jácome e Erick León, 2022
#include <LiquidCrystal.h>
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // pinos usados no LCD: (rs, enable, d4, d5, d6, d7)
MAX30105 particleSensor;
#define BUFFER_LENGTH 100 // tamanho dos vetores de dados. Deve ser 100!
#define N 10 // pontos medidos a cada cálculo de SpO2
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
uint16_t irBuffer[BUFFER_LENGTH]; // vetor dos dados do LED ir
uint16_t redBuffer[BUFFER_LENGTH]; // vetor dos dados do LED vermelho
#else
uint32_t irBuffer[BUFFER_LENGTH]; // vetor dos dados do LED ir
uint32_t redBuffer[BUFFER_LENGTH]; // vetor dos dados do LED vermelho
#endif
int32_t spo2; // guarda o valor de SpO2
int8_t validSPO2; // guarda se a medida é válida
int32_t heartRate; // guarda a frequência cardíaca
int8_t validHeartRate; // guarda se a frequência é válida
byte readLED = 13; // pino do LED interno do Arduino
// esta função só roda uma vez, no início
void setup()
{
// inicializa o LCD
lcd.begin(16, 2); // usando um display LCD de 16x2 caracteres
lcd.clear();
pinMode(readLED, OUTPUT);
// Inicializa o sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST))
{
lcd.setCursor(0, 0);
lcd.println(F("Sem sensor!"));
while (1); // Se não encontra o sensor, fica parado aqui em um loop infinito
}
// configura o sensor
byte ledBrightness = 60; // Opções: 0=desligado a 255=50mA; padrão: 60
byte sampleAverage = 4; // Opções: 1, 2, 4, 8, 16, 32; padrão: 4
byte ledMode = 2; // Opções: 1=apenas verm, 2=verm+IR, 3=verm+IR+verde; padrão: 2
byte sampleRate = 100; // Opções: 50, 100, 200, 400, 800, 1000, 1600, 3200; padrão: 100
int pulseWidth = 411; // Opções: 69, 118, 215, 411; padrão: 411
int adcRange = 4096; // Opções: 2048, 4096, 8192, 16384; padrão: 4096
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange);
lcd.setCursor(0, 0);
lcd.println(F("Iniciando medida"));
// Completa os vetores com medidas:
for (byte i = 0 ; i < BUFFER_LENGTH ; i++)
{
while (particleSensor.available() == false) // se ainda não tem novos dados...
particleSensor.check(); // verifica se tem novos dados
redBuffer[i] = particleSensor.getRed(); // guarda dado do LED vermelho no vetor
irBuffer[i] = particleSensor.getIR(); // guarda dado do LED ir no vetor
particleSensor.nextSample(); // dados lidos... fazer nova leitura
}
}
// esta função se repete indefinidamente
void loop()
{
// desloca os valores nos vetores em N posições
for (byte i = N; i < BUFFER_LENGTH; i++)
{
redBuffer[i - N] = redBuffer[i];
irBuffer[i - N] = irBuffer[i];
}
// faz mais N medições, nas últimas N posições dos vetores
for (byte i = BUFFER_LENGTH-N; i < BUFFER_LENGTH; i++)
{
while (particleSensor.available() == false) // se ainda não tem novos dados...
particleSensor.check(); // verifica se tem novos dados
// pisca o LED interno do Arduino para avisar que está medindo
digitalWrite(readLED, !digitalRead(readLED));
redBuffer[i] = particleSensor.getRed(); // guarda dado do LED vermelho no vetor
irBuffer[i] = particleSensor.getIR(); // guarda dado do LED ir no vetor
particleSensor.nextSample(); // dados lidos... fazer nova leitura
}
// calcula SpO2 e frequência cardíaca
maxim_heart_rate_and_oxygen_saturation(irBuffer, BUFFER_LENGTH, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
// apresenta valores no display LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("SPO2="));
if(validSPO2){
lcd.print(spo2, DEC);
lcd.print(F("%"));
}
else{
lcd.print(F("???"));
}
if(validHeartRate){
lcd.print(F(", "));
lcd.print(heartRate, DEC);
lcd.print(F("bpm"));
}
lcd.setCursor(0, 1);
lcd.print(F("Red="));
lcd.print(redBuffer[BUFFER_LENGTH-1]/256);
lcd.print(F(" IR="));
lcd.print(irBuffer[BUFFER_LENGTH-1]/256);
}
A saída no display desse código funcionando é vista a seguir:
Esse e outros códigos do blog você encontra neste link: https://github.com/edlbcamargo/embarcasaude |
Entendendo o código
Assim como no código anterior, iniciamos declarando as bibliotecas, com uma diferença: aqui usamos a biblioteca spo2_algorithm.h para calcular a oximetria.
Em seguida declaramos os módulos LCD e MAX30102, que é compatível com o MAX30105.
Para o algoritmo de SpO2 funcionar, são necessários 100 dados medidos. Assim, declaramos o tamanho desses vetores (linha 13) e os vetores em si (linhas 16 a 22). Aqui temos um bloco interessante, definido pelos comandos #if, #else, . #endif. Eles dizem ao compilador que, caso uma variável seja declarada, deve ser considerado um bloco de código (linhas 17 e 18), e caso essa variável não deja declarada, deve ser considerado outro bloco (linhas 20 e 21). A variável em questão (no caso são 2: __AVR_ATmega328P__ e __AVR_ATmega168__) nos diz que o Arduino em uso é um Arduino com pouca memória, como o Arduino Uno, por exemplo, que tem apenas 2kB de memória dinâmica. Assim, caso esteja sendo usado um Arduino Uno, o compilador considera os vetores como do tipo uint16, que ocupam metade da memória das variáveis uint32. Isso é importante porque, neste programa, não sobra memória suficiente no Uno para guardar todos os 100 valores dos vetores sendo do tipo uint32. Caso você vá utilizar um Arduino MEGA, por exemplo, que tem mais memória, o compilador vai ignorar as linhas 17 e 18 e usar as linhas 20 e 21, declarando assim os vetores como uint32.
Nas linhas 24 a 28, declaramos as variáveis globais que serão utilizadas no programa.
Em seguida temos nossa função setup(), que roda apenas uma vez na inicialização. Nela inicializamos o display e o módulo, e configuramos o módulo. Podemos ver que este módulo de oximetria possui vários parâmetros de configuração, como intensidade do brilho do sensor (ledBrightness), por exemplo, que pode ser deixado num valor mais alto caso os valores dos sensores estejam muito baixos, ou mais baixo, caso estejam no limite (no nosso caso, os valores dos sensores vermelho e IR apresentados no display variam de 0 a 255). Ao final da função, os vetores de medida são preenchidos com 100 valores cada.
A função loop() é declarada a seguir. Aqui, ela faz o seguinte: desloca os valores dos vetores em N posições (onde N é definido na linha 14), faz N novas medidas, guardando essas medidas nos vetores, de modo que agora os vetores possuem as últimas 100 medidas novamente, e calcula os valores de frequência cardíaca e SpO2. Por último, caso os valores calculados sejam válidos, mostra os mesmos no display LCD, e repete todo o processo.
Texto por: Lucas Jácome Cabral e Erick León
Revisão: Erick León