Sensor de batimentos cardíacos com Arduino: guia de BPM com Pulse Sensor e KY-039
Guia do sensor de batimentos cardíacos com Arduino: ligação do Pulse Sensor e do KY-039, BPM com a PulseSensorPlayground, onda de pulso no Plotter Serial, OLED e código para ESP32 e Pico.
Um sensor de pulso para Arduino, também chamado de sensor de batimentos cardíacos, é um pequeno módulo que acompanha com luz o fluxo de sangue na ponta do dedo ou no lóbulo da orelha e mede quantas vezes o seu coração bate por minuto, o BPM (beats per minute, batimentos por minuto). Os dois modelos mais comuns na eletrônica de hobby são o Pulse Sensor, de placa redonda, e o KY-039, com LED infravermelho. Os dois são ligados com três fios e lidos por um único pino analógico, mas diferem muito na qualidade do sinal e na dificuldade de escrever um código confiável para eles.
Neste guia você vai aprender, passo a passo, em qual princípio físico um sensor de pulso se baseia, como ver uma onda de pulso real no Plotter Serial, como calcular o BPM com a biblioteca PulseSensorPlayground, como tratar o sinal fraco do KY-039 com um algoritmo sem biblioteca e como mostrar o resultado em um display OLED com um ícone de coração. O código para ESP32 e Raspberry Pi Pico, os fatores que estragam uma medição e a diferença para o MAX30102 também estão aqui. Uma observação importante logo de início: esses sensores não são dispositivos médicos e não devem ser usados em nenhuma decisão relacionada à saúde.

Como funciona um sensor de pulso? Fotopletismografia (PPG)
Todos os sensores de pulso funcionam com um método chamado fotopletismografia (PPG). Cada vez que o coração se contrai, ele envia uma onda de sangue para as artérias; quando essa onda chega aos pequenos vasos da ponta do dedo, o volume de sangue no tecido aumenta por um instante. Como a hemoglobina do sangue absorve luz, quanto mais sangue houver, menos luz é refletida de volta pelo tecido ou o atravessa. O sensor ilumina o tecido com um LED, e um sensor de luz mede a luz que volta; essa pequena oscilação regular na quantidade de luz é o próprio pulso.
A parte difícil é que quase toda a luz medida é constante. A pele, os músculos, os ossos e o sangue das veias absorvem sempre a mesma quantidade de luz; isso é chamado de componente DC do sinal. A componente AC, que muda a cada batimento, costuma ser só cerca de 1–2% da luz total. Ou seja, o verdadeiro trabalho do sensor é separar e amplificar uma pequena onda que viaja sobre um nível grande e estável. Uma onda de PPG mostra uma subida rápida até um pico (o pico sistólico), seguida de uma descida lenta; o pequeno entalhe no meio da descida e a pequena elevação logo depois dele (a incisura dicrótica) vêm do fechamento da valva aórtica. Um bom algoritmo conta cada batimento uma única vez e não confunde essa incisura com um segundo batimento.
A forma como a luz é enviada ao tecido também importa. O Pulse Sensor usa o método reflexivo: o LED e o sensor de luz ficam na mesma face e medem a luz que a pele espalha de volta. A luz verde dele é fortemente absorvida pela hemoglobina e dá um sinal claro dos vasos logo abaixo da pele; é também por isso que você vê LEDs verdes na parte de trás dos smartwatches. O KY-039 usa o método transmissivo: um LED infravermelho ilumina um lado do dedo e um fototransistor do outro lado mede a luz que o atravessa. A luz infravermelha penetra mais fundo no tecido, mas, como o KY-039 não tem circuito para amplificar o sinal, a onda resultante é muito pequena.
| Característica | Valor no Pulse Sensor |
|---|---|
| Método de medição | PPG reflexiva |
| Fonte de luz | LED verde |
| Sensor de luz | APDS-9008, sensibilidade máxima em 565 nm |
| Alimentação | 3–5 V |
| Corrente | Cerca de 4 mA (em 5 V) |
| Saída | Analógica; metade da tensão de alimentação em repouso (~2,5 V em 5 V, ~512 no Uno) |
| Diâmetro da placa | Cerca de 16 mm (0,625 polegada) |
| Fios | No original: vermelho +, preto GND, roxo sinal; as cores podem mudar nos clones |
Pulse Sensor, KY-039 ou MAX30102: diferenças

| Característica | Pulse Sensor | KY-039 | MAX30102 |
|---|---|---|---|
| Método | PPG reflexiva | PPG transmissiva (dedo no meio) | PPG reflexiva |
| Luz | LED verde | LED infravermelho | Vermelho (660 nm) + infravermelho (880 nm) |
| Amplificador e filtro | Sim (amplificador operacional) | Não | ADC interno de 18 bits, cancelamento de luz ambiente |
| Saída | Analógica | Analógica (muito fraca) | Digital, I2C (endereço 0x57) |
| Alimentação | 3–5 V | 3,3–5 V | 3,3–5 V, conforme o módulo |
| Qualidade do sinal | Boa | Fraca, sujeita a ruído | Muito boa |
| SpO2 (oxigênio) | Não | Não | Sim (com um algoritmo) |
| Biblioteca | PulseSensor Playground | Sem biblioteca pronta | SparkFun MAX3010x |
Se você está começando e só quer medir o pulso, o Pulse Sensor é a escolha mais fácil: como o sinal é amplificado e filtrado na própria placa, você vê uma onda nítida no Plotter Serial já na primeira tentativa. O amplificador operacional na parte de trás eleva essa pequena variação a um nível que o Arduino consegue ler; você pode testar como esse ganho é calculado na ferramenta Amplificador operacional. O KY-039 é barato e vem na maioria dos kits de sensores, mas a saída dele varia só alguns passos do ADC, então um código simples com limite fixo não dá resultados confiáveis. Se você também quer medir a saturação de oxigênio no sangue (SpO2), a escolha certa é o MAX30102; falamos dele em uma seção própria mais abaixo.
Ligação do sensor de pulso: Arduino Uno, ESP32 e Pico
| Pino do sensor | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| + (VCC, vermelho) | 5V | 3V3 | 3V3(OUT), pino 36 |
| − (GND, preto) | GND | GND | GND, pino 38 |
| S (sinal, roxo) | A0 | GPIO 34 (ADC1) | GP26 (ADC0), pino 31 |
| LED de batimento (opcional) | D13 (LED embutido) | GPIO 2 (LED embutido) | LED da placa |
A mesma tabela vale para o KY-039: o pino S vai para a entrada analógica, o + para a alimentação e o − para o GND. Confira se o LED infravermelho está dobrado sobre o fototransistor e se o seu dedo cabe entre os dois. Os componentes na parte de trás do Pulse Sensor ficam expostos; se um dedo ou uma superfície suada encostar atrás, isso gera ruído. Por isso é um bom hábito colar na frente o adesivo transparente que vem no kit e cobrir a parte de trás com cola quente. Se você usar um LED externo, calcule o resistor dele com a ferramenta Resistor para LED; você também pode ver o mapa de pinos da sua placa na ferramenta Pinagem.
Primeiro teste: ver a onda de pulso bruta no Plotter Serial
Antes de passar para o BPM, confira se o sensor realmente enxerga um pulso. O código abaixo não usa nenhuma biblioteca; ele lê o sinal 100 vezes por segundo e o envia para o Plotter Serial. Carregue o código, abra Ferramentas > Plotter Serial na Arduino IDE e ajuste a velocidade para 115200 baud. Se você nunca usou o Plotter Serial, o artigo Monitor Serial do Arduino explica como a janela funciona.
1// Nabız sensörü ham sinyal testi (Seri Çizici) / Pulse sensor raw signal test (Serial Plotter)
2const int PULSE_PIN = A0; // sensörün S (mor) kablosu / sensor S (purple) wire
3const unsigned long PERIOD_MS = 10; // 10 ms = saniyede 100 örnek / 10 ms = 100 samples per second
4
5unsigned long lastSample = 0;
6
7void setup() {
8 Serial.begin(115200); // Seri Çizici'de de 115200 seç / pick 115200 in the Serial Plotter too
9 lastSample = millis();
10}
11
12void loop() {
13 if (millis() - lastSample >= PERIOD_MS) {
14 lastSample += PERIOD_MS; // sabit aralıkla örnekle / sample at a fixed interval
15 int raw = analogRead(PULSE_PIN); // 0-1023 arası değer / value between 0 and 1023
16 Serial.print("Sinyal:");
17 Serial.print(raw);
18 Serial.println(",Orta:512"); // boştaki orta seviye çizgisi / idle midpoint line
19 }
20}Sem dedo, a linha do Pulse Sensor fica reta em torno de 512, com uma leve tremedeira; isso é metade da alimentação e mostra que o sensor está ligado corretamente. Quando você apoia de leve a ponta do dedo no sensor, em poucos segundos aparece uma onda que sobe rápido e desce devagar a cada batimento; a amplitude dela costuma ficar entre 100 e 300 passos. Se você não vê a onda, não avance para o código de BPM: primeiro corrija a ligação, a alimentação e a pressão do dedo. Se os picos viram uma linha reta grudada em 0 ou em 1023, o sinal está saturando (clipping); geralmente o dedo está se mexendo ou o sensor está sendo apertado demais. Se você tem curiosidade de saber de onde vem a faixa 0–1023 de uma leitura analógica, veja o artigo ADC e conversão analógico-digital.
Medindo o BPM com a PulseSensorPlayground
- Abra o Gerenciador de Bibliotecas na Arduino IDE, busque PulseSensor Playground e instale a biblioteca. Para mais detalhes sobre como instalar bibliotecas, veja o artigo como instalar uma biblioteca no Arduino.
- Ligue o sensor como na tabela: + no 5V, − no GND e S no A0.
- Carregue o código abaixo e abra o Plotter Serial em 115200 baud; a primeira linha mostra o sinal bruto e a segunda mostra o valor de BPM.
- Coloque o dedo no sensor, apoie a mão na mesa e fique parado por 10 segundos; a biblioteca ajusta a média nos primeiros batimentos.
- Se preferir ver os números linha por linha no Monitor Serial, mude a constante PLOTTER do código para false; assim, o BPM e o tempo entre batimentos (IBI) são impressos a cada batimento.
1// PulseSensor Playground ile BPM ölçme / Measuring BPM with PulseSensor Playground
2#include <PulseSensorPlayground.h>
3
4const int PULSE_PIN = A0; // sensörün S (mor) kablosu / sensor S (purple) wire
5const int LED_PIN = LED_BUILTIN; // her atışta yanan LED (D13) / LED lit on each beat (D13)
6const int THRESHOLD = 550; // başlangıç eşiği, boşta ~512 / starting threshold, idle ~512
7const bool PLOTTER = true; // true: Seri Çizici, false: Seri Monitör / true: Serial Plotter, false: Serial Monitor
8
9PulseSensorPlayground pulseSensor; // kütüphane nesnesi / library object
10unsigned long lastPrint = 0;
11
12void setup() {
13 Serial.begin(115200);
14 pulseSensor.analogInput(PULSE_PIN);
15 pulseSensor.blinkOnPulse(LED_PIN);
16 pulseSensor.setThreshold(THRESHOLD);
17 if (!pulseSensor.begin()) { // 2 ms'lik zamanlayıcıyı başlatır / starts the 2 ms timer
18 Serial.println(F("PulseSensor baslatilamadi / could not start"));
19 while (true) {}
20 }
21}
22
23void loop() {
24 // Atış anı: BPM ve atışlar arası süre / moment of a beat: BPM and inter-beat interval
25 if (pulseSensor.sawStartOfBeat() && !PLOTTER) {
26 Serial.print(F("BPM: "));
27 Serial.print(pulseSensor.getBeatsPerMinute());
28 Serial.print(F(" IBI: "));
29 Serial.print(pulseSensor.getInterBeatIntervalMs());
30 Serial.println(F(" ms"));
31 }
32
33 // 20 ms'de bir çizim satırı / one plotter line every 20 ms
34 if (PLOTTER && millis() - lastPrint >= 20) {
35 lastPrint = millis();
36 Serial.print(F("Sinyal:"));
37 Serial.print(pulseSensor.getLatestSample());
38 Serial.print(F(",BPM:"));
39 Serial.println(pulseSensor.getBeatsPerMinute());
40 }
41}| Função | O que faz |
|---|---|
| analogInput(pino) | Seleciona o pino analógico em que o sensor está ligado (A0 por padrão) |
| blinkOnPulse(pino) | Acende o LED deste pino enquanto o sinal está acima do limite, ou seja, durante cada batimento |
| fadeOnPulse(pino) | Acende o LED de um pino PWM a cada batimento e o apaga devagar |
| setThreshold(valor) | Limite inicial; quando nenhum batimento é encontrado, o limite volta a esse valor |
| begin() (inicialização) | Inicia a amostragem; retorna true se der certo |
| sawStartOfBeat() (novo batimento) | Retorna true se um novo batimento começou desde a última chamada |
| getBeatsPerMinute() (BPM) | BPM calculado pela média dos últimos 10 intervalos entre batimentos |
| getInterBeatIntervalMs() (IBI) | Tempo entre os dois últimos batimentos (IBI), em milissegundos |
| getLatestSample() (última leitura) | A última leitura bruta (0–1023) |
| isInsideBeat() (dentro do batimento) | Retorna true enquanto o sinal está acima do limite |
A biblioteca lê o sensor em segundo plano com uma interrupção de timer a cada 2 ms (500 vezes por segundo), então a amostragem continua estável mesmo que você faça tarefas longas no loop(). A cada batimento, ela registra o pico e o vale da onda e move o limite exatamente para o meio entre os dois; em outras palavras, o valor que você passa para setThreshold() é só um ponto de partida. Ela ignora subidas que chegam em menos de 250 ms depois do batimento anterior ou antes de passarem três quintos do último intervalo entre batimentos, para que a incisura dicrótica não seja contada como um segundo batimento. O BPM é calculado pela média dos últimos 10 intervalos entre batimentos; se nenhum batimento for encontrado por 2,5 segundos, tudo é zerado e getBeatsPerMinute() retorna 0.
Escolha o limite inicial olhando o sinal bruto: 30–50 passos acima do maior valor que a linha atinge sem dedo é um bom começo (entre 530 e 600 na maioria dos sensores). Se o limite for baixo demais, o ruído é contado como batimento mesmo sem dedo; se for alto demais, pulsos fracos passam despercebidos. Mais um aviso: no Arduino Uno a biblioteca usa o Timer1 para a amostragem, então o analogWrite() não funciona nos pinos 9 e 10. Se você adicionar a biblioteca Servo ao projeto, a biblioteca passa para o Timer2; aí quem é afetado são o PWM dos pinos 3 e 11 e a função tone().
Para uma leitura precisa: pressão do dedo, luz ambiente e movimento
A maior parte do que estraga uma leitura de pulso tem a ver com a forma de segurar o sensor, não com o código. Teste os pontos abaixo um por um com o Plotter Serial aberto; ver como cada um muda a onda é o jeito mais rápido de entender o sensor.
- Pressão do dedo: pressione de leve, mas de forma constante. Se você encostar de leve demais, cada tremor do dedo gera um ruído maior que a onda; se apertar demais, você comprime os capilares, o fluxo de sangue cai e a onda achata ou até desaparece. Os melhores resultados vêm prendendo o sensor no dedo com uma fita de velcro.
- Luz ambiente: a luz do sol e das lâmpadas entra no sensor pelas laterais. Na rede de 60 Hz do Brasil, muitas lâmpadas piscam 120 vezes por segundo (100 vezes em redes de 50 Hz), e essa cintilação entra no sinal. Cubra totalmente a área em volta do sensor com o dedo ou enrole uma faixa escura por cima.
- Movimento: um cabo balançando, falar, mexer o dedo ou andar produz sinais muito maiores que o pulso. Apoie a mão na mesa e fique parado durante a medição.
- Mãos frias: em um dedo frio, os vasos se contraem e o sinal fica mais fraco. Aqueça as mãos ou prenda o sensor no lóbulo da orelha; o lóbulo também sofre menos com o movimento.
- Suor e oleosidade: a pele úmida espalha a luz de outro jeito e suja a superfície do sensor. Use o adesivo protetor transparente na frente e limpe o sensor de vez em quando com um pano seco.
- Tempo de estabilização: nos primeiros 5–10 segundos depois que você coloca o dedo, o sinal se estabiliza e a média se completa; ignore os valores que aparecerem nesse período.
Monitor cardíaco em um display OLED
O Plotter Serial serve bem na mesa, mas um monitor cardíaco portátil precisa de uma telinha. O código abaixo desenha na parte de cima de um OLED SSD1306 de 0,96 polegada o BPM em números grandes e um ícone de coração que cresce a cada batimento, e na parte de baixo uma onda de pulso ao vivo que varre a tela da esquerda para a direita. Ligue o OLED nos pinos A4 (SDA) e A5 (SCL) do Uno e a alimentação dele no 5V e no GND; a instalação da biblioteca do display e o ajuste do endereço são explicados em detalhes no artigo display OLED de 0,96 polegada (SSD1306).
1// Pulse Sensor + 0,96" OLED: BPM, kalp simgesi ve nabız dalgası / BPM, heart icon and pulse wave
2#include <Wire.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5#include <PulseSensorPlayground.h>
6
7Adafruit_SSD1306 display(128, 64, &Wire, -1); // 128x64, reset pini yok / no reset pin
8PulseSensorPlayground pulseSensor;
9
10const int PULSE_PIN = A0; // sensörün S pini / sensor S pin
11const int THRESHOLD = 550; // başlangıç eşiği / starting threshold
12const int GRAPH_TOP = 24; // dalga alanının üst kenarı / top edge of the wave area
13const int GRAPH_H = 40; // dalga alanının yüksekliği / height of the wave area
14
15int x = 0; // tarama sütunu (0-127) / sweep column (0-127)
16int lastY = GRAPH_TOP + GRAPH_H / 2; // önceki noktanın y'si / y of the previous point
17int lo = 1023, hi = 0; // bu taramadaki en küçük/büyük değer / min/max in this sweep
18int scaleLo = 400, scaleHi = 624; // çizimde kullanılan aralık / range used for drawing
19unsigned long lastDraw = 0, beatTime = 0;
20
21// İki daire ve bir üçgenle kalp çiz / draw a heart from two circles and a triangle
22void drawHeart(int cx, int cy, bool big) {
23 int r = big ? 6 : 4; // atış anında büyük kalp / bigger heart on a beat
24 display.fillCircle(cx - r, cy - r / 2, r, SSD1306_WHITE);
25 display.fillCircle(cx + r, cy - r / 2, r, SSD1306_WHITE);
26 display.fillTriangle(cx - 2 * r, cy, cx + 2 * r, cy, cx, cy + 2 * r, SSD1306_WHITE);
27}
28
29void setup() {
30 Serial.begin(115200);
31 if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C adresi 0x3C / I2C address 0x3C
32 Serial.println(F("SSD1306 allocation failed"));
33 while (true) {}
34 }
35 display.clearDisplay();
36 pulseSensor.analogInput(PULSE_PIN);
37 pulseSensor.setThreshold(THRESHOLD);
38 pulseSensor.begin(); // örnekleme kesmeyle sürer / sampling runs in an interrupt
39}
40
41void loop() {
42 if (pulseSensor.sawStartOfBeat()) beatTime = millis(); // atış anını sakla / store the beat time
43
44 if (millis() - lastDraw < 40) return; // saniyede 25 kare / 25 frames per second
45 lastDraw = millis();
46
47 // Alt bölüm: soldan sağa taranan nabız dalgası / bottom: pulse wave swept left to right
48 int s = pulseSensor.getLatestSample();
49 if (s < lo) lo = s;
50 if (s > hi) hi = s;
51 int y = map(constrain(s, scaleLo, scaleHi), scaleLo, scaleHi, GRAPH_TOP + GRAPH_H - 1, GRAPH_TOP);
52 display.drawFastVLine(x, GRAPH_TOP, GRAPH_H, SSD1306_BLACK); // eski sütunu sil / erase the old column
53 display.drawFastVLine((x + 1) % 128, GRAPH_TOP, GRAPH_H, SSD1306_BLACK); // önündeki boşluk / gap ahead
54 if (x > 0) display.drawLine(x - 1, lastY, x, y, SSD1306_WHITE);
55 lastY = y;
56 if (++x >= 128) { // tarama bitti: ölçeği yenile / sweep done: refresh the scale
57 x = 0;
58 if (hi - lo > 20) { scaleLo = lo; scaleHi = hi; }
59 lo = 1023;
60 hi = 0;
61 }
62
63 // Üst bölüm: kalp ve BPM / top: heart and BPM
64 display.fillRect(0, 0, 128, GRAPH_TOP, SSD1306_BLACK);
65 drawHeart(14, 10, millis() - beatTime < 150);
66 int bpm = pulseSensor.getBeatsPerMinute();
67 display.setTextColor(SSD1306_WHITE);
68 display.setTextSize(3);
69 display.setCursor(40, 0);
70 if (bpm > 0) display.print(bpm);
71 else display.print(F("--")); // parmak yok / no finger
72 display.setTextSize(1);
73 display.setCursor(104, 14);
74 display.print(F("BPM"));
75 display.display(); // tamponu ekrana gönder / push the buffer to the screen
76}O código atualiza a tela 25 vezes por segundo e acrescenta um ponto à onda em cada quadro, então a largura da tela mostra cerca de 5 segundos de registro do pulso. No fim de cada varredura, a onda é reescalada pelo menor e pelo maior valor daquela varredura, para que um sinal fraco ou forte caiba na tela. Enviar um quadro ao display pelo I2C leva cerca de 25–30 ms, mas, como a biblioteca faz a amostragem em uma interrupção, nenhum batimento se perde nesse meio-tempo. No Uno, o buffer do display ocupa 1 KB de RAM; como o código desenha a onda direto na tela, sem guardá-la em um array, a memória é suficiente, embora sobrem só cerca de 450 bytes livres.
Medindo o pulso com o KY-039
No KY-039, o fototransistor está ligado em série com um resistor, e a tensão de saída muda com a luz que ele recebe: mais luz significa tensão mais baixa, menos luz, tensão mais alta. Não há amplificador nem filtro no meio. Com o dedo no lugar, um batimento costuma mover essa tensão só 2–10 passos do ADC, cerca de 10–50 mV; até uma pequena mudança na luz ambiente é maior que isso. Por isso, o código escrito para o Pulse Sensor e um limite fixo como 550 não funcionam no KY-039; é preciso primeiro limpar o sinal e depois usar um limite que se adapta ao tamanho da própria onda.
O código abaixo faz isso em quatro passos. Primeiro, ele lê o pino analógico sem parar durante 20 ms (cerca de 175 leituras) e tira a média; isso reduz o ruído aleatório e, como 20 ms é exatamente um período da rede de 50 Hz, cancela quase totalmente a cintilação das lâmpadas (na rede de 60 Hz do Brasil, use a janela de 60 Hz da tabela abaixo). Depois, suaviza o sinal com um filtro passa-baixa leve e remove o nível DC com uma média móvel bem lenta, deixando só a onda de pulso. No terceiro passo, calcula duas envoltórias que acompanham os picos e os vales da onda e coloca o limite no meio entre elas. Por fim, um batimento é contado quando o sinal cruza o limite subindo, e o BPM é obtido pela média dos últimos 8 intervalos entre batimentos.
1// KY-039 ile nabız ölçme (kütüphanesiz) / Heart rate with the KY-039 (no library)
2const int SENSOR_PIN = A0; // KY-039 S pini / KY-039 S pin
3const int LED_PIN = LED_BUILTIN; // atışta yanan LED / LED lit on each beat
4const bool PLOTTER = true; // true: Seri Çizici, false: Seri Monitör / true: Serial Plotter, false: Serial Monitor
5const unsigned long WINDOW_US = 20000; // 20 ms = 50 Hz şebekenin bir periyodu / one period of 50 Hz mains
6const float MIN_AMP = 1.2; // tepe-tepe gürültü sınırı (ADC birimi) / peak-to-peak noise limit (ADC units)
7const int AVG_BEATS = 8; // BPM için ortalanan aralık sayısı / intervals averaged for BPM
8
9float smooth, baseline; // süzülmüş sinyal ve DC seviye / filtered signal and DC level
10float posEnv = 0, negEnv = 0; // tepe ve çukur zarfları / peak and trough envelopes
11bool armed = true; // yeni atışa hazır mı / ready for a new beat
12unsigned long lastBeat = 0; // son atışın zamanı / time of the last beat
13unsigned int ibi[AVG_BEATS]; // atışlar arası süreler (ms) / inter-beat intervals (ms)
14int ibiCount = 0, ibiIndex = 0, bpm = 0;
15
16// 20 ms boyunca okuyup ortalama al / read for 20 ms and average
17float readWindow() {
18 unsigned long start = micros();
19 long sum = 0;
20 int n = 0;
21 while (micros() - start < WINDOW_US) {
22 sum += analogRead(SENSOR_PIN);
23 n++;
24 }
25 return (float)sum / n;
26}
27
28float averageIbi() {
29 if (ibiCount == 0) return 500.0; // geçmiş yoksa 120 BPM varsay (ilk bekleme 350 ms) / assume 120 BPM without history (first wait 350 ms)
30 unsigned long sum = 0;
31 for (int i = 0; i < ibiCount; i++) sum += ibi[i];
32 return (float)sum / ibiCount;
33}
34
35void addInterval(unsigned int ms) {
36 ibi[ibiIndex] = ms; // halka tampon / ring buffer
37 ibiIndex = (ibiIndex + 1) % AVG_BEATS;
38 if (ibiCount < AVG_BEATS) ibiCount++;
39 bpm = (int)(60000.0 / averageIbi() + 0.5);
40}
41
42void setup() {
43 Serial.begin(115200);
44 pinMode(LED_PIN, OUTPUT);
45 smooth = baseline = readWindow(); // başlangıç seviyesi / starting level
46}
47
48void loop() {
49 float x = readWindow(); // saniyede 50 örnek / 50 samples per second
50 unsigned long now = millis();
51
52 smooth += (x - smooth) * 0.5; // hafif alçak geçiren süzgeç / light low-pass filter
53 baseline += (smooth - baseline) * 0.05; // yavaş DC takibi (~0,4 s) / slow DC tracking (~0.4 s)
54 float ac = smooth - baseline; // yalnız nabız dalgası / pulse wave only
55
56 // Zarflar: uçları hemen yakala, yavaşça bırak / envelopes: catch extremes fast, release slowly
57 posEnv = (ac > posEnv) ? ac : posEnv * 0.99;
58 negEnv = (ac < negEnv) ? ac : negEnv * 0.99;
59 float amp = posEnv - negEnv; // tepe-tepe genlik / peak-to-peak amplitude
60 float mid = (posEnv + negEnv) / 2; // eşik: dalganın ortası / threshold: middle of the wave
61
62 unsigned long sinceBeat = now - lastBeat;
63 float refractory = constrain(averageIbi() * 0.7, 250.0, 600.0); // dikrotik çentiği atla / skip the dicrotic notch
64
65 if (armed && amp > MIN_AMP && ac > mid && sinceBeat > refractory) {
66 armed = false; // atış bulundu / beat found
67 digitalWrite(LED_PIN, HIGH);
68 if (sinceBeat < 2000) addInterval(sinceBeat); // 30 BPM'den yavaşı alma / ignore gaps slower than 30 BPM
69 lastBeat = now;
70 if (!PLOTTER && bpm > 0) {
71 Serial.print(F("BPM: "));
72 Serial.println(bpm);
73 }
74 }
75 if (!armed && ac < mid - amp * 0.25) { // dalga yeterince indi: yeniden hazır / wave fell enough: re-arm
76 armed = true;
77 digitalWrite(LED_PIN, LOW);
78 }
79 if (sinceBeat > 2500 && ibiCount > 0) { // 2,5 s atış yok: sıfırla / no beat for 2.5 s: reset
80 ibiCount = 0;
81 bpm = 0;
82 }
83
84 if (PLOTTER) {
85 Serial.print(F("Nabiz:"));
86 Serial.print(ac);
87 Serial.print(F(",Esik:"));
88 Serial.println(mid);
89 }
90}| Constante / valor | O que faz |
|---|---|
| WINDOW_US = 20000 (janela) | Janela da média; cancela a cintilação da rede de 50 Hz (use 16667 em países de 60 Hz, como o Brasil) |
| 0.5 (smooth, suavização) | Filtro passa-baixa leve; suaviza saltos bruscos |
| 0.05 (baseline, linha de base) | Acompanha o nível DC em cerca de 0,4 segundo; remove mudanças lentas na pressão do dedo |
| 0.99 (envoltórias) | Velocidade de liberação das envoltórias de pico e de vale (cerca de 2 segundos) |
| MIN_AMP = 1.2 (amplitude mínima) | Uma onda abaixo dessa amplitude pico a pico é considerada ruído |
| refractory (período refratário) | 70% do último intervalo médio (250–600 ms; 350 ms enquanto ainda não há histórico, para não perder um pulso rápido); impede que a incisura dicrótica conte como um segundo batimento |
| mid - amp * 0.25 (rearme) | Nível de rearme; nenhum novo batimento é procurado até a onda descer o suficiente |
Quando você carregar o código e abrir o Plotter Serial, vai ver duas linhas: Nabiz (pulso) mostra a onda limpa e Esik (limite), o limite adaptativo que passa pelo meio dela. Veja quanto a linha Nabiz oscila sem dedo e ajuste MIN_AMP um pouco acima desse valor pico a pico; diminua-o se o seu sinal for muito fraco. Para saber mais sobre como suavizar sinais, veja o artigo filtragem de dados de sensores. O mesmo código também funciona com o Pulse Sensor; nesse caso, basta aumentar MIN_AMP para cerca de 20.
Sensor de pulso com o ESP32
No ESP32, alimente o sensor pelo pino 3V3 e ligue o sinal ao GPIO 34. Esse pino pertence ao grupo ADC1; os pinos do ADC2 não podem ser lidos com o Wi-Fi ligado, então, se você for enviar os dados do pulso pela internet, precisa escolher um dos pinos do ADC1 (GPIO 32–39). O ADC do ESP32 é de 12 bits (0–4095) e, com alimentação de 3,3 V, o Pulse Sensor dá um valor em torno de 2000 em repouso.
As versões recentes da PulseSensorPlayground também são compatíveis com o ESP32, mas a biblioteca usa as funções de timer da versão 3.x do pacote de placas do ESP32 e não compila com a versão 2.x. Nos exemplos, ela reduz o ADC para 10 bits com analogReadResolution(10), para que o limite seja dado na mesma escala 0–1023 do Uno (685 no exemplo do ESP32). Para não depender das diferenças de versão, abaixo está um sketch sem biblioteca que usa o mesmo algoritmo da seção do KY-039; ele funciona com as duas versões do pacote de placas.
1// ESP32 + Pulse Sensor: kütüphanesiz BPM / BPM without a library
2const int SENSOR_PIN = 34; // ADC1 pini, Wi-Fi açıkken de çalışır / ADC1 pin, works with Wi-Fi on
3const int LED_PIN = 2; // çoğu DevKit'te dahili LED / built-in LED on most DevKits
4const unsigned long PERIOD_MS = 10; // saniyede 100 örnek / 100 samples per second
5const float MIN_AMP = 80; // 12 bitte tepe-tepe gürültü sınırı / peak-to-peak noise limit in 12-bit units
6const int AVG_BEATS = 8; // BPM için ortalanan aralık sayısı / intervals averaged for BPM
7
8float smooth, baseline, posEnv = 0, negEnv = 0;
9bool armed = true;
10unsigned long lastSample = 0, lastBeat = 0;
11unsigned long ibi[AVG_BEATS];
12int ibiCount = 0, ibiIndex = 0, bpm = 0;
13
14// 8 okumanın ortalaması: ESP32 ADC'si gürültülüdür / average of 8 reads: the ESP32 ADC is noisy
15float readSensor() {
16 long sum = 0;
17 for (int i = 0; i < 8; i++) sum += analogRead(SENSOR_PIN);
18 return sum / 8.0;
19}
20
21float averageIbi() {
22 if (ibiCount == 0) return 500.0; // geçmiş yoksa 120 BPM varsay (ilk bekleme 350 ms) / assume 120 BPM without history (first wait 350 ms)
23 unsigned long sum = 0;
24 for (int i = 0; i < ibiCount; i++) sum += ibi[i];
25 return (float)sum / ibiCount;
26}
27
28void setup() {
29 Serial.begin(115200);
30 pinMode(LED_PIN, OUTPUT);
31 analogReadResolution(12); // 0-4095 arası değer / values from 0 to 4095
32 smooth = baseline = readSensor();
33 lastSample = millis();
34}
35
36void loop() {
37 if (millis() - lastSample < PERIOD_MS) return;
38 lastSample += PERIOD_MS; // sabit aralıkla örnekle / sample at a fixed interval
39 unsigned long now = millis();
40
41 float x = readSensor();
42 smooth += (x - smooth) * 0.3; // alçak geçiren süzgeç (~6 Hz) / low-pass filter (~6 Hz)
43 baseline += (smooth - baseline) * 0.02; // DC takibi (~0,5 s) / DC tracking (~0.5 s)
44 float ac = smooth - baseline;
45
46 posEnv = (ac > posEnv) ? ac : posEnv * 0.995; // tepe zarfı / peak envelope
47 negEnv = (ac < negEnv) ? ac : negEnv * 0.995; // çukur zarfı / trough envelope
48 float amp = posEnv - negEnv;
49 float mid = (posEnv + negEnv) / 2;
50
51 unsigned long sinceBeat = now - lastBeat;
52 float refractory = constrain(averageIbi() * 0.7, 250.0, 600.0);
53
54 if (armed && amp > MIN_AMP && ac > mid && sinceBeat > refractory) {
55 armed = false;
56 digitalWrite(LED_PIN, HIGH);
57 if (sinceBeat < 2000) { // 30-240 BPM aralığı / 30-240 BPM range
58 ibi[ibiIndex] = sinceBeat;
59 ibiIndex = (ibiIndex + 1) % AVG_BEATS;
60 if (ibiCount < AVG_BEATS) ibiCount++;
61 bpm = (int)(60000.0 / averageIbi() + 0.5);
62 Serial.printf("BPM: %d IBI: %lu ms\n", bpm, sinceBeat);
63 }
64 lastBeat = now;
65 }
66 if (!armed && ac < mid - amp * 0.25) {
67 armed = true;
68 digitalWrite(LED_PIN, LOW);
69 }
70 if (sinceBeat > 2500 && ibiCount > 0) { // parmak çekildi / finger removed
71 ibiCount = 0;
72 bpm = 0;
73 Serial.println("Parmak yok / no finger");
74 }
75}Neste código, o sinal é lido 100 vezes por segundo e, como o ADC do ESP32 é ruidoso, cada amostra é a média de 8 leituras. Os valores de limite e das envoltórias estão ajustados para a escala de 12 bits, então MIN_AMP é 80 (cerca de 65 mV). O verdadeiro trunfo do ESP32 é o Wi-Fi: você pode transmitir o BPM ao vivo para uma página web por WebSocket, enviar uma mensagem no Telegram quando o pulso passar de um limite ou registrar as suas leituras no Google Sheets.
Medindo o pulso com o Raspberry Pi Pico e MicroPython
No Raspberry Pi Pico, ligue o pino + do sensor ao 3V3(OUT) (pino 36), o − ao GND (pino 38) e o S ao GP26 (ADC0, pino 31). No MicroPython, read_u16() retorna um valor de 0 a 65535; o código o desloca 4 bits para a direita para trazê-lo à mesma escala 0–4095 do ESP32, assim os valores de limite continuam iguais nas duas placas. Se você salvar o código no Pico como main.py pelo Thonny, ele roda sozinho toda vez que a placa é ligada.
1# Raspberry Pi Pico + Pulse Sensor: MicroPython ile BPM / BPM with MicroPython
2from machine import ADC, Pin
3import time
4
5sensor = ADC(26) # GP26 = ADC0, sensörün S pini / sensor S pin
6led = Pin("LED", Pin.OUT) # kartın üzerindeki LED / on-board LED
7
8PERIOD_MS = 10 # saniyede 100 örnek / 100 samples per second
9MIN_AMP = 80 # 12 bitte tepe-tepe gürültü sınırı / peak-to-peak noise limit (12-bit)
10AVG_BEATS = 8 # BPM için ortalanan aralık / intervals averaged for BPM
11
12def read12():
13 # 4 okumanın ortalaması, 0-4095 / average of 4 reads, 0-4095
14 s = 0
15 for _ in range(4):
16 s += sensor.read_u16() >> 4
17 return s / 4
18
19smooth = baseline = read12()
20pos_env = neg_env = 0.0
21armed = True
22ibis = []
23bpm = 0
24last_beat = time.ticks_ms()
25next_t = time.ticks_ms()
26
27while True:
28 while time.ticks_diff(time.ticks_ms(), next_t) < 0:
29 pass # bir sonraki örnek zamanını bekle / wait for the next sample time
30 next_t = time.ticks_add(next_t, PERIOD_MS)
31 now = time.ticks_ms()
32
33 x = read12()
34 smooth += (x - smooth) * 0.3 # alçak geçiren süzgeç / low-pass filter
35 baseline += (smooth - baseline) * 0.02 # DC takibi / DC tracking
36 ac = smooth - baseline
37
38 pos_env = ac if ac > pos_env else pos_env * 0.995
39 neg_env = ac if ac < neg_env else neg_env * 0.995
40 amp = pos_env - neg_env
41 mid = (pos_env + neg_env) / 2
42
43 since = time.ticks_diff(now, last_beat)
44 avg_ibi = sum(ibis) / len(ibis) if ibis else 500 # geçmiş yoksa 120 BPM varsay / assume 120 BPM without history
45 refractory = min(max(avg_ibi * 0.7, 250), 600) # dikrotik çentiği atla / skip the dicrotic notch
46
47 if armed and amp > MIN_AMP and ac > mid and since > refractory:
48 armed = False
49 led.on()
50 if since < 2000: # 30 BPM'den yavaşı alma / ignore gaps slower than 30 BPM
51 ibis.append(since)
52 if len(ibis) > AVG_BEATS:
53 ibis.pop(0)
54 bpm = round(60000 / (sum(ibis) / len(ibis)))
55 print("BPM:", bpm, "IBI:", since)
56 last_beat = now
57 if not armed and ac < mid - amp * 0.25:
58 armed = True
59 led.off()
60 if since > 2500 and ibis: # parmak çekildi / finger removed
61 ibis = []
62 bpm = 0
63 print("Parmak yok / no finger")O código funciona com a mesma lógica da versão para ESP32: ele faz uma amostragem a cada 10 ms, remove o nível DC, encontra o batimento com um limite adaptativo e calcula o BPM pela média dos últimos 8 intervalos. A cada batimento, o LED da placa acende, e o BPM e o intervalo entre batimentos são impressos na janela Shell do Thonny. Se a sintaxe do Python é novidade para você, o artigo fundamentos do MicroPython é um bom ponto de partida.
Diferença para o MAX30102: medindo o oxigênio no sangue (SpO2)
O MAX30102 é um sensor de chip único que a Analog Devices (antiga Maxim Integrated) projetou para medir a frequência cardíaca e a saturação de oxigênio no sangue. Dentro dele há dois LEDs, vermelho (660 nm) e infravermelho (880 nm), um fotodiodo, um circuito de cancelamento de luz ambiente e um ADC de 18 bits. Ele mesmo faz a medição, guarda as amostras em uma memória de 32 amostras (FIFO) e as envia como números pelo barramento I2C (endereço 0x57). Como o ruído captado pelos fios analógicos e o limite do ADC de 10 bits do Arduino deixam de importar, o sinal dele é muito mais limpo que o do Pulse Sensor.
O segredo da medição de SpO2 é usar luz de duas cores diferentes. A hemoglobina que transporta oxigênio absorve mais luz infravermelha e menos luz vermelha; a hemoglobina que já liberou o oxigênio faz o contrário. O sensor mede as componentes AC e DC da onda de pulso para as duas luzes e calcula a razão R = (AC_vermelho / DC_vermelho) / (AC_infravermelho / DC_infravermelho); a SpO2 é obtida a partir dessa razão com uma curva de calibração empírica. O Pulse Sensor e o KY-039 usam luz de uma só cor, então não conseguem calcular essa razão, ou seja, não medem oxigênio. A biblioteca mais popular para o MAX30102 no Arduino é a SparkFun MAX3010x; o exemplo Example5_HeartRate calcula a frequência cardíaca e o Example8_SPO2 calcula a saturação de oxigênio.
Um sensor de pulso não é um dispositivo médico
Há motivos concretos para isso. Monitores cardíacos e oxímetros de pulso médicos são calibrados dentro de limites de precisão definidos e testados em diferentes tons de pele, com baixo fluxo sanguíneo e em movimento; os módulos de hobby e os algoritmos que escrevemos não passam por esses testes. Além disso, medir o pulso não é o mesmo que avaliar o ritmo cardíaco: para entender problemas de ritmo é preciso um ECG, que registra o sinal elétrico do coração. Como informação geral, a frequência cardíaca de repouso de um adulto costuma ficar entre 60 e 100 batimentos por minuto; em quem pratica exercícios regularmente, ela pode cair para 40–60. Se a sua leitura ficar fora dessa faixa, confira primeiro o sensor e o código; se continuar preocupado, converse com um médico.
Ideias de projetos com sensor de pulso
- Luz que pulsa com o coração: a cada batimento, acenda em vermelho um anel de LEDs WS2812B e apague devagar; mude a cor do azul para o vermelho à medida que o BPM sobe.
- Monitor cardíaco com bipe: toque um bipe curto em um buzzer toda vez que sawStartOfBeat() retornar true; um projeto divertido que imita o som dos monitores de hospital.
- Monitor cardíaco portátil: coloque o código do OLED em uma caixa com uma bateria e um circuito de carga; desligue a tela quando a medição terminar para a bateria durar mais.
- Teste de recuperação pós-exercício: meça logo depois de terminar o exercício e calcule quantos batimentos o pulso cai no primeiro minuto; registre o valor todos os dias e acompanhe a sua evolução.
- Registrador de frequência cardíaca: grave os valores de BPM com data e hora em um cartão SD ou envie-os para o Excel e faça um gráfico.
- Jogo de exercício de respiração: mostre na tela como o pulso muda enquanto você respira de forma lenta e profunda; você vence quando leva o pulso abaixo de um valor-alvo.
Erros comuns e como corrigi-los
| Sintoma | Causa provável | Solução |
|---|---|---|
| BPM sempre 0 ou “--” | Nenhum batimento detectado: limite alto demais, sem dedo ou pino S na entrada errada | Confira a onda com o sketch do sinal bruto; mova o limite para o meio da onda |
| BPM muito alto, como 150–220 | Ruído ou a incisura dicrótica contados como um segundo batimento | Aumente o limite, firme o dedo, bloqueie a luz ambiente |
| BPM aparece mesmo sem dedo | Limite abaixo do ruído em repouso; cintilação das lâmpadas entrando no sinal | Ajuste o limite 30–50 passos acima do maior valor em repouso e cubra o sensor |
| Sinal parado, reto em 0 ou 1023 | + e − invertidos, fio partido, pino S no lugar errado ou sensor com defeito | Confira a ligação com a tabela; use 3V3 no ESP32 e no Pico |
| Há onda, mas ela vive se desfazendo | Dedo se mexendo ou pressão mudando | Prenda o sensor com uma fita de velcro, apoie a mão na mesa e fique parado |
| Nenhuma onda no KY-039 | LED não dobrado sobre o fototransistor ou luz ambiente forte saturando o fototransistor | Dobre o LED para que fique sobre o dedo, use o sensor na sombra, diminua MIN_AMP |
| “too few arguments to function 'hw_timer_t* timerBegin(uint8_t, uint16_t, bool)'” no ESP32 (argumentos de menos para timerBegin) | A PulseSensorPlayground está sendo compilada com a versão 2.x do pacote de placas do ESP32 | Atualize o pacote de placas para a 3.x ou use o código para ESP32 sem biblioteca deste artigo |
| analogWrite() não funciona no pino 9 ou 10 | A biblioteca usa o Timer1 no Uno | Use os pinos 3, 5, 6 ou 11 para PWM |
Um sensor de pulso é um daqueles raros projetos que ensinam leitura analógica, filtragem de sinais, interrupções de timer e gráficos em tempo real, tudo ao mesmo tempo com um único pino analógico, e você vê o resultado no próprio corpo. Se você quer um projeto para iniciantes que também mede luz, o sensor de luz LDR é um bom próximo passo. No app BlueGrays você encontra projetos que usam as peças deste artigo, como ler a intensidade da luz com um LDR, o buzzer, projetos com ESP32 e display OLED e um efeito de respiração com PWM no Pico, com esquemas do circuito e código passo a passo.
Perguntas frequentes
Qual é a precisão de um sensor de pulso com Arduino?
Quando bem posicionado e com a pessoa parada, um Pulse Sensor costuma medir a frequência cardíaca de repouso com erro de poucos BPM. Movimento, luz ambiente, mãos frias e a pressão do dedo aumentam o erro rapidamente. O sinal do KY-039 é bem mais fraco, então as leituras oscilam mais; nenhum desses sensores serve para medição médica.
Qual é a diferença entre o Pulse Sensor e o KY-039?
O Pulse Sensor usa um LED verde e o método reflexivo, amplifica e filtra o sinal com um amplificador operacional na placa e fica em repouso na metade da tensão de alimentação. O KY-039 mede a luz infravermelha que atravessa o dedo, não tem amplificador na placa e a saída dele varia só alguns passos do ADC. Por isso o Pulse Sensor funciona facilmente com uma biblioteca pronta, enquanto o KY-039 precisa de filtragem e de um limite adaptativo.
Um sensor de pulso consegue medir o oxigênio no sangue (SpO2)?
O Pulse Sensor e o KY-039, não, porque o cálculo de SpO2 depende de comparar a luz em dois comprimentos de onda diferentes, vermelho e infravermelho. Para isso, você precisa de um sensor com dois LEDs, como o MAX30102. Mesmo a leitura do MAX30102 é uma estimativa não calibrada e não substitui um oxímetro médico.
Por que o meu sensor de pulso mostra valores muito altos ou instáveis?
A causa mais comum é o ruído ou a incisura dicrótica da onda de pulso serem contados como um segundo batimento; nesse caso, o BPM sai mais ou menos o dobro do valor real. Aumente o limite, prenda o dedo no lugar com uma fita de velcro e proteja o sensor da luz. Olhar a onda bruta no Plotter Serial mostra a origem do problema na hora.
O Pulse Sensor funciona com o ESP32 e o Raspberry Pi Pico?
Sim, mas nas duas placas você deve alimentar o sensor pelo pino 3V3, porque as entradas analógicas delas não suportam mais de 3,3 V. No ESP32, use um pino do ADC1, como o GPIO 34, para não conflitar com o Wi-Fi; no Pico, use o GP26, o GP27 ou o GP28. A PulseSensorPlayground exige a versão 3.x do pacote de placas do ESP32 e, no MicroPython, você escreve o seu próprio algoritmo, como mostrado neste artigo.
Qual é a frequência cardíaca normal?
Em adultos em repouso, a frequência cardíaca costuma ficar entre 60 e 100 batimentos por minuto; em quem pratica exercícios regularmente pode ser de 40–60 e, em crianças, é mais alta. O pulso sobe naturalmente com exercício, estresse, cafeína e febre. Esta é uma informação geral; converse com um médico sobre as suas leituras ou qualquer sintoma.
Projetos para montar depois desta leitura
Ver todos os projetos
Medição da intensidade da luz com LDR
Quer ver a luminosidade do seu quarto em números? O LDR, um pequeno componente sensível à luz, mede a luz ambiente, e o Arduino exibe o valor na tela do computador a cada meio segundo. Cubra o sensor

Leitura da intensidade da luz com módulo LDR e ESP32
Acompanhe em tempo real no Monitor Serial o valor bruto do ADC do módulo LDR e a porcentagem relativa de luz calculada a partir de referências de escuridão e claridade. Essa porcentagem não é uma medida em lux. O projeto ensina a lógica de calibração do módulo. É ideal fazê-lo antes do projeto 10, pois ajuda a determinar o valor do limiar.

Uso do buzzer
Já se perguntou como uma campainha ou um alarme produz som? O Arduino faz um pequeno buzzer emitir bipes em intervalos curtos: bipe, pausa, bipe, pausa. Você vê como a eletricidade produz som e dá o p

Temperatura e umidade com DHT11
Qual é a temperatura do seu quarto? O ar está úmido ou seco? O sensor DHT11 mede a temperatura e a umidade, e o Arduino mostra os valores em um display LCD de duas linhas, atualizando a cada poucos se

Voltímetro digital com display LCD
Descubra a tensão de uma pilha ou de um adaptador de alimentação com um medidor feito por você. Um divisor formado por dois resistores reduz a tensão alta a um nível que a placa consegue ler, e o Ardu

ESP32 HTTPClient + API de clima (com display OLED SSD1306)
Neste projeto, usamos o ESP32 para fazer uma chamada REST à API de clima wttr.in pela internet com HTTPClient.h. Diferentemente da versão anterior com LCD I2C, aqui usamos um display OLED SSD1306 (128×64), que permite usar fontes grandes, ícones vetoriais e uma apresentação visual rica em uma única tela. Sete ícones vetoriais de condições do tempo (ensolarado, parcialmente nublado, nublado, chuvoso, com neve, com tempestade e com neblina) são desenhados durante a execução, sem arquivos bitmap.

ESP32 em sono profundo — Economia de bateria (OLED + contador na RAM RTC)
O modo de sono profundo do ESP32 reduz o consumo de corrente para ~10 µA ao desligar a CPU e a maioria dos periféricos, sendo essencial para dispositivos IoT alimentados por bateria. Neste projeto, usamos três fontes de despertar: um temporizador (15 segundos), um pino externo (EXT0 — botão no GPIO 33) e toque (T0/GPIO 4). As variáveis marcadas com RTC_DATA_ATTR são preservadas durante o sono profundo, mantendo os contadores de inicializações e de despertares por temporizador e botão.

Raspberry Pi Pico: variação de brilho do LED com PWM (efeito de respiração)
O LED não só pisca — ele respira! Com o PWM (modulação por largura de pulso) do Raspberry Pi Pico, o brilho do LED aumenta e diminui lentamente, como a luz de um MacBook em repouso. Aprenda a usar a classe machine.PWM, duty_u16() (brilho de 16 bits, de 0–65535), frequência PWM (freq(1000) — 1.000 ciclos de ligar/desligar por segundo, sem cintilação visível), laços com passos usando range() (crescentes e decrescentes) e saída semelhante à analógica em um pino digital.
Mais artigos
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

