Cet article ne se veut pas un guide exhaustif, mais plutôt un recueil de sources de matériel et de recommandations. Dans l'article, je souhaite aborder les problèmes auxquels j'ai dû faire face lors du choix des outils logiciels pour le développement de projets, ainsi que certains cas d'application pratique des modules ESP32. Dans le prochain article, je souhaite montrer un exemple illustratif d'utilisation de l'ESP32 comme contrôleur de contrôle pour une petite plate-forme mobile à deux roues. Par conséquent, nous examinerons ici des détails tels que:
- Choisir un environnement de développement;
- Mise en place de l'environnement de travail, compilation et chargement du projet ESP-IDF;
- Traitement du signal d'entrée / sortie GPIO;
- Modulation de largeur d'impulsion Ă l'aide du module MCPWM;
- Compteur matériel PCNT;
- Connexion Ă WI-Fi et MQTT.
Présentation du module ESP32-WROOM-32E
Selon la fiche technique, le module contient:
MCU
- ESP32-D0WD-V3 intégré, microprocesseur Xtensa bicœur 32 bits LX6, jusqu'à 240 MHz
- 448 Ko ROM pour le démarrage et les fonctions principales
- 520 KB SRAM pour les données et les instructions
- 16 Ko de SRAM dans RTC
Wifi
- 802.11b / g / n
- DĂ©bit binaire: 802.11n jusqu'Ă 150 Mbps
- Agrégation A-MPDU et A-MSDU
- Prise en charge de l'intervalle de garde de 0,4 µs
- Plage de fréquence centrale du canal d'exploitation: 2412 ~ 2484 MHz
Bluetooth
- Spécifications Bluetooth V4.2 BR / EDR et Bluetooth LE
- Émetteur de classe 1, classe 2 et classe 3
- AFH
- CVSD et SBC
Matériel
- Interfaces: SD card, UART, SPI, SDIO, I 2 C, LED PWM, Motor PWM, I 2 S, IR, pulse counter, GPIO, capacitive touch sensor, ADC, DAC
- 40 MHz crystal oscillator
- 4 MB SPI flash
- Operating voltage/Power supply: 3.0 ~ 3.6 V
- Operating temperature range: –40 ~ 85 °C
- Dimensions: See Table 1
Certification
- Bluetooth certification: BQB
- RF certification: FCC/CE-RED/SRRC
- Green certification: REACH/RoHS
Schéma fonctionnel fonctionnel
Plus de détails sur les fonctionnalités du microcontrôleur peuvent être trouvés sur Wikipedia .
Le module est basé sur le microcircuit ESP32-D0WD-V3 *. La puce intégrée est conçue dans un souci d'évolutivité et d'adaptabilité. L'unité centrale de traitement contient deux cœurs qui peuvent être contrôlés individuellement, et la vitesse d'horloge du processeur est réglable de 80 MHz à 240 MHz. La puce dispose également d'un coprocesseur de faible puissance qui peut être utilisé à la place du processeur pour économiser de l'énergie lors de l'exécution de tâches qui ne nécessitent pas beaucoup de puissance de calcul, telles que la surveillance de l'état des broches. ESP32 intègre un riche ensemble de périphériques allant de capteurs tactiles capacitifs, capteurs à effet Hall, interface de carte SD, Ethernet, SPI haute vitesse, UART, I²S et I²C.
La documentation technique est présentée sur la ressource officielle .
Des informations sur le brochage du module ESP-WROOM-32 peuvent être facilement trouvées sur les espaces ouverts du réseau, comme ici
Choisir un environnement de développement
IDE Arduino
Les microcontrôleurs de la famille AVR, puis de la plateforme Arduino, sont apparus bien avant l'ESP32. L'une des principales caractéristiques de l'Arduino est sa barrière à l'entrée relativement faible, permettant à presque tout le monde de créer quelque chose rapidement et facilement. La plate-forme a apporté une contribution importante à la communauté du matériel open source et a permis à un grand nombre de radioamateurs de la rejoindre. L'IDE Arduino est téléchargeable gratuitement hors site . Malgré les limites évidentes par rapport à un environnement de développement professionnel, l'IDE Arduino couvre 90% de ce qui est requis pour les projets de loisirs. Il existe également un nombre suffisant d'articles sur le réseau sur le thème de l'installation et de la configuration de l'IDE Arduino pour la programmation des modules ESP32, par exemple: Arduino core pour l'ESP32 , habr.com, voltiq.ru et randomnerdtutorials.com .
Lors de la programmation de l'ESP32 dans l'environnement Arduino, vous devez prendre en compte le brochage comme indiqué sur la page arduino-esp32 .
Brochage du module ESP32
Le principal avantage de cette approche de développement est la saisie rapide et la facilité de création de projets utilisant les mêmes principes que pour Arduino. Et aussi l'utilisation de nombreuses bibliothèques, comme pour Arduino. Une autre fonctionnalité intéressante est la possibilité de combiner les bibliothèques Arduino et les principes de conception avec le cadre original ESP-IDF.
PlatformIO
Comme indiqué sur la ressource officielle : «Cross-platform PlatformIO IDE et Unified Debugger · Analyseur de code statique et test d'unité à distance. Système de construction multi-plateforme et multi-architecture · Explorateur de fichiers de micrologiciel et inspection de la mémoire »En d'autres termes, PlatformIO est un écosystème pour le développement de périphériques embarqués prenant en charge plusieurs plates-formes, notamment Arduino et ESP32. L'IDE est Visual Studio Code ou Atom. L'installation et la configuration sont assez simples - après avoir installé l'éditeur de code, sélectionnez PlatformIO dans la liste des plugins et installez. Encore une fois, il y a beaucoup de matériel sur ce sujet sur le net, à partir de la source officielle ici et ici , et en continuant avec des articles avec des illustrations détaillées ici et ici....
Par rapport à l'IDE Arduino, PlatformIO possède toutes les qualités d'un environnement de développement moderne: organisation de projet, prise en charge des plug-ins, complétion de code, et bien plus encore.
Une fonctionnalité de développement sur PlatformIO est une structure de projet unifiée pour toutes les plates-formes
project_dir
├── lib
│ └── README
├── platformio.ini
└── src
└── main.cpp
Chaque projet PlatformIO contient un fichier de configuration nommé platformio.ini à la racine du projet. platformio.ini a des sections (chacune désignée par un [titre]) et des paires clé / valeur dans les sections. Lignes commençant par un caractère point-point-virgule ";" sont ignorés et peuvent être utilisés pour les commentaires. Plusieurs paramètres de valeur peuvent être spécifiés de deux manières:
- séparer la valeur par "," (virgule + espace);
- format multiligne, oĂą chaque nouvelle ligne commence par au moins deux espaces.
La prochaine fonctionnalité de développement pour ESP32 est la possibilité de choisir un framework: Arduino ou ESP-IDF. En choisissant Arduino comme cadre, nous obtenons les avantages de développement décrits précédemment.
PlatformIO comprend des outils pratiques pour créer, télécharger et déboguer des projets
Cadre de développement Espressif IoT
Pour ESP32, Espressif a développé un framework appelé IoT Development Framework connu sous le nom de «ESP-IDF». Il peut être trouvé sur Github . Le projet contient une très bonne documentation et est fourni avec des exemples que vous pouvez prendre comme base. La configuration et la configuration de l'environnement sont bien documentées dans la section Premiers pas . Il existe plusieurs options pour installer et travailler avec le framework.
Cloner un projet à partir du référentiel et installer manuellement les utilitaires.
Cloner un projet depuis Github
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
Pour Windows, l'installation des utilitaires de développement est possible à l'aide du programme d' installation ou à l'aide de scripts pour la ligne de commande:
cd %userprofile%\esp\esp-idf
install.bat
Pour PowerShell
cd ~/esp/esp-idf
./install.ps1
Pour Linux et macOS
cd ~/esp/esp-idf
./install.sh
L'étape suivante consiste à configurer les variables d'environnement . Si les outils de développement ont été installés sur Windows à l'aide du programme d'installation, un raccourci vers la console de commande est ajouté au menu et au bureau, après quoi vous pouvez ouvrir le shell de commande et travailler avec des projets. Sinon, pour exécuter un shell de commande Windows:
%userprofile%\esp\esp-idf\export.bat
ou Windows PowerShell:
.$HOME/esp/esp-idf/export.ps1
Linux et macOS:
. $HOME/esp/esp-idf/export.sh
Vous devez faire attention Ă l'espace entre le point et le chemin du script.
Plus loin dans le guide, il est recommandé d'ajouter un alias au script pour définir les variables d'environnement dans le profil utilisateur si vous travaillez sous Linux ou macOS. Pour ce faire, copiez et collez la commande suivante dans votre profil shell (.profile, .bashrc, .zprofile, etc.):
alias get_idf='. $HOME/esp/esp-idf/export.sh'
En appelant la commande get_idf dans la console, les variables d'environnement requises sont exportées. Dans mon cas, il était également nécessaire d'enregistrer un alias pour démarrer l'environnement virtuel python
alias esp_va=’source $HOME/.espressif/python_env/idf4.2_py2.7_env/bin/activate’
et ajoutez-le Ă l'alias suivant
alias get_idf='esp_ve && . $HOME/esp/esp-idf/export.sh'
Pour créer un nouveau projet à partir de zéro, vous pouvez cloner les sources depuis github.com ou copier depuis le répertoire avec des exemples esp-idf / examples / get-started / hello_world /.
Des informations sur la structure du projet, la compilation, le chargement, les utilitaires de configuration, etc. se trouvent ici .
Le projet est un répertoire avec la structure suivante:
- myProject/
- CMakeLists.txt
- sdkconfig
- components/ - component1/ - CMakeLists.txt
- Kconfig
- src1.c
- component2/ - CMakeLists.txt
- Kconfig
- src1.c
- include/ - component2.h
- main/ - CMakeLists.txt
- src1.c
- src2.c
- build/
La configuration du projet est contenue dans le fichier sdkconfig dans le répertoire racine. Pour modifier les paramètres, vous devez appeler la commande idf.py menuconfig (ou éventuellement idf.py.exe menuconfig sous Windows).
Habituellement, deux applications sont créées dans un projet - "project app" (le fichier exécutable principal, c'est-à -dire votre firmware personnalisé) et "bootloader app" (programme du bootloader du projet).
Les «composants» sont des morceaux modulaires de code autonome qui sont compilés dans des bibliothèques statiques (fichiers .a) et liés à l'application. Certains d'entre eux sont fournis par ESP-IDF lui-même, d'autres peuvent être obtenus auprès d'autres sources.
L'utilitaire de ligne de commande idf.py fournit une interface pour gérer facilement les versions de projet. Son emplacement sur Windows est% userprofile% \. Espressif \ tools \ idf-exe \ 1.0.1 \ idf.py.exe. Elle contrôle les instruments suivants:
- CMake - configure le projet Ă construire
- Générateur de projet de console: Ninja ou GNU Make)
- esptool.py - pour les modules clignotants.
Chaque projet possède un fichier CMakeLists.txt de niveau supérieur qui contient les paramètres de génération pour l'ensemble du projet. La configuration de fichier minimale comprend les lignes requises suivantes:
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)
Un projet ESP-IDF peut être considéré comme un ensemble de composants dans lequel le répertoire principal est le composant principal qui exécute le code. Par conséquent, ce répertoire contient également le fichier CMakeLists.txt. Le plus souvent, sa structure est similaire:
idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")
Où il est indiqué que le fichier source main.c doit être enregistré pour le composant, et les fichiers d'en-tête sont contenus dans le répertoire courant. Si nécessaire, vous pouvez renommer le répertoire principal en définissant EXTRA_COMPONENT_DIRS dans le projet CMakeLists.txt. Plus de détails peuvent être trouvés ici .
De plus, le répertoire contient le fichier main.c d'origine (le nom peut être n'importe quel) avec un point d'entrée - la fonction void app_main (void).
Les composants personnalisés seront créés dans le répertoire des composants. Le processus est décrit plus en détail dans la section Exigences des composants.
La connexion du module ESP32 à un ordinateur se fait dans la plupart des cas à l'aide d'un câble USB comme les cartes Arduino en raison du chargeur de démarrage existant. Le processus est décrit plus en détail ici... La seule chose nécessaire est la présence d'un pilote de convertisseur USB vers UART dans le système, qui peut être téléchargé à partir de la source donnée. Après avoir installé le pilote, vous devez déterminer le numéro de port COM dans le système pour charger le micrologiciel compilé dans le module.
Configurer le projet.
Les paramètres par défaut conviennent dans la plupart des cas. Mais pour appeler l'interface du menu de la console, vous devez aller dans le répertoire du projet et taper dans la ligne de commande:
idf.py menuconfig
Menu avec les paramètres de configuration
Après avoir appelé cette commande, le fichier sdkconfig sera créé s'il ne l'était pas auparavant ou s'il a été reconfiguré. Dans les didacticiels précédents, vous verrez des commandes make menuconfig qui sont obsolètes.
L'ajout de paramètres personnalisés au fichier sdkconfig est possible manuellement, par exemple:
#
# WiFi Settings
#
CONFIG_ESP_HOST_NAME=" "
CONFIG_ESP_WIFI_SSID=" "
CONFIG_ESP_WIFI_PASSWORD=""
Mais la méthode préférée utilise un fichier de configuration supplémentaire Kconfig.projbuild, qui doit être situé dans le répertoire avec le composant. Le contenu du fichier peut être le suivant:
# put here your custom config value
menu "Example Configuration"
config ESP_WIFI_SSID
string "Keenetic"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
endmenu
Après avoir appelé la commande idf.py menuconfig, une section supplémentaire est automatiquement ajoutée dans le fichier sdkconfig. L'appel de la commande idf.py menuconfig est également possible dans le projet PlatformIO, cependant, vous devez prendre en compte le fait que la structure du projet PlatformIO est différente de l'ESP-IDF classique, ce qui peut entraîner la régénération du fichier sdkconfig et la modification des paramètres personnalisés. Ici, les options ci-dessus sont possibles - éditer le fichier à la main, renommer temporairement le répertoire src dans main, ou configurer le fichier CMakeLists.txt
Compilation et chargement du projet.
Pour créer un projet, vous devez taper la commande
idf.py build
Cette commande compilera l'application et tous les composants ESP-IDF, puis générera le chargeur, la table de partition et les binaires de l'application.
$ idf.py build
Running cmake in directory /path/to/hello_world/build
Executing "cmake -G Ninja --warn-uninitialized /path/to/hello_world"...
Warn about uninitialized values.
-- Found Git: /usr/bin/git (found version "2.17.0")
-- Building empty aws_iot component due to configuration
-- Component names: ...
-- Component paths: ...
... (more lines of build system output)
[527/527] Generating hello-world.bin
esptool.py v2.3.1
Project build complete. To flash, run this command:
../../../components/esptool_py/esptool/esptool.py -p (PORT) -b 921600 write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x10000 build/hello-world.bin build 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin
or run 'idf.py -p PORT flash'
Il ne faut pas oublier que le processus de compilation initial d'un projet, même simple, prend du temps.Par conséquent, contrairement au framework Arduino, de nombreux modules ESP-IDF supplémentaires sont compilés. Une modification ultérieure des sources ne conduit qu'à la compilation des mêmes fichiers. Une exception est le changement de configuration.
Pour télécharger les binaires compilés (bootloader.bin, partition-table.bin et hello-world.bin) sur la carte ESP32, exécutez la commande:
idf.py -p PORT [-b BAUD] flash
où nous remplaçons PORT par ce dont nous avons besoin (COM1, / dev / ttyUSB1), et nous pouvons éventuellement modifier la vitesse de téléchargement en spécifiant les valeurs requises pour BAUD
Pour suivre le programme chargé, vous pouvez utiliser n'importe quel utilitaire de surveillance de port com, tel que HTerm , CoolTerm , ou utilisez l'utilitaire de surveillance IDF Monitor , pour le démarrer, entrez la commande:
idf.py -p PORT monitor
Plugin ESP-IDF Eclipse
La documentation pour l'installation et la configuration du plugin se trouve ici
Préréglages à utiliser:
- Java 11 et supérieur; (bien que cela fonctionne sur java 8, peut-être à cause de ces problèmes);
- Python 3.5 et supérieur;
- Eclipse 2020-06 CDT;
- Git;
- ESP-IDF 4.0 et supérieur;
Le plugin est assez bien intégré dans l'environnement de développement, automatise la part du lion des fonctionnalités. Mais, malheureusement, pas sans une mouche dans la pommade. Dans les versions Eclipse ultérieures à 2019-09, il existe toujours un bogue d'indexation des fichiers source dans les projets ESP-IDF sous Windows
, ainsi que d'autres problèmes lorsque le projet ne se construit tout simplement pas pour des raisons inconnues. Seules la fermeture du projet et le redémarrage d'Eclipse sont utiles.
Extension de code ESP-IDF Visual Studio
Et la dernière option, à mon avis, la plus intéressante est le plugin officiel pour Visual Studio Code.
Comme PlatformIO, il peut être facilement installé à partir de la section extensions. L'installation et la configuration du framework ESP-IDF dans cette extension sont présentées sous la forme d'un menu d'intégration, également décrit dans la description. Le téléchargement et l'installation de tous les composants se produisent automatiquement au cours du processus de passage par les étapes du menu. Toutes les captures d'écran du processus peuvent être citées, mais elles sont intuitives et nécessitent peu ou pas d'explications. En faveur de PlatformIO, on peut noter une boîte à outils plus pratique pour la construction, le téléchargement et le suivi d'un projet. En revanche, le plug-in ESP-IDF est contrôlé à l'aide d'un menu de commandes qui peut être appelé à l'aide de la touche F1 ou d'une combinaison de touches décrites dans le manuel.
Configuration initiale du plugin
L'avantage d'utiliser le plugin est que la structure classique du projet est respectée, il n'est pas nécessaire de bricoler en quelque sorte les paramètres (dans PlatformIO, un tel besoin se fait sentir). Il y a une nuance, si nous voulons ouvrir un projet précédemment créé en code Visual studio avec le plugin ESP-IDF, il suffit de copier le répertoire .vscode à la racine du projet, qui peut être obtenu en générant au moins une fois un projet modèle en utilisant ESP- Plug-in IDF.
Menu de commande
FreeRTOS
Selon wikipedia, FreeRTOS est un système d'exploitation multitâche en temps réel (RTOS) pour les systèmes embarqués. FreeRTOS fournit le multitâche en partageant le temps CPU entre tous les threads, ou dans la terminologie du système d'exploitation, les tâches. À mon avis, le manuel FreeRTOS le plus complet et le plus intelligible en russe est ici . Dans la langue d'origine, les manuels peuvent être étudiés à partir de la source officielle . Je ne donnerai qu'une image de l'état des tâches.
FreeRTOS a été porté sur une grande variété de plates-formes matérielles, y compris les processeurs Xtensa utilisés dans l'ESP32. Plus de détails peuvent être trouvés dans la documentation.
GPIO
GPIO ou entrée / sortie universelle est la capacité de contrôler discrètement une broche avec un signal «1» ou «0».
Comme son nom l'indique, ces broches ont deux modes de fonctionnement: entrée ou sortie. Dans le premier cas, nous lisons la valeur, dans le second, nous l'écrivons. Un autre facteur important lorsqu'il s'agit de GPIO est le niveau de tension. L'ESP32 est un appareil 3,3 V. Par conséquent, soyez prudent lorsque vous travaillez avec d'autres appareils qui ont une tension de 5 V ou plus. Il est également important de comprendre que le courant maximum qui peut être appliqué à la broche GPIO est de 12 mA. Pour utiliser les fonctions GPIO fournies par ESP-IDF, nous devons connecter l'en-tête driver / gpio.h. Vous pouvez ensuite appeler gpio_pad_select_gpio () pour spécifier la fonction de cette broche. Il existe 34 GPIO différents disponibles sur l'ESP32. Ils sont désignés comme:
- GPIO_NUM_0 - GPIO_NUM_19
- GPIO_NUM_21 - GPIO_NUM_23
- GPIO_NUM_25 - GPIO_NUM_27
- GPIO_NUM_32 - GPIO_NUM_39
La numérotation suivante n'est pas incluse dans le nombre de broches 20, 24, 28, 29, 30 et 31. Le
tableau de brochage se trouve ici .
Veuillez noter que les broches GPIO_NUM_34 - GPIO_NUM_39 - utilisent uniquement le mode d'entrée. Ils ne peuvent pas être utilisés pour la sortie de signal. De plus, les broches 6, 7, 8, 9, 10 et 11 sont utilisées pour interagir avec une carte flash externe via SPI, il n'est pas recommandé de les utiliser à d'autres fins, mais si vous le souhaitez vraiment, vous le pouvez. Le type de données gpio_num_t est une énumération avec des valeurs correspondant aux numéros de broches. Il est recommandé d'utiliser ces valeurs plutôt que des nombres. La direction des broches est définie à l'aide de la fonction gpio_set_direction (). Par exemple, pour définir une broche comme sortie:
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
Pour définir une broche comme entrée:
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_INPUT);
Si nous avons configuré GPIO en sortie, nous pouvons définir sa valeur sur 1 ou 0 en appelant gpio_set_level ().
L'exemple suivant change de GPIO une fois par seconde:
gpio_pad_select_gpio(GPIO_NUM_17);
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
while(1) {
printf("Off\n");
gpio_set_level(GPIO_NUM_17, 0);
vTaskDelay(1000 / portTICK_RATE_MS);
printf("On\n");
gpio_set_level(GPIO_NUM_17, 1);
vTaskDelay(1000 / portTICK_RATE_MS);
}
Au lieu de définir tous les attributs des broches individuelles, nous pouvons définir les propriétés d'un ou plusieurs contacts en appelant la fonction gpio_config (). Il prend une structure gpio_config_t comme entrée et définit la direction, le pull up, le pull down et les paramètres d'interruption pour toutes les broches représentées dans le masque de bits.
Par exemple:
gpio_config_t gpioConfig;
gpioConfig.pin_bit_mask = (1 << 16) | (1 << 17);
gpioConfig.mode = GPIO_MODE_OUTPUT;
gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;
gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpioConfig.intr_type = GPIO_INTR_DISABLE;
gpio_config(&gpioConfig);
Paramètres de tirage vers le haut et vers le bas
On lit généralement que la broche d'entrée GPIO est haute ou basse. Cela signifie qu'il est connecté à une source d'alimentation ou à la terre. Cependant, si la broche n'est connectée à rien, elle est alors dans un état "flottant". Il est souvent nécessaire de régler le niveau initial d'une broche non connectée sur haut ou bas. Dans ce cas, un tirage matériel (connexion utilisant des résistances) ou logiciel de la sortie est effectué, respectivement, vers + V - tirage vers le haut ou vers 0 - tirage vers le bas. Dans le SDK ESP32, nous pouvons définir un GPIO comme un pull up ou un pull down à l'aide de la fonction gpio_set_pull_mode (). Cette fonction prend comme entrée le numéro de la broche que nous voulons définir et le mode pull-up associé à cette broche.
Par exemple:
gpio_set_pull_mode (21, GPIO_PULLUP_ONLY);
Gestion des interruptions GPIO
Pour détecter une modification du signal d'entrée sur une broche, nous pouvons périodiquement interroger son état, mais ce n'est pas la meilleure solution pour plusieurs raisons. Tout d'abord, nous devons parcourir le contrôle, gaspillant du temps CPU. Deuxièmement, au moment de l'interrogation, l'état de la broche peut ne plus être pertinent en raison du retard et vous pouvez ignorer les signaux d'entrée. La solution à ces problèmes est l'interruption. Une interruption est comme une sonnette. Sans sonner, nous devrons vérifier périodiquement si quelqu'un se trouve à la porte. Dans le code source, nous pouvons définir une fonction de rappel d'interruption qui sera appelée lorsque la broche change la valeur de son signal. Nous pouvons également déterminer ce qui provoque l'appel du gestionnaire en définissant les paramètres suivants:
- DĂ©sactiver - ne provoque pas d'interruption lorsque le signal change;
- PosEdge - appelle le gestionnaire d'interruption lors du passage de faible à élevé;
- NegEdge - appelle un gestionnaire d'interruption lors du passage de haut en bas;
- AnyEdge - invoque le gestionnaire d'interruption soit lors du passage de faible à élevé, soit lors du passage de haut à bas;
Un gestionnaire d'interruption peut être marqué pour se charger dans la RAM au moment de la compilation. Par défaut, le code généré est en mémoire flash. Si vous le marquez au préalable comme IRAM_ATTR, il sera prêt pour une exécution immédiate à partir de la RAM.
void IRAM_ATTR my_gpio_isr_handle(void *arg) {...}
Ceux qui ont travaillé avec des microcontrôleurs savent que le traitement des signaux d'entrée à partir des boutons s'accompagne d'un rebond de contact. Ce qui peut être interprété comme une série de transitions, et donc une série d'événements de gestionnaire d'interruption. Pour ce faire, nous devons ajouter la gestion du rebond de contact au code. Pour ce faire, nous devons lire l'événement d'origine, attendre que les vibrations disparaissent, puis rééchantillonner l'état d'entrée.
L'exemple suivant illustre la gestion des interruptions des signaux d'entrée. Je vous recommande vivement de vous familiariser avec la gestion des files d'attente dans FreeRTOS pour une meilleure compréhension du code, si vous ne le connaissez pas déjà . L'exemple montre deux tâches:
- test1_task, qui est déverrouillé lorsqu'un événement d'interruption se produit lorsque le signal est activé sur la broche 25 et que le message "Enregistré un clic" s'affiche une fois sur la console;
- test2_task est périodiquement interrogé, et lorsque le signal sur la broche 26 est activé, le message "GPIO 26 est haut!" est émis vers la console toutes les 100 ms.
L'exemple a également un minuteur logiciel xTimer réglé, il est facultatif dans ce cas, plutôt comme un exemple de retard asynchrone.
L'anti-rebond est effectué à l'aide de la fonction timeval_durationBeforeNow , qui vérifie si la presse dure plus de 100 ms . Il existe d'autres modèles de logiciels anti-rebond, mais la signification est à peu près la même. ESP-IDF comprend également un exemple du fonctionnement de GPIO.
Traitement du signal d'entrée
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include "c_timeutils.h"
#include "freertos/timers.h"
static char tag[] = "test_intr";
static QueueHandle_t q1;
TimerHandle_t xTimer;
#define TEST_GPIO (25)
static void handler(void *args) {
gpio_num_t gpio;
gpio = TEST_GPIO;
xQueueSendToBackFromISR(q1, &gpio, NULL);
}
void test1_task(void *ignore) {
struct timeval lastPress;
ESP_LOGD(tag, ">> test1_task");
gpio_num_t gpio;
q1 = xQueueCreate(10, sizeof(gpio_num_t));
gpio_config_t gpioConfig;
gpioConfig.pin_bit_mask = GPIO_SEL_25;
gpioConfig.mode = GPIO_MODE_INPUT;
gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;
gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpioConfig.intr_type = GPIO_INTR_POSEDGE;
gpio_config(&gpioConfig);
gpio_install_isr_service(0);
gpio_isr_handler_add(TEST_GPIO, handler, NULL);
while(1) {
//ESP_LOGD(tag, "Waiting on queue");
BaseType_t rc = xQueueReceive(q1, &gpio, portMAX_DELAY);
//ESP_LOGD(tag, "Woke from queue wait: %d", rc);
struct timeval now;
gettimeofday(&now, NULL);
if (timeval_durationBeforeNow(&lastPress) > 100) {
if(gpio_get_level(GPIO_NUM_25)) {
ESP_LOGD(tag, "Registered a click");
if( xTimerStart( xTimer, 0 ) != pdPASS ) {
// The timer could not be set into the Active state.
}
}
}
lastPress = now;
}
vTaskDelete(NULL);
}
void test2_task(void *ignore) {
gpio_set_direction(GPIO_NUM_26, GPIO_MODE_INPUT);
gpio_set_pull_mode(GPIO_NUM_26, GPIO_PULLDOWN_ONLY);
while(true) {
if(gpio_get_level(GPIO_NUM_26)) {
ESP_LOGD(tag, "GPIO 26 is high!");
if( xTimerStart( xTimer, 0 ) != pdPASS ) {
// The timer could not be set into the Active state.
}
}
vTaskDelay(100/portTICK_PERIOD_MS);
}
}
void vTimerCallback( TimerHandle_t pxTimer ) {
ESP_LOGD(tag, "The timer has expired!");
}
void app_main(void)
{
xTaskCreate(test1_task, "test_task1", 5000, NULL, 8, NULL);
xTaskCreate(test2_task, "test_task2", 5000, NULL, 8, NULL);
xTimer = xTimerCreate("Timer", // Just a text name, not used by the kernel.
2000/portTICK_PERIOD_MS, // The timer period in ticks.
pdFALSE, // The timers will auto-reload themselves when they expire.
( void * ) 1, // Assign each timer a unique id equal to its array index.
vTimerCallback // Each timer calls the same callback when it expires.
);
}
PCNT (compteur d'impulsions)
Le module PCNT (Pulse Counter) est conçu pour compter le nombre de fronts montants et / ou descendants du signal d'entrée. Chaque bloc du module a un registre signé 16 bits et deux canaux qui peuvent être configurés pour augmenter ou diminuer la valeur du compteur. Chaque canal a un signal d'entrée qui capture la modification du signal, ainsi qu'une entrée de commande qui peut être utilisée pour activer ou désactiver le comptage. Les entrées ont des filtres supplémentaires qui peuvent être utilisés pour éliminer les pics de signal indésirables.
Le compteur PCNT a huit unités indépendantes, numérotées de 0 à 7. Dans l'API, elles sont spécifiées à l'aide de pcnt_unit_t. Chaque module dispose de deux canaux indépendants, numérotés 0 et 1, indiqués par pcnt_channel_t.
La configuration est fournie séparément pour chaque canal de périphérique à l'aide de pcnt_config_t et couvre:
- Numéro d'unité et numéro de canal auquel appartient cette configuration;
- Numéros GPIO d'entrée d'impulsion et d'entrée de porte;
- Deux paires de paramètres, pcnt_ctrl_mode_t et pcnt_count_mode_t, pour définir comment le compteur réagit en fonction de l'état du signal de commande et comment les fronts montants / descendants sont comptés.
- Deux valeurs limites (min / max) utilisées pour définir les points de surveillance et déclencher des interruptions lorsque le compteur d'impulsions atteint une certaine limite.
La configuration d'un canal spécifique se fait ensuite en appelant la fonction pcnt_unit_config () avec la structure de configuration pcnt_config_t ci-dessus comme paramètre d'entrée.
Pour désactiver une impulsion ou une entrée de commande dans la configuration, vous devez spécifier PCNT_PIN_NOT_USED au lieu du numéro GPIO.
Après la configuration avec pcnt_unit_config (), le compteur démarre immédiatement. La valeur du compteur accumulé peut être vérifiée en appelant pcnt_get_counter_value ().
Les fonctions suivantes vous permettent de contrĂ´ler le fonctionnement du compteur: pcnt_counter_pause (), pcnt_counter_resume () et pcnt_counter_clear ()
Il est également possible de changer dynamiquement les modes de compteur précédemment définis en utilisant pcnt_unit_config () en appelant pcnt_set_mode ().
Si vous le souhaitez, la broche d'entrée d'impulsion et la broche d'entrée de commande peuvent être modifiées à la volée en utilisant pcnt_set_pin ().
Le module PCNT a des filtres sur chacune des entrées d'impulsion et de contrôle, ajoutant la possibilité d'ignorer les pointes courtes dans les signaux. La longueur des impulsions ignorées est fournie dans les cycles d'horloge APB_CLK en appelant pcnt_set_filter_value (). Les paramètres de filtre actuels peuvent être vérifiés avec pcnt_get_filter_value (). Le cycle APB_CLK fonctionne à 80 MHz.
Le filtre est démarré / mis en pause en appelant pcnt_filter_enable () / pcnt_filter_disable ().
Les événements suivants, définis dans pcnt_evt_type_t, peuvent déclencher une interruption. L'événement se produit lorsque le compteur d'impulsions atteint certaines valeurs:
- : counter_l_lim counter_h_lim, pcnt_config_t;
- 0 1, pcnt_set_event_value ().
- = 0
Pour enregistrer, activer ou désactiver l'interruption pour les événements ci-dessus, vous devez appeler pcnt_isr_register (), pcnt_intr_enable () et pcnt_intr_disable (). Pour activer ou désactiver les événements lorsque les seuils sont atteints, vous devrez également appeler pcnt_event_enable () et pcnt_event_disable ().
Pour vérifier quels seuils sont actuellement définis, utilisez la fonction pcnt_get_event_value ().
Un exemple d'ESP-IDF est présenté ici .
J'ai utilisé un compteur PCNT pour calculer la vitesse de la roue. Pour ce faire, il est nécessaire de compter le nombre d'impulsions par tour, puis de remettre à zéro le compteur.
Exemple de code
typedef struct {
uint16_t delay; //delay im ms
int pin;
int ctrl_pin;
pcnt_channel_t channel;
pcnt_unit_t unit;
int16_t count;
} speed_sensor_params_t;
esp_err_t init_speed_sensor(speed_sensor_params_t* params) {
/* Prepare configuration for the PCNT unit */
pcnt_config_t pcnt_config;
// Set PCNT input signal and control GPIOs
pcnt_config.pulse_gpio_num = params->pin;
pcnt_config.ctrl_gpio_num = params->ctrl_pin;
pcnt_config.channel = params->channel;
pcnt_config.unit = params->unit;
// What to do on the positive / negative edge of pulse input?
pcnt_config.pos_mode = PCNT_COUNT_INC; // Count up on the positive edge
pcnt_config.neg_mode = PCNT_COUNT_DIS; // Keep the counter value on the negative edge
pcnt_config.lctrl_mode = PCNT_MODE_REVERSE; // Reverse counting direction if low
pcnt_config.hctrl_mode = PCNT_MODE_KEEP; // Keep the primary counter mode if high
pcnt_config.counter_h_lim = INT16_MAX;
pcnt_config.counter_l_lim = - INT16_MAX;
/* Initialize PCNT unit */
esp_err_t err = pcnt_unit_config(&pcnt_config);
/* Configure and enable the input filter */
pcnt_set_filter_value(params->unit, 100);
pcnt_filter_enable(params->unit);
/* Initialize PCNT's counter */
pcnt_counter_pause(params->unit);
pcnt_counter_clear(params->unit);
/* Everything is set up, now go to counting */
pcnt_counter_resume(params->unit);
return err;
}
int32_t calculateRpm(speed_sensor_params_t* params) {
pcnt_get_counter_value(params->unit, &(params->count));
int32_t rpm = 60*(1000/params->delay)*params->count/PULSE_PER_TURN;
pcnt_counter_clear(params->unit);
return rpm;
}
Modulation de largeur d'impulsion (PWM) Ă l'aide du module MCPWM
Des informations sur le module sont présentées ici
Il existe de nombreux articles sur le net sur le thème du PWM , surtout si vous recherchez en relation avec Arduino.
Wikipedia donne une définition courte et succincte - Modulation de largeur d'impulsion (PWM) - le processus de contrôle de l'alimentation en allumant et éteignant l'appareil. Le principe du contrôle PWM est de changer la largeur d'impulsion à une amplitude et une fréquence constantes du signal.
La fréquence PWM d'Arduino est de 488,28 Hz, la résolution est de 8 bits (0 ... 255) et il est possible d'utiliser six broches matérielles 3, 5, 6, 9, 10, 11. Cependant, en utilisant les paramètres de registre du microcontrôleur AVR, vous pouvez obtenir d'autres valeurs Fréquence PWM.
Le microcontrôleur ESP32 a dans son arsenal un module MCPWM séparé, ou plutôt deux modules, dont chacun a trois paires de broches PWM.De
plus, dans la documentation, les sorties d'un bloc séparé sont marquées PWMxA / PWMxB.
Un schéma fonctionnel plus détaillé du bloc MCPWM est présenté ci-dessous. Chaque paire A / B peut être synchronisée avec l'un des trois minuteries: Minuterie 0, 1 et 2. La même minuterie peut être utilisée pour synchroniser plus d'une paire de sorties PWM. Chaque unité peut également collecter des données d'entrée telles que des signaux de synchronisation, détecter des alarmes telles qu'une surintensité ou une surtension du moteur, et recevoir une rétroaction avec des signaux de capture tels que la position du rotor.
L'étendue de la configuration dépend du type de moteur, en particulier du nombre de sorties et d'entrées nécessaires et de la séquence des signaux pour la commande du moteur.
Dans notre cas, nous décrirons une configuration simple pour entraîner un moteur à courant continu à balais qui n'utilise que quelques-unes des ressources MCPWM disponibles. Un exemple de circuit est illustré ci-dessous. Il comprend un pont en H pour commuter la polarisation de la tension fournie au moteur (M) et fournir un courant suffisant pour le piloter.
La configuration comprend les Ă©tapes suivantes:
- Sélection du bloc MPWn qui sera utilisé pour piloter le moteur. Deux modules sont disponibles sur la carte ESP32 parmi ceux répertoriés dans mcpwm_unit_t.
- Initialise deux GPIO en tant que sorties sur le module sélectionné en appelant mcpwm_gpio_init (). Les deux signaux de sortie sont généralement utilisés pour entraîner le moteur vers la droite ou vers la gauche. Tous les paramètres de signal disponibles sont répertoriés dans mcpwm_io_signals_t. Pour définir plusieurs broches à la fois, utilisez la fonction mcpwm_set_pin () avec mcpwm_pin_config_t.
- Sélection de la minuterie. Il y a trois minuteries disponibles sur l'appareil. Les minuteries sont répertoriées dans mcpwm_timer_t.
- Définition de la fréquence du minuteur et du bootstrap dans la structure mcpwm_config_t.
- Appel de mcpwm_init () avec les paramètres ci-dessus.
Les méthodes de contrôle PWM sont les suivantes:
- mcpwm_set_signal_high () mcpwm_set_signal_low (). . A B .
- — , mcpwm_start () mcpwm_stop (). .
- , mcpwm_set_duty () . mcpwm_set_duty_in_us (), . mcpwm_get_duty (). , mcpwm_set_duty_type (). A B mcpwm_generator_t. . mcpwm_init (), , mcpwm_duty_type_t.
Voici un exemple de code pour un moteur Ă balais:
dans mon projet, j'ai pratiquement utilisé le code de l'exemple, en le corrigeant légèrement et en ajoutant une deuxième commande de moteur. Pour un contrôle indépendant des canaux PWM, chacun d'eux doit être configuré avec un temporisateur séparé, par exemple, MCPWM_TIMER_0 et CPWM_TIMER_1:
Exemple de code
void mcpwm_example_gpio_initialize(void)
{
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_PWM1A_OUT);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1B, GPIO_PWM1B_OUT);
//mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_SYNC_0, GPIO_SYNC0_IN);
mcpwm_config_t pwm_config;
pwm_config.frequency = 1000; //frequency = 500Hz,
pwm_config.cmpr_a = 0; //duty cycle of PWMxA = 0
pwm_config.cmpr_b = 0; //duty cycle of PWMxb = 0
pwm_config.counter_mode = MCPWM_UP_COUNTER;
pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config); //Configure PWM0A & PWM0B with above settings
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config); //Configure PWM0A & PWM0B with above settings
// deadtime (see clock source changes in mcpwm.c file)
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_BYPASS_FED, 80, 80); // 1us deadtime
mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_BYPASS_FED, 80, 80);
}
Se connecter au Wi-Fi et travailler avec MQTT
Le sujet du protocole Wi-FI est assez vaste. Une série d'articles séparés sera nécessaire pour décrire le protocole. Dans le guide officiel, consultez la section Pilote Wi-Fi . La description de l'API logicielle est ici . Des exemples de code peuvent être consultés ici Les
bibliothèques Wi-Fi prennent en charge la configuration et la surveillance des fonctions de réseau Wi-Fi ESP32. Les configurations suivantes sont disponibles:
- ( STA Wi-Fi). ESP32 .
- AP ( Soft-AP ). ESP32.
- AP-STA (ESP32 , ).
- (WPA, WPA2, WEP . .)
- ( ).
- Wi-Fi IEEE802.11.
MQTT
Vous pouvez vous familiariser avec le sujet ici ou ici . Le manuel ESP-IDF avec des exemples est ici .
Pour configurer MQTT dans le code, vous devez d'abord vous connecter à un réseau Wi-Fi. Ensuite, établissez une connexion avec le courtier. Le message est traité dans un callback dont le paramètre est l'événement esp_mqtt_event_handle_t. Si le type d'événement est MQTT_EVENT_DATA, la rubrique et les données peuvent être analysées. Vous pouvez personnaliser différents comportements suite à une connexion, une déconnexion et des abonnements à des sujets réussis.
Exemple de connexion Wi-Fi:
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
wifi_config_t sta_config = {
.sta = {
.ssid = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD,
.bssid_set = false
}
};
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config) );
ESP_LOGI(TAG, "start the WIFI SSID:[%s] password:[%s]", CONFIG_ESP_WIFI_SSID, "******");
ESP_ERROR_CHECK( esp_wifi_start() );
ESP_LOGI(TAG, "Waiting for wifi");
xEventGroupWaitBits(wifi_event_group, BIT0, false, true, portMAX_DELAY);
//MQTT init
mqtt_event_group = xEventGroupCreate();
mqtt_app_start(mqtt_event_group);
Connexion au courtier MQTT
void mqtt_app_start(EventGroupHandle_t event_group)
{
mqtt_event_group = event_group;
const esp_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipse.org:1883", //mqtt://mqtt.eclipse.org:1883
.event_handle = mqtt_event_handler,
.keepalive = 10,
.lwt_topic = "esp32/status/activ",
.lwt_msg = "0",
.lwt_retain = 1,
};
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_start(client);
Gestionnaire MQTT
esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client;
int msg_id;
command_t command;
// your_context_t *context = event.context;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
xEventGroupSetBits(mqtt_event_group, BIT1);
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
msg_id = esp_mqtt_client_subscribe(client, "esp32/car/#", 0);
msg_id = esp_mqtt_client_subscribe(client, "esp32/camera/#", 0);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, "esp32/status/activ", "1", 0, 0, 1);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
memset(topic, 0, strlen(topic));
memset(data, 0, strlen(data));
strncpy(topic, event->topic, event->topic_len);
strncpy(data, event->data, event->data_len);
command_t command = {
.topic = topic,
.message = data,
};
parseCommand(&command);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
break;
default:
break;
}
return ESP_OK;
}
Ceci conclut mon histoire sur l'utilisation du module ESP32. L'article a pris en compte des exemples sur ESP-IDF, en tant que cadre qui tire le meilleur parti des ressources du module. La programmation utilisant d'autres plates-formes telles que javaScript, MicroPython, Lua peut être trouvée sur les ressources associées. Dans le prochain article, comme déjà mentionné, je donnerai un exemple pratique d'utilisation d'un microcontrôleur, et comparerai également l'approche logicielle d'Arduino et d'ESP-IDF.