feat(formacoes): módulo 2 (DHT22 + LCD 16x2 no Wokwi), PDFs e página de entrada
Release / Plano (reaproveitar a imagem?) (push) Successful in 7s
CI / Lint e auditorias (push) Successful in 1m48s
CI / Trivy (dependências e segredos) (push) Successful in 12s
CI / Imagem Docker (linux/arm64) (push) Failing after 2m27s
Release / ci (push) Failing after 4m31s
Release / Publica a imagem (linux/arm64) (push) Skipped
Release / Cria a release no Gitea (push) Skipped
- Formação 2 da LASER: slides visuais, apostila, projeto do Wokwi e scanner I2C, validados no simulador (leitura normal, temperatura negativa e sensor desconectado). Circuito com pull-ups de 10 kΩ e 4,7 kΩ para 3V3 e aviso sobre os pull-ups de 5 V da plaquinha PCF8574. - PDFs prontos dos slides e das apostilas dos módulos 1 e 2; slides-pdf agora também exporta a apostila. - Página de entrada em /formacoes e /formacoes/N, gerada por scripts/formacoes-viewer.mjs a partir do formacao.json de cada pasta. - A apostila passou de index.html para apostila.html. - Corrige o link do Fritzing da apostila 1 (/apostilas/1 não existe). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -6,6 +6,26 @@ Tudo o que muda de relevante no ESPDocs está aqui. O formato segue o
|
||||
|
||||
## [Não lançado]
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Formação 2 da LASER em `/formacoes/2`: estação de temperatura e umidade com DHT22, LCD 16x2
|
||||
I2C e ESP32-S3, simulada no Wokwi com o framework Arduino. Slides, apostila, projeto do
|
||||
Wokwi e programa de scanner I2C, tudo validado no simulador.
|
||||
- Página de entrada para cada formação (`/formacoes/1`, `/formacoes/2`) e lista geral em
|
||||
`/formacoes`, geradas por `npm run formacoes-viewer` a partir do `formacao.json` de cada pasta.
|
||||
- PDFs prontos dos slides e das apostilas das formações 1 e 2. `npm run slides-pdf -- N`
|
||||
agora gera também o PDF da apostila.
|
||||
|
||||
### Alterado
|
||||
|
||||
- A apostila de cada formação passou de `index.html` para `apostila.html`; o `index.html`
|
||||
agora é a página de entrada.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- O link do arquivo do Fritzing na apostila da formação 1 apontava para `/apostilas/1/`,
|
||||
caminho que não existe.
|
||||
|
||||
## [1.7.0] - 2026-11-02
|
||||
|
||||
### Adicionado
|
||||
|
||||
@@ -65,6 +65,15 @@ export default function middleware(request) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Material das formações: /formacoes e /formacoes/N mostram a página de
|
||||
// entrada gerada em public/formacoes. Sem isto a rota cairia na regra de
|
||||
// URL antiga e iria para /pt/formacoes, que não existe.
|
||||
if (/^\/formacoes(\/\d+)?\/?$/.test(pathname)) {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = `${pathname.replace(/\/$/, "")}/index.html`;
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
// 1. Raiz: negociação de idioma (307) considerando apenas idiomas ATIVOS
|
||||
if (pathname === "/") {
|
||||
const targetLocale = resolveRootLocale(request);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"favicons": "node scripts/generate-favicons.mjs",
|
||||
"auditar-temas": "node scripts/auditar-temas.mjs",
|
||||
"slides-pdf": "node scripts/slides-pdf.mjs",
|
||||
"formacoes-viewer": "node scripts/formacoes-viewer.mjs",
|
||||
"check:pinos": "NODE_NO_WARNINGS=1 node scripts/verifica-pinos-restricoes.mjs && NODE_NO_WARNINGS=1 node scripts/verifica-pinos-busca.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"modulo": 1,
|
||||
"titulo": ["Conhecendo a", "família ESP32"],
|
||||
"subtitulo": "Do silício à bancada: como esses microcontroladores funcionam por dentro, o que cada uma das doze séries faz e não faz, e como escolher o chip certo. Termina com um DHT11 lendo temperatura e umidade num ESP32-S3.",
|
||||
"descricao": "Slides, apostila e arquivos do módulo 1 da formação em ESP32 da LASER: arquitetura, as doze séries da família e uma prática com DHT11 e ESP32-S3.",
|
||||
"fichas": [["Duração", "3 horas"], ["Formato", "presencial"], ["Placa", "ESP32-S3-DevKitC-1"], ["Prática", "DHT11 + monitor serial"]],
|
||||
"pratica": {
|
||||
"titulo": "Prática na bancada",
|
||||
"texto": "Esquema de ligação do DHT11 ao ESP32-S3, com o arquivo editável do Fritzing para montar ou adaptar.",
|
||||
"imagem": "pratica.png",
|
||||
"meta": "Fritzing · capítulo 9 da apostila",
|
||||
"acoes": [["Ver esquema", "pratica.png", false], ["Baixar .fzz", "pratica.fzz", true]]
|
||||
},
|
||||
"grupos": [
|
||||
{ "titulo": "Material de aula", "arquivos": [
|
||||
{ "arquivo": "slide.html", "tipo": "slides", "descricao": "Os slides da formação. Abrem no navegador, em tela cheia, prontos para projetar.", "abrir": true },
|
||||
{ "arquivo": "apostila.html", "tipo": "apostila", "descricao": "A apostila completa, paginada em A4 no próprio navegador, com glossário e capítulos extras.", "abrir": true }
|
||||
]},
|
||||
{ "titulo": "Para baixar e imprimir", "arquivos": [
|
||||
{ "arquivo": "LASER-ESP32-Modulo-1-Slides.pdf", "tipo": "pdf", "descricao": "Os slides em PDF 16:9, um slide por página.", "abrir": true, "baixar": true },
|
||||
{ "arquivo": "LASER-ESP32-Modulo-1-Apostila.pdf", "tipo": "pdf", "descricao": "A apostila em PDF A4, pronta para imprimir.", "abrir": true, "baixar": true }
|
||||
]},
|
||||
{ "titulo": "Prática", "arquivos": [
|
||||
{ "arquivo": "pratica.fzz", "tipo": "fritzing", "descricao": "Montagem do DHT11 com o ESP32-S3, editável no Fritzing.", "baixar": true },
|
||||
{ "arquivo": "pratica.png", "tipo": "imagem", "descricao": "O mesmo esquema em imagem, para consultar sem instalar nada.", "abrir": true }
|
||||
]},
|
||||
{ "titulo": "Imagens", "arquivos": [
|
||||
{ "arquivo": "esp32-s3-blocos.png", "tipo": "imagem", "descricao": "Diagrama de blocos do ESP32-S3, usado no capítulo 2.", "abrir": true }
|
||||
]}
|
||||
],
|
||||
"guias": [
|
||||
{ "titulo": "Montando a prática", "itens": [
|
||||
"Ligue o <b>VCC</b> do DHT11 ao <b>3V3</b>, o <b>DATA</b> ao <b>GPIO12</b> e o <b>GND</b> ao <b>GND</b>.",
|
||||
"Coloque o resistor de <b>10 kΩ</b> entre DATA e 3V3: é o pull-up da linha de dados.",
|
||||
"Instale a <b>DHT sensor library</b>, da Adafruit, na IDE do Arduino.",
|
||||
"Na porta <b>USB</b> da placa, ative <i>USB CDC On Boot</i>; na porta <b>UART</b>, desative."
|
||||
]},
|
||||
{ "titulo": "Abrindo o arquivo do Fritzing", "texto": "O <code>pratica.fzz</code> abre no <a href=\"https://fritzing.org\">Fritzing</a>, onde dá para trocar o pino, mover os fios e exportar o esquema em imagem ou PDF." }
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 33 KiB |
@@ -2073,7 +2073,7 @@ Temperatura: 26.1 C | Umidade: 62.0 %</code></pre>
|
||||
<h1>Obrigado.<strong>Perguntas?</strong></h1>
|
||||
<p class="capa-sub">
|
||||
A apostila completa deste módulo, com o glossário, os capítulos que não couberam no tempo
|
||||
e o arquivo do Fritzing, está em <strong>index.html</strong>, na mesma pasta.
|
||||
e o arquivo do Fritzing, está em <strong>espdocs.cienciaembarcada.com.br/formacoes/1</strong>.
|
||||
</p>
|
||||
<div class="capa-autor">
|
||||
<strong>Lucas Rayan Guerra</strong>
|
||||
|
||||
@@ -0,0 +1,800 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>LASER · Formação em ESP32 · Módulo 2: Estação de Temperatura e Umidade</title>
|
||||
<meta name="author" content="Lucas Rayan Guerra">
|
||||
<meta name="description" content="Apostila do módulo 2 da formação em ESP32 da LASER: DHT22, LCD 16x2 I2C e ESP32-S3, simulados no Wokwi com o framework Arduino.">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="../../marca/espdocs-marca.svg">
|
||||
<link rel="stylesheet" href="../assets/apostila.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p class="aviso-carregando">Paginando a apostila…</p>
|
||||
|
||||
<!-- ══════════════════════════════ CAPA ══════════════════════════════ -->
|
||||
<section class="pagina-capa">
|
||||
<div class="capa">
|
||||
<img class="capa-curvas" src="../assets/capa-curvas.svg" alt="">
|
||||
|
||||
<div class="capa-marca">
|
||||
<img src="../assets/laser-escuro.svg" alt="LASER">
|
||||
<span>Liga Acadêmica de Sistemas Embarcados<br>Centro de Informática · UFPE</span>
|
||||
</div>
|
||||
|
||||
<div class="capa-selo">Formação em ESP32 · Módulo 2</div>
|
||||
|
||||
<h1 class="capa-titulo">Estação de<br><strong>temperatura e umidade</strong></h1>
|
||||
|
||||
<p class="capa-sub">
|
||||
Um DHT22, um display LCD 16x2 e um ESP32-S3, primeiro no simulador Wokwi e depois na
|
||||
bancada. Dois protocolos de comunicação, um programa curto e os cuidados elétricos que
|
||||
fazem o circuito funcionar fora da tela também.
|
||||
</p>
|
||||
|
||||
<div class="capa-rodape">
|
||||
<div>
|
||||
<strong>Lucas Rayan Guerra</strong>
|
||||
Ciência Embarcada · ESPDocs
|
||||
linkedin.com/in/lucasrguerra
|
||||
</div>
|
||||
<div style="text-align:right">
|
||||
<strong>Material de apoio</strong>
|
||||
Formação presencial · uso livre para fins educacionais
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════ APRESENTAÇÃO ══════════════════════════ -->
|
||||
<section class="capitulo" id="apresentacao">
|
||||
<div class="capitulo-abertura">
|
||||
<div>
|
||||
<h2>Apresentação</h2>
|
||||
<p class="subtitulo">O que vamos construir, o que você precisa e como usar esta apostila.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
No módulo 1 lemos um sensor e imprimimos o resultado no monitor serial, que só existe
|
||||
enquanto há um computador ligado à placa. Neste módulo o projeto ganha <strong>uma tela
|
||||
própria</strong>: um display LCD de 16 colunas por 2 linhas mostra a temperatura e a
|
||||
umidade medidas por um <strong>DHT22</strong>, e o conjunto passa a funcionar sozinho,
|
||||
ligado em qualquer carregador USB.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Toda a formação acontece no <strong>Wokwi</strong>, um simulador de microcontroladores que
|
||||
roda no navegador. Ele compila o mesmo código que você gravaria na placa, simula o ESP32-S3,
|
||||
o sensor e o display, e deixa você mudar a temperatura com um controle deslizante. Assim
|
||||
cada pessoa monta e testa o circuito sem precisar de componentes, e quem quiser repetir em
|
||||
casa com peças de verdade encontra no capítulo 7 o que muda fora do simulador.
|
||||
</p>
|
||||
|
||||
<h3>O que você vai levar deste módulo</h3>
|
||||
<ul>
|
||||
<li>Montar e simular um projeto completo de ESP32 no Wokwi, do zero.</li>
|
||||
<li>Entender como o DHT22 transmite uma medição por um único fio.</li>
|
||||
<li>Entender o barramento I2C: dois fios, endereços e resistores de pull-up.</li>
|
||||
<li>Escrever no LCD 16x2, inclusive com um caractere que você mesmo desenha.</li>
|
||||
<li>Saber o que precisa mudar para o mesmo circuito funcionar na bancada.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Pré-requisitos</h3>
|
||||
<p>
|
||||
Ter acompanhado o módulo 1 ajuda, mas não é obrigatório. Basta saber o que são
|
||||
<code>setup()</code> e <code>loop()</code> num programa Arduino. Para a simulação você
|
||||
precisa só de um navegador atualizado; não é preciso instalar nada nem criar conta.
|
||||
</p>
|
||||
|
||||
<h3>Os arquivos deste módulo</h3>
|
||||
<p>
|
||||
Tudo o que aparece nesta apostila foi compilado e executado no Wokwi, e os arquivos estão
|
||||
na mesma pasta da formação:
|
||||
</p>
|
||||
<table>
|
||||
<thead><tr><th style="width:34%">Arquivo</th><th>Conteúdo</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque"><a href="wokwi/sketch.ino">sketch.ino</a></td><td>O programa da estação</td></tr>
|
||||
<tr><td class="destaque"><a href="wokwi/diagram.json">diagram.json</a></td><td>O circuito: peças, posições e fios</td></tr>
|
||||
<tr><td class="destaque"><a href="wokwi/libraries.txt">libraries.txt</a></td><td>As bibliotecas que o Wokwi deve instalar</td></tr>
|
||||
<tr><td class="destaque"><a href="wokwi/scanner-i2c.ino">scanner-i2c.ino</a></td><td>Programa de diagnóstico do barramento I2C</td></tr>
|
||||
<tr><td class="destaque"><a href="modulo-2-wokwi.zip">modulo-2-wokwi.zip</a></td><td>Os quatro arquivos acima num só pacote</td></tr>
|
||||
<tr><td class="destaque"><a href="LASER-ESP32-Modulo-2-Apostila.pdf">Apostila em PDF</a></td><td>Esta apostila, pronta para imprimir</td></tr>
|
||||
<tr><td class="destaque"><a href="LASER-ESP32-Modulo-2-Slides.pdf">Slides em PDF</a></td><td>Os slides da formação, em 16:9</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grupo">
|
||||
<p class="junto">Ao longo do texto você encontra quatro tipos de caixa:</p>
|
||||
<div class="caixa dica"><div class="caixa-titulo">Dica</div><p>Atalho, boa prática ou recomendação que economiza tempo.</p></div>
|
||||
<div class="caixa atencao"><div class="caixa-titulo">Atenção</div><p>Ponto que costuma causar problema se passar despercebido.</p></div>
|
||||
<div class="caixa erro"><div class="caixa-titulo">Erro comum</div><p>Armadilha concreta, com o sintoma que ela produz e como escapar.</p></div>
|
||||
<div class="caixa curiosidade"><div class="caixa-titulo">Curiosidade</div><p>Contexto ou detalhe técnico que ajuda a fixar o conceito.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════════════════════ SUMÁRIO ═════════════════════════════ -->
|
||||
<section class="capitulo">
|
||||
<div class="capitulo-abertura">
|
||||
<div>
|
||||
<h2>Sumário</h2>
|
||||
<p class="subtitulo">Oito capítulos, da peça solta ao circuito funcionando.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sumario">
|
||||
<a href="#cap1"><span class="num">1</span><span class="txt">O projeto</span><span class="pontos"></span></a>
|
||||
<a href="#cap2"><span class="num">2</span><span class="txt">O sensor DHT22</span><span class="pontos"></span></a>
|
||||
<a href="#cap3"><span class="num">3</span><span class="txt">O display LCD 16x2 e o barramento I2C</span><span class="pontos"></span></a>
|
||||
<a href="#cap4"><span class="num">4</span><span class="txt">O simulador Wokwi</span><span class="pontos"></span></a>
|
||||
<a href="#cap5"><span class="num">5</span><span class="txt">Montagem</span><span class="pontos"></span></a>
|
||||
<a href="#cap6"><span class="num">6</span><span class="txt">O programa</span><span class="pontos"></span></a>
|
||||
<a href="#cap7"><span class="num">7</span><span class="txt">Da simulação para a bancada</span><span class="pontos"></span></a>
|
||||
<a href="#cap8"><span class="num">8</span><span class="txt">Problemas comuns e desafios</span><span class="pontos"></span></a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 1 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap1">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">1</div>
|
||||
<div>
|
||||
<h2>O projeto</h2>
|
||||
<p class="subtitulo">Três peças, dois protocolos e uma tela que mostra o ambiente.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
A estação é composta por três peças. O <strong>DHT22</strong> mede temperatura e umidade
|
||||
relativa do ar. O <strong>ESP32-S3</strong> pede uma medição a cada dois segundos, formata
|
||||
os números e os envia ao <strong>LCD 16x2</strong>, que os mantém na tela até a próxima
|
||||
atualização.
|
||||
</p>
|
||||
|
||||
<figure>
|
||||
<img src="circuito-wokwi.png" alt="Circuito completo no simulador Wokwi: o DHT22 à esquerda, a placa ESP32-S3-DevKitC-1 ao centro, três resistores no alto ligados ao 3V3 e o LCD 16x2 embaixo mostrando Temp: 26.5°C e Umid: 62.0 %." style="max-height:120mm">
|
||||
<figcaption>
|
||||
O circuito deste módulo rodando no Wokwi. O display mostra a leitura que o DHT22 simulado
|
||||
está informando: 26,5 °C e 62 % de umidade.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<p>
|
||||
Cada peça conversa com o ESP32 de um jeito diferente, e é isso que torna o projeto bom para
|
||||
aprender. O DHT22 usa um <strong>protocolo próprio de um fio</strong>, em que o valor de
|
||||
cada bit está na duração de um pulso. O LCD usa o <strong>I2C</strong>, um barramento
|
||||
padronizado de dois fios em que cada dispositivo tem um endereço. Quem entende esses dois
|
||||
protocolos já entende a maior parte dos sensores e módulos baratos do mercado.
|
||||
</p>
|
||||
|
||||
<h3>Lista de material</h3>
|
||||
<p>No Wokwi as peças já existem. Para montar em bancada você vai precisar de:</p>
|
||||
<table>
|
||||
<thead><tr><th style="width:44%">Item</th><th style="width:10%">Qtd.</th><th>Observação</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">ESP32-S3-DevKitC-1</td><td>1</td><td>Qualquer variante de memória serve</td></tr>
|
||||
<tr><td class="destaque">Sensor DHT22 (AM2302)</td><td>1</td><td>O componente branco de 4 pinos</td></tr>
|
||||
<tr><td class="destaque">Display LCD 16x2 com módulo I2C</td><td>1</td><td>A plaquinha PCF8574 vem soldada atrás</td></tr>
|
||||
<tr><td class="destaque">Conversor de nível I2C (BSS138)</td><td>1</td><td>Explicado no capítulo 7</td></tr>
|
||||
<tr><td class="destaque">Resistor de 10 kΩ</td><td>1</td><td>Pull-up do DHT22 (marrom, preto, laranja)</td></tr>
|
||||
<tr><td class="destaque">Resistor de 4,7 kΩ</td><td>2</td><td>Pull-ups do I2C (amarelo, violeta, vermelho)</td></tr>
|
||||
<tr><td class="destaque">Protoboard, jumpers e cabo USB de dados</td><td>—</td><td>Cabo só de carga não grava a placa</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 2 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap2">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">2</div>
|
||||
<div>
|
||||
<h2>O sensor DHT22</h2>
|
||||
<p class="subtitulo">Dois sensores numa caixinha e um protocolo de um fio só.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Dentro do DHT22 existem dois elementos de medição e um pequeno microcontrolador. A umidade
|
||||
é medida por um <strong>sensor capacitivo</strong>: um material que absorve vapor d'água
|
||||
e, com isso, muda a capacitância. A temperatura vem de um <strong>termistor</strong>, um
|
||||
resistor cujo valor varia com o calor. O microcontrolador interno lê os dois, aplica a
|
||||
calibração gravada de fábrica e entrega os números já prontos, em formato digital.
|
||||
</p>
|
||||
|
||||
<h3>DHT22 ou DHT11</h3>
|
||||
<p>No módulo 1 usamos o DHT11. O DHT22 tem a mesma pinagem e o mesmo protocolo, mas mede melhor:</p>
|
||||
<table>
|
||||
<thead><tr><th style="width:34%">Característica</th><th style="width:33%">DHT11</th><th>DHT22</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">Faixa de temperatura</td><td>0 a 50 °C</td><td class="sim">−40 a 80 °C</td></tr>
|
||||
<tr><td class="destaque">Precisão de temperatura</td><td>±2 °C</td><td class="sim">±0,5 °C</td></tr>
|
||||
<tr><td class="destaque">Faixa de umidade</td><td>20 a 90 %</td><td class="sim">0 a 100 %</td></tr>
|
||||
<tr><td class="destaque">Precisão de umidade</td><td>±5 %</td><td class="sim">±2 a 5 %</td></tr>
|
||||
<tr><td class="destaque">Resolução</td><td>1 °C e 1 %</td><td class="sim">0,1 °C e 0,1 %</td></tr>
|
||||
<tr><td class="destaque">Intervalo mínimo entre leituras</td><td class="sim">1 s</td><td>2 s</td></tr>
|
||||
<tr><td class="destaque">Alimentação</td><td>3,3 a 5 V</td><td>3,3 a 6 V</td></tr>
|
||||
</tbody>
|
||||
<caption>Valores típicos dos datasheets da Aosong. Confira sempre o do seu fornecedor.</caption>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
O preço é a única desvantagem real do DHT22, e o intervalo de dois segundos entre leituras
|
||||
raramente importa: temperatura e umidade de um ambiente não mudam mais rápido do que isso.
|
||||
</p>
|
||||
|
||||
<h3>Os quatro pinos</h3>
|
||||
<table>
|
||||
<thead><tr><th style="width:16%">Pino</th><th style="width:22%">Nome</th><th>Ligação</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">1</td><td>VCC</td><td>3V3 do ESP32</td></tr>
|
||||
<tr><td class="destaque">2</td><td>DATA</td><td>GPIO12, com pull-up de 10 kΩ para o 3V3</td></tr>
|
||||
<tr><td class="destaque">3</td><td>NC</td><td>Não conectado</td></tr>
|
||||
<tr><td class="destaque">4</td><td>GND</td><td>GND do ESP32</td></tr>
|
||||
</tbody>
|
||||
<caption>Pinos contados da esquerda para a direita, com a grade do sensor virada para você.</caption>
|
||||
</table>
|
||||
|
||||
<h3>Como uma medição atravessa um único fio</h3>
|
||||
<p>
|
||||
O ESP32 e o sensor dividem o mesmo fio de dados, e por isso nenhum dos dois "força" nível
|
||||
alto: os dois apenas puxam a linha para o terra quando precisam de um zero e a soltam no
|
||||
resto do tempo. Quem traz a linha de volta para o nível alto é o resistor de
|
||||
<strong>pull-up</strong>. Uma leitura acontece assim:
|
||||
</p>
|
||||
|
||||
<div class="passo">
|
||||
<div class="passo-num">1</div>
|
||||
<div><h3>O ESP32 pede a medição</h3><p>Puxa a linha para baixo por cerca de 1 ms e depois a solta.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">2</div>
|
||||
<div><h3>O sensor confirma</h3><p>Responde com 80 µs em nível baixo seguidos de 80 µs em nível alto.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">3</div>
|
||||
<div>
|
||||
<h3>Chegam 40 bits</h3>
|
||||
<p>
|
||||
Cada bit começa com 50 µs em baixo. Depois vem um pulso alto: se ele dura cerca de
|
||||
26 µs é um <strong>0</strong>; se dura cerca de 70 µs é um <strong>1</strong>.
|
||||
São 16 bits de umidade, 16 de temperatura e 8 de verificação.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<figure>
|
||||
<svg class="diagrama" viewBox="0 0 640 150" role="img" aria-label="Diagrama de tempo: pedido do ESP32, resposta do sensor, um bit 0 com pulso alto curto e um bit 1 com pulso alto longo.">
|
||||
<g stroke="#9aa9b3" stroke-width="0.8" stroke-dasharray="2 3" fill="none">
|
||||
<line x1="50" y1="40" x2="630" y2="40"/><line x1="50" y1="100" x2="630" y2="100"/>
|
||||
</g>
|
||||
<text x="44" y="43" font-size="9" text-anchor="end" fill="#5b6b75" font-family="JetBrains Mono, monospace">alto</text>
|
||||
<text x="44" y="103" font-size="9" text-anchor="end" fill="#5b6b75" font-family="JetBrains Mono, monospace">baixo</text>
|
||||
<polyline fill="none" stroke="#003049" stroke-width="2.2" stroke-linejoin="round"
|
||||
points="50,40 70,40 70,100 170,100 170,40 205,40 205,100 255,100 255,40 305,40 305,100 345,100 345,40 375,40 375,100 415,100 415,40 500,40 500,100 540,100 540,40 630,40"/>
|
||||
<g font-family="JetBrains Mono, monospace" font-size="9.5" fill="#D62828" text-anchor="middle">
|
||||
<text x="120" y="30">~1 ms</text><text x="280" y="30">80 µs</text><text x="360" y="30">26 µs</text><text x="457" y="30">70 µs</text>
|
||||
</g>
|
||||
<g font-family="Inter, sans-serif" font-size="10" fill="#1d2b33" text-anchor="middle">
|
||||
<text x="120" y="125">ESP32 pede</text><text x="255" y="125">sensor confirma</text><text x="360" y="125">bit 0</text><text x="477" y="125">bit 1</text><text x="595" y="125" fill="#5b6b75">…40 bits</text>
|
||||
</g>
|
||||
</svg>
|
||||
<figcaption>
|
||||
O valor de cada bit está na <strong>largura</strong> do pulso alto. Sem o pull-up a
|
||||
linha sobe devagar demais, os tempos saem errados e a leitura falha.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<div class="caixa atencao">
|
||||
<div class="caixa-titulo">Sensor solto ou módulo em plaquinha</div>
|
||||
<p>
|
||||
O DHT22 também é vendido soldado numa plaquinha de três pinos, que já traz o resistor de
|
||||
pull-up. Se o seu for assim, o resistor externo de 10 kΩ é dispensável. O sensor solto,
|
||||
de quatro pinos, <strong>precisa</strong> dele.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 3 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap3">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">3</div>
|
||||
<div>
|
||||
<h2>O display LCD 16x2 e o barramento I2C</h2>
|
||||
<p class="subtitulo">Trinta e dois caracteres, quatro fios e um endereço.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
O LCD 16x2 mostra duas linhas de dezesseis caracteres. Cada caractere é uma matriz de
|
||||
<strong>5 por 8 pontos</strong>, e o controlador do display, o HD44780 ou um compatível,
|
||||
já sabe desenhar letras, números e símbolos comuns. Ele também guarda até
|
||||
<strong>oito caracteres personalizados</strong>, que você desenha ponto a ponto. É assim
|
||||
que este projeto mostra o símbolo de grau.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sozinho, o display precisa de pelo menos seis fios de dados e controle. Por isso ele quase
|
||||
sempre vem com uma plaquinha soldada atrás, baseada no chip <strong>PCF8574</strong>, que
|
||||
converte esses sinais para o barramento <strong>I2C</strong>: com ela, bastam quatro fios
|
||||
para alimentar e controlar o display. A plaquinha também tem um <strong>trimpot</strong>
|
||||
para ajustar o contraste.
|
||||
</p>
|
||||
|
||||
<h3>Como o I2C funciona</h3>
|
||||
<p>
|
||||
O I2C usa dois fios: <strong>SDA</strong>, por onde passam os dados, e
|
||||
<strong>SCL</strong>, o relógio que marca o ritmo da conversa. O ESP32 é o
|
||||
<em>controlador</em>: é sempre ele quem inicia a comunicação. Os outros dispositivos são
|
||||
<em>alvos</em>, e cada um tem um endereço de 7 bits. Vários dispositivos podem dividir os
|
||||
mesmos dois fios, porque só responde quem tiver o endereço chamado.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th style="width:30%">Plaquinha</th><th style="width:22%">Endereço</th><th>Como saber</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">PCF8574 ou PCF8574T</td><td class="mono">0x27</td><td>A mais comum, e a usada no Wokwi</td></tr>
|
||||
<tr><td class="destaque">PCF8574A</td><td class="mono">0x3F</td><td>Mesma placa, outro chip</td></tr>
|
||||
</tbody>
|
||||
<caption>Na dúvida, o programa de scanner do capítulo 7 descobre o endereço por você.</caption>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Assim como no DHT22, as linhas do I2C são de <strong>dreno aberto</strong>: os
|
||||
dispositivos só puxam para baixo, e são resistores de pull-up que trazem SDA e SCL de volta
|
||||
ao nível alto. Valores entre 2,2 kΩ e 10 kΩ funcionam na velocidade padrão; aqui usamos
|
||||
4,7 kΩ, o valor mais comum.
|
||||
</p>
|
||||
|
||||
<div class="caixa erro">
|
||||
<div class="caixa-titulo">O display liga mas não mostra nada</div>
|
||||
<p>
|
||||
Com o contraste no mínimo, a tela acende e parece vazia; com ele no máximo, aparecem
|
||||
só retângulos cheios. Antes de desconfiar do código, gire o trimpot azul atrás do
|
||||
display com uma chave de fenda até os caracteres aparecerem. No Wokwi isso não
|
||||
existe, porque o contraste simulado já vem ajustado.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 4 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap4">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">4</div>
|
||||
<div>
|
||||
<h2>O simulador Wokwi</h2>
|
||||
<p class="subtitulo">Um laboratório de eletrônica que abre no navegador.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
O Wokwi simula o processador do ESP32-S3 instrução por instrução e simula também cada peça
|
||||
ligada a ele. O programa é compilado com o mesmo <strong>núcleo Arduino da Espressif</strong>
|
||||
que você instalaria na IDE, então o código que funciona aqui é o mesmo que vai para a placa.
|
||||
</p>
|
||||
|
||||
<h3>Os três arquivos de um projeto</h3>
|
||||
<table>
|
||||
<thead><tr><th style="width:28%">Arquivo</th><th>Para que serve</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">sketch.ino</td><td>O programa, exatamente como na IDE do Arduino.</td></tr>
|
||||
<tr><td class="destaque">diagram.json</td><td>As peças, a posição de cada uma, os fios e as propriedades, como a temperatura inicial do DHT22.</td></tr>
|
||||
<tr><td class="destaque">libraries.txt</td><td>Uma biblioteca por linha. O Wokwi baixa todas antes de compilar.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Abrindo o projeto deste módulo</h3>
|
||||
|
||||
<div class="passo">
|
||||
<div class="passo-num">1</div>
|
||||
<div><h3>Crie um projeto vazio</h3><p>Acesse <strong>wokwi.com/projects/new/esp32-s3</strong>. Não é preciso criar conta para simular.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">2</div>
|
||||
<div><h3>Substitua o programa e o circuito</h3><p>Cole o conteúdo de <code>sketch.ino</code> na aba de mesmo nome e o de <code>diagram.json</code> na aba <em>diagram.json</em>, apagando o que já estava lá.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">3</div>
|
||||
<div><h3>Adicione as bibliotecas</h3><p>Abra a aba <em>Library Manager</em>, clique em <strong>+</strong> e adicione <strong>DHT sensor library</strong> e <strong>LiquidCrystal I2C</strong>. O Wokwi cria o <code>libraries.txt</code> sozinho.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">4</div>
|
||||
<div><h3>Simule</h3><p>Clique no botão verde. A primeira compilação leva alguns segundos; depois o display acende e o monitor serial aparece embaixo.</p></div>
|
||||
</div>
|
||||
|
||||
<div class="caixa dica">
|
||||
<div class="caixa-titulo">Mude o clima com um clique</div>
|
||||
<p>
|
||||
Com a simulação rodando, clique no DHT22: aparecem dois controles deslizantes, um de
|
||||
temperatura e outro de umidade. Mova-os e veja o display acompanhar na leitura seguinte.
|
||||
É o jeito mais rápido de testar o que o programa faz com valores extremos.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="caixa atencao">
|
||||
<div class="caixa-titulo">Salvar exige conta</div>
|
||||
<p>
|
||||
Sem login a simulação funciona normalmente, mas o projeto se perde ao fechar a aba. Para
|
||||
guardar o seu trabalho, entre com uma conta gratuita e clique em <strong>Save</strong>,
|
||||
ou mantenha os três arquivos no seu computador.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 5 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap5">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">5</div>
|
||||
<div>
|
||||
<h2>Montagem</h2>
|
||||
<p class="subtitulo">Nove ligações e três resistores.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Todos os pinos usados ficam na <strong>coluna esquerda</strong> da placa, o que deixa a
|
||||
fiação curta e sem cruzamentos. As cores seguem uma convenção que vale a pena adotar
|
||||
também na bancada: vermelho para alimentação, preto para terra, e uma cor por sinal.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th style="width:38%">De</th><th style="width:22%">Para</th><th>Cor e função</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">DHT22 VCC</td><td>3V3</td><td>Vermelho · alimentação</td></tr>
|
||||
<tr><td class="destaque">DHT22 DATA</td><td>GPIO12</td><td>Amarelo · dados do sensor</td></tr>
|
||||
<tr><td class="destaque">DHT22 GND</td><td>GND</td><td>Preto · terra</td></tr>
|
||||
<tr><td class="destaque">Resistor 10 kΩ</td><td>GPIO12 ↔ 3V3</td><td>Pull-up do DHT22</td></tr>
|
||||
<tr><td class="destaque">LCD VCC</td><td>5V</td><td>Vermelho · o display precisa de 5 V</td></tr>
|
||||
<tr><td class="destaque">LCD GND</td><td>GND</td><td>Preto · terra</td></tr>
|
||||
<tr><td class="destaque">LCD SDA</td><td>GPIO8</td><td>Azul · dados do I2C</td></tr>
|
||||
<tr><td class="destaque">LCD SCL</td><td>GPIO9</td><td>Roxo · relógio do I2C</td></tr>
|
||||
<tr><td class="destaque">2 resistores de 4,7 kΩ</td><td>GPIO8 e GPIO9 ↔ 3V3</td><td>Pull-ups do I2C</td></tr>
|
||||
</tbody>
|
||||
<caption>GPIO8 e GPIO9 são os pinos I2C padrão do ESP32-S3 no núcleo Arduino.</caption>
|
||||
</table>
|
||||
|
||||
<figure>
|
||||
<img src="circuito-wokwi.png" alt="Fiação no Wokwi: um barramento vermelho de 3V3 no alto alimenta o DHT22 e os três resistores; do resistor de 10 kΩ desce um fio amarelo até o GPIO12, e dos dois resistores de 4,7 kΩ descem os fios azul e roxo até o GPIO8 e o GPIO9, que seguem para o SDA e o SCL do LCD." style="max-height:92mm">
|
||||
<figcaption>
|
||||
O fio vermelho no alto funciona como um barramento de 3V3: dele saem a alimentação do
|
||||
DHT22 e o lado de cima dos três resistores. O lado de baixo de cada resistor encontra a
|
||||
linha de sinal correspondente.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<div class="caixa curiosidade">
|
||||
<div class="caixa-titulo">Por que pull-up para 3V3, e não para 5V</div>
|
||||
<p>
|
||||
Os pinos do ESP32-S3 trabalham em 3,3 V e <strong>não toleram 5 V</strong>. Um resistor
|
||||
de pull-up define a tensão de repouso da linha, então ligá-lo ao 5V colocaria 5 V
|
||||
direto no GPIO. O display é alimentado em 5 V, mas as linhas de sinal precisam ficar
|
||||
em 3,3 V. Na bancada isso exige um cuidado a mais, explicado no capítulo 7.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 6 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap6">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">6</div>
|
||||
<div>
|
||||
<h2>O programa</h2>
|
||||
<p class="subtitulo">Um programa curto, lido em três partes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
O programa é lido aqui em três partes, exatamente como foi compilado e executado no Wokwi.
|
||||
O arquivo completo, pronto para colar, está em <a href="wokwi/sketch.ino">wokwi/sketch.ino</a>.
|
||||
</p>
|
||||
|
||||
<h3>1. Configuração</h3>
|
||||
<pre class="miudo"><code><span class="c-pre">#include</span> <span class="c-str"><Wire.h></span>
|
||||
<span class="c-pre">#include</span> <span class="c-str"><DHT.h></span>
|
||||
<span class="c-pre">#include</span> <LiquidCrystal_I2C.h>
|
||||
|
||||
<span class="c-pre">#define</span> PINO_DHT <span class="c-num">12</span> <span class="c-com">// fio amarelo</span>
|
||||
<span class="c-pre">#define</span> TIPO_DHT DHT22
|
||||
<span class="c-pre">#define</span> PINO_SDA <span class="c-num">8</span> <span class="c-com">// I2C padrão do ESP32-S3</span>
|
||||
<span class="c-pre">#define</span> PINO_SCL <span class="c-num">9</span>
|
||||
|
||||
DHT <span class="c-fun">dht</span>(PINO_DHT, TIPO_DHT);
|
||||
LiquidCrystal_I2C <span class="c-fun">lcd</span>(<span class="c-num">0x27</span>, <span class="c-num">16</span>, <span class="c-num">2</span>); <span class="c-com">// endereço, colunas, linhas</span>
|
||||
|
||||
<span class="c-com">// Símbolo de grau: 8 linhas de 5 pixels</span>
|
||||
<span class="c-key">byte</span> grau[<span class="c-num">8</span>] = { <span class="c-num">0b01100</span>, <span class="c-num">0b10010</span>, <span class="c-num">0b10010</span>, <span class="c-num">0b01100</span>, <span class="c-num">0</span>, <span class="c-num">0</span>, <span class="c-num">0</span>, <span class="c-num">0</span> };</code></pre>
|
||||
<p>
|
||||
As três bibliotecas cuidam do trabalho pesado: <code>Wire</code> é o I2C do ESP32,
|
||||
<code>DHT</code> fala o protocolo do sensor e <code>LiquidCrystal_I2C</code> traduz
|
||||
comandos como "escreva na linha 2" para o que a plaquinha PCF8574 entende. Os
|
||||
<code>#define</code> reúnem no topo tudo o que depende da montagem: se você mudar um fio de
|
||||
pino, só precisa mudar uma linha.
|
||||
</p>
|
||||
<p>
|
||||
O vetor <code>grau</code> desenha o símbolo de grau. Cada número é uma linha do caractere,
|
||||
e cada bit <code>1</code> acende um dos cinco pontos daquela linha:
|
||||
</p>
|
||||
|
||||
<figure>
|
||||
<svg class="diagrama" viewBox="0 0 640 150" role="img" aria-label="Grade de 5 por 8 pontos mostrando o desenho do símbolo de grau, com o valor binário de cada linha ao lado.">
|
||||
<g font-family="JetBrains Mono, monospace" font-size="11" fill="#1d2b33">
|
||||
<text x="250" y="26">0b01100</text><text x="250" y="42">0b10010</text><text x="250" y="58">0b10010</text><text x="250" y="74">0b01100</text>
|
||||
<text x="250" y="90" fill="#9aa9b3">0</text><text x="250" y="106" fill="#9aa9b3">0</text><text x="250" y="122" fill="#9aa9b3">0</text><text x="250" y="138" fill="#9aa9b3">0</text>
|
||||
</g>
|
||||
<g fill="#003049">
|
||||
<rect x="177" y="14" width="14" height="14"/><rect x="193" y="14" width="14" height="14"/>
|
||||
<rect x="161" y="30" width="14" height="14"/><rect x="209" y="30" width="14" height="14"/>
|
||||
<rect x="161" y="46" width="14" height="14"/><rect x="209" y="46" width="14" height="14"/>
|
||||
<rect x="177" y="62" width="14" height="14"/><rect x="193" y="62" width="14" height="14"/>
|
||||
</g>
|
||||
<g fill="none" stroke="#c9d3d9" stroke-width="0.8">
|
||||
<rect x="160" y="13" width="80" height="128"/>
|
||||
<path d="M176 13V141M192 13V141M208 13V141M224 13V141M160 29H240M160 45H240M160 61H240M160 77H240M160 93H240M160 109H240M160 125H240"/>
|
||||
</g>
|
||||
<rect x="330" y="40" width="180" height="50" rx="6" fill="#7fae1a"/><text x="420" y="74" font-family="JetBrains Mono, monospace" font-size="26" fill="#1d2b0a" text-anchor="middle">26.5°C</text>
|
||||
<text x="420" y="108" font-family="Inter, sans-serif" font-size="10" fill="#5b6b75" text-anchor="middle">o que aparece no display</text>
|
||||
</svg>
|
||||
<figcaption>
|
||||
<code>lcd.createChar(0, grau)</code> guarda o desenho na posição 0 da memória do display,
|
||||
e <code>lcd.write(0)</code> o imprime como qualquer outro caractere.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3>2. setup()</h3>
|
||||
<pre class="miudo"><code><span class="c-key">void</span> <span class="c-fun">setup</span>() {
|
||||
Serial.<span class="c-fun">begin</span>(<span class="c-num">115200</span>);
|
||||
Wire.<span class="c-fun">begin</span>(PINO_SDA, PINO_SCL);
|
||||
dht.<span class="c-fun">begin</span>();
|
||||
|
||||
lcd.<span class="c-fun">init</span>();
|
||||
lcd.<span class="c-fun">backlight</span>();
|
||||
lcd.<span class="c-fun">createChar</span>(<span class="c-num">0</span>, grau);
|
||||
lcd.<span class="c-fun">setCursor</span>(<span class="c-num">0</span>, <span class="c-num">0</span>);
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">"LASER ESP32-S3"</span>);
|
||||
lcd.<span class="c-fun">setCursor</span>(<span class="c-num">0</span>, <span class="c-num">1</span>);
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">"Iniciando..."</span>);
|
||||
<span class="c-fun">delay</span>(<span class="c-num">2000</span>); <span class="c-com">// o DHT22 precisa de ~2 s para a primeira leitura</span>
|
||||
lcd.<span class="c-fun">clear</span>();
|
||||
}</code></pre>
|
||||
<p>
|
||||
<code>Wire.begin(8, 9)</code> inicia o I2C nos pinos da montagem. Em seguida o display é
|
||||
inicializado, a luz de fundo é ligada e o caractere de grau é registrado. A mensagem
|
||||
"Iniciando..." não é só enfeite: ela cobre os <strong>dois segundos</strong> que o DHT22
|
||||
precisa depois de ligado para entregar a primeira medição válida.
|
||||
</p>
|
||||
|
||||
<h3>3. loop()</h3>
|
||||
<pre class="miudo"><code><span class="c-key">void</span> <span class="c-fun">loop</span>() {
|
||||
<span class="c-key">float</span> umidade = dht.<span class="c-fun">readHumidity</span>();
|
||||
<span class="c-key">float</span> temperatura = dht.<span class="c-fun">readTemperature</span>();
|
||||
|
||||
<span class="c-key">if</span> (<span class="c-fun">isnan</span>(umidade) || <span class="c-fun">isnan</span>(temperatura)) {
|
||||
lcd.<span class="c-fun">setCursor</span>(<span class="c-num">0</span>, <span class="c-num">0</span>);
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">"Falha no sensor "</span>);
|
||||
Serial.<span class="c-fun">println</span>(<span class="c-str">"Falha na leitura do DHT22"</span>);
|
||||
} <span class="c-key">else</span> {
|
||||
lcd.<span class="c-fun">setCursor</span>(<span class="c-num">0</span>, <span class="c-num">0</span>);
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">"Temp: "</span>);
|
||||
lcd.<span class="c-fun">print</span>(temperatura, <span class="c-num">1</span>);
|
||||
lcd.<span class="c-fun">write</span>(<span class="c-num">0</span>); <span class="c-com">// o caractere de grau criado acima</span>
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">"C "</span>);
|
||||
|
||||
lcd.<span class="c-fun">setCursor</span>(<span class="c-num">0</span>, <span class="c-num">1</span>);
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">"Umid: "</span>);
|
||||
lcd.<span class="c-fun">print</span>(umidade, <span class="c-num">1</span>);
|
||||
lcd.<span class="c-fun">print</span>(<span class="c-str">" % "</span>);
|
||||
|
||||
Serial.<span class="c-fun">printf</span>(<span class="c-str">"Temperatura: %.1f C | Umidade: %.1f %%\n"</span>, temperatura, umidade);
|
||||
}
|
||||
|
||||
<span class="c-fun">delay</span>(<span class="c-num">2000</span>); <span class="c-com">// o DHT22 aceita no máximo uma leitura a cada 2 s</span>
|
||||
}</code></pre>
|
||||
<p>
|
||||
A cada volta, o programa lê umidade e temperatura. Se qualquer uma vier como
|
||||
<code>NaN</code>, a sigla de <em>not a number</em> com que a biblioteca avisa que a leitura
|
||||
falhou, o display mostra "Falha no sensor". Caso contrário, os valores são escritos com
|
||||
uma casa decimal. O <code>delay(2000)</code> respeita o intervalo mínimo do DHT22.
|
||||
</p>
|
||||
|
||||
<div class="caixa erro">
|
||||
<div class="caixa-titulo">Restos de texto na tela</div>
|
||||
<p>
|
||||
O LCD só sobrescreve as posições em que você escreve; o resto continua como estava.
|
||||
Se a temperatura cair de "26.5" para "-5.2" e depois para "8.0", o texto fica mais
|
||||
curto e sobram caracteres da leitura anterior. Os espaços em <code>"C "</code> e
|
||||
<code>" % "</code> existem para apagar essas sobras. Chamar <code>lcd.clear()</code> a
|
||||
cada leitura também resolveria, mas faz a tela piscar.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3>Os três testes feitos no Wokwi</h3>
|
||||
<table>
|
||||
<thead><tr><th style="width:36%">Situação</th><th>O que o display mostrou</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">Sala, 26,5 °C e 62 %</td><td><img src="lcd-funcionando.png" alt="LCD mostrando Temp: 26.5°C e Umid: 62.0 %" style="height:24mm"></td></tr>
|
||||
<tr><td class="destaque">Frio, −5,2 °C e 88,4 %</td><td><img src="lcd-frio.png" alt="LCD mostrando Temp: -5.2°C e Umid: 88.4 %" style="height:24mm"></td></tr>
|
||||
<tr><td class="destaque">Fio de dados desconectado</td><td><img src="lcd-falha.png" alt="LCD mostrando Falha no sensor" style="height:24mm"></td></tr>
|
||||
</tbody>
|
||||
<caption>O terceiro teste confirma que o programa detecta a falha em vez de mostrar números inventados.</caption>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 7 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap7">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">7</div>
|
||||
<div>
|
||||
<h2>Da simulação para a bancada</h2>
|
||||
<p class="subtitulo">O que o Wokwi perdoa e o hardware de verdade não.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
O simulador é ideal para aprender e testar a lógica, mas ele modela os componentes como
|
||||
peças perfeitas. Na bancada, três detalhes fazem diferença.
|
||||
</p>
|
||||
|
||||
<h3>1. Os pull-ups escondidos na plaquinha I2C</h3>
|
||||
<p>
|
||||
Quase toda plaquinha PCF8574 vendida no Brasil já traz <strong>resistores de pull-up
|
||||
ligados ao VCC dela</strong>. Com o display alimentado em 5 V, esses resistores puxam SDA e
|
||||
SCL para 5 V. Somar os nossos 4,7 kΩ para 3V3 não resolve: os dois resistores formam um
|
||||
divisor, e a linha fica em torno de 4 V, ainda acima do que o ESP32-S3 suporta.
|
||||
</p>
|
||||
<p>Há duas saídas corretas:</p>
|
||||
<table>
|
||||
<thead><tr><th style="width:32%">Solução</th><th>Como fazer</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="destaque">Conversor de nível (recomendado)</td>
|
||||
<td>Um módulo bidirecional com BSS138, de quatro canais. O lado <strong>LV</strong> vai no 3V3 e nos GPIO8/9; o lado <strong>HV</strong> vai no 5V e no SDA/SCL do display. O módulo já tem pull-ups dos dois lados, então os 4,7 kΩ externos saem do circuito.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="destaque">Remover os pull-ups da plaquinha</td>
|
||||
<td>Dessoldar os dois resistores ligados a SDA e SCL na plaquinha e manter os 4,7 kΩ para 3V3, exatamente como no Wokwi. O PCF8574 reconhece 3,3 V como nível alto mesmo alimentado em 5 V.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="caixa dica">
|
||||
<div class="caixa-titulo">Como saber se a sua plaquinha tem pull-ups</div>
|
||||
<p>
|
||||
Com tudo desligado, meça com o multímetro a resistência entre o pino <strong>SDA</strong>
|
||||
e o <strong>VCC</strong> da plaquinha. Algo entre 2 kΩ e 10 kΩ indica um pull-up. Se não
|
||||
houver continuidade, ela não tem, e os 4,7 kΩ para 3V3 bastam.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="caixa atencao">
|
||||
<div class="caixa-titulo">"Mas eu liguei direto e funcionou"</div>
|
||||
<p>
|
||||
É comum funcionar: o resistor limita a corrente e o ESP32 tem diodos de proteção que
|
||||
desviam o excesso. Funcionar não é o mesmo que estar dentro da especificação. A Espressif
|
||||
não garante os pinos acima de 3,6 V, e um circuito fora da especificação pode falhar
|
||||
com o tempo ou em outra placa. Em projeto que vai ficar ligado, use o conversor.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3>2. Descubra o endereço do display</h3>
|
||||
<p>
|
||||
Se o display acende mas não mostra nada mesmo com o contraste ajustado, o endereço pode
|
||||
ser <code>0x3F</code> em vez de <code>0x27</code>. Este programa varre o barramento e
|
||||
imprime o endereço de cada dispositivo que responder. No Wokwi ele encontra o display em
|
||||
<code>0x27</code>:
|
||||
</p>
|
||||
|
||||
<pre class="miudo"><code><span class="c-com">// Scanner I2C: lista o endereço de cada dispositivo no barramento.</span>
|
||||
<span class="c-com">// Use antes do programa principal se o LCD não mostrar nada.</span>
|
||||
|
||||
<span class="c-pre">#include</span> <span class="c-str"><Wire.h></span>
|
||||
|
||||
<span class="c-key">void</span> <span class="c-fun">setup</span>() {
|
||||
Serial.<span class="c-fun">begin</span>(<span class="c-num">115200</span>);
|
||||
Wire.<span class="c-fun">begin</span>(<span class="c-num">8</span>, <span class="c-num">9</span>); <span class="c-com">// SDA, SCL</span>
|
||||
<span class="c-fun">delay</span>(<span class="c-num">500</span>);
|
||||
|
||||
Serial.<span class="c-fun">println</span>(<span class="c-str">"Procurando dispositivos I2C..."</span>);
|
||||
<span class="c-key">int</span> encontrados = <span class="c-num">0</span>;
|
||||
<span class="c-key">for</span> (<span class="c-key">byte</span> endereco = <span class="c-num">1</span>; endereco < <span class="c-num">127</span>; endereco++) {
|
||||
Wire.<span class="c-fun">beginTransmission</span>(endereco);
|
||||
<span class="c-key">if</span> (Wire.<span class="c-fun">endTransmission</span>() == <span class="c-num">0</span>) {
|
||||
Serial.<span class="c-fun">printf</span>(<span class="c-str">"Dispositivo em 0x%02X\n"</span>, endereco);
|
||||
encontrados++;
|
||||
}
|
||||
}
|
||||
<span class="c-key">if</span> (encontrados == <span class="c-num">0</span>) Serial.<span class="c-fun">println</span>(<span class="c-str">"Nenhum dispositivo. Confira SDA, SCL e alimentacao."</span>);
|
||||
}
|
||||
|
||||
<span class="c-key">void</span> <span class="c-fun">loop</span>() {}</code></pre>
|
||||
|
||||
<pre><code>Procurando dispositivos I2C...
|
||||
Dispositivo em 0x27</code></pre>
|
||||
|
||||
<h3>3. Gravando na placa</h3>
|
||||
<p>
|
||||
Na IDE do Arduino, instale o suporte ao ESP32 e as bibliotecas <strong>DHT sensor
|
||||
library</strong> (aceitando a dependência <strong>Adafruit Unified Sensor</strong>) e
|
||||
<strong>LiquidCrystal I2C</strong>, de Frank de Brabander. Selecione a placa
|
||||
<strong>ESP32S3 Dev Module</strong> e lembre do detalhe do módulo 1: na porta marcada
|
||||
<strong>USB</strong>, a opção <em>USB CDC On Boot</em> precisa estar em
|
||||
<strong>Enabled</strong> para o monitor serial funcionar; na porta <strong>UART</strong>,
|
||||
em <strong>Disabled</strong>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════ CAPÍTULO 8 ═══════════════════════════ -->
|
||||
<section class="capitulo" id="cap8">
|
||||
<div class="capitulo-abertura">
|
||||
<div class="capitulo-numero">8</div>
|
||||
<div>
|
||||
<h2>Problemas comuns e desafios</h2>
|
||||
<p class="subtitulo">O que fazer quando não funciona, e para onde ir quando funcionar.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead><tr><th style="width:34%">Sintoma</th><th>Causa provável</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="destaque">"Falha no sensor" o tempo todo</td><td>Fio de dados fora do GPIO12, falta do pull-up de 10 kΩ ou VCC e GND invertidos.</td></tr>
|
||||
<tr><td class="destaque">Display aceso e sem texto</td><td>Contraste desajustado, endereço errado ou SDA e SCL trocados.</td></tr>
|
||||
<tr><td class="destaque">Retângulos cheios na linha de cima</td><td>O display recebeu energia mas não foi inicializado: o I2C não está chegando.</td></tr>
|
||||
<tr><td class="destaque">Display apagado</td><td>Falta o 5V no VCC do display ou o <code>lcd.backlight()</code>.</td></tr>
|
||||
<tr><td class="destaque">Caracteres estranhos no fim da linha</td><td>Sobras da leitura anterior; veja a caixa "Restos de texto na tela".</td></tr>
|
||||
<tr><td class="destaque">Erro de compilação com <code>DHT.h</code></td><td>Biblioteca não instalada, ou instalada sem a Adafruit Unified Sensor.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Desafios</h3>
|
||||
<div class="passo">
|
||||
<div class="passo-num">1</div>
|
||||
<div><h3>Mínima e máxima</h3><p>Guarde a menor e a maior temperatura desde que a placa ligou e alterne a segunda linha entre a umidade e esses dois valores.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">2</div>
|
||||
<div><h3>Alerta de calor</h3><p>Ligue um LED, ou use o LED RGB que já existe no GPIO38 da placa, quando a temperatura passar de 30 °C. Faça a luz de fundo do display piscar junto.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">3</div>
|
||||
<div><h3>Sensação térmica</h3><p>A biblioteca DHT tem a função <code>computeHeatIndex()</code>. Mostre o índice de calor no lugar da umidade quando o ar estiver quente e úmido.</p></div>
|
||||
</div>
|
||||
<div class="passo">
|
||||
<div class="passo-num">4</div>
|
||||
<div><h3>Sem <code>delay()</code></h3><p>Reescreva o <code>loop()</code> usando <code>millis()</code>, para que o programa possa fazer outras coisas, como ler um botão, entre uma leitura e outra.</p></div>
|
||||
</div>
|
||||
|
||||
<div class="caixa dica">
|
||||
<div class="caixa-titulo">Próximo módulo</div>
|
||||
<p>
|
||||
A estação já mede e mostra. O passo natural é publicar esses dados: o ESP32-S3 tem
|
||||
Wi-Fi, e com poucas linhas a mais a mesma leitura pode ir para uma página web ou para
|
||||
um servidor MQTT.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="caixa dica">
|
||||
<div class="caixa-titulo">Dúvidas, correções e contato</div>
|
||||
<p>
|
||||
Correções e sugestões são bem-vindas e ajudam as próximas turmas. Fale direto com o
|
||||
autor por <a href="mailto:contato@lucasrguerra.dev.br">contato@lucasrguerra.dev.br</a>
|
||||
ou pelo LinkedIn, em
|
||||
<a href="https://linkedin.com/in/lucasrguerra">linkedin.com/in/lucasrguerra</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:12mm;padding-top:4mm;border-top:0.8pt solid var(--linha);font-size:8.5pt;color:var(--tinta-3);text-align:center">
|
||||
LASER · Liga Acadêmica de Sistemas Embarcados · Centro de Informática · UFPE<br>
|
||||
Formação em ESP32 · Módulo 2: Estação de Temperatura e Umidade
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
window.PagedConfig = {
|
||||
before: () => document.querySelector(".aviso-carregando")?.remove(),
|
||||
};
|
||||
</script>
|
||||
<script src="../assets/paged.polyfill.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"modulo": 2,
|
||||
"titulo": ["Estação de", "temperatura e umidade"],
|
||||
"subtitulo": "Um DHT22, um display LCD 16x2 e um ESP32-S3, simulados no Wokwi com o framework Arduino. Dois protocolos de comunicação, um programa curto e os cuidados elétricos que fazem o circuito funcionar também fora da tela.",
|
||||
"descricao": "Slides, apostila, código e projeto do Wokwi do módulo 2 da formação em ESP32 da LASER: DHT22, LCD 16x2 I2C e ESP32-S3.",
|
||||
"fichas": [["Duração", "2 horas"], ["Formato", "presencial"], ["Simulador", "Wokwi"], ["Framework", "Arduino"]],
|
||||
"pratica": {
|
||||
"titulo": "Projeto no Wokwi",
|
||||
"texto": "O circuito e o código desta formação, testados no simulador: leitura normal, temperatura negativa e sensor desconectado.",
|
||||
"imagem": "circuito-wokwi.png",
|
||||
"meta": "4 arquivos · simule sem instalar nada",
|
||||
"acoes": [["Baixar o projeto", "modulo-2-wokwi.zip", true], ["Abrir o Wokwi", "https://wokwi.com/projects/new/esp32-s3", false]]
|
||||
},
|
||||
"grupos": [
|
||||
{ "titulo": "Material de aula", "arquivos": [
|
||||
{ "arquivo": "slide.html", "tipo": "slides", "descricao": "Os slides da formação. Abrem no navegador, em tela cheia, prontos para projetar.", "abrir": true },
|
||||
{ "arquivo": "apostila.html", "tipo": "apostila", "descricao": "A apostila completa, paginada em A4 no próprio navegador, com o passo a passo e o código comentado.", "abrir": true }
|
||||
]},
|
||||
{ "titulo": "Para baixar e imprimir", "arquivos": [
|
||||
{ "arquivo": "LASER-ESP32-Modulo-2-Slides.pdf", "tipo": "pdf", "descricao": "Os slides em PDF 16:9, um slide por página.", "abrir": true, "baixar": true },
|
||||
{ "arquivo": "LASER-ESP32-Modulo-2-Apostila.pdf", "tipo": "pdf", "descricao": "A apostila em PDF A4, pronta para imprimir.", "abrir": true, "baixar": true }
|
||||
]},
|
||||
{ "titulo": "Projeto do Wokwi", "arquivos": [
|
||||
{ "arquivo": "modulo-2-wokwi.zip", "tipo": "zip", "descricao": "Os quatro arquivos abaixo num só pacote.", "baixar": true },
|
||||
{ "arquivo": "wokwi/sketch.ino", "tipo": "codigo", "descricao": "O programa da estação: lê o DHT22 e escreve no LCD a cada 2 segundos.", "abrir": true, "baixar": true },
|
||||
{ "arquivo": "wokwi/diagram.json", "tipo": "codigo", "descricao": "O circuito: peças, posições, fios e os três resistores de pull-up.", "abrir": true, "baixar": true },
|
||||
{ "arquivo": "wokwi/libraries.txt", "tipo": "codigo", "descricao": "As bibliotecas que o Wokwi instala antes de compilar.", "abrir": true, "baixar": true },
|
||||
{ "arquivo": "wokwi/scanner-i2c.ino", "tipo": "codigo", "descricao": "Diagnóstico: lista o endereço de cada dispositivo no barramento I2C.", "abrir": true, "baixar": true }
|
||||
]},
|
||||
{ "titulo": "Imagens da simulação", "arquivos": [
|
||||
{ "arquivo": "circuito-wokwi.png", "tipo": "imagem", "descricao": "O circuito completo rodando no Wokwi.", "abrir": true },
|
||||
{ "arquivo": "lcd-funcionando.png", "tipo": "imagem", "descricao": "Teste 1: leitura normal, 26,5 °C e 62 %.", "abrir": true },
|
||||
{ "arquivo": "lcd-frio.png", "tipo": "imagem", "descricao": "Teste 2: temperatura negativa, −5,2 °C e 88,4 %.", "abrir": true },
|
||||
{ "arquivo": "lcd-falha.png", "tipo": "imagem", "descricao": "Teste 3: fio de dados desconectado, erro detectado.", "abrir": true }
|
||||
]}
|
||||
],
|
||||
"guias": [
|
||||
{ "titulo": "Rodando no Wokwi", "itens": [
|
||||
"Abra <a href=\"https://wokwi.com/projects/new/esp32-s3\">wokwi.com/projects/new/esp32-s3</a>. Não precisa de conta.",
|
||||
"Cole o <code>sketch.ino</code> e o <code>diagram.json</code> nas abas de mesmo nome.",
|
||||
"Em <i>Library Manager</i>, adicione <b>DHT sensor library</b> e <b>LiquidCrystal I2C</b>.",
|
||||
"Clique no botão verde. Com a simulação rodando, clique no DHT22 para mudar temperatura e umidade."
|
||||
]},
|
||||
{ "titulo": "Montando na bancada", "itens": [
|
||||
"Pull-up de <b>10 kΩ</b> no DATA do DHT22 e de <b>4,7 kΩ</b> no SDA e no SCL, todos para o <b>3V3</b>.",
|
||||
"A plaquinha I2C do LCD costuma trazer pull-ups para 5 V: use um <b>conversor de nível BSS138</b> ou retire esses resistores.",
|
||||
"Tela vazia? Gire o trimpot de contraste e rode o <code>scanner-i2c.ino</code> para conferir o endereço.",
|
||||
"Os detalhes estão no capítulo 7 da apostila."
|
||||
]}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>LASER · Formação em ESP32 · Módulo 2: Estação de temperatura e umidade</title>
|
||||
<meta name="description" content="Slides, apostila, código e projeto do Wokwi do módulo 2 da formação em ESP32 da LASER: DHT22, LCD 16x2 I2C e ESP32-S3.">
|
||||
<meta name="author" content="Lucas Rayan Guerra">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/marca/espdocs-marca.svg">
|
||||
<link rel="stylesheet" href="/formacoes/assets/viewer.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Gerado por scripts/formacoes-viewer.mjs a partir do formacao.json. Não edite à mão. -->
|
||||
|
||||
<header class="topo">
|
||||
<div class="envoltorio">
|
||||
<a href="/formacoes"><img src="/formacoes/assets/laser.svg" alt="LASER"></a>
|
||||
<span class="topo-liga">Liga Acadêmica de Sistemas Embarcados<br>Centro de Informática · UFPE</span>
|
||||
<nav><a href="#arquivos">Arquivos</a><a href="#como-usar">Como usar</a><a href="/formacoes">Formações</a></nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="abertura">
|
||||
<div class="envoltorio">
|
||||
<div>
|
||||
<div class="selo">Formação em ESP32 · Módulo 2</div>
|
||||
<h1>Estação de<strong>temperatura e umidade</strong></h1>
|
||||
<p class="abertura-sub">Um DHT22, um display LCD 16x2 e um ESP32-S3, simulados no Wokwi com o framework Arduino. Dois protocolos de comunicação, um programa curto e os cuidados elétricos que fazem o circuito funcionar também fora da tela.</p>
|
||||
<ul class="fichas"><li>Duração: <b>2 horas</b></li><li>Formato: <b>presencial</b></li><li>Simulador: <b>Wokwi</b></li><li>Framework: <b>Arduino</b></li></ul>
|
||||
<div class="acoes">
|
||||
<a class="botao primario" href="/formacoes/2/slide.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 5v14l11-7z"/></svg>Apresentar os slides</a>
|
||||
<a class="botao" href="/formacoes/2/apostila.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6c-2-1.5-5-2-8-1.5v14c3-.5 6 0 8 1.5 2-1.5 5-2 8-1.5v-14c-3-.5-6 0-8 1.5zM12 6v14"/></svg>Ler a apostila</a>
|
||||
<a class="botao" href="#baixar"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v12M6 10l6 6 6-6M5 20h14"/></svg>Baixar em PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="previas">
|
||||
<a class="p-apostila" href="/formacoes/2/apostila.html" aria-label="Abrir a apostila"><img src="/formacoes/2/previa-apostila.jpg" alt="Capa da apostila do módulo 2" width="640" height="905"></a>
|
||||
<a class="p-slides" href="/formacoes/2/slide.html" aria-label="Abrir os slides"><img src="/formacoes/2/previa-slides.jpg" alt="Capa dos slides do módulo 2" width="960" height="540"></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr class="feixe">
|
||||
|
||||
<section class="bloco" id="baixar">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Comece por aqui</p>
|
||||
<h2>Três formas de usar este material</h2>
|
||||
<p class="bloco-sub">Para apresentar, para estudar e para pôr a mão no circuito. Os PDFs são a versão final, para quem só quer o material pronto.</p>
|
||||
<div class="portas">
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="/formacoes/2/slide.html"><img src="/formacoes/2/previa-slides.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<h3>Slides</h3>
|
||||
<p>31 slides para projetar direto do navegador, com navegação por teclado e panorama.</p>
|
||||
<div class="porta-meta">PDF · 31 págs · 1,6 MB</div>
|
||||
<div class="acoes">
|
||||
<a class="botao pequeno primario" href="/formacoes/2/slide.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 5v14l11-7z"/></svg>Apresentar</a>
|
||||
<a class="botao pequeno" href="/formacoes/2/LASER-ESP32-Modulo-2-Slides.pdf" download><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v12M6 10l6 6 6-6M5 20h14"/></svg>PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="/formacoes/2/apostila.html"><img src="/formacoes/2/previa-apostila.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<h3>Apostila</h3>
|
||||
<p>O conteúdo completo, com explicações, tabelas e o código comentado, paginado em A4.</p>
|
||||
<div class="porta-meta">PDF · 20 págs · 761 KB</div>
|
||||
<div class="acoes">
|
||||
<a class="botao pequeno primario" href="/formacoes/2/apostila.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 6c-2-1.5-5-2-8-1.5v14c3-.5 6 0 8 1.5 2-1.5 5-2 8-1.5v-14c-3-.5-6 0-8 1.5zM12 6v14"/></svg>Ler</a>
|
||||
<a class="botao pequeno" href="/formacoes/2/LASER-ESP32-Modulo-2-Apostila.pdf" download><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v12M6 10l6 6 6-6M5 20h14"/></svg>PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="porta">
|
||||
<a class="porta-imagem clara" href="/formacoes/2/circuito-wokwi.png" target="_blank" rel="noopener"><img src="/formacoes/2/circuito-wokwi.png" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<h3>Projeto no Wokwi</h3>
|
||||
<p>O circuito e o código desta formação, testados no simulador: leitura normal, temperatura negativa e sensor desconectado.</p>
|
||||
<div class="porta-meta">4 arquivos · simule sem instalar nada</div>
|
||||
<div class="acoes"><a class="botao pequeno primario" href="/formacoes/2/modulo-2-wokwi.zip" download><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v12M6 10l6 6 6-6M5 20h14"/></svg>Baixar o projeto</a><a class="botao pequeno" href="https://wokwi.com/projects/new/esp32-s3" rel="noopener"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>Abrir o Wokwi</a></div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bloco" id="roteiro">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Roteiro da aula</p>
|
||||
<h2>6 partes, 31 slides</h2>
|
||||
<p class="bloco-sub">Cada parte abre os slides direto no ponto em que ela começa.</p>
|
||||
<ol class="roteiro">
|
||||
<li><a href="/formacoes/2/slide.html#4"><b>1</b><span>O DHT22<small>slide 4 · 5 slides</small></span></a></li>
|
||||
<li><a href="/formacoes/2/slide.html#9"><b>2</b><span>LCD e I2C<small>slide 9 · 5 slides</small></span></a></li>
|
||||
<li><a href="/formacoes/2/slide.html#14"><b>3</b><span>Wokwi<small>slide 14 · 3 slides</small></span></a></li>
|
||||
<li><a href="/formacoes/2/slide.html#17"><b>4</b><span>Montagem<small>slide 17 · 2 slides</small></span></a></li>
|
||||
<li><a href="/formacoes/2/slide.html#19"><b>5</b><span>O programa<small>slide 19 · 7 slides</small></span></a></li>
|
||||
<li><a href="/formacoes/2/slide.html#26"><b>6</b><span>Para a bancada<small>slide 26 · 6 slides</small></span></a></li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bloco" id="arquivos">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Todos os arquivos</p>
|
||||
<h2>O que tem nesta formação</h2>
|
||||
<p class="bloco-sub">Tudo o que foi usado na aula, com o que é cada arquivo e para que serve.</p>
|
||||
|
||||
<div class="grupo-arquivos">
|
||||
<h3>Material de aula</h3>
|
||||
<ul class="arquivos">
|
||||
<li class="arquivo">
|
||||
<div class="icone "><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5h18v12H3z"/><path d="M8 21h8M12 17v4"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">slide.html</div>
|
||||
<div class="arquivo-desc">Os slides da formação. Abrem no navegador, em tela cheia, prontos para projetar.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">72 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/slide.html">Abrir</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone "><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4.5A1.5 1.5 0 0 1 5.5 3H20v16H5.5A1.5 1.5 0 0 0 4 20.5z"/><path d="M4 20.5A1.5 1.5 0 0 0 5.5 22H20"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">apostila.html</div>
|
||||
<div class="arquivo-desc">A apostila completa, paginada em A4 no próprio navegador, com o passo a passo e o código comentado.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">43 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/apostila.html">Abrir</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grupo-arquivos">
|
||||
<h3>Para baixar e imprimir</h3>
|
||||
<ul class="arquivos">
|
||||
<li class="arquivo">
|
||||
<div class="icone pdf"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H6v18h12V7z"/><path d="M14 3v4h4"/><path d="M9 13h6M9 17h4"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">LASER-ESP32-Modulo-2-Slides.pdf</div>
|
||||
<div class="arquivo-desc">Os slides em PDF 16:9, um slide por página.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">31 págs · 1,6 MB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/LASER-ESP32-Modulo-2-Slides.pdf" target="_blank" rel="noopener">Abrir</a><a href="/formacoes/2/LASER-ESP32-Modulo-2-Slides.pdf" download>Baixar</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone pdf"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H6v18h12V7z"/><path d="M14 3v4h4"/><path d="M9 13h6M9 17h4"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">LASER-ESP32-Modulo-2-Apostila.pdf</div>
|
||||
<div class="arquivo-desc">A apostila em PDF A4, pronta para imprimir.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">20 págs · 761 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/LASER-ESP32-Modulo-2-Apostila.pdf" target="_blank" rel="noopener">Abrir</a><a href="/formacoes/2/LASER-ESP32-Modulo-2-Apostila.pdf" download>Baixar</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grupo-arquivos">
|
||||
<h3>Projeto do Wokwi</h3>
|
||||
<ul class="arquivos">
|
||||
<li class="arquivo">
|
||||
<div class="icone codigo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 3H6v18h12V7z"/><path d="M14 3v4h4"/><path d="M11 6h1M11 9h1M11 12h1M10.5 15h2v3h-2z"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">modulo-2-wokwi.zip</div>
|
||||
<div class="arquivo-desc">Os quatro arquivos abaixo num só pacote.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">2 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/modulo-2-wokwi.zip" download>Baixar</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone codigo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 8-4 4 4 4M16 8l4 4-4 4M13.5 5l-3 14"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">sketch.ino</div>
|
||||
<div class="arquivo-desc">O programa da estação: lê o DHT22 e escreve no LCD a cada 2 segundos.</div>
|
||||
<details class="codigo-fonte"><summary>Ver código</summary><pre><code>// Formação em ESP32 · LASER · Módulo 2
|
||||
// Estação de temperatura e umidade: DHT22 + LCD 16x2 I2C + ESP32-S3
|
||||
|
||||
#include <Wire.h>
|
||||
#include <DHT.h>
|
||||
#include <LiquidCrystal_I2C.h>
|
||||
|
||||
#define PINO_DHT 12 // fio amarelo
|
||||
#define TIPO_DHT DHT22
|
||||
#define PINO_SDA 8 // I2C padrão do ESP32-S3
|
||||
#define PINO_SCL 9
|
||||
|
||||
DHT dht(PINO_DHT, TIPO_DHT);
|
||||
LiquidCrystal_I2C lcd(0x27, 16, 2); // endereço, colunas, linhas
|
||||
|
||||
// Símbolo de grau: 8 linhas de 5 pixels
|
||||
byte grau[8] = { 0b01100, 0b10010, 0b10010, 0b01100, 0, 0, 0, 0 };
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Wire.begin(PINO_SDA, PINO_SCL);
|
||||
dht.begin();
|
||||
|
||||
lcd.init();
|
||||
lcd.backlight();
|
||||
lcd.createChar(0, grau);
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("LASER ESP32-S3");
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Iniciando...");
|
||||
delay(2000); // o DHT22 precisa de ~2 s para a primeira leitura
|
||||
lcd.clear();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
float umidade = dht.readHumidity();
|
||||
float temperatura = dht.readTemperature();
|
||||
|
||||
if (isnan(umidade) || isnan(temperatura)) {
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("Falha no sensor ");
|
||||
Serial.println("Falha na leitura do DHT22");
|
||||
} else {
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("Temp: ");
|
||||
lcd.print(temperatura, 1);
|
||||
lcd.write(0); // o caractere de grau criado acima
|
||||
lcd.print("C ");
|
||||
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Umid: ");
|
||||
lcd.print(umidade, 1);
|
||||
lcd.print(" % ");
|
||||
|
||||
Serial.printf("Temperatura: %.1f C | Umidade: %.1f %%\n", temperatura, umidade);
|
||||
}
|
||||
|
||||
delay(2000); // o DHT22 aceita no máximo uma leitura a cada 2 s
|
||||
}</code></pre></details>
|
||||
</div>
|
||||
<div class="arquivo-tam">2 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/wokwi/sketch.ino" download>Baixar</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone codigo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 8-4 4 4 4M16 8l4 4-4 4M13.5 5l-3 14"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">diagram.json</div>
|
||||
<div class="arquivo-desc">O circuito: peças, posições, fios e os três resistores de pull-up.</div>
|
||||
<details class="codigo-fonte"><summary>Ver código</summary><pre><code>{
|
||||
"version": 1,
|
||||
"author": "Lucas Rayan Guerra",
|
||||
"editor": "wokwi",
|
||||
"parts": [
|
||||
{
|
||||
"type": "board-esp32-s3-devkitc-1",
|
||||
"id": "esp",
|
||||
"top": 0,
|
||||
"left": 0,
|
||||
"attrs": {}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-dht22",
|
||||
"id": "dht",
|
||||
"top": 15,
|
||||
"left": -250,
|
||||
"attrs": {
|
||||
"temperature": "26.5",
|
||||
"humidity": "62"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-lcd1602",
|
||||
"id": "lcd",
|
||||
"top": 320,
|
||||
"left": -60,
|
||||
"attrs": {
|
||||
"pins": "i2c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-resistor",
|
||||
"id": "r_dht",
|
||||
"top": 9,
|
||||
"left": -173,
|
||||
"rotate": 90,
|
||||
"attrs": {
|
||||
"value": "10000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-resistor",
|
||||
"id": "r_sda",
|
||||
"top": 9,
|
||||
"left": -83,
|
||||
"rotate": 90,
|
||||
"attrs": {
|
||||
"value": "4700"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-resistor",
|
||||
"id": "r_scl",
|
||||
"top": 9,
|
||||
"left": -63,
|
||||
"rotate": 90,
|
||||
"attrs": {
|
||||
"value": "4700"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
[
|
||||
"esp:TX",
|
||||
"$serialMonitor:RX",
|
||||
"",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"esp:RX",
|
||||
"$serialMonitor:TX",
|
||||
"",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"lcd:VCC",
|
||||
"esp:5V",
|
||||
"red",
|
||||
[
|
||||
"h-20",
|
||||
"v-100"
|
||||
]
|
||||
],
|
||||
[
|
||||
"lcd:GND",
|
||||
"esp:GND.1",
|
||||
"black",
|
||||
[
|
||||
"h-30",
|
||||
"v-90"
|
||||
]
|
||||
],
|
||||
[
|
||||
"lcd:SDA",
|
||||
"esp:8",
|
||||
"blue",
|
||||
[
|
||||
"h-40",
|
||||
"v-200"
|
||||
]
|
||||
],
|
||||
[
|
||||
"lcd:SCL",
|
||||
"esp:9",
|
||||
"purple",
|
||||
[
|
||||
"h-50",
|
||||
"v-170"
|
||||
]
|
||||
],
|
||||
[
|
||||
"dht:VCC",
|
||||
"r_dht:1",
|
||||
"red",
|
||||
[
|
||||
"v12",
|
||||
"h-25",
|
||||
"v-159",
|
||||
"h100"
|
||||
]
|
||||
],
|
||||
[
|
||||
"dht:SDA",
|
||||
"esp:12",
|
||||
"gold",
|
||||
[
|
||||
"v77"
|
||||
]
|
||||
],
|
||||
[
|
||||
"dht:GND",
|
||||
"esp:GND.1",
|
||||
"black",
|
||||
[
|
||||
"v118"
|
||||
]
|
||||
],
|
||||
[
|
||||
"r_dht:1",
|
||||
"esp:3V3.1",
|
||||
"red",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"r_dht:2",
|
||||
"esp:12",
|
||||
"gold",
|
||||
[
|
||||
"v181"
|
||||
]
|
||||
],
|
||||
[
|
||||
"r_sda:1",
|
||||
"esp:3V3.1",
|
||||
"red",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"r_sda:2",
|
||||
"esp:8",
|
||||
"blue",
|
||||
[
|
||||
"v124"
|
||||
]
|
||||
],
|
||||
[
|
||||
"r_scl:1",
|
||||
"esp:3V3.1",
|
||||
"red",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"r_scl:2",
|
||||
"esp:9",
|
||||
"purple",
|
||||
[
|
||||
"v152"
|
||||
]
|
||||
]
|
||||
],
|
||||
"dependencies": {}
|
||||
}</code></pre></details>
|
||||
</div>
|
||||
<div class="arquivo-tam">2 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/wokwi/diagram.json" download>Baixar</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone codigo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 8-4 4 4 4M16 8l4 4-4 4M13.5 5l-3 14"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">libraries.txt</div>
|
||||
<div class="arquivo-desc">As bibliotecas que o Wokwi instala antes de compilar.</div>
|
||||
<details class="codigo-fonte"><summary>Ver código</summary><pre><code>DHT sensor library
|
||||
Adafruit Unified Sensor
|
||||
LiquidCrystal I2C</code></pre></details>
|
||||
</div>
|
||||
<div class="arquivo-tam">61 B</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/wokwi/libraries.txt" download>Baixar</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone codigo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 8-4 4 4 4M16 8l4 4-4 4M13.5 5l-3 14"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">scanner-i2c.ino</div>
|
||||
<div class="arquivo-desc">Diagnóstico: lista o endereço de cada dispositivo no barramento I2C.</div>
|
||||
<details class="codigo-fonte"><summary>Ver código</summary><pre><code>// Scanner I2C: lista o endereço de cada dispositivo no barramento.
|
||||
// Use antes do programa principal se o LCD não mostrar nada.
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Wire.begin(8, 9); // SDA, SCL
|
||||
delay(500);
|
||||
|
||||
Serial.println("Procurando dispositivos I2C...");
|
||||
int encontrados = 0;
|
||||
for (byte endereco = 1; endereco < 127; endereco++) {
|
||||
Wire.beginTransmission(endereco);
|
||||
if (Wire.endTransmission() == 0) {
|
||||
Serial.printf("Dispositivo em 0x%02X\n", endereco);
|
||||
encontrados++;
|
||||
}
|
||||
}
|
||||
if (encontrados == 0) Serial.println("Nenhum dispositivo. Confira SDA, SCL e alimentacao.");
|
||||
}
|
||||
|
||||
void loop() {}</code></pre></details>
|
||||
</div>
|
||||
<div class="arquivo-tam">664 B</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/wokwi/scanner-i2c.ino" download>Baixar</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grupo-arquivos">
|
||||
<h3>Imagens da simulação</h3>
|
||||
<ul class="arquivos">
|
||||
<li class="arquivo">
|
||||
<div class="icone imagem"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m21 17-5-5-9 8"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">circuito-wokwi.png</div>
|
||||
<div class="arquivo-desc">O circuito completo rodando no Wokwi.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">44 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/circuito-wokwi.png" target="_blank" rel="noopener">Abrir</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone imagem"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m21 17-5-5-9 8"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">lcd-funcionando.png</div>
|
||||
<div class="arquivo-desc">Teste 1: leitura normal, 26,5 °C e 62 %.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">8 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/lcd-funcionando.png" target="_blank" rel="noopener">Abrir</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone imagem"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m21 17-5-5-9 8"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">lcd-frio.png</div>
|
||||
<div class="arquivo-desc">Teste 2: temperatura negativa, −5,2 °C e 88,4 %.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">8 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/lcd-frio.png" target="_blank" rel="noopener">Abrir</a></div>
|
||||
</li>
|
||||
<li class="arquivo">
|
||||
<div class="icone imagem"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m21 17-5-5-9 8"/></svg></div>
|
||||
<div>
|
||||
<div class="arquivo-nome">lcd-falha.png</div>
|
||||
<div class="arquivo-desc">Teste 3: fio de dados desconectado, erro detectado.</div>
|
||||
|
||||
</div>
|
||||
<div class="arquivo-tam">7 KB</div>
|
||||
<div class="arquivo-acoes"><a href="/formacoes/2/lcd-falha.png" target="_blank" rel="noopener">Abrir</a></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bloco" id="como-usar">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Como usar</p>
|
||||
<h2>Passo a passo</h2>
|
||||
<div class="guias">
|
||||
<div class="guia">
|
||||
<h3>Apresentando os slides</h3>
|
||||
<p>Abra <a href="/formacoes/2/slide.html">slide.html</a> no navegador. O palco é 16:9 e se ajusta a qualquer tela ou projetor.</p>
|
||||
<dl class="atalhos">
|
||||
<dt><kbd>→</kbd><kbd>←</kbd></dt><dd>avançar e voltar (ou clique nas metades da tela)</dd>
|
||||
<dt><kbd>F</kbd></dt><dd>tela cheia</dd>
|
||||
<dt><kbd>O</kbd></dt><dd>panorama com todos os slides</dd>
|
||||
<dt><kbd>B</kbd></dt><dd>tela preta, para pausar a projeção</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="guia">
|
||||
<h3>Lendo e imprimindo a apostila</h3>
|
||||
<p>A <a href="/formacoes/2/apostila.html">versão online</a> se pagina em A4 sozinha ao abrir, o que leva alguns segundos.</p>
|
||||
<p>Para papel, prefira o <a href="/formacoes/2/LASER-ESP32-Modulo-2-Apostila.pdf" download>PDF pronto</a>: ele já sai com margens, cores e quebras de página certas, sem configurar a impressora.</p>
|
||||
</div>
|
||||
<div class="guia">
|
||||
<h3>Rodando no Wokwi</h3>
|
||||
<ol><li>Abra <a href="https://wokwi.com/projects/new/esp32-s3">wokwi.com/projects/new/esp32-s3</a>. Não precisa de conta.</li><li>Cole o <code>sketch.ino</code> e o <code>diagram.json</code> nas abas de mesmo nome.</li><li>Em <i>Library Manager</i>, adicione <b>DHT sensor library</b> e <b>LiquidCrystal I2C</b>.</li><li>Clique no botão verde. Com a simulação rodando, clique no DHT22 para mudar temperatura e umidade.</li></ol>
|
||||
|
||||
</div>
|
||||
<div class="guia">
|
||||
<h3>Montando na bancada</h3>
|
||||
<ol><li>Pull-up de <b>10 kΩ</b> no DATA do DHT22 e de <b>4,7 kΩ</b> no SDA e no SCL, todos para o <b>3V3</b>.</li><li>A plaquinha I2C do LCD costuma trazer pull-ups para 5 V: use um <b>conversor de nível BSS138</b> ou retire esses resistores.</li><li>Tela vazia? Gire o trimpot de contraste e rode o <code>scanner-i2c.ino</code> para conferir o endereço.</li><li>Os detalhes estão no capítulo 7 da apostila.</li></ol>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="rodape">
|
||||
<div class="envoltorio">
|
||||
<span><strong>LASER</strong> · Liga Acadêmica de Sistemas Embarcados · CIn · UFPE</span>
|
||||
<span>Material de <strong>Lucas Rayan Guerra</strong> · uso livre para fins educacionais · <a href="mailto:contato@lucasrguerra.dev.br">contato@lucasrguerra.dev.br</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"version": 1,
|
||||
"author": "Lucas Rayan Guerra",
|
||||
"editor": "wokwi",
|
||||
"parts": [
|
||||
{
|
||||
"type": "board-esp32-s3-devkitc-1",
|
||||
"id": "esp",
|
||||
"top": 0,
|
||||
"left": 0,
|
||||
"attrs": {}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-dht22",
|
||||
"id": "dht",
|
||||
"top": 15,
|
||||
"left": -250,
|
||||
"attrs": {
|
||||
"temperature": "26.5",
|
||||
"humidity": "62"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-lcd1602",
|
||||
"id": "lcd",
|
||||
"top": 320,
|
||||
"left": -60,
|
||||
"attrs": {
|
||||
"pins": "i2c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-resistor",
|
||||
"id": "r_dht",
|
||||
"top": 9,
|
||||
"left": -173,
|
||||
"rotate": 90,
|
||||
"attrs": {
|
||||
"value": "10000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-resistor",
|
||||
"id": "r_sda",
|
||||
"top": 9,
|
||||
"left": -83,
|
||||
"rotate": 90,
|
||||
"attrs": {
|
||||
"value": "4700"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "wokwi-resistor",
|
||||
"id": "r_scl",
|
||||
"top": 9,
|
||||
"left": -63,
|
||||
"rotate": 90,
|
||||
"attrs": {
|
||||
"value": "4700"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
[
|
||||
"esp:TX",
|
||||
"$serialMonitor:RX",
|
||||
"",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"esp:RX",
|
||||
"$serialMonitor:TX",
|
||||
"",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"lcd:VCC",
|
||||
"esp:5V",
|
||||
"red",
|
||||
[
|
||||
"h-20",
|
||||
"v-100"
|
||||
]
|
||||
],
|
||||
[
|
||||
"lcd:GND",
|
||||
"esp:GND.1",
|
||||
"black",
|
||||
[
|
||||
"h-30",
|
||||
"v-90"
|
||||
]
|
||||
],
|
||||
[
|
||||
"lcd:SDA",
|
||||
"esp:8",
|
||||
"blue",
|
||||
[
|
||||
"h-40",
|
||||
"v-200"
|
||||
]
|
||||
],
|
||||
[
|
||||
"lcd:SCL",
|
||||
"esp:9",
|
||||
"purple",
|
||||
[
|
||||
"h-50",
|
||||
"v-170"
|
||||
]
|
||||
],
|
||||
[
|
||||
"dht:VCC",
|
||||
"r_dht:1",
|
||||
"red",
|
||||
[
|
||||
"v12",
|
||||
"h-25",
|
||||
"v-159",
|
||||
"h100"
|
||||
]
|
||||
],
|
||||
[
|
||||
"dht:SDA",
|
||||
"esp:12",
|
||||
"gold",
|
||||
[
|
||||
"v77"
|
||||
]
|
||||
],
|
||||
[
|
||||
"dht:GND",
|
||||
"esp:GND.1",
|
||||
"black",
|
||||
[
|
||||
"v118"
|
||||
]
|
||||
],
|
||||
[
|
||||
"r_dht:1",
|
||||
"esp:3V3.1",
|
||||
"red",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"r_dht:2",
|
||||
"esp:12",
|
||||
"gold",
|
||||
[
|
||||
"v181"
|
||||
]
|
||||
],
|
||||
[
|
||||
"r_sda:1",
|
||||
"esp:3V3.1",
|
||||
"red",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"r_sda:2",
|
||||
"esp:8",
|
||||
"blue",
|
||||
[
|
||||
"v124"
|
||||
]
|
||||
],
|
||||
[
|
||||
"r_scl:1",
|
||||
"esp:3V3.1",
|
||||
"red",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"r_scl:2",
|
||||
"esp:9",
|
||||
"purple",
|
||||
[
|
||||
"v152"
|
||||
]
|
||||
]
|
||||
],
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
DHT sensor library
|
||||
Adafruit Unified Sensor
|
||||
LiquidCrystal I2C
|
||||
@@ -0,0 +1,23 @@
|
||||
// Scanner I2C: lista o endereço de cada dispositivo no barramento.
|
||||
// Use antes do programa principal se o LCD não mostrar nada.
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Wire.begin(8, 9); // SDA, SCL
|
||||
delay(500);
|
||||
|
||||
Serial.println("Procurando dispositivos I2C...");
|
||||
int encontrados = 0;
|
||||
for (byte endereco = 1; endereco < 127; endereco++) {
|
||||
Wire.beginTransmission(endereco);
|
||||
if (Wire.endTransmission() == 0) {
|
||||
Serial.printf("Dispositivo em 0x%02X\n", endereco);
|
||||
encontrados++;
|
||||
}
|
||||
}
|
||||
if (encontrados == 0) Serial.println("Nenhum dispositivo. Confira SDA, SCL e alimentacao.");
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Formação em ESP32 · LASER · Módulo 2
|
||||
// Estação de temperatura e umidade: DHT22 + LCD 16x2 I2C + ESP32-S3
|
||||
|
||||
#include <Wire.h>
|
||||
#include <DHT.h>
|
||||
#include <LiquidCrystal_I2C.h>
|
||||
|
||||
#define PINO_DHT 12 // fio amarelo
|
||||
#define TIPO_DHT DHT22
|
||||
#define PINO_SDA 8 // I2C padrão do ESP32-S3
|
||||
#define PINO_SCL 9
|
||||
|
||||
DHT dht(PINO_DHT, TIPO_DHT);
|
||||
LiquidCrystal_I2C lcd(0x27, 16, 2); // endereço, colunas, linhas
|
||||
|
||||
// Símbolo de grau: 8 linhas de 5 pixels
|
||||
byte grau[8] = { 0b01100, 0b10010, 0b10010, 0b01100, 0, 0, 0, 0 };
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Wire.begin(PINO_SDA, PINO_SCL);
|
||||
dht.begin();
|
||||
|
||||
lcd.init();
|
||||
lcd.backlight();
|
||||
lcd.createChar(0, grau);
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("LASER ESP32-S3");
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Iniciando...");
|
||||
delay(2000); // o DHT22 precisa de ~2 s para a primeira leitura
|
||||
lcd.clear();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
float umidade = dht.readHumidity();
|
||||
float temperatura = dht.readTemperature();
|
||||
|
||||
if (isnan(umidade) || isnan(temperatura)) {
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("Falha no sensor ");
|
||||
Serial.println("Falha na leitura do DHT22");
|
||||
} else {
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("Temp: ");
|
||||
lcd.print(temperatura, 1);
|
||||
lcd.write(0); // o caractere de grau criado acima
|
||||
lcd.print("C ");
|
||||
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Umid: ");
|
||||
lcd.print(umidade, 1);
|
||||
lcd.print(" % ");
|
||||
|
||||
Serial.printf("Temperatura: %.1f C | Umidade: %.1f %%\n", temperatura, umidade);
|
||||
}
|
||||
|
||||
delay(2000); // o DHT22 aceita no máximo uma leitura a cada 2 s
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/* =========================================================================
|
||||
Página de entrada de cada formação (/formacoes/N).
|
||||
|
||||
Mesma identidade dos slides: base escura tirada do prussiano da marca, o
|
||||
feixe como traço de assinatura e as três famílias tipográficas da LASER.
|
||||
A página é gerada por scripts/formacoes-viewer.mjs a partir do
|
||||
formacao.json de cada pasta; não edite o index.html à mão.
|
||||
========================================================================= */
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--breu: #041d2b;
|
||||
--breu-2: #06283a;
|
||||
--breu-3: #0a3a53;
|
||||
--prussiano: #003049;
|
||||
--vermelho: #D62828;
|
||||
--laranja: #F77F00;
|
||||
--ambar: #FCBF49;
|
||||
--creme: #EAE2B7;
|
||||
|
||||
--gelo: #f2f8fb;
|
||||
--texto: #cddde6;
|
||||
--texto-2: #93aebd;
|
||||
--linha: rgba(146, 187, 207, 0.18);
|
||||
--linha-forte: rgba(146, 187, 207, 0.38);
|
||||
--painel: rgba(255, 255, 255, 0.04);
|
||||
--painel-2: rgba(255, 255, 255, 0.07);
|
||||
|
||||
--feixe: linear-gradient(90deg, #1b6d92, var(--creme), var(--ambar), var(--laranja), var(--vermelho));
|
||||
|
||||
--display: "Outfit", system-ui, sans-serif;
|
||||
--corpo: "Inter", system-ui, sans-serif;
|
||||
--mono: "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
--largura: 1120px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--breu);
|
||||
color: var(--texto);
|
||||
font-family: var(--corpo);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
background:
|
||||
radial-gradient(50% 40% at 85% 0%, rgba(252, 191, 73, 0.10), transparent 70%),
|
||||
radial-gradient(45% 45% at 0% 100%, rgba(0, 48, 73, 0.6), transparent 70%);
|
||||
}
|
||||
|
||||
a { color: var(--ambar); text-decoration-thickness: 1px; text-underline-offset: 3px; }
|
||||
a:hover { color: var(--creme); }
|
||||
:focus-visible { outline: 2px solid var(--ambar); outline-offset: 3px; border-radius: 6px; }
|
||||
|
||||
code, kbd {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.86em;
|
||||
padding: 0.1em 0.42em;
|
||||
border-radius: 5px;
|
||||
background: var(--painel-2);
|
||||
border: 1px solid var(--linha);
|
||||
color: var(--gelo);
|
||||
}
|
||||
kbd { border-bottom-width: 2px; }
|
||||
|
||||
.envoltorio { max-width: var(--largura); margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Barra do topo
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
.topo { border-bottom: 1px solid var(--linha); }
|
||||
.topo .envoltorio { display: flex; align-items: center; gap: 16px; min-height: 68px; }
|
||||
.topo img { height: 26px; display: block; }
|
||||
.topo-liga { font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--texto-2); line-height: 1.4; border-left: 1px solid var(--linha-forte); padding-left: 16px; }
|
||||
.topo nav { margin-left: auto; display: flex; gap: 20px; }
|
||||
.topo nav a { color: var(--texto); text-decoration: none; font-size: 14px; }
|
||||
.topo nav a:hover { color: var(--ambar); }
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Abertura
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
.abertura { padding: 64px 0 56px; }
|
||||
.abertura .envoltorio { display: grid; grid-template-columns: 1.25fr 0.75fr; gap: 48px; align-items: center; }
|
||||
|
||||
.selo {
|
||||
display: inline-flex; gap: 10px; align-items: center;
|
||||
font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase;
|
||||
color: var(--ambar);
|
||||
}
|
||||
.selo::after { content: ""; width: 48px; height: 1px; background: var(--ambar); opacity: 0.5; }
|
||||
|
||||
h1 {
|
||||
font-family: var(--display);
|
||||
font-weight: 300;
|
||||
font-size: clamp(38px, 5.4vw, 60px);
|
||||
line-height: 1.04;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--gelo);
|
||||
margin: 18px 0 18px;
|
||||
}
|
||||
h1 strong { display: block; font-weight: 800; }
|
||||
|
||||
.abertura-sub { font-size: 18px; color: var(--texto); max-width: 54ch; margin: 0 0 24px; }
|
||||
|
||||
.fichas { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 30px; padding: 0; list-style: none; }
|
||||
.fichas li { font-size: 13px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--linha-forte); color: var(--texto); background: var(--painel); }
|
||||
.fichas li b { color: var(--gelo); font-weight: 600; }
|
||||
|
||||
.acoes { display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
|
||||
.botao {
|
||||
display: inline-flex; align-items: center; gap: 10px;
|
||||
padding: 12px 20px; border-radius: 10px;
|
||||
font-family: var(--corpo); font-weight: 600; font-size: 15px; text-decoration: none;
|
||||
border: 1px solid var(--linha-forte); color: var(--gelo); background: var(--painel);
|
||||
transition: background 0.15s, border-color 0.15s, transform 0.15s;
|
||||
}
|
||||
.botao:hover { background: var(--painel-2); border-color: var(--ambar); color: var(--gelo); transform: translateY(-1px); }
|
||||
.botao svg { width: 18px; height: 18px; flex: none; }
|
||||
.botao.primario { background: var(--ambar); border-color: var(--ambar); color: #1d1300; }
|
||||
.botao.primario:hover { background: var(--creme); border-color: var(--creme); color: #1d1300; }
|
||||
.botao.pequeno { padding: 8px 14px; font-size: 14px; }
|
||||
|
||||
/* Pilha das duas prévias: o slide na frente, a apostila atrás. */
|
||||
.previas { position: relative; aspect-ratio: 4 / 3.3; }
|
||||
.previas a { position: absolute; display: block; border-radius: 10px; overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55); border: 1px solid var(--linha-forte); transition: transform 0.2s; }
|
||||
.previas img { display: block; width: 100%; height: auto; }
|
||||
.previas .p-apostila { width: 46%; right: 0; top: 0; transform: rotate(4deg); }
|
||||
.previas .p-slides { width: 88%; left: 0; bottom: 6%; }
|
||||
.previas a:hover { transform: translateY(-4px) rotate(0deg); z-index: 2; }
|
||||
|
||||
.feixe { height: 3px; background: var(--feixe); border: 0; margin: 0; }
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Seções
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
section.bloco { padding: 64px 0 8px; }
|
||||
|
||||
.rotulo {
|
||||
font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase;
|
||||
color: var(--ambar); margin: 0 0 8px;
|
||||
}
|
||||
h2 { font-family: var(--display); font-weight: 700; font-size: 32px; line-height: 1.15; color: var(--gelo); margin: 0 0 8px; letter-spacing: -0.01em; }
|
||||
.bloco-sub { color: var(--texto-2); margin: 0 0 28px; max-width: 70ch; }
|
||||
h3 { font-family: var(--display); font-weight: 700; font-size: 20px; color: var(--gelo); margin: 0 0 6px; }
|
||||
|
||||
/* Três portas de entrada. */
|
||||
.portas { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
|
||||
.porta { display: flex; flex-direction: column; border: 1px solid var(--linha); border-radius: 14px; background: var(--painel); overflow: hidden; }
|
||||
.porta-imagem { aspect-ratio: 16 / 9; background: var(--breu-2); border-bottom: 1px solid var(--linha); overflow: hidden; display: grid; place-items: center; }
|
||||
.porta-imagem img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
|
||||
.porta-imagem.clara img { object-fit: contain; background: #2f2f2f; }
|
||||
.porta-corpo { padding: 20px 20px 22px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
|
||||
.porta-corpo p { margin: 0; color: var(--texto); font-size: 15px; }
|
||||
.porta-meta { font-family: var(--mono); font-size: 12px; color: var(--texto-2); letter-spacing: 0.04em; }
|
||||
.porta .acoes { margin-top: auto; padding-top: 6px; gap: 8px; }
|
||||
|
||||
/* Lista de arquivos. */
|
||||
.grupo-arquivos { margin: 0 0 30px; }
|
||||
.grupo-arquivos h3 { font-size: 14px; font-family: var(--mono); letter-spacing: 0.16em; text-transform: uppercase; color: var(--texto-2); font-weight: 600; margin: 0 0 10px; }
|
||||
.arquivos { list-style: none; margin: 0; padding: 0; border: 1px solid var(--linha); border-radius: 12px; overflow: hidden; }
|
||||
.arquivo { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto auto; gap: 16px; align-items: center; padding: 14px 18px; background: var(--painel); }
|
||||
.arquivo + .arquivo { border-top: 1px solid var(--linha); }
|
||||
.arquivo:hover { background: var(--painel-2); }
|
||||
.icone { width: 44px; height: 44px; border-radius: 10px; display: grid; place-items: center; background: var(--breu-3); color: var(--ambar); }
|
||||
.icone svg { width: 22px; height: 22px; }
|
||||
.icone.pdf { color: #ff8b8b; background: rgba(214, 40, 40, 0.14); }
|
||||
.icone.codigo { color: #6fdcb0; background: rgba(111, 220, 176, 0.1); }
|
||||
.icone.imagem { color: #7cc4ff; background: rgba(78, 168, 255, 0.12); }
|
||||
.arquivo-nome { font-family: var(--mono); font-size: 14.5px; color: var(--gelo); font-weight: 600; word-break: break-all; }
|
||||
.arquivo-desc { font-size: 14px; color: var(--texto-2); margin-top: 2px; }
|
||||
.arquivo-tam { font-family: var(--mono); font-size: 12.5px; color: var(--texto-2); white-space: nowrap; text-align: right; }
|
||||
.arquivo-acoes { display: flex; gap: 6px; }
|
||||
.arquivo-acoes a { font-size: 13px; font-weight: 600; text-decoration: none; padding: 6px 12px; border-radius: 8px; border: 1px solid var(--linha-forte); color: var(--gelo); white-space: nowrap; }
|
||||
.arquivo-acoes a:hover { border-color: var(--ambar); color: var(--ambar); }
|
||||
|
||||
/* Código embutido: .ino e .txt costumam baixar em vez de abrir, então o
|
||||
conteúdo vem na própria página. */
|
||||
.codigo-fonte { margin-top: 8px; }
|
||||
.codigo-fonte summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ambar); width: max-content; }
|
||||
.codigo-fonte pre { margin: 10px 0 0; max-height: 420px; overflow: auto; padding: 14px 16px; border-radius: 10px; background: rgba(0, 14, 22, 0.7); border: 1px solid var(--linha); border-left: 3px solid var(--laranja); font-family: var(--mono); font-size: 13px; line-height: 1.55; color: #dbeaf2; tab-size: 2; }
|
||||
.codigo-fonte pre code { background: none; border: 0; padding: 0; font-size: inherit; }
|
||||
|
||||
/* Como usar. */
|
||||
.guias { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
||||
.guia { border: 1px solid var(--linha); border-radius: 14px; padding: 22px 22px 18px; background: var(--painel); }
|
||||
.guia ol, .guia ul { margin: 10px 0 0; padding-left: 20px; }
|
||||
.guia li { margin: 6px 0; font-size: 15px; }
|
||||
.guia li::marker { color: var(--ambar); font-family: var(--mono); }
|
||||
.guia p { margin: 8px 0 0; font-size: 15px; }
|
||||
.atalhos { display: grid; grid-template-columns: auto 1fr; gap: 8px 14px; margin-top: 12px; font-size: 14.5px; align-items: center; }
|
||||
.atalhos dt { display: flex; gap: 4px; }
|
||||
.atalhos dd { margin: 0; color: var(--texto); }
|
||||
|
||||
/* Roteiro da aula. */
|
||||
.roteiro { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; counter-reset: parte; }
|
||||
.roteiro a { display: flex; gap: 14px; align-items: baseline; padding: 14px 16px; border-radius: 12px; border: 1px solid var(--linha); background: var(--painel); text-decoration: none; color: var(--texto); height: 100%; }
|
||||
.roteiro a:hover { border-color: var(--ambar); background: var(--painel-2); }
|
||||
.roteiro b { font-family: var(--display); font-size: 26px; line-height: 1; color: var(--ambar); min-width: 1.4em; }
|
||||
.roteiro span { font-weight: 600; color: var(--gelo); }
|
||||
.roteiro small { display: block; font-family: var(--mono); font-size: 12px; color: var(--texto-2); font-weight: 400; margin-top: 2px; }
|
||||
|
||||
/* Hub /formacoes. */
|
||||
.formacoes { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 22px; }
|
||||
|
||||
/* Rodapé. */
|
||||
.rodape { margin-top: 80px; border-top: 1px solid var(--linha); padding: 28px 0 40px; font-size: 14px; color: var(--texto-2); }
|
||||
.rodape .envoltorio { display: flex; flex-wrap: wrap; gap: 12px 32px; justify-content: space-between; }
|
||||
.rodape strong { color: var(--texto); }
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
Telas menores
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.abertura { padding: 40px 0; }
|
||||
.abertura .envoltorio { grid-template-columns: 1fr; gap: 40px; }
|
||||
.previas { max-width: 520px; }
|
||||
.portas, .guias { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.envoltorio { padding: 0 16px; }
|
||||
.topo-liga { display: none; }
|
||||
.topo nav { gap: 14px; }
|
||||
.arquivo { grid-template-columns: 40px minmax(0, 1fr); row-gap: 10px; padding: 14px; }
|
||||
.icone { width: 40px; height: 40px; }
|
||||
.arquivo-tam { grid-column: 2; text-align: left; }
|
||||
.arquivo-acoes { grid-column: 2; }
|
||||
h2 { font-size: 26px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
.botao, .previas a { transition: none; }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>LASER · Formação em ESP32</title>
|
||||
<meta name="description" content="Slides, apostilas e arquivos de prática da formação em ESP32 da LASER, Liga Acadêmica de Sistemas Embarcados da UFPE.">
|
||||
<meta name="author" content="Lucas Rayan Guerra">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/marca/espdocs-marca.svg">
|
||||
<link rel="stylesheet" href="/formacoes/assets/viewer.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Gerado por scripts/formacoes-viewer.mjs a partir do formacao.json. Não edite à mão. -->
|
||||
|
||||
<header class="topo">
|
||||
<div class="envoltorio">
|
||||
<a href="/formacoes"><img src="/formacoes/assets/laser.svg" alt="LASER"></a>
|
||||
<span class="topo-liga">Liga Acadêmica de Sistemas Embarcados<br>Centro de Informática · UFPE</span>
|
||||
<nav><a href="#formacoes">Módulos</a></nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="abertura">
|
||||
<div class="envoltorio" style="grid-template-columns:1fr">
|
||||
<div>
|
||||
<div class="selo">Formação em ESP32</div>
|
||||
<h1>Material das<strong>formações da LASER</strong></h1>
|
||||
<p class="abertura-sub">Slides, apostilas e arquivos de prática de cada módulo, para acompanhar a aula, revisar depois ou imprimir.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr class="feixe">
|
||||
<section class="bloco" id="formacoes">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Módulos</p>
|
||||
<h2>2 formações disponíveis</h2>
|
||||
<div class="formacoes">
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="/formacoes/1"><img src="/formacoes/1/previa-slides.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<div class="porta-meta">MÓDULO 1 · 3 HORAS</div>
|
||||
<h3>Conhecendo a família ESP32</h3>
|
||||
<p>Do silício à bancada: como esses microcontroladores funcionam por dentro, o que cada uma das doze séries faz e não faz, e como escolher o chip certo. Termina com um DHT11 lendo temperatura e umidade num ESP32-S3.</p>
|
||||
<div class="acoes"><a class="botao pequeno primario" href="/formacoes/1"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>Ver o material</a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="/formacoes/2"><img src="/formacoes/2/previa-slides.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<div class="porta-meta">MÓDULO 2 · 2 HORAS</div>
|
||||
<h3>Estação de temperatura e umidade</h3>
|
||||
<p>Um DHT22, um display LCD 16x2 e um ESP32-S3, simulados no Wokwi com o framework Arduino. Dois protocolos de comunicação, um programa curto e os cuidados elétricos que fazem o circuito funcionar também fora da tela.</p>
|
||||
<div class="acoes"><a class="botao pequeno primario" href="/formacoes/2"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>Ver o material</a></div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="rodape">
|
||||
<div class="envoltorio">
|
||||
<span><strong>LASER</strong> · Liga Acadêmica de Sistemas Embarcados · CIn · UFPE</span>
|
||||
<span>Material de <strong>Lucas Rayan Guerra</strong> · uso livre para fins educacionais · <a href="mailto:contato@lucasrguerra.dev.br">contato@lucasrguerra.dev.br</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* Gera a página de entrada de cada formação e a lista geral de formações.
|
||||
*
|
||||
* npm run formacoes-viewer
|
||||
*
|
||||
* Cada pasta public/formacoes/N/ descreve o próprio material num
|
||||
* formacao.json. Daqui saem:
|
||||
* public/formacoes/N/index.html a página servida em /formacoes/N
|
||||
* public/formacoes/index.html a lista servida em /formacoes
|
||||
*
|
||||
* O que dá para medir não fica no JSON: tamanho de cada arquivo, número de
|
||||
* páginas dos PDFs e o roteiro da aula, lido das aberturas de seção do
|
||||
* slide.html. Assim a página não desatualiza quando o material muda.
|
||||
*
|
||||
* Os links são absolutos (/formacoes/N/...) porque o middleware serve a
|
||||
* página em /formacoes/N, sem barra final, e um link relativo apontaria para
|
||||
* a pasta de cima.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const raiz = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const pastaFormacoes = path.join(raiz, "public", "formacoes");
|
||||
|
||||
const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
|
||||
function tamanho(bytes) {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1).replace(".", ",")} MB`;
|
||||
}
|
||||
|
||||
/* Conta os objetos /Type /Page de um PDF. Basta para os PDFs do Chrome, que
|
||||
não comprimem a árvore de páginas. */
|
||||
function paginasPdf(arquivo) {
|
||||
const bruto = fs.readFileSync(arquivo, "latin1");
|
||||
return (bruto.match(/\/Type\s*\/Page(?![s\w])/g) || []).length;
|
||||
}
|
||||
|
||||
/* As aberturas de seção do slide.html viram o roteiro da aula, cada uma com
|
||||
link para o slide em que começa. */
|
||||
function roteiro(pasta) {
|
||||
const html = fs.readFileSync(path.join(pasta, "slide.html"), "utf8");
|
||||
const slides = [...html.matchAll(/<section class="slide([^"]*)"[^>]*data-titulo="([^"]*)"/g)];
|
||||
const partes = [];
|
||||
slides.forEach((m, i) => {
|
||||
if (!/\bsecao\b/.test(m[1])) return;
|
||||
partes.push({ indice: i + 1, titulo: m[2].replace(/^§\d+\s*·\s*/, "") });
|
||||
});
|
||||
partes.forEach((p, i) => {
|
||||
const fim = i + 1 < partes.length ? partes[i + 1].indice - 1 : slides.length;
|
||||
p.quantos = fim - p.indice + 1;
|
||||
});
|
||||
return { partes, total: slides.length };
|
||||
}
|
||||
|
||||
const ICONES = {
|
||||
slides: '<path d="M3 5h18v12H3z"/><path d="M8 21h8M12 17v4"/>',
|
||||
apostila: '<path d="M4 4.5A1.5 1.5 0 0 1 5.5 3H20v16H5.5A1.5 1.5 0 0 0 4 20.5z"/><path d="M4 20.5A1.5 1.5 0 0 0 5.5 22H20"/>',
|
||||
pdf: '<path d="M14 3H6v18h12V7z"/><path d="M14 3v4h4"/><path d="M9 13h6M9 17h4"/>',
|
||||
codigo: '<path d="m8 8-4 4 4 4M16 8l4 4-4 4M13.5 5l-3 14"/>',
|
||||
zip: '<path d="M14 3H6v18h12V7z"/><path d="M14 3v4h4"/><path d="M11 6h1M11 9h1M11 12h1M10.5 15h2v3h-2z"/>',
|
||||
imagem: '<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="m21 17-5-5-9 8"/>',
|
||||
fritzing: '<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M8 8h3v3H8zM13 13h3v3h-3zM11 9.5h3.5V13"/>',
|
||||
seta: '<path d="M5 12h14M13 6l6 6-6 6"/>',
|
||||
baixar: '<path d="M12 4v12M6 10l6 6 6-6M5 20h14"/>',
|
||||
tocar: '<path d="M7 5v14l11-7z"/>',
|
||||
livro: '<path d="M12 6c-2-1.5-5-2-8-1.5v14c3-.5 6 0 8 1.5 2-1.5 5-2 8-1.5v-14c-3-.5-6 0-8 1.5zM12 6v14"/>',
|
||||
};
|
||||
const icone = (nome) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONES[nome]}</svg>`;
|
||||
const CLASSE_ICONE = { pdf: "pdf", codigo: "codigo", zip: "codigo", imagem: "imagem" };
|
||||
|
||||
const cabecalho = (titulo, descricao) => `<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${esc(titulo)}</title>
|
||||
<meta name="description" content="${esc(descricao)}">
|
||||
<meta name="author" content="Lucas Rayan Guerra">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/svg+xml" href="/marca/espdocs-marca.svg">
|
||||
<link rel="stylesheet" href="/formacoes/assets/viewer.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Gerado por scripts/formacoes-viewer.mjs a partir do formacao.json. Não edite à mão. -->
|
||||
`;
|
||||
|
||||
const topo = (nav) => `
|
||||
<header class="topo">
|
||||
<div class="envoltorio">
|
||||
<a href="/formacoes"><img src="/formacoes/assets/laser.svg" alt="LASER"></a>
|
||||
<span class="topo-liga">Liga Acadêmica de Sistemas Embarcados<br>Centro de Informática · UFPE</span>
|
||||
<nav>${nav}</nav>
|
||||
</div>
|
||||
</header>`;
|
||||
|
||||
const rodape = `
|
||||
<footer class="rodape">
|
||||
<div class="envoltorio">
|
||||
<span><strong>LASER</strong> · Liga Acadêmica de Sistemas Embarcados · CIn · UFPE</span>
|
||||
<span>Material de <strong>Lucas Rayan Guerra</strong> · uso livre para fins educacionais · <a href="mailto:contato@lucasrguerra.dev.br">contato@lucasrguerra.dev.br</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
function paginaFormacao(pasta, dados) {
|
||||
const n = dados.modulo;
|
||||
const base = `/formacoes/${n}`;
|
||||
const url = (arq) => (/^https?:/.test(arq) ? arq : `${base}/${arq}`);
|
||||
const info = (arq) => {
|
||||
const caminho = path.join(pasta, arq);
|
||||
if (!fs.existsSync(caminho)) throw new Error(`formacao ${n}: não achei ${arq}`);
|
||||
const bytes = fs.statSync(caminho).size;
|
||||
const paginas = arq.endsWith(".pdf") ? paginasPdf(caminho) : 0;
|
||||
return { bytes, paginas };
|
||||
};
|
||||
const { partes, total } = roteiro(pasta);
|
||||
const pdfSlides = `LASER-ESP32-Modulo-${n}-Slides.pdf`;
|
||||
const pdfApostila = `LASER-ESP32-Modulo-${n}-Apostila.pdf`;
|
||||
const iSlides = info(pdfSlides);
|
||||
const iApostila = info(pdfApostila);
|
||||
|
||||
const fichas = dados.fichas.map(([k, v]) => `<li>${esc(k)}: <b>${esc(v)}</b></li>`).join("");
|
||||
const acao = ([rotulo, arq, baixar], primario) =>
|
||||
`<a class="botao pequeno${primario ? " primario" : ""}" href="${url(arq)}"${baixar ? " download" : ""}${/^https?:/.test(arq) ? ' rel="noopener"' : ""}>${icone(baixar ? "baixar" : "seta")}${esc(rotulo)}</a>`;
|
||||
|
||||
const grupos = dados.grupos.map((g) => {
|
||||
const itens = g.arquivos.map((a) => {
|
||||
const { bytes, paginas } = info(a.arquivo);
|
||||
const nome = path.basename(a.arquivo);
|
||||
const meta = paginas ? `${paginas} págs · ${tamanho(bytes)}` : tamanho(bytes);
|
||||
const botoes = [];
|
||||
if (a.tipo === "codigo") {
|
||||
botoes.push(`<a href="${url(a.arquivo)}" download>Baixar</a>`);
|
||||
} else {
|
||||
if (a.abrir) botoes.push(`<a href="${url(a.arquivo)}"${a.tipo === "imagem" || a.tipo === "pdf" ? ' target="_blank" rel="noopener"' : ""}>Abrir</a>`);
|
||||
if (a.baixar) botoes.push(`<a href="${url(a.arquivo)}" download>Baixar</a>`);
|
||||
}
|
||||
let codigo = "";
|
||||
if (a.tipo === "codigo") {
|
||||
const fonte = fs.readFileSync(path.join(pasta, a.arquivo), "utf8");
|
||||
codigo = `<details class="codigo-fonte"><summary>Ver código</summary><pre><code>${esc(fonte.trimEnd())}</code></pre></details>`;
|
||||
}
|
||||
return `
|
||||
<li class="arquivo">
|
||||
<div class="icone ${CLASSE_ICONE[a.tipo] || ""}">${icone(a.tipo)}</div>
|
||||
<div>
|
||||
<div class="arquivo-nome">${esc(nome)}</div>
|
||||
<div class="arquivo-desc">${a.descricao}</div>
|
||||
${codigo}
|
||||
</div>
|
||||
<div class="arquivo-tam">${meta}</div>
|
||||
<div class="arquivo-acoes">${botoes.join("")}</div>
|
||||
</li>`;
|
||||
}).join("");
|
||||
return `
|
||||
<div class="grupo-arquivos">
|
||||
<h3>${esc(g.titulo)}</h3>
|
||||
<ul class="arquivos">${itens}
|
||||
</ul>
|
||||
</div>`;
|
||||
}).join("");
|
||||
|
||||
const guias = [
|
||||
`<div class="guia">
|
||||
<h3>Apresentando os slides</h3>
|
||||
<p>Abra <a href="${base}/slide.html">slide.html</a> no navegador. O palco é 16:9 e se ajusta a qualquer tela ou projetor.</p>
|
||||
<dl class="atalhos">
|
||||
<dt><kbd>→</kbd><kbd>←</kbd></dt><dd>avançar e voltar (ou clique nas metades da tela)</dd>
|
||||
<dt><kbd>F</kbd></dt><dd>tela cheia</dd>
|
||||
<dt><kbd>O</kbd></dt><dd>panorama com todos os slides</dd>
|
||||
<dt><kbd>B</kbd></dt><dd>tela preta, para pausar a projeção</dd>
|
||||
</dl>
|
||||
</div>`,
|
||||
`<div class="guia">
|
||||
<h3>Lendo e imprimindo a apostila</h3>
|
||||
<p>A <a href="${base}/apostila.html">versão online</a> se pagina em A4 sozinha ao abrir, o que leva alguns segundos.</p>
|
||||
<p>Para papel, prefira o <a href="${url(pdfApostila)}" download>PDF pronto</a>: ele já sai com margens, cores e quebras de página certas, sem configurar a impressora.</p>
|
||||
</div>`,
|
||||
...dados.guias.map((g) => `<div class="guia">
|
||||
<h3>${esc(g.titulo)}</h3>
|
||||
${g.itens ? `<ol>${g.itens.map((i) => `<li>${i}</li>`).join("")}</ol>` : ""}
|
||||
${g.texto ? `<p>${g.texto}</p>` : ""}
|
||||
</div>`),
|
||||
].join("\n\t\t");
|
||||
|
||||
const listaRoteiro = partes.map((p, i) => `
|
||||
<li><a href="${base}/slide.html#${p.indice}"><b>${i + 1}</b><span>${esc(p.titulo)}<small>slide ${p.indice} · ${p.quantos} slide${p.quantos > 1 ? "s" : ""}</small></span></a></li>`).join("");
|
||||
|
||||
const titulo = `LASER · Formação em ESP32 · Módulo ${n}: ${dados.titulo.join(" ")}`;
|
||||
|
||||
return cabecalho(titulo, dados.descricao) + topo(`<a href="#arquivos">Arquivos</a><a href="#como-usar">Como usar</a><a href="/formacoes">Formações</a>`) + `
|
||||
|
||||
<main>
|
||||
<section class="abertura">
|
||||
<div class="envoltorio">
|
||||
<div>
|
||||
<div class="selo">Formação em ESP32 · Módulo ${n}</div>
|
||||
<h1>${esc(dados.titulo[0])}<strong>${esc(dados.titulo[1])}</strong></h1>
|
||||
<p class="abertura-sub">${esc(dados.subtitulo)}</p>
|
||||
<ul class="fichas">${fichas}</ul>
|
||||
<div class="acoes">
|
||||
<a class="botao primario" href="${base}/slide.html">${icone("tocar")}Apresentar os slides</a>
|
||||
<a class="botao" href="${base}/apostila.html">${icone("livro")}Ler a apostila</a>
|
||||
<a class="botao" href="#baixar">${icone("baixar")}Baixar em PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="previas">
|
||||
<a class="p-apostila" href="${base}/apostila.html" aria-label="Abrir a apostila"><img src="${base}/previa-apostila.jpg" alt="Capa da apostila do módulo ${n}" width="640" height="905"></a>
|
||||
<a class="p-slides" href="${base}/slide.html" aria-label="Abrir os slides"><img src="${base}/previa-slides.jpg" alt="Capa dos slides do módulo ${n}" width="960" height="540"></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr class="feixe">
|
||||
|
||||
<section class="bloco" id="baixar">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Comece por aqui</p>
|
||||
<h2>Três formas de usar este material</h2>
|
||||
<p class="bloco-sub">Para apresentar, para estudar e para pôr a mão no circuito. Os PDFs são a versão final, para quem só quer o material pronto.</p>
|
||||
<div class="portas">
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="${base}/slide.html"><img src="${base}/previa-slides.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<h3>Slides</h3>
|
||||
<p>${total} slides para projetar direto do navegador, com navegação por teclado e panorama.</p>
|
||||
<div class="porta-meta">PDF · ${iSlides.paginas} págs · ${tamanho(iSlides.bytes)}</div>
|
||||
<div class="acoes">
|
||||
<a class="botao pequeno primario" href="${base}/slide.html">${icone("tocar")}Apresentar</a>
|
||||
<a class="botao pequeno" href="${url(pdfSlides)}" download>${icone("baixar")}PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="${base}/apostila.html"><img src="${base}/previa-apostila.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<h3>Apostila</h3>
|
||||
<p>O conteúdo completo, com explicações, tabelas e o código comentado, paginado em A4.</p>
|
||||
<div class="porta-meta">PDF · ${iApostila.paginas} págs · ${tamanho(iApostila.bytes)}</div>
|
||||
<div class="acoes">
|
||||
<a class="botao pequeno primario" href="${base}/apostila.html">${icone("livro")}Ler</a>
|
||||
<a class="botao pequeno" href="${url(pdfApostila)}" download>${icone("baixar")}PDF</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="porta">
|
||||
<a class="porta-imagem clara" href="${url(dados.pratica.imagem)}" target="_blank" rel="noopener"><img src="${url(dados.pratica.imagem)}" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<h3>${esc(dados.pratica.titulo)}</h3>
|
||||
<p>${esc(dados.pratica.texto)}</p>
|
||||
<div class="porta-meta">${esc(dados.pratica.meta)}</div>
|
||||
<div class="acoes">${dados.pratica.acoes.map((a, i) => acao(a, i === 0)).join("")}</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bloco" id="roteiro">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Roteiro da aula</p>
|
||||
<h2>${partes.length} partes, ${total} slides</h2>
|
||||
<p class="bloco-sub">Cada parte abre os slides direto no ponto em que ela começa.</p>
|
||||
<ol class="roteiro">${listaRoteiro}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bloco" id="arquivos">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Todos os arquivos</p>
|
||||
<h2>O que tem nesta formação</h2>
|
||||
<p class="bloco-sub">Tudo o que foi usado na aula, com o que é cada arquivo e para que serve.</p>
|
||||
${grupos}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bloco" id="como-usar">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Como usar</p>
|
||||
<h2>Passo a passo</h2>
|
||||
<div class="guias">
|
||||
${guias}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
` + rodape;
|
||||
}
|
||||
|
||||
function paginaLista(formacoes) {
|
||||
const cartoes = formacoes.map(({ dados }) => {
|
||||
const base = `/formacoes/${dados.modulo}`;
|
||||
const duracao = (dados.fichas.find(([k]) => k === "Duração") || [])[1];
|
||||
return `
|
||||
<article class="porta">
|
||||
<a class="porta-imagem" href="${base}"><img src="${base}/previa-slides.jpg" alt="" loading="lazy"></a>
|
||||
<div class="porta-corpo">
|
||||
<div class="porta-meta">MÓDULO ${dados.modulo}${duracao ? ` · ${esc(duracao.toUpperCase())}` : ""}</div>
|
||||
<h3>${esc(dados.titulo.join(" "))}</h3>
|
||||
<p>${esc(dados.subtitulo)}</p>
|
||||
<div class="acoes"><a class="botao pequeno primario" href="${base}">${icone("seta")}Ver o material</a></div>
|
||||
</div>
|
||||
</article>`;
|
||||
}).join("");
|
||||
|
||||
return cabecalho("LASER · Formação em ESP32", "Slides, apostilas e arquivos de prática da formação em ESP32 da LASER, Liga Acadêmica de Sistemas Embarcados da UFPE.") + topo(`<a href="#formacoes">Módulos</a>`) + `
|
||||
|
||||
<main>
|
||||
<section class="abertura">
|
||||
<div class="envoltorio" style="grid-template-columns:1fr">
|
||||
<div>
|
||||
<div class="selo">Formação em ESP32</div>
|
||||
<h1>Material das<strong>formações da LASER</strong></h1>
|
||||
<p class="abertura-sub">Slides, apostilas e arquivos de prática de cada módulo, para acompanhar a aula, revisar depois ou imprimir.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<hr class="feixe">
|
||||
<section class="bloco" id="formacoes">
|
||||
<div class="envoltorio">
|
||||
<p class="rotulo">Módulos</p>
|
||||
<h2>${formacoes.length} formações disponíveis</h2>
|
||||
<div class="formacoes">${cartoes}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
` + rodape;
|
||||
}
|
||||
|
||||
const formacoes = fs.readdirSync(pastaFormacoes)
|
||||
.filter((d) => /^\d+$/.test(d) && fs.existsSync(path.join(pastaFormacoes, d, "formacao.json")))
|
||||
.sort((a, b) => a - b)
|
||||
.map((d) => {
|
||||
const pasta = path.join(pastaFormacoes, d);
|
||||
return { pasta, dados: JSON.parse(fs.readFileSync(path.join(pasta, "formacao.json"), "utf8")) };
|
||||
});
|
||||
|
||||
for (const { pasta, dados } of formacoes) {
|
||||
fs.writeFileSync(path.join(pasta, "index.html"), paginaFormacao(pasta, dados));
|
||||
console.log(`public/formacoes/${dados.modulo}/index.html`);
|
||||
}
|
||||
fs.writeFileSync(path.join(pastaFormacoes, "index.html"), paginaLista(formacoes));
|
||||
console.log("public/formacoes/index.html");
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Exporta os slides de uma formação para PDF em 16:9 exato.
|
||||
* Exporta o material de uma formação para PDF: os slides em 16:9 exato e a
|
||||
* apostila em A4, já paginada pelo Paged.js.
|
||||
*
|
||||
* npm run slides-pdf → módulo 1
|
||||
* npm run slides-pdf -- 2 → módulo 2
|
||||
*
|
||||
* A página do PDF tem 1280 x 720 px (338,667 x 190,5 mm), a mesma caixa do
|
||||
* palco do slide.html, então nada é reescalado nem cortado. Sai um arquivo por
|
||||
* formação, ao lado do próprio slide.html.
|
||||
* A página do PDF dos slides tem 1280 x 720 px (338,667 x 190,5 mm), a mesma
|
||||
* caixa do palco do slide.html, então nada é reescalado nem cortado. Os dois
|
||||
* arquivos saem ao lado do slide.html e da apostila.html da formação.
|
||||
*
|
||||
* Existe porque a caixa de impressão do navegador depende de o usuário lembrar
|
||||
* de marcar "gráficos de segundo plano" e "margens: nenhuma". Aqui isso é fixo.
|
||||
@@ -22,6 +23,7 @@ const raiz = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const modulo = (process.argv[2] || "1").replace(/\D/g, "") || "1";
|
||||
const pastaModulo = path.join(raiz, "public", "formacoes", modulo);
|
||||
const saida = path.join(pastaModulo, `LASER-ESP32-Modulo-${modulo}-Slides.pdf`);
|
||||
const saidaApostila = path.join(pastaModulo, `LASER-ESP32-Modulo-${modulo}-Apostila.pdf`);
|
||||
|
||||
if (!fs.existsSync(path.join(pastaModulo, "slide.html"))) {
|
||||
console.error(`Não achei public/formacoes/${modulo}/slide.html`);
|
||||
@@ -38,6 +40,7 @@ const TIPOS = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".json": "application/json",
|
||||
".woff2": "font/woff2",
|
||||
};
|
||||
|
||||
const servidor = http.createServer((req, res) => {
|
||||
@@ -55,7 +58,7 @@ await new Promise((r) => servidor.listen(0, "127.0.0.1", r));
|
||||
const porta = servidor.address().port;
|
||||
const url = `http://127.0.0.1:${porta}/formacoes/${modulo}/slide.html`;
|
||||
|
||||
console.log(`[1/3] Abrindo ${url}`);
|
||||
console.log(`[1/4] Abrindo ${url}`);
|
||||
const navegador = await puppeteer.launch({ headless: "new", args: ["--no-sandbox"] });
|
||||
|
||||
try {
|
||||
@@ -63,7 +66,7 @@ try {
|
||||
await pagina.goto(url, { waitUntil: "networkidle0", timeout: 60000 });
|
||||
await pagina.evaluate(() => document.fonts.ready);
|
||||
|
||||
console.log("[2/3] Conferindo o encaixe de cada slide no 16:9");
|
||||
console.log("[2/4] Conferindo o encaixe de cada slide no 16:9");
|
||||
await pagina.emulateMediaType("print");
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
@@ -80,7 +83,7 @@ try {
|
||||
console.log(` ${caixas.length} slides, todos em 1280x720`);
|
||||
}
|
||||
|
||||
console.log("[3/3] Gerando o PDF");
|
||||
console.log("[3/4] Gerando o PDF dos slides");
|
||||
await pagina.pdf({
|
||||
path: saida,
|
||||
printBackground: true,
|
||||
@@ -89,7 +92,30 @@ try {
|
||||
});
|
||||
|
||||
const kb = (fs.statSync(saida).size / 1024).toFixed(0);
|
||||
console.log(`\nPronto: ${path.relative(raiz, saida)} (${kb} KB, ${caixas.length} páginas em 16:9)`);
|
||||
console.log(` ${path.relative(raiz, saida)} (${kb} KB, ${caixas.length} páginas em 16:9)`);
|
||||
|
||||
/* A apostila é paginada no navegador pelo Paged.js, que não avisa quando
|
||||
termina. Espera-se o número de folhas parar de crescer. */
|
||||
console.log("[4/4] Paginando e gerando o PDF da apostila");
|
||||
const apostila = await navegador.newPage();
|
||||
await apostila.goto(`http://127.0.0.1:${porta}/formacoes/${modulo}/apostila.html`, { waitUntil: "networkidle0", timeout: 120000 });
|
||||
await apostila.waitForFunction(() => document.querySelectorAll(".pagedjs_page").length > 1, { timeout: 120000 });
|
||||
let folhas = 0;
|
||||
for (;;) {
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
const agora = await apostila.evaluate(() => document.querySelectorAll(".pagedjs_page").length);
|
||||
if (agora === folhas) break;
|
||||
folhas = agora;
|
||||
}
|
||||
await apostila.evaluate(() => document.fonts.ready);
|
||||
await apostila.pdf({
|
||||
path: saidaApostila,
|
||||
printBackground: true,
|
||||
preferCSSPageSize: true,
|
||||
margin: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
});
|
||||
const kbA = (fs.statSync(saidaApostila).size / 1024).toFixed(0);
|
||||
console.log(` ${path.relative(raiz, saidaApostila)} (${kbA} KB, ${folhas} páginas em A4)`);
|
||||
} finally {
|
||||
await navegador.close();
|
||||
servidor.close();
|
||||
|
||||