Disparo Acelerado e Contínuo ao Manter um Botão Pressionado com STM32F4

Visão Geral do Recurso

Em sistemas embarcados, a funcionalidade de um botão pode ser estendida além do simples toque, permitindo a detecção de pressionamentos contínuos para acionar sequências de comandos com um padrão de tempo específico. Este artigo detalha a implementação de um sistema que detecta quando um botão em um microcontrolador STM32F4 é mantido pressionado, e então inicia uma sequência de disparos que acelera gradualmente até atingir uma velocidade constante. A biblioteca HAL da STMicroelectronics será utilizada para gerenciar as interações com o hardware.

Componentes Necessários

Para construir este exemplo, você percisará dos seguintes itens:

  • Uma placa de desenvolvimento STM32F4 (com um microcontrolador STM32F407VGT6 ou equivalente).
  • Um botão tátil (push-button) independente.
  • Um resistor (tipicamente entre 220Ω e 330Ω, embora não estritamente necessário se usar pull-up interno).
  • Fios de conexão.

Configuração Física do Botão

O botão será conectado ao pino PB0 do microcontrolador STM32F4. Para uma configuração simples e eficaz, conecte um terminal do botão ao pino PB0 e o outro terminal ao terra (GND). Utilizaremos o resistor de pull-up interno do STM32F4, fazendo com que o pino PB0 leia um estaddo LÓGICO ALTO (HIGH) quando o botão estiver solto e LÓGICO BAIXO (LOW) quando pressionado.

Configuração do GPIO no STM32F4

O pino PB0 será configurado como uma entrada digital com pull-up interno. Esta configuração é ideal para detecção de botão, pois garante que o pino não fique flutuante quando o botão está solto, proporcionando um estado elétrico bem definido.

O primeiro passo é habilitar o clock para a porta GPIO B, onde o pino PB0 está localizado, e em seguida configurar os parâmetros específicos do pino usando a estrutura GPIO_InitTypeDef.

/* Habilita o clock para a porta B */
__HAL_RCC_GPIOB_CLK_ENABLE();

/* Declara e inicializa a estrutura de configuração GPIO */
GPIO_InitTypeDef cfg_gpio = {0};

/* Define o pino a ser configurado (PB0) */
cfg_gpio.Pin = GPIO_PIN_0;
/* Configura como entrada */
cfg_gpio.Mode = GPIO_MODE_INPUT;
/* Ativa o resistor de pull-up interno */
cfg_gpio.Pull = GPIO_PULLUP;
/* Define a velocidade do pino (baixa para botões, suficiente) */
cfg_gpio.Speed = GPIO_SPEED_FREQ_LOW;

/* Aplica as configurações ao GPIOB */
HAL_GPIO_Init(GPIOB, &cfg_gpio);

Estrutura do Módulo de Controle do Botão

Para manter o código modular e fcailitar a manutenção, a lógica de detecção e controle do botão será encapsulada em arquivos de cabeçalho (.h) e de implementação (.c). Sugerimos criar um diretório App/Input e, dentro dele, os arquivos input_button.h e input_button.c. É crucial adicionar o caminho para este diretório nas configurações de inclusão do seu projeto (por exemplo, no Keil MDK ou STM32CubeIDE).

Arquivo: input_button.h

Este arquivo define as constantes de temporização para o anti-rebatimento e para os limites de tempo de pressionamento, além de declarar os protótipos das funções que compõem o módulo do botão.

#ifndef INPUT_BUTTON_H
#define INPUT_BUTTON_H

#include "stm32f4xx_hal.h"

/* Tempos em milissegundos para detecção e controle */
#define TEMPO_ANTI_REBOTE       20      // Duração da janela de anti-rebatimento
#define LIMITE_PRESSAO_CURTA    500     // Duração máxima para considerar um toque curto
#define LIMITE_PRESSAO_LONGA    1500    // Duração mínima para considerar um toque longo
#define PASSO_ACELERACAO        100     // Quantidade de milissegundos a subtrair do intervalo
#define INTERVALO_MINIMO        200     // Intervalo mínimo de disparo (velocidade constante)

/* Enumeração para os estados de toque do botão */
typedef enum {
    BTN_NENHUM_TOQUE = 0,
    BTN_TOQUE_CURTO,
    BTN_TOQUE_LONGO
} TipoToqueBotao;

/**
 * @brief Inicializa o pino GPIO configurado para o botão.
 */
void Botao_ConfigurarGPIO(void);

/**
 * @brief Avalia o estado físico do botão e retorna o tipo de toque detectado.
 * @return Um valor da enumeração TipoToqueBotao.
 */
TipoToqueBotao Botao_ObterEstado(void);

/**
 * @brief Gerencia a lógica de disparo acelerado e contínuo para toques longos.
 *        Esta função deve ser chamada repetidamente no loop principal.
 */
void Botao_GerenciarDisparoContinuo(void);

#endif /* INPUT_BUTTON_H */

Arquivo: input_button.c

Este arquivo contém a implementação das funções declaradas no cabeçalho, incluindo a lógica para anti-rebatimento, detecção de toque curto e longo, e o mecanismo de disparo acelerado/contínuo.

#include "input_button.h"

/* Variáveis estáticas para rastreamento de estado interno do botão */
static uint32_t momento_pressionamento = 0; // Timestamp do início do pressionamento
static uint8_t estado_interno_botao = 0; // 0: Solto, 1: Pressionado (curto/indefinido), 2: Pressionado (longo)
static uint32_t ultimo_disparo_continuo = 0; // Último timestamp de um disparo contínuo
static uint32_t intervalo_disparo_atual = 0; // Intervalo de tempo entre os disparos contínuos

/**
 * @brief Implementa a configuração GPIO do botão.
 */
void Botao_ConfigurarGPIO(void) {
    GPIO_InitTypeDef configuracao_gpio = {0};

    __HAL_RCC_GPIOB_CLK_ENABLE(); // Habilita o clock para a porta B

    configuracao_gpio.Pin = GPIO_PIN_0;
    configuracao_gpio.Mode = GPIO_MODE_INPUT;
    configuracao_gpio.Pull = GPIO_PULLUP; // Ativa pull-up interno
    configuracao_gpio.Speed = GPIO_SPEED_FREQ_LOW;

    HAL_GPIO_Init(GPIOB, &configacao_gpio);
}

/**
 * @brief Analisa o estado físico do botão e retorna o tipo de toque.
 *        Incorpora um mecanismo de anti-rebatimento.
 * @return TipoToqueBotao (BTN_NENHUM_TOQUE, BTN_TOQUE_CURTO, BTN_TOQUE_LONGO).
 */
TipoToqueBotao Botao_ObterEstado(void) {
    // Implementação de anti-rebatimento simples
    static uint32_t debounce_timer = 0;
    static GPIO_PinState ultimo_estado_fisico = GPIO_PIN_SET; // Botão inicialmente solto
    GPIO_PinState estado_atual_fisico = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);

    if (estado_atual_fisico != ultimo_estado_fisico) {
        debounce_timer = HAL_GetTick();
    }
    ultimo_estado_fisico = estado_atual_fisico;

    // Se ainda estamos na janela de anti-rebatimento, ignorar
    if ((HAL_GetTick() - debounce_timer) < TEMPO_ANTI_REBOTE) {
        return BTN_NENHUM_TOQUE;
    }

    if (estado_atual_fisico == GPIO_PIN_RESET) { // Botão Pressionado (nível baixo)
        if (estado_interno_botao == 0) {
            momento_pressionamento = HAL_GetTick();
            estado_interno_botao = 1; // Marca como "pressionado, aguardando identificação"
        }
        
        uint32_t duracao_pressionamento = HAL_GetTick() - momento_pressionamento;

        if (duracao_pressionamento >= LIMITE_PRESSAO_LONGA) {
            estado_interno_botao = 2; // Identificado como toque longo
            return BTN_TOQUE_LONGO;
        } else if (duracao_pressionamento >= LIMITE_PRESSAO_CURTA) {
            // Detecta toque curto APENAS UMA VEZ enquanto ainda não é longo
            if (estado_interno_botao == 1) { 
                estado_interno_botao = 0; // Reseta para detectar novo toque curto após liberação
                return BTN_TOQUE_CURTO;
            }
        }
    } else { // Botão Solto (nível alto)
        estado_interno_botao = 0; // Reseta o estado interno ao liberar
        return BTN_NENHUM_TOQUE;
    }

    return BTN_NENHUM_TOQUE; // Estado padrão
}

/**
 * @brief Gerencia a lógica de disparo acelerado para toques longos.
 *        Deve ser chamada frequentemente (e.g., no loop principal) para funcionar.
 */
void Botao_GerenciarDisparoContinuo(void) {
    if (estado_interno_botao == 2) { // Apenas se o botão está em estado de toque longo
        uint32_t tempo_atual = HAL_GetTick();
        
        // Inicializa o intervalo de disparo na primeira vez que o toque longo é detectado
        if (intervalo_disparo_atual == 0) {
             intervalo_disparo_atual = LIMITE_PRESSAO_CURTA; // Define um intervalo inicial
             ultimo_disparo_continuo = tempo_atual;
             // <-- ADICIONE A AÇÃO DO PRIMEIRO DISPARO AQUI (opcional) -->
             // Exemplo: HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); // Toggle LED
        }

        // Verifica se é hora de um novo disparo
        if (tempo_atual - ultimo_disparo_continuo >= intervalo_disparo_atual) {
            ultimo_disparo_continuo = tempo_atual;
            
            // <-- ADICIONE A AÇÃO DE DISPARO AQUI -->
            // Exemplo: HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); // Toggle LED azul no STM32F4-Discovery

            // Lógica de aceleração: diminui o intervalo até atingir o mínimo
            if (intervalo_disparo_atual > INTERVALO_MINIMO) {
                intervalo_disparo_atual -= PASSO_ACELERACAO;
                if (intervalo_disparo_atual < INTERVALO_MINIMO) {
                    intervalo_disparo_atual = INTERVALO_MINIMO;
                }
            }
        }
    } else {
        // Se o botão não está em toque longo, reseta o intervalo de disparo
        intervalo_disparo_atual = 0;
    }
}

Implementação no Loop Principal (main.c)

No arquivo main.c, após as inicializações do sistema e do clock, as funções do módulo do botão devem ser chamadas. A função Botao_ObterEstado() deve ser chamada repetidamente para monitorar o estado do botão, e Botao_GerenciarDisparoContinuo() deve ser acionada quando um toque longo for detectado.

#include "stm32f4xx_hal.h"
#include "input_button.h" // Inclui o módulo do botão

/* Assume-se que SystemClock_Config() está definida em outro lugar ou gerada pelo CubeMX */
extern void SystemClock_Config(void); 

int main(void) {
    /* Inicializa o subsistema HAL */
    HAL_Init();

    /* Configura o clock do sistema */
    SystemClock_Config();

    /* Configura o GPIO para o botão */
    Botao_ConfigurarGPIO();

    // <-- ADICIONE AQUI OUTRAS INICIALIZAÇÕES DE PERIFÉRICOS, SE NECESSÁRIO -->
    // Exemplo: Inicialização de LEDs, UART, etc.
    // __HAL_RCC_GPIOD_CLK_ENABLE();
    // GPIO_InitTypeDef LedGPIO;
    // LedGPIO.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    // LedGPIO.Mode = GPIO_MODE_OUTPUT_PP;
    // LedGPIO.Pull = GPIO_NOPULL;
    // LedGPIO.Speed = GPIO_SPEED_FREQ_LOW;
    // HAL_GPIO_Init(GPIOD, &LedGPIO);


    while (1) {
        /* Obtém o tipo de toque atual do botão */
        TipoToqueBotao tipo_toque = Botao_ObterEstado();

        switch (tipo_toque) {
            case BTN_TOQUE_CURTO:
                /* Ação a ser executada em um toque curto */
                // Exemplo: HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13); // Toggle LED azul
                break;
            case BTN_TOQUE_LONGO:
                /* Ação a ser executada quando o botão é mantido pressionado (toque longo) */
                Botao_GerenciarDisparoContinuo(); // Lida com a aceleração e disparo contínuo
                break;
            case BTN_NENHUM_TOQUE:
            default:
                /* Nenhuma ação necessária ou estado de botão não relevante */
                break;
        }
    }
}

Tags: STM32 HAL GPIO Embarcado Botão

Publicado em 7-4 17:15