Este artigo não pretende ser um guia completo, mas uma coleção de fontes de material e recomendações. No artigo, quero abordar as questões que tive de enfrentar ao escolher ferramentas de software para desenvolvimento de projetos, bem como alguns casos de aplicação prática de módulos ESP32. No próximo artigo, quero mostrar um exemplo ilustrativo do uso do ESP32 como controlador de controle para uma pequena plataforma móvel de duas rodas. Portanto, aqui vamos considerar detalhes como:
- Escolhendo um ambiente de desenvolvimento;
- Configurando o ambiente de trabalho, compilando e carregando o projeto ESP-IDF;
- Processamento de sinal de entrada / saída GPIO;
- Modulação de largura de pulso usando o módulo MCPWM;
- Contador de hardware PCNT;
- Conexão com WI-Fi e MQTT.
Visão geral do módulo ESP32-WROOM-32E
De acordo com a ficha técnica, o módulo contém:
MCU
- ESP32-D0WD-V3 integrado, microprocessador Xtensa dual-core LX6 de 32 bits, até 240 MHz
- 448 KB de ROM para inicialização e funções básicas
- 520 KB SRAM para dados e instruções
- 16 KB SRAM em RTC
Wi-fi
- 802.11b / g / n
- Taxa de bits: 802.11n até 150 Mbps
- Agregação A-MPDU e A-MSDU
- Suporte de intervalo de guarda de 0,4 µs
- Faixa de frequência central do canal operacional: 2412 ~ 2484 MHz
Bluetooth
- Especificação Bluetooth V4.2 BR / EDR e Bluetooth LE
- Transmissor classe 1, classe 2 e classe 3
- AFH
- CVSD e SBC
Hardware
- 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
Diagrama de blocos funcionais
Mais detalhes sobre os recursos do microcontrolador podem ser encontrados na Wikipedia .
O módulo é baseado no microcircuito ESP32-D0WD-V3 *. O chip integrado foi projetado com escalabilidade e adaptabilidade em mente. A unidade de processamento central contém dois núcleos que podem ser controlados individualmente e a velocidade do clock da CPU é ajustável de 80 MHz a 240 MHz. O chip também possui um coprocessador de baixo consumo de energia que pode ser usado no lugar da CPU para economizar energia ao executar tarefas que não requerem muito poder de computação, como monitorar o status dos pinos. ESP32 integra um rico conjunto de periféricos que variam de sensores de toque capacitivos, sensores Hall, interface de cartão SD, Ethernet, SPI de alta velocidade, UART, I²S e I²C.
A documentação técnica é apresentada no recurso oficial .
Informações sobre a pinagem do módulo ESP-WROOM-32 podem ser facilmente encontradas nos espaços abertos da rede, como aqui
Escolha de um ambiente de desenvolvimento
IDE Arduino
Microcontroladores da família AVR, e depois a plataforma Arduino, surgiram muito antes do ESP32. Um dos principais recursos do Arduino é sua barreira de entrada relativamente baixa, permitindo que quase qualquer pessoa crie algo com rapidez e facilidade. A plataforma deu uma importante contribuição para a comunidade de hardware de código aberto e permitiu a adesão de um grande número de rádios amadores. O Arduino IDE pode ser baixado gratuitamente em um local externo . Apesar das limitações óbvias em comparação com um ambiente de desenvolvimento profissional, o Arduino IDE cobre 90% do que é necessário para projetos de hobby. Também há um número suficiente de artigos na rede sobre o tópico de instalação e configuração do IDE Arduino para a programação de módulos ESP32, por exemplo: núcleo do Arduino para ESP32 , habr.com, voltiq.ru e randomnerdtutorials.com .
Ao programar o ESP32 no ambiente Arduino, você precisa levar em consideração a pinagem conforme indicado na página arduino-esp32 .
Pinagem do módulo ESP32
A principal vantagem dessa abordagem de design é a entrada rápida e a facilidade de criação de projetos, usando os mesmos princípios do Arduino. E também o uso de várias bibliotecas, como para o Arduino. Outro recurso interessante é a capacidade de combinar bibliotecas Arduino e princípios de design com a estrutura ESP-IDF original.
PlatformIO
Conforme declarado no recurso oficial : “PlatformIO IDE e Unified Debugger multiplataforma · Static Code Analyzer e Remote Unit Testing. Sistema de construção multiplataforma e multi-arquitetura · Explorador de arquivos de firmware e inspeção de memória »Em outras palavras, PlatformIO é um ecossistema para desenvolvimento de dispositivos embarcados com suporte a múltiplas plataformas, incluindo Arduino e ESP32. O IDE é Visual Studio Code ou Atom. A instalação e configuração são bastante simples - após instalar o editor de código, selecione PlatformIO na lista de plug-ins e instale. Novamente, há muitos materiais sobre este tópico na rede, começando com a fonte oficial aqui e aqui , e continuando com artigos com ilustrações detalhadas aqui e aqui....
Comparado ao Arduino IDE, PlatformIO tem todas as qualidades de um ambiente de desenvolvimento moderno: organização de projetos, suporte a plug-ins, conclusão de código e muito mais.
Um recurso de desenvolvimento no PlatformIO é uma estrutura de projeto unificada para todas as plataformas
project_dir
├── lib
│ └── README
├── platformio.ini
└── src
└── main.cpp
Cada projeto PlatformIO contém um arquivo de configuração denominado platformio.ini na raiz do projeto. platformio.ini tem seções (cada uma denotada por um [título]) e pares de chave / valor dentro das seções. Linhas começando com um caractere ponto-ponto e vírgula ";" ignorado e pode ser usado para comentários. Vários parâmetros de valor podem ser especificados de duas maneiras:
- separando o valor com "," (vírgula + espaço);
- formato multilinhas, onde cada nova linha começa com pelo menos dois espaços.
O próximo recurso de desenvolvimento do ESP32 é a capacidade de escolher uma estrutura: Arduino ou ESP-IDF. Ao escolher o Arduino como estrutura, obtemos os benefícios de desenvolvimento descritos anteriormente.
PlatformIO inclui ferramentas convenientes para construção, upload e depuração de projetos
Estrutura de desenvolvimento Espressif IoT
Para ESP32, a Espressif desenvolveu uma estrutura chamada IoT Development Framework, conhecida como “ESP-IDF”. Ele pode ser encontrado no Github . O projeto contém uma documentação muito boa e é fornecido com exemplos que você pode usar como base. A configuração e a configuração do ambiente estão bem documentadas na seção Primeiros passos . Existem várias opções para instalar e trabalhar com a estrutura.
Clonar um projeto do repositório e instalar manualmente os utilitários.
Clonando um projeto do Github
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
Para Windows, a instalação de utilitários de desenvolvimento é possível usando o instalador ou usando scripts para a linha de comando:
cd %userprofile%\esp\esp-idf
install.bat
Para PowerShell
cd ~/esp/esp-idf
./install.ps1
Para Linux e macOS
cd ~/esp/esp-idf
./install.sh
A próxima etapa é configurar as variáveis de ambiente . Se as ferramentas de desenvolvimento foram instaladas no Windows usando o instalador, um atalho para o console de comando é adicionado ao menu e à área de trabalho, após o qual você pode abrir o shell de comando e trabalhar com projetos. Como alternativa, para executar um shell de comando do Windows:
%userprofile%\esp\esp-idf\export.bat
ou Windows PowerShell:
.$HOME/esp/esp-idf/export.ps1
Linux e macOS:
. $HOME/esp/esp-idf/export.sh
Você deve prestar atenção ao espaço entre o período e o caminho para o script.
Mais adiante no guia, é recomendado adicionar um alias ao script para definir as variáveis de ambiente no perfil do usuário se você trabalhar no Linux ou macOS. Para fazer isso, copie e cole o seguinte comando em seu perfil de shell (.profile, .bashrc, .zprofile, etc.):
alias get_idf='. $HOME/esp/esp-idf/export.sh'
Chamando o comando get_idf no console, as variáveis de ambiente necessárias são exportadas. No meu caso, também foi necessário registrar um alias para iniciar o ambiente virtual python
alias esp_va=’source $HOME/.espressif/python_env/idf4.2_py2.7_env/bin/activate’
e adicione-o ao próximo alias
alias get_idf='esp_ve && . $HOME/esp/esp-idf/export.sh'
Para criar um novo projeto do zero, você pode clonar os fontes de github.com ou copiar do diretório com exemplos esp-idf / examples / get-started / hello_world /.
Informações sobre a estrutura do projeto, compilação, carregamento, utilitários de configuração, etc. estão localizadas aqui .
O projeto é um diretório com a seguinte estrutura:
- 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/
A configuração do projeto está contida no arquivo sdkconfig no diretório raiz. Para alterar as configurações, você precisa chamar o comando idf.py menuconfig (ou possivelmente idf.py.exe menuconfig no Windows).
Normalmente, dois aplicativos são criados em um projeto - "app de projeto" (o arquivo executável principal, ou seja, seu firmware personalizado) e "app bootloader" (programa do carregador de boot do projeto).
"Componentes" são partes modulares de código autônomo que são compiladas em bibliotecas estáticas (arquivos .a) e vinculadas ao aplicativo. Alguns deles são fornecidos pelo próprio ESP-IDF, outros podem ser obtidos de outras fontes.
O utilitário de linha de comando idf.py fornece uma interface para gerenciar facilmente compilações de projeto. Sua localização no Windows é% userprofile% \. Espressif \ tools \ idf-exe \ 1.0.1 \ idf.py.exe. Ela controla os seguintes instrumentos:
- CMake - configura o projeto para construir
- Construtor de projetos de console: Ninja ou GNU Make)
- esptool.py - para módulos de flash.
Cada projeto tem um arquivo CMakeLists.txt de nível superior que contém configurações de construção para todo o projeto. A configuração mínima do arquivo inclui as seguintes linhas obrigatórias:
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)
Um projeto ESP-IDF pode ser pensado como uma coleção de componentes em que o diretório principal é o principal componente que executa o código. Portanto, esse diretório também contém o arquivo CMakeLists.txt. Na maioria das vezes, sua estrutura é semelhante:
idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")
Onde é indicado que o arquivo fonte main.c deve ser registrado para o componente, e os arquivos de cabeçalho estão contidos no diretório atual. Se necessário, você pode renomear o diretório principal definindo EXTRA_COMPONENT_DIRS no projeto CMakeLists.txt. Mais detalhes podem ser encontrados aqui .
Além disso, o diretório contém o arquivo main.c original (o nome pode ser qualquer) com um ponto de entrada - a função void app_main (void).
Os componentes personalizados serão criados no diretório de componentes. O processo é descrito com mais detalhes na seção Requisitos do componente.
Conectar o módulo ESP32 a um computador na maioria dos casos é feito usando um cabo USB como placas Arduino devido ao bootloader existente. O processo é descrito com mais detalhes aqui... A única coisa que é necessária é a presença de um driver conversor USB para UART no sistema, que pode ser baixado da fonte fornecida. Depois de instalar o driver, você precisa determinar o número da porta COM no sistema para carregar o firmware compilado no módulo.
Configurando o projeto.
As configurações padrão são adequadas na maioria dos casos. Mas para chamar a interface do menu do console, você precisa ir ao diretório do projeto e digitar na linha de comando:
idf.py menuconfig
Menu com definições de configuração
Depois de chamar este comando, o arquivo sdkconfig será criado se não foi anteriormente ou se foi reconfigurado. Em tutoriais anteriores, você verá comandos make menuconfig que são obsoletos.
Adicionar configurações personalizadas ao arquivo sdkconfig é possível manualmente, por exemplo:
#
# WiFi Settings
#
CONFIG_ESP_HOST_NAME=" "
CONFIG_ESP_WIFI_SSID=" "
CONFIG_ESP_WIFI_PASSWORD=""
Mas o método preferido é usar um arquivo de configuração adicional Kconfig.projbuild, que deve estar localizado no diretório com o componente. O conteúdo do arquivo pode ser o seguinte:
# 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
Depois de chamar o comando idf.py menuconfig, uma seção adicional é automaticamente adicionada ao arquivo sdkconfig. Chamar o comando idf.py menuconfig também é possível no projeto PlatformIO, no entanto, você precisa levar em consideração o fato de que a estrutura do projeto PlatformIO é diferente do ESP-IDF clássico, o que pode fazer com que o arquivo sdkconfig seja gerado novamente e ajuste as configurações personalizadas. As opções acima são possíveis aqui - editar o arquivo manualmente, renomear temporariamente o diretório src no principal ou configurar o arquivo CMakeLists.txt
Compilando e carregando o projeto.
Para construir um projeto, você precisa digitar o comando
idf.py build
Este comando compilará o aplicativo e todos os componentes ESP-IDF e, em seguida, gerará o carregador, a tabela de partição e os binários do aplicativo.
$ 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'
Deve-se ter em mente que o processo de compilação inicial de até mesmo um projeto simples leva tempo, então, ao contrário da estrutura do Arduino, muitos módulos ESP-IDF adicionais são compilados. Modificações posteriores das fontes somente levam à compilação dos mesmos arquivos. Uma exceção é a mudança de configuração.
Para baixar os binários compilados (bootloader.bin, partition-table.bin e hello-world.bin) para a placa ESP32, execute o comando:
idf.py -p PORT [-b BAUD] flash
onde substituímos PORT pelo que precisamos (COM1, / dev / ttyUSB1) e podemos opcionalmente alterar a velocidade de download especificando os valores necessários para BAUD.
Para rastrear o programa carregado, você pode usar qualquer utilitário de monitoramento de porta de comunicação, como HTerm , CoolTerm , ou use o utilitário de monitoramento IDF Monitor , para iniciá-lo, digite o comando:
idf.py -p PORT monitor
Plugin ESP-IDF Eclipse
A documentação para instalar e configurar o plugin está localizada aqui
Predefinições para uso:
- Java 11 e superior; (embora funcione em java 8, possivelmente devido a essas falhas);
- Python 3.5 e superior;
- Eclipse 2020-06 CDT;
- Git;
- ESP-IDF 4.0 e superior;
O plugin é muito bem integrado ao ambiente de desenvolvimento, automatiza a maior parte da funcionalidade. Mas, infelizmente, não sem uma mosca na sopa. Nas versões do Eclipse posteriores a 2019-09, os projetos ESP-IDF no Windows ainda têm um bug com a indexação de arquivos de origem.
Além disso, há outras falhas quando o projeto simplesmente não é compilado por algum motivo desconhecido. Apenas fechar o projeto e reiniciar o Eclipse ajuda.
Extensão de código ESP-IDF Visual Studio
E por último, na minha opinião a opção mais interessante é o plugin oficial do Visual Studio Code.
Como PlatformIO, ele pode ser facilmente instalado na seção de extensões. A instalação e configuração da estrutura ESP-IDF nesta extensão é apresentada como um menu integrado, que também é descrito na descrição. O download e a instalação de todos os componentes ocorrem automaticamente no processo de passar pelos estágios do menu. Todas as imagens do processo podem ser citadas, mas são intuitivas e requerem pouca ou nenhuma explicação. Em favor do PlatformIO, um kit de ferramentas mais conveniente para construir, baixar e monitorar um projeto pode ser observado. Em contraste, o plug-in ESP-IDF é controlado usando um menu de comando que pode ser chamado usando a tecla F1 ou uma combinação de teclas descritas no manual.
Configuração inicial do plugin
A vantagem de usar o plugin é que a estrutura clássica do projeto é respeitada, não há necessidade de se confundir de alguma forma com as configurações (no PlatformIO essa necessidade surge). Há uma nuance, se quisermos abrir um projeto criado anteriormente no código do Visual Studio com o plugin ESP-IDF, então precisamos apenas copiar o diretório .vscode para a raiz do projeto, que pode ser obtido gerando pelo menos uma vez um projeto modelo usando ESP- Plug-in IDF.
Menu de comando
FreeRTOS
De acordo com a wikipedia, o FreeRTOS é um sistema operacional multitarefa em tempo real (RTOS) para sistemas embarcados. O FreeRTOS oferece multitarefa ao compartilhar o tempo da CPU por todos os threads ou, na terminologia do sistema operacional, tarefas. Na minha opinião, o manual FreeRTOS mais completo e inteligível em russo está aqui . No idioma original, os manuais podem ser estudados na fonte oficial . Vou apenas dar uma imagem do status das tarefas.
FreeRTOS foi portado para uma ampla variedade de plataformas de hardware, incluindo os processadores Xtensa usados no ESP32. Mais detalhes podem ser encontrados na documentação.
GPIOs
GPIO ou entrada / saída universal é a capacidade de controlar discretamente um pino com um sinal "1" ou "0".
Como o nome indica, esses pinos têm dois modos de operação - entrada ou saída. No primeiro caso, lemos o valor, no segundo, anotamos. Outro fator importante ao lidar com GPIOs é o nível de tensão. O ESP32 é um dispositivo de 3,3 V. Portanto, tenha cuidado ao trabalhar com outros dispositivos que tenham uma tensão de 5 V ou superior. Também é importante entender que a corrente máxima que pode ser aplicada ao pino GPIO é de 12 mA. Para usar as funções GPIO fornecidas pelo ESP-IDF, precisamos conectar o cabeçalho driver / gpio.h. Você pode então chamar gpio_pad_select_gpio () para especificar a função deste pino. Existem 34 GPIOs diferentes disponíveis no ESP32. Eles são designados como:
- GPIO_NUM_0 - GPIO_NUM_19
- GPIO_NUM_21 - GPIO_NUM_23
- GPIO_NUM_25 - GPIO_NUM_27
- GPIO_NUM_32 - GPIO_NUM_39
A seguinte numeração não está incluída no número dos pinos 20, 24, 28, 29, 30 e 31. A
tabela de pinagem pode ser encontrada aqui .
Observe que os pinos GPIO_NUM_34 - GPIO_NUM_39 - usam apenas o modo de entrada. Eles não podem ser usados para saída de sinal. Além disso, os pinos 6, 7, 8, 9, 10 e 11 são usados para interagir com um flash card externo via SPI, não é recomendado usá-los para outros fins, mas se você realmente quiser, pode. O tipo de dados gpio_num_t é uma enumeração com valores correspondentes a números de pinos. É recomendável usar esses valores em vez de números. A direção do pino é definida usando a função gpio_set_direction (). Por exemplo, para definir um pino como saída:
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);
Para definir um pino como entrada:
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_INPUT);
Se configuramos GPIO como uma saída, podemos definir seu valor para 1 ou 0 chamando gpio_set_level ().
O exemplo a seguir muda GPIOs uma vez por segundo:
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);
}
Como alternativa para definir todos os atributos de pinos individuais, podemos definir as propriedades de um ou mais contatos chamando a função gpio_config (). Ele recebe uma estrutura gpio_config_t como entrada e define a direção, puxar para cima, puxar para baixo e configurações de interrupção para todos os pinos representados na máscara de bits.
Por exemplo:
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);
Configurações de puxar para cima e para baixo
Geralmente é lido se o pino de entrada GPIO está alto ou baixo. Isso significa que ele está conectado a uma fonte de alimentação ou aterramento. No entanto, se o pino não estiver conectado a nada, ele estará em um estado "flutuante". Freqüentemente, é necessário definir o nível inicial de um pino não conectado como alto ou baixo. Neste caso, o hardware (conexão usando resistores) ou software pull-up da saída é executado, respectivamente, para + V - puxar para cima ou para 0 - puxar para baixo. No SDK ESP32, podemos definir um GPIO como um pull up ou pull down usando a função gpio_set_pull_mode (). Esta função leva como entrada o número do pino que queremos definir e o modo pull-up associado a esse pino.
Por exemplo:
gpio_set_pull_mode (21, GPIO_PULLUP_ONLY);
Tratamento de interrupções GPIO
Para detectar uma mudança no sinal de entrada em um pino, podemos pesquisar periodicamente seu status, mas essa não é a melhor solução por vários motivos. Primeiro, devemos percorrer a verificação, desperdiçando tempo de CPU. Em segundo lugar, no momento da votação, o estado do pino pode não ser mais relevante devido ao atraso e você pode pular os sinais de entrada. A solução para esses problemas é a interrupção. Uma interrupção é como uma campainha. Sem tocar, teremos que verificar periodicamente se alguém está na porta. No código-fonte, podemos definir uma função de retorno de chamada de interrupção que será chamada quando o pino alterar o valor de seu sinal. Também podemos determinar o que está fazendo com que o manipulador seja chamado definindo os seguintes parâmetros:
- Desativar - não dispara uma interrupção quando o sinal muda;
- PosEdge - chame o manipulador de interrupção ao mudar de baixo para alto;
- NegEdge - chama um manipulador de interrupção ao mudar de alto para baixo;
- AnyEdge - invoca o tratador de interrupção ao mudar de baixo para alto ou de alto para baixo;
Um manipulador de interrupção pode ser marcado para carregar na RAM em tempo de compilação. Por padrão, o código gerado está na memória flash. Se você marcá-lo como IRAM_ATTR de antemão, ele estará pronto para execução imediata da RAM.
void IRAM_ATTR my_gpio_isr_handle(void *arg) {...}
Aqueles que trabalharam com microcontroladores sabem que o processamento dos sinais de entrada dos botões é acompanhado pelo salto de contato. Que pode ser interpretado como uma série de transições e, portanto, uma série de eventos de tratamento de interrupção. Para fazer isso, devemos adicionar o tratamento de rejeição de contato ao código. Para fazer isso, precisamos ler o evento original, esperar até que as vibrações diminuam e, em seguida, refazer a amostra do estado de entrada.
O exemplo a seguir demonstra o tratamento de interrupções de sinais de entrada. Recomendo enfaticamente que você se familiarize com o gerenciamento de filas no FreeRTOS para um melhor entendimento do código, caso ainda não esteja familiarizado com ele. O exemplo mostra duas tarefas:
- test1_task, que é desbloqueado quando um evento de interrupção ocorre quando o sinal é ativado no pino 25 e a mensagem "Registered a click" é exibida no console uma vez;
- test2_task é pesquisado periodicamente e quando o sinal no pino 26 é ativado, a mensagem “GPIO 26 is high!” é enviada para o console a cada 100 ms.
O exemplo também possui um temporizador de software configurado xTimer, ele é opcional neste caso, ao invés de um exemplo de atraso assíncrono.
O anti-salto é executado usando a função timeval_durationBeforeNow , que verifica se a pressão dura mais de 100ms. Existem outros padrões de software anti-bounce, mas o significado é quase o mesmo. ESP-IDF também inclui um exemplo de como funciona o GPIO.
Processamento de sinal de entrada
#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 (contador de pulso)
O módulo PCNT (Contador de pulso) é projetado para contar o número de bordas ascendentes e / ou descendentes do sinal de entrada. Cada bloco do módulo possui um registro assinado de 16 bits e dois canais que podem ser configurados para aumentar ou diminuir o valor do contador. Cada canal possui um sinal de entrada que captura a mudança no sinal, bem como uma entrada de controle que pode ser usada para habilitar ou desabilitar a contagem. As entradas possuem filtros adicionais que podem ser usados para eliminar picos de sinal indesejados.
O contador PCNT possui oito unidades independentes, numeradas de 0 a 7. Na API, elas são especificadas usando pcnt_unit_t. Cada módulo possui dois canais independentes, numerados 0 e 1, indicados por pcnt_channel_t.
A configuração é fornecida separadamente para cada canal de dispositivo usando pcnt_config_t e abrange:
- Número da unidade e número do canal ao qual esta configuração pertence;
- Números GPIO de entrada de pulso e entrada de porta;
- Dois pares de parâmetros, pcnt_ctrl_mode_t e pcnt_count_mode_t, para definir como o contador reage dependendo do estado do sinal de controle e como as bordas de subida / descida são contadas.
- Dois valores de limite (mín. / Máx.) Que são usados para definir pontos de controle e disparar interrupções quando o contador de pulsos atinge um certo limite.
A configuração de um canal específico é então feita chamando a função pcnt_unit_config () com a estrutura de configuração pcnt_config_t acima como um parâmetro de entrada.
Para desativar um pulso ou entrada de controle na configuração, é necessário especificar PCNT_PIN_NOT_USED em vez do número GPIO.
Após configurar com pcnt_unit_config (), o contador começa a funcionar imediatamente. O valor do contador acumulado pode ser verificado chamando pcnt_get_counter_value ().
As funções a seguir permitem controlar como o contador funciona: pcnt_counter_pause (), pcnt_counter_resume () e pcnt_counter_clear ()
Também é possível alterar dinamicamente os modos de contador previamente definidos usando pcnt_unit_config () chamando pcnt_set_mode ().
Se desejado, o pino de entrada de pulso e o pino de entrada de controle podem ser alterados em tempo real usando pcnt_set_pin ().
O módulo PCNT possui filtros em cada uma das entradas de pulso e controle, adicionando a capacidade de ignorar picos curtos nos sinais. O comprimento dos pulsos ignorados é fornecido em ciclos de clock APB_CLK chamando pcnt_set_filter_value (). As configurações de filtro atuais podem ser verificadas com pcnt_get_filter_value (). O ciclo APB_CLK opera a 80 MHz.
O filtro é iniciado / pausado chamando pcnt_filter_enable () / pcnt_filter_disable ().
Os eventos a seguir, definidos em pcnt_evt_type_t, podem disparar uma interrupção. O evento ocorre quando o contador de pulsos atinge certos valores:
- : counter_l_lim counter_h_lim, pcnt_config_t;
- 0 1, pcnt_set_event_value ().
- = 0
Para registrar, habilitar ou desabilitar a interrupção dos eventos acima, você deve chamar pcnt_isr_register (), pcnt_intr_enable () e pcnt_intr_disable (). Para habilitar ou desabilitar eventos quando os limites forem atingidos, você também precisará chamar pcnt_event_enable () e pcnt_event_disable ().
Para verificar quais limites estão definidos atualmente, use a função pcnt_get_event_value ().
Um exemplo do ESP-IDF é apresentado aqui .
Usei um contador PCNT para calcular a velocidade da roda. Para fazer isso, é necessário contar o número de pulsos por rotação e, em seguida, zerar o contador.
Código de amostra
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;
}
Modulação de largura de pulso (PWM) usando o módulo MCPWM
Informações sobre o módulo são apresentadas aqui
Existem muitos artigos na rede sobre o tema PWM , principalmente se você pesquisar em relação ao Arduino.
A Wikipedia fornece uma definição curta e sucinta - modulação por largura de pulso (PWM) - o processo de controlar a energia ao ligar e desligar o dispositivo. O princípio do controle PWM é alterar a largura do pulso em uma amplitude e frequência constantes do sinal.
A frequência PWM do Arduino é 488,28 Hz, a resolução é 8 bits (0 ... 255), e é possível usar seis pinos de hardware 3, 5, 6, 9, 10, 11. No entanto, usando as configurações de registro do microcontrolador AVR, você pode obter outros valores Frequência PWM.
O microcontrolador ESP32 tem em seu arsenal um módulo MCPWM separado, ou melhor, dois módulos, cada um dos quais com três pares de pinos
PWM.Além disso, na documentação, as saídas de um bloco separado são marcadas como PWMxA / PWMxB.
Um diagrama de blocos mais detalhado do bloco MCPWM é apresentado abaixo. Cada par A / B pode ser sincronizado com qualquer um dos três temporizadores: Temporizador 0, 1 e 2. O mesmo temporizador pode ser usado para sincronizar mais de um par de saídas PWM. Cada unidade também pode coletar dados de entrada, como sinais de sincronização, detectar alarmes como sobrecorrente ou sobretensão do motor e receber feedback com sinais de captura, como posição do rotor.
O escopo da configuração depende do tipo de motor, em particular quantas saídas e entradas são necessárias e qual será a seqüência de sinais para controlar o motor.
Em nosso caso, descrevemos uma configuração simples para acionar um motor DC escovado que usa apenas alguns dos recursos MCPWM disponíveis. Um exemplo de circuito é mostrado abaixo. Inclui uma ponte H para comutar a polarização da tensão fornecida ao motor (M) e fornecer corrente suficiente para acioná-lo.
A configuração inclui as seguintes etapas:
- Seleção do bloco MPWn que será usado para acionar o motor. Existem dois módulos disponíveis na placa ESP32 daqueles listados em mcpwm_unit_t.
- Inicializa dois GPIOs como saídas no módulo selecionado chamando mcpwm_gpio_init (). Os dois sinais de saída geralmente são usados para acionar o motor para a direita ou esquerda. Todos os parâmetros de sinal disponíveis estão listados em mcpwm_io_signals_t. Para definir mais de um pino por vez, use a função mcpwm_set_pin () junto com mcpwm_pin_config_t.
- Seleção do temporizador. Existem três temporizadores disponíveis no dispositivo. Os temporizadores estão listados em mcpwm_timer_t.
- Configurando a frequência do cronômetro e bootstrap na estrutura mcpwm_config_t.
- Chamando mcpwm_init () com os parâmetros acima.
Os métodos de controle PWM são os seguintes:
- 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.
Um exemplo de código para motor escovado está aqui.
No meu projeto, eu praticamente usei o código do exemplo, corrigindo-o levemente e adicionando um segundo controle de motor. Para controle independente dos canais PWM, cada um deles deve ser configurado com um temporizador separado, por exemplo MCPWM_TIMER_0 e CPWM_TIMER_1:
Código de amostra
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);
}
Conectando-se ao WI-Fi e trabalhando com MQTT
O tópico do protocolo Wi-FI é bastante extenso. Uma série de artigos separados será necessária para descrever o protocolo. No guia oficial, consulte a seção do driver Wi-Fi . Uma descrição da API do software está aqui . Os exemplos de código podem ser vistos aqui
Bibliotecas Wi-Fi fornecem suporte para configurar e monitorar funções de rede Wi-Fi ESP32. As seguintes configurações estão disponíveis:
- ( STA Wi-Fi). ESP32 .
- AP ( Soft-AP ). ESP32.
- AP-STA (ESP32 , ).
- (WPA, WPA2, WEP . .)
- ( ).
- Wi-Fi IEEE802.11.
MQTT
Você pode se familiarizar com o assunto aqui ou aqui . O manual ESP-IDF com exemplos está aqui .
Para configurar MQTT em código, primeiro você precisa se conectar a uma rede Wi-Fi. Em seguida, estabeleça uma conexão com o corretor. A mensagem é processada em um retorno de chamada, cujo parâmetro é o evento esp_mqtt_event_handle_t. Se o tipo de evento for MQTT_EVENT_DATA, o tópico e os dados podem ser analisados. Você pode personalizar comportamentos diferentes como resultado de uma conexão, desconexão e assinaturas de tópicos bem-sucedidas.
Exemplo de conexão 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);
Conectando-se ao corretor 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);
Manipulador 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;
}
Isso conclui minha história sobre o uso do módulo ESP32. O artigo considerou exemplos no ESP-IDF, como um framework que aproveita ao máximo os recursos do módulo. Programação usando outras plataformas, como javaScript, MicroPython, Lua pode ser encontrada nos recursos relacionados. No próximo artigo, conforme já mencionado, darei um exemplo prático do uso de um microcontrolador, e também compararei a abordagem de software do Arduino e do ESP-IDF.