Vamos brincar com o MicroPython em um ESP8266 (ou ESP32) utilizando o um Jupyter Notebook. A idéia é obter dados de sensores, agindo em um mundo físico diretamente do Jupyter.
1. Introdução
Em um artigo anterior publicado no Medium, explorei como controlar um Raspberry Pi usando-se do Jupyter Notebook: Computação Física Usando o Jupyter Notebook.
Foi uma ótima experiência, e uma vez que o projeto funcionou muito bem, pensei:
“Que tal testar também o Jupyter Notebook em um ESP8266 (ou mesmo no ESP32) usando como linguagem o MicroPython?”
Como sabemos, o Jupyter Notebook é um aplicativo Web de código aberto que permite criar e compartilhar documentos que contêm código ativo, equações, visualizações e texto narrativo. Os usos incluem limpeza e transformação de dados, simulação numérica, modelagem estatística, visualização de dados, aprendizado de máquina e muito mais. Para o “muito mais”, exploraremos a “Computação Física”.
Até agora em meus projetos, explorei IoT e computação física utilizando o ESP8266–01, ESP8266–12E (NodeMCU) e o ESP32, programados pelo IDE do Arduino, utilizando-se de sua linguagem C / C ++. Mas outra ótima linguagem que pode ser usada na programação desses dispositivos é o MicroPython:
O MicroPython é uma implementação enxuta e eficiente da linguagem de programação Python 3 que inclui um pequeno subconjunto da biblioteca padrão do Python e é otimizada para rodar em microcontroladores e em ambientes restritos. O objetivo é ser tão compatível com o Python normal quanto possível para permitir que você transfira código com facilidade do desktop para um microcontrolador ou sistema embarcado.
Além disso, acho que usar o Jupyter Notebook para programar um dispositivo ESP usando o MicroPython pode ser uma ótima ferramenta para ensinar Computação Física para crianças e também ajudar os cientistas a acessar rapidamente o mundo real utilizando-se de sensores na aquisição de dados.
Isso é o que tentaremos realizar neste tutorial:
- Saída de um sinal digital para ligar / desligar um LED
- Ler uma entrada digital (através de um botão)
- Saída de um sinal PWM para o controle do brilho de um LED
- Controlar a posição de um servomotor utilizando-se de uma saída PWM
- Leitura de sinal analógico (luminosidade) usando um LDR
- Leitura de temperatura via bus 1-Wire (DS18B20)
- Leitura de temperatura e umidade (DHT22)
- Exibir dados usando um OLED via barramento I2C.
2. Instalando o MicroPython
A primeira coisa a fazer com um novo NodeMCU (ou ESP32), é apagar o que quer esteja carregado em sua memória, carregando um novo firmware, o qual será o interpretador MicroPython.
A. Obtendo o novo FW:
Vá para o pagina: Downloads no site oficial do MicroPython e faça o download do FW apropriado para o seu dispositivo:
Por exemplo, para o ESP8266, a versão mais recente no momento em que publiquei este artigo é:
esp8266-20180511-v1.9.4.bin (Latest 01Jun18)
(você pode encontrar detalhes de como instalar o FW aqui )
O ideal é criar um diretório onde você irá trabalhar com o MicroPython. Por exemplo, no caso de um Mac, a partir do seu diretório raiz execute:
cd Documents
mkdir MicroPython
cd MicroPython
B. Mova o firmware para o ESP8266 (ou ESP32) baixado, para o diretório recém-criado.
NESTE PONTO: Conecte seu NodeMCU ou ESP32 em seu PC usando o cabo USB serial.
C. Verifique onde está a porta serial que está sendo usada pelo seu dispositivo usando o comando:
ls /dev/tty.*
No meu caso, encontrei:
/dev/tty.SLAB_USBtoUART
D. Instale esptool (ferramenta usada para gravar / apagar FW nos dispositivos)
pip install esptool
E. Apague o que estiver carregado no NodeMCU:
esptool.py --port /dev/tty.SLAB_USBtoUART erase_flash
F. Grave (“Flash”) o novo FW:
esptool.py --port /dev/tty.SLAB_USBtoUART --baud 460800 write_flash --flash_size=detect 0 esp8266-20180511-v1.9.4.bin
Depois de ter o Firmware instalado, você poderá brincar com o REPL * no Terminal usando o comando Screen:
screen /dev/tty.SLAB_USBtoUART 115200
>>> print (‘hello ESP8266’)
>>> hello ESP8266
Se você estiver no REPL, utilize:
[Ctrl + C] para interromper a execução de um programa e
[Ctrl + A] [K] [Y] para sair e retornar ao terminal.
* REPL significa “ Read Evaluate Print Loop” e é o nome dado ao prompt interativo do MicroPython, qual você pode acessar no ESP8266. Você poderá aprender mais sobre a REPL aqui.
3. Instalando o kernel do MicroPython Jupyter
Para interagir com um MicroPython do ESP8266 ou ESP32 (sobre seu REPL serial), precisaremos instalar um kernel específico no Jupyter.
Este procedimento será necessário ser feito apenas uma vez.
Do website Documentação , no site oficial do Projeto Jupyter, podemos listar todos os “kernels mantidos pela comunidade”. De lá, seremos enviados para:
Considerando que seu PC tenha o Python 3 instalado (no meu caso, é um Mac), clone o repositório do Kernel usando o comando abaixo em seu Terminal :
git clone https://github.com/goatchurchprime/jupyter_micropython_kernel.git
Em seguida, instale a biblioteca (no modo editável) no Python3 usando o comando:
pip install -e jupyter_micropython_kernel
Isso criará um pequeno arquivo que possibilitará futuros updates na biblioteca, utilizado-se do comando “git update”.
As coisas poderão dar errado neste ponto e você poderá precisar de “pip3” ou “sudo pip” se você tiver várias versões diferentes do python instalado (como foi meu caso).
Instale o kernel no Jupyter usando o comando:
python -m jupyter_micropython_kernel.install
Isso criará o arquivo “.local / share / jupyter / kernels / micropython / kernel.json” que o jupyter usará para referenciar seus kernels.
Para descobrir onde seus “kernelspecs” estão armazenados, você pode digitar:
jupyter kernelspec list
O PrintScreen acima, mostra a lista de kernels que eu tenho instalado em minha máquina. Note que em meu caso, instalei o kernel do MicroPython usando o comando PIP3 e assim, o Kernel não está no mesmo diretório que os demais, pois recebi um erro ao tentar instalar meu kernel usando o PIP.
Agora execute o Jupyter Notebook:
jupyter notebook
Mantenha esta janela aberta durante todo o tempo em que voce trabalhar com o Jupyter Notebbok.
Uma “Home Page” para o Jupyter Notebok abrirá automaticamente em seu web browser default.
No notebook, clique o botão “New”, no canto superior direito, você verá o nome de exibição do kernel listado: “MicroPython – USB”
Na primeira célula, você precisará definir a porta e a taxa de transmissão que será utilizada (115200 baud funciona bem):
%serialconnect to --port=/dev/tty.SLAB_USBtoUART --baud=115200
Como resposta, a célula retornará:
Connecting to --port=/dev/tty.SLAB_USBtoUART --baud=115200
Ready.
E é isso! Quando o “Ready” aparecer, você poderá executar comandos do MicroPython diretamente nas células do Jupyter Notebook.
Para variar, tentemos:
print ('hello esp8266')
Você deve receber a resposta do ESP8266 como uma saída na célula:
hello esp8266
4. Piscando um LED
Como de costume, vamos começar nossa jornada pelos caminhos da computação física, “piscando um LED”.
Abaixo, a pinagem típica de um NodeMCU (ESP8266-12E V1.0):
Os pinos disponíveis no ESP8266 são: 0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16, que correspondem aos números de pinos GPIO reais do chip ESP8266. Note que muitas placas disponíveis no mercado, utilizam uma numeração própria, por exemplo, D0, D1,….
Instale um LED no pino 0 do NodeMCU (D3) e faça um teste, ligando-o e desligando-o:
# import library to deal with pins:
from machine import Pin
# define pin 0 as output
led = Pin(0, Pin.OUT)
# define value of "led" as "1" or "True" to turn on the LED
led.value(1)
# define value of "led" as "0" or "False" to turn off the LED
led.value(0)
# also you can use .on() or .off methods to control the pin:
led.on()
led.off()
Agora, vamos importar a função “sleep”, que é parte da biblioteca “time” e criar um programa para piscar o LED (no caso 5 vezes):
from time import sleep
for i in range(5):
led.on()
sleep(0.5)
led.off()
sleep(0.5)
5: Entrada do sinal digital
O dado digital mais simples proveniente de um sensor que você poderá ler em um NodeMCU é um botão de pressão (“push-button”).
Instalemos um botão entre o pino 13 (D7) e o terra (GND) como mostrado no diagrama.
Nosso botão será conectado de forma que o estado normal do pino 13 seja “Alto” (logo, usaremos um resistor de “pull-up” interno para garantir esse estado). Quando pressionado, o pino 13 ficará “baixo”.
# define pin 13 as an input and activate an internal Pull-up resistor:
button = Pin(13, Pin.IN, Pin.PULL_UP)
# Read button state:
print(button.value())
Quando você executa a célula acima sem pressionar o botão, o resultado será:
1
Pressionando o botão, execute a célula novamente:
# Read button state:
print(button.value())
O resultado é agora:
0
Note que parar de pressionar o botão o “valor da célula”não retorna para “1”. Para ver “1”, você deve executar a célula novamente.
Vamos agora fazer um pequeno programa para ligar o LED somente se o botão for pressionado:
print (button.value())
if button.value() == 0:
led.on()
else:
led.off()
6. PWM (modulação por largura de pulso)
O PWM pode ser ativado em todos os pinos, exceto no pino 16 (D0). Existe uma frequência única para todos os canais, com um intervalo entre 1 e 1000 (medido em Hz). O ciclo de trabalho (“Duty-Cycle”)está entre 0 e 1023, inclusive.
Comece chamando as bibliotecas apropriadas:
from machine import Pin, PWM
Vários comandos individuais relacionados com o PWM estão disponíveis:
pwm0 = PWM(Pin(0)) # create PWM object from a pin
pwm0.freq() # get current frequency
pwm0.freq(1000) # set frequency
pwm0.duty() # get current duty cycle
pwm0.duty(200) # set duty cycle
pwm0.deinit() # turn off PWM on the pin
Ou você pode configurar o pino tudo de uma só vez:
pwm2 = PWM(Pin(2), freq=500, duty=512)
Variemos o brilho do LED conectado ao pino 0, partindo de “totalmente apagado”(0) para “totalmente aceso” (1023), variando o dutycicle de 20 em 20:
from machine import Pin, PWM
pwm0 = PWM(Pin(0), freq=1000, duty=0)
for i in range (0,1023,20):
pwm0.duty(i)
sleep(0.1)
pwm0.duty(0)
pwm0.deinit()
E agora, que tal controlarmos um servomotor?
Vamos instalar um pequeno passatempo servo no nosso NodeMCU, como mostrado no diagrama. Observe que estou conectando o Servo VCC ao NodeMCU + 3.3V. Está tudo bem para este tutorial, mas em projetos reais, você deve conectar o Servo VCC a uma fonte de alimentação externa de + 5V (não se esqueça de conectar os GNDs ao NodeMCU GND).
O pino de dados do servo será conectado ao pino 14 do NodeMCU (D5).
Servos geralmente trabalham com freqüência de 50Hz e, em seguida, um ciclo de trabalho entre cerca de 40 e 115 irá posicioná-los de 0 a 180 graus, respectivamente. Um ciclo de trabalho de 77 posicionará o servo em seu valor central (90 graus).
servo = PWM(Pin(14), freq=50)
Teste o servo em diferentes posições:
# Minimum position (angle 0)
servo.duty(40)
# Maximun position (angle 180)
servo.duty(40)
# center position (angle 90)
servo.duty(40)
Você também pode criar um programa de troca simples para testar seu servo:
# swipping servo
step = 2
for i in range (40, 115, step):
servo.duty(i)
sleep (0.1)
step = -1*step
for i in range (115, 40, step):
servo.duty(i)
sleep (0.1)
servo.duty(77)
Abaixo do resultado:
Eu não estou usando o sonar aqui, então vou deixar para você desenvolver um código para usá-lo. É simples, em frente! Tente!

7. Entrada Analógica (medindo Luminosidade)
O ESP8266 tem um único pino A0 que pode ser usado para ler sinais analógicos e convertê-los em um valor digital. Você pode construir um objeto referente ao pino ADC usando:
from machine import ADC
adc = ADC(0)
Em seguida, você poderá ler o valor do pino A0 usando:
adc.read()
O pino analógico pode ser usado para ler, por exemplo, um valor variável obtido de um potenciômetro como um divisor de tensão. Isso pode ser traduzido como uma saída para controlar o brilho do LED ou mover um servo para uma posição específica.
Tente você desenvolver um código para isto, baseado no que aprendemos até agora.
Outro exemplo útil é capturar dados de um sensor analógico, como temperatura (LM35), radiação ultravioleta (UV) (conforme mostrado neste tutorial ) ou luminosidade usando um LDR (“Resistor variável por luz”).
Um LDR diminui sua resistência quando a luminosidade aumenta. Assim, você poderá criar um divisor de tensão com um LDR e um resistor, conforme mostrado no diagrama:
Lendo o valor da tensão (analógica) diretamente sobre o resistor, obteremos um sinal diretamente proporcional à luminosidade. Deixe o sensor exposto à luz e leia o valor do ADC. Cubra o sensor e você deverá obter um valor menor.
No meu caso, obtive:
- Luz Máxima ==> valor adc > 850
- Luz mínima ==> valor adc < 40
8. Controlando dispositivos 1-Wire
O barramento do tipo 1-Wire é um barramento serial que usa apenas um único fio para comunicação (além de fios para terra e energia, claro). O sensor de temperatura DS18B20 é um dispositivo do tipo 1-Wire muito popular e aqui mostramos como usar o módulo “onewire” para ler tal dispositivo.
Para o código a seguir funcionar, você precisará ter pelo menos um sensor de temperatura DS18B20 com sua linha de dados conectada ao GPIO 2 (D4).
Você também deverá alimentar o sensor e conectar um resistor de 4.7k Ohm entre seu pino de dados e o pino de alimentação, conforme mostrado no diagrama.
Importe as bibliotecas:
import onewire, ds18x20
Defina em qual pino o dispositivo 1-Wire estará conectado. Em nosso caso ==> pino 2 (D4)
dat = Pin(2)
Crie o objeto onewire:
ds = ds18x20.DS18X20(onewire.OneWire(dat))
Procure dispositivos no barramento. Lembre-se que você pode ter vários dispositivos conectados no mesmo barramento.
sensors = ds.scan()
print('found devices:', sensors)
“sensors” é uma matriz com o endereço de todos os sensores 1-Wire conectados. Nós usaremos “sensors [0]” para apontar para o nosso sensor.
Note que você deverá executar a função convert_temp () sempre que iniciar uma leitura de temperatura e então esperar pelo menos 750ms antes de ler o valor (não se esqueça de importar a biblioteca de tempo, “time”).
Para ler o valor use: ds.read_temp (sensors [0]):
ds.convert_temp()
time.sleep_ms(750)
print(ds.read_temp(sensors[0]))
9. Leitura de temperatura e umidade, usando o sensor DHT
Os sensores DHT (Digital Humidity & Temperature) são sensores digitais de baixo custo com sensores de umidade capacitivos e termistores para medir o ar ambiente. Eles possuem integrado um chip que lida com conversão analógica para digital e fornece uma interface digital utilizando apenas um único fio de dados. Sensores mais novos também fornecem uma interface I2C.
Os sensores DHT11 (azul) e o DHT22 (branco) fornecem a mesma interface digital, no entanto, o DHT22 requer um objeto separado, pois tem um cálculo mais complexo. DHT22 tem resolução de uma casa decimal para leituras de umidade e temperatura. Já o DHT11 tem como resolução números inteiros para ambos. Um protocolo personalizado é utilizado para obter as medições do sensor.
Conecte o DHT22 conforme mostrado no diagrama. O pino de dados será conectado ao pino 12 do NodeMCU (D6).
Para usar a interface DHT, construa o objeto referente ao seu pino de dados. Comece chamando a biblioteca:
from dht import DHT22
Defina o pino apropriado e construa o objeto:
data = DHT22(Pin(12))
Obter os valores de temperatura e umidade:
data.measure()
temp = data.temperature()
hum = data.humidity()
print('Temp: {}oC'.format(temp))
print('Hum: {}%'.format(hum))
O DHT11 pode ser chamado não mais que uma vez por segundo e o DHT22 uma vez a cada dois segundos para resultados mais precisos. A precisão do sensor degrada com o tempo. Cada sensor suporta um intervalo de operação diferente. Consulte as folhas de dados do fabricante para maiores detalhes.
Os sensores DHT22 agora são vendidos com o nome AM2302 e são idênticos.
10. I2C – Usando um display OLED
O I2C é um protocolo de dois fios para comunicação entre dispositivos. No nível físico, consiste em 2 fios:
- SCL e SDA, o relógio (“Clock”) e a linha de dados, respectivamente.
Objetos I2C são criados anexados a um barramento específico. Eles podem ser inicializados quando criados ou inicializados posteriormente.
Primeiro, vamos importar a biblioteca:
from machine import I2C
Considerando um dispositivo conectado no Pino 4 (SDA) e no Pino 5 (SCL), vamos criar um objeto i2c:
i2c = I2C(scl=Pin(5), sda=Pin(4))
Agora, você deve verificar o barramento I2C para verificar eventuais dispositivos instalados no barramento. A função abaixo fará isso, retornando o número de dispositivos conectados, bem como seu endereço:
def scanI2c():
print('Scan i2c bus...')
devices = i2c.scan()
if len(devices) == 0:
print("No i2c device !")
else:
print('i2c devices found:',len(devices))
for device in devices:
print("Decimal address: ",device," | Hexa address: ",hex(device))
Instalemos um display OLED I2C em nosso NodeMCU, como mostrado no diagrama. O display é o SSD 1306 (128 x 64).
Executando a função de varredura:
scanI2c()
obteremos como resultado que 1 dispositivo foi encontrado no endereço 0x3c.
Este endereço será usado para a criação do objeto oled como mostrado abaixo:
import ssd1306
i2c = I2C(scl=Pin(5), sda=Pin(4))
oled = ssd1306.SSD1306_I2C(128, 64, i2c, 0x3c)
Alguns métodos para gerenciar a exibição:
poweroff(), turns off the screen. Convenient for battery operation.
contrast(), to adjust the contrast
invert(), invert the colors of the screen (finally white and black!)
show(), to refresh the view
fill(), to fill the screen in black (1) or white (0)
pixel(), to turn on a particular pixel
scroll(), scroll the screen.
text(), to display on text at the indicated x, y position
Draw lines hline(), vline() or any line line()
Draw a rect rect rectangle() or rectangle filled fill_rect()
Testar nossa display:
oled.fill(0)
oled.text("Hello esp8266", 0, 0)
oled.show()
E agora, exibamos os dados do sensor DHT22 no OLED:
data.measure()
temp = data.temperature()
hum = data.humidity()
oled.fill(0)
oled.text("Temp: " + str(temp) + "oC", 0, 0)
oled.text("Hum: " + str(hum) + "%",0, 16)
oled.show()
11. Indo mais longe
Este artigo fornece os componentes para você construir um projeto mais robusto, utilizando o MicroPython como linguagem de programação e o Jupyter Notebook como ferramenta para um rápido desenvolvimento, teste e análises.
Claro, que se você quiser rodar um programa escrito em MicroPython em um NodeMCU independente de seu PC (e do Jupyter), você deverá salvar seu código como um arquivo “main.py” por exemplo, usando um editor de texto qualquer e depois baixá-lo em seu dispositivo utilizando o “ Ampy ”, que é um utilitário desenvolvido pela Adafruit, para interagir com uma placa MicroPython através de uma conexão serial.
O Ampy é uma ferramenta de linha de comando simples para manipular arquivos e executar código em uma placa MicroPython por meio de sua conexão serial. Com o Ampy você poderá enviar arquivos desde seu computador para o sistema de arquivos da placa MicroPython, baixar arquivos de uma placa para o seu computador e até mesmo enviar um script Python a ser executado para uma placa .
Instalação:
sudo pip3 install adafruit-ampy
Ampy fi criado para interagir com uma placa MicroPython por meio de sua conexão serial. Você precisará ter sua placa conectada e de todos os drivers para acessá-la. Logo, por exemplo, para listar os arquivos instalados no NodeMCU, execute um comando como:
ampy --port /dev/tty.SLAB_USBtoUART ls
Por conveniência, você pode definir uma variável de ambiente AMPY_PORT que será usada caso o parâmetro “port” não for especificado. Por exemplo, no Linux ou OSX:
export AMPY_PORT=/dev/tty.SLAB_USBtoUART
Então, a partir de agora, você pode simplificar os comandos:
Listar arquivos internos do NodeMCU:
ampy ls
Você poderá ler um arquivo instalado em seu NodeMCU utilzando:
ampy get boot.py
Depois de criar um arquivo utilizando seu editor de texto (nano por exemplo), você poderá instalá-lo em seu NodeMCU usando:
ampy put main.py
Agora, quando você pressionar o botão “Reset” em seu NodeMcu, o programa que será executado primeiro será o “main.py”.
Para o Windows, consulte as instruções da Adafruit aqui .
12. Conclusão
Como sempre, espero que este projeto possa ajudar a outros a encontrarem seu caminho no emocionante mundo da eletrônica!
Para detalhes e código final, por favor visite o meu depositário do GitHub:
Saludos desde el sur del mundo!
Vejo vocês em meu próximo post!
Obrigado,
Marcelo
Como faço para gravar o arquivo no nodecmu, para quando ele reinicializar ele executar o codigo?
CurtirCurtir