{ "cells": [ { "cell_type": "markdown", "id": "3500ad92-1a3e-4448-86af-30a81ce8edc0", "metadata": {}, "source": [ "# G: MicroPython\n", "\n", "Un microcontrolador es un circuito integrado programable. Normalmente incluye una unidad central de procesamiento (CPU), memoria y periféricos de entrada/salida ([Wikipedia](https://es.wikipedia.org/wiki/Microcontrolador)).\n", "\n", "En los últimos años ha habido una revolución en el campo de los microcontroladores tras la aparición de la iniciativa [Arduino](https://www.arduino.cc/), que es una plataforma electrónica de código abierto basada en *software* y *hardware* fáciles de usar. A partir de dicha iniciativa (de la cual han salido múltiples clones muy baratos) el público en general ha comenzado a adoptar los microcontroladores para crear sus propios dispositivos. Quizás el ejemplo más paradigmático de dicha adocpción fue la aparición de las impresoras 3D caseras, que basan mucho de su funcionamiento en este tipo de microcontroladores.\n", "\n", "Además de Arduino hay otra serie de microcontroladores, como por ejemplo los basado en los chips [ESP32](https://es.wikipedia.org/wiki/ESP32) que son en líneas generales más potentes: procesador más rápido, más memoria, conectividad WiFi y Bluetooth integrada... ([ESP32 vs. Arduino: The Differences](https://all3dp.com/2/esp32-vs-arduino-differences/)).\n", "\n", "[MicroPython](https://micropython.org/) es una implementación de Python 3 y algunas de sus bibliotecas estándar más básicas diseñado para trabajar sobre microcontroladores. De hecho los creadores de MicroPython han desarrollado su propia familia de microcontroladores ([pyboard](https://store.micropython.org/pyb-features)). Sin embargo, MicroPython puede instalarse y usarse en otros microcontroladores. En este *notebook* hablaremos sobre su instalación y uso más básico en la familia de microcontroladores ESP32 (en realidad la mayoría de lo que aquí se cuente es perfectamente válido para otros microcontroladores compatibles con MicroPython)." ] }, { "cell_type": "markdown", "id": "df7b5e9d-25f5-44a1-93c8-5f6e78ac8706", "metadata": {}, "source": [ "## Microcontroladores ESP32\n", "\n", "Esta familia de microcontroladores tiene un aspecto similar al siguiente:\n", "\n", "![](ficherosAuxiliares/ESP32.jpg)\n", "\n", "Como se puede ver en la imagen vienen con un puerto USB con el que conectarlos al ordenador y/o alimentarlo (puede funcionar sin conexión a ordenador). Además tiene multitud de pines con diversos usos, aunque la mayoría son usados para poder conectar diversos sensores y/o actuadores. Dichos sensores son muy baratos y permiten hacer mediciones de gran cantidad de variables:\n", "\n", "+ Temperatura\n", "+ Humedad\n", "+ Fines de carrera\n", "+ Presencia de gases o particulas en el aire\n", "+ Luz\n", "+ Presión\n", "+ Presencia\n", "+ ...\n", "\n", "Los actuadores que se pueden acoplar también tienen una gran heterogeneidad:\n", "\n", "+ Luces (leds)\n", "+ Relés (u optoacopladores)\n", "+ Motores de corriente continua\n", "+ Motores paso a paso\n", "+ Servomotores\n", "+ Buzzers\n", "+ ...\n", "\n", "Además estos microcontroladores traen de serie *Bluetooth* y *WiFi* (con un alcance limitado, todo hay que decirlo), con lo que se amplian mucho las posibilidad de conectividad." ] }, { "cell_type": "markdown", "id": "85535eb8-b748-4e3a-91e5-1735d258e80b", "metadata": {}, "source": [ "## Instalando MicroPython en el ESP32\n", "\n", "Estas instrucciones están basadas en [esta página](https://micropython.org/download/esp32/). \n", "\n", "Para instalar MicroPython en el microcontrolador vamos a hacer uso de la herramienta `esptool`. Para instalarla:\n", "\n", "```\n", "> conda install -c conda-forge esptool\n", "```\n", "\n", "Se trata de un script Python capaz de conectarse a las placas, formatear la memoria de la misma y volcar en ellas el *firmware* de MicroPython. Conectaremos nuestro microcontrolador a nuestro ordenador a través de un cable USB y comprobaremos en que ruta se ha desplegado (en Linux típicamente en `/dev/ttyXXXX`. En lo que sigue asumiremos que la placa aparece en `/dev/ttyUSB0`.\n", "\n", "Para poder instalar el firmware, primero tenemos que borrar la memoria *flash* de la placa:\n", "\n", "```\n", "> esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash\n", "```\n", "\n", "A continuación bajaremos el *firmware* que vamos a [descargar](https://micropython.org/download/esp32/) la última versión de dicho *firmware*. En el momento de la escritura de este tutorial dicha versión es la `1.18` ([esp32-20220117-v1.18.bin](https://micropython.org/resources/firmware/esp32-20220117-v1.18.bin)). Una vez descargado lo cargaremos dentro de la memoria del microcontrolador:\n", "\n", "```\n", "> esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20220117-v1.18.bin\n", "```\n", "\n", "¡Ya tendremos la placa preparada para ejecutar código Python!" ] }, { "cell_type": "markdown", "id": "1d6bc046-5645-4e0f-ae5f-06bd740ff4c7", "metadata": {}, "source": [ "## Ejecutando órdenes en el microcontrolador (accediendo a la consola *REPL*)\n", "\n", "Estas instrucciones están basadas en [esta página](https://docs.micropython.org/en/latest/esp8266/tutorial/repl.html). \n", "\n", "La manera más básica para ejecutar código Python en nuestro microcontrolador es accediendo a la consola *REPL* (*Read Evaluate Print Loop*). Para conectar a dicha consola tenemos que usar algún programa terminal que permita conectar a un puerto serie de nuestro ordenador. En Linux por ejemplo podemos usar `picocom`. En Windows se puede usar [TeraTerm](https://ttssh2.osdn.jp/index.html.en) y en Mac se puede usar el comando `screen`. Para instalar `picocom` en distribuciones de Linux basadas en Debian: \n", "\n", "```\n", "> sudo apt install picocom\n", "```\n", "\n", "Y para conectarnos con ella al microcontrolador:\n", "\n", "```\n", "> picocom /dev/ttyUSB0 -b115200\n", "```\n", "\n", "¡Ya estamos dentro de la placa!. Podemos ejecutar cualquier instrucción Python y la placa ejecutará dicho código y nos mostrará los resultados. Por ejemplo:\n", "\n", "```python\n", ">>> print(\"ProAm!\")\n", "ProAm!\n", "```\n", "\n", "```python\n", ">>> for i in range(1, 6):\n", "... print(i)\n", "1\n", "2\n", "3\n", "4\n", "5\n", "```" ] }, { "cell_type": "markdown", "id": "a449a858-02cf-4b04-a35a-cc6d766f9257", "metadata": {}, "source": [ "## Interaccionando con los pines del microcontrolador\n", "\n", "Vamos a hacer un circuito muy sencillo que nos permita encender y apagar un led. Para ello vamos a conectar dicho led siguiendo este esquema:\n", "\n", "![](ficherosAuxiliares/ESP32_led.jpg)\n", "\n", "Básicamente vamos a conectar la patilla positiva del led (¡la patilla larga!) a uno de los pines `GPIO` (pines de entrada/salida), el 5 en la figura. La otra patilla negativa (la corta) la pondremos en serie con una resistencia de $220 \\Omega$ hasta el pin de tierra (*ground* o `GND`). En nuestro caso nuestro circuito real tiene esta pinta (en este ejemplo hemos conectado el led a otro pin `GPIO`, el `23`):\n", "\n", "![](ficherosAuxiliares/ESP32_led_real.jpg)\n", "\n", "Pues bien, para encender dicho led solo tenemos que ejecutar el siguiente código:\n", "\n", "```python\n", ">>> import machine # Módulo para tener acceso a los pines\n", ">>> import time # Funciones básicas de control de tiempo\n", "\n", ">>> pin = machine.Pin(23, machine.Pin.OUT) # Vamos a usar el pin para \"salida\" (encenderlo o apagarlo). Ajustar el número a donde has conectado el led\n", "\n", ">>> pin.on() # Encendemos el pin\n", "```\n", "\n", "Y para apagarlo:\n", "\n", "```python\n", ">>> pin.off()\n", "```\n", "\n", "Y si queremos hacer una secuencia de 10 encendidos / apagados:\n", "\n", "```python\n", ">>> def toggle(p):\n", "... p.value(not p.value())\n", "\n", ">>> for i in range(20):\n", "... toggle(p)\n", "... time.sleep_ms(500) # Esperamos 500 ms\n", "```\n", "\n", "Por cierto, para salir de `picocom` hay que pulsar `Ctrl+a+q` (pulsar `Ctrl` y sin soltar pulsar `a` y después `q`).\n", "\n", "Las placas de MicroPython permiten conectar con una consola de tipo *REPL* pero mediante un interfaz web y a través del módulo *WiFi*, con lo que no habría ni que conectarlas al ordenador físicamente para poder interaccionar con ellas. Puedes encontrar un poco más de información al respecto en [Access WebREPL](https://learn.adafruit.com/micropython-basics-esp8266-webrepl/access-webrepl)." ] }, { "cell_type": "markdown", "id": "8a1d6c92-28ba-4709-8afa-1e5fbb2e5713", "metadata": {}, "source": [ "## Accediendo al sistema de archivos\n", "\n", "Para esta sección nos hemos basado en [esta página](https://www.digikey.com/en/maker/projects/micropython-basics-load-files-run-code/fb1fcedaf11e4547943abfdd8ad825ce)\n", "\n", "Las placas que preparemos con micropython tienen un sistema de archivos simple al cual podemos subir los ficheros que necesitemos. Para acceder a dicho sistema de ficheros, mandar o recuperar ficheros del mismo así como ejecutar programas que tengamos escritos en nuestro ordenador vamos a hacer uso del programa `Ampy` ([Adafruit MicroPython tool](https://github.com/scientifichackers/ampy)). Podemos instalarlo con:\n", "\n", "```\n", "> pip3 install adafruit-ampy\n", "```\n", "\n", "Podemos consultar los ficheros que tiene nuestro microcontrolador:\n", "\n", "```\n", "> ampy --port /dev/ttyUSB0 ls\n", "```\n", "\n", "Podemos traernos algun fichero que esté en el microcontrolador a nuestro ordenador:\n", "\n", "```\n", "> ampy --port /dev/ttyUSB0 get boot.py boot.py\n", "```\n", "\n", "El primer `boot.py` es el fichero en el microcontrolador. El segundo `boot.py` es el nombre del fichero que se guardará en nuestro ordenador.\n", "\n", "También podemos mandar un fichero `main.py` ([enlace](ficherosAuxiliares/main.py)) a la placa con el siguiente comando:\n", "\n", "```\n", "> ampy --port /dev/ttyUSB0 put main.py\n", "```\n", "\n", "El contenido de dicho fichero es casi idéntico al ejemplo que ejecutamos antes en la consola *REPL*:\n", "\n", "```python\n", "import machine\n", "import time\n", "\n", "pin = machine.Pin(5, machine.Pin.OUT)\n", "\n", "pin.on()\n", "\n", "def toggle(p):\n", " p.value(not p.value())\n", "\n", "for i in range(100):\n", " toggle(p)\n", " time.sleep_ms(500)\n", "\n", "```\n", "\n", "Si reseteamos la placa se ejecutará directamente ese programa. De hecho, cuando la placa se inicializa primero ejecuta el fichero `boot.py` y a continuación se ejecuta `main.py`.\n", "\n", "Para borrar un fichero del sistema de ficheros del microcontrolador:\n", "\n", "```\n", "> ampy --port /dev/ttyUSB0 rm main.py\n", "```" ] }, { "cell_type": "markdown", "id": "fc0d5cca-06eb-466c-9300-4e4f44049057", "metadata": {}, "source": [ "## Moviendo un motor paso a paso\n", "\n", "Los motores paso a paso (*stepper*) es un motor de corriente continua en el que se puede controlar la rotación del mismo en un determinado número de pasos por cada rotación. Puedes encontrar más información sobre estos motores en [Motor paso a paso – tipos y ejemplos del uso de motores paso a paso](https://www.tme.eu/es/news/library-articles/page/41861/Motor-paso-a-paso-tipos-y-ejemplos-del-uso-de-motores-paso-a-paso/).\n", "\n", "En este ejemplo vamos a conectar un motor paso a paso haciendo uso de una controladora de motor (*driver*) similar a las famosas [A4988](https://www.diarioelectronicohoy.com/blog/descripcion-del-driver-a4988). Dichas controladoras simplifican el control de estos motores ya que usualmente solo necesitamos conectar 3 pines:\n", "\n", "+ `EN` (*enable*): Para activar el driver\n", "+ `DIR`: Para indicar la dirección de giro (sentido de las agujas del reloj (*CW*) o sentido contrario de las agujas del reloj (*CCW*)\n", "+ `STEP`: Para indicar el momento en el que debe darse el paso\n", "\n", "Además hay que conectar los pines de `GND` y `5V` (o `3.3V`, dependiendo del voltaje al que funcione tu microcontrolador) y una fuente de alimentación externa (típicamente de `12V`) ya que los motores paso a paso usualmente requieren una intensidad de corriente mucho mayor que la que proporciona un microcontrolador. Haremos un ciruito como el de la foto:\n", "\n", "![](ficherosAuxiliares/ESP32_motor_real.jpg)\n", "\n", "Para este ejemplo hemos conectado los siguientes pines:\n", "\n", "| `ESP32` | `Driver` |\n", "|:---------:|:--------:|\n", "| `GPIO 5` | `STEP` |\n", "| `GPIO 19` | `DIR` |\n", "| `GND` | `EN` |\n", "| `GND` | `GND` |\n", "| `3.3V` | `Vin` |\n", "\n", "Y podemos proceder a programar la placa. Haremos uso de una biblioteca que se llama `AccelStepper` ([repositorio](https://github.com/pedromneto97/AccelStepper-MicroPython)). Pero ojo, el código del repositorio tiene un bug. Nosotros lo hemos corregido. Puedes bajar el fichero necesario de: [AccelStepper.py](ficherosAuxiliares/AccelStepper.py). Lo mandamos a la placa para poder usarlo en nuestro programa:\n", "\n", "```\n", "> ampy --port /dev/ttyUSB0 put AccelStepper.py\n", "```\n", "\n", "El programa que vamos a ejecutar en nuestro microcontrolador es el siguiente [mueveMotor.py](ficherosAuxiliares/mueveMotor.py):\n", "\n", "```python\n", "from accelstepper import AccelStepper # Importamos la biblioteca\n", "\n", "motor = AccelStepper(1, 5, 19, 0, 0, 1) # Creamos un objeto de tipo AccelStepper. El primer parámetro es para indicar que usamos un driver de tipo DIR STEP. El segundo parámetro es el pin de STEP y el tercero el de STEP. Los siguientes parámetros no sirven, pero hay que ponerlos.\n", "\n", "motor.set_acceleration(200) # Indicamos la aceleración ( pasos / s^2 )\n", "motor.set_max_speed(400) # Indicamos la velocidad máxima (pasos por segundo)\n", "\n", "for i in range(10):\n", "\n", " if i % 2 == 0:\n", " motor.move_to(1600) # Nos movemos al paso 1600 (una vuelta son 1600 pasos según nuestro driver y motor)\n", " else:\n", " motor.move_to(0) \n", "\n", " while motor.distance_to_go() != 0: # Mientras queden pasos por dar\n", " motor.run() # Esta función hay que llamarla lo más rápido posible. Es la que calcula si hay que mandar o no un paso al motor de acuerdo a la velocidad actual\n", "```\n", "\n", "Para ejecutar el programa podemos usar el comando `run` de `ampy`:\n", "\n", "```\n", "> ampy --port /dev/ttyUSB0 run mueveMotor.py\n", "```\n", "\n", "El motor debe dar 10 vueltas alternando la dirección entre vuelta y vuelta." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.13" } }, "nbformat": 4, "nbformat_minor": 5 }