Faça seu Macropad DIY com a Franzininho WiFi LAB

Um teclado de atalhos, também conhecido como macropad, é um dispositivo de entrada que possui um conjunto de teclas programáveis para executar funções específicas ou atalhos. Essas teclas podem ser configuradas para abrir programas, iniciar macros, inserir texto, controlar volume, ajustar brilho e executar diversas outras funções no computador.

Esses dispositivos são extremamente úteis para editores de vídeo, áudio, imagens e gamers, pois permitem que tarefas frequentes sejam executadas rapidamente, sem a necessidade de acessar menus ou usar atalhos complexos. Além disso, podem ser utilizados para automatizar tarefas repetitivas e aumentar a produtividade.

Hoje, com o acesso a placas makers como o Arduino e a Franzininho WiFi, é possível criar um Macropad DIY funcional e totalmente personalizado. Ele pode ser adaptado para pessoas com limitações motoras, com teclas maiores, espaçadas ou acionamentos alternativos.

Este projeto apresenta um Macropad DIY programável com a Franzininho WiFi LAB01, utilizando CircuitPython. O macropad automatiza atalhos de teclado e comandos multimídia, tornando-se uma excelente ferramenta para desenvolvedores, editores de vídeo, designers e gamers.

Além disso, o display OLED exibe o perfil ativo, temperatura e umidade ambiente, trazendo um toque extra ao projeto.

Circuito do Macropad DIY

Para facilitar o desenvolvimento e o uso do Macropad no dia a dia, utilizei a Franzininho WiFi LAB01, que já possui todos os recursos necessários (e extras) para o projeto.

Macropad DIY com a Franzininho WiFi LAB

Principais Funcionalidades do Macropad DIY

  • 6 botões programáveis para atalhos personalizados.
  • Troca de perfis dinamicamente, sem necessidade de reiniciar.
  • Suporte a comandos multimídia, como controle de volume, brilho e mídia.
  • Exibição de informações no OLED, incluindo perfil ativo, temperatura e umidade.
  • Feedback sonoro com buzzer para indicar ações.
  • LED RGB animado para efeitos visuais dinâmicos.
  • Modo noturno baseado no sensor LDR, ajustando o brilho do OLED automaticamente.
  • Suporte a teclas combinadas, como ALT + TABCTRL + C, entre outras.

Bibliotecas necessárias

Para que o código funcione corretamente no CircuitPython, é necessário instalar algumas bibliotecas na pasta lib/ da Franzininho WiFi LAB01.

Baixe o CircuitPython Libraries Bundle: Baixar bibliotecas

Arquivos necessários na pasta lib/

  1. Bibliotecas HID (Teclado e Controles de Mídia):
    • adafruit_hid/ (pasta completa)
  2. Biblioteca para Display OLED (SSD1306 via I2C):
    • adafruit_ssd1306.mpy
    • fonts/font5x8.bin 
  3. Biblioteca para Sensor de Temperatura e Umidade (DHT11):
    • adafruit_dht.mpy

Passo a passo para instalação:

  1. Baixe o bundle de bibliotecas compatível com sua versão do CircuitPython.
  2. Extraia os arquivos listados acima.
  3. Copie para a pasta lib/ da Franzininho WiFi LAB01.
  4. Reinicie a placa e execute o código.

Código-Fonte do Macropad DIY

O projeto é open-source e foi desenvolvido usando CircuitPython. O código está dividido em:

  • code.py – Código principal que gerencia os botões, perfis, OLED e feedback visual/sonoro.
import time
import board
import digitalio
import usb_hid
import json
import storage
import busio
import adafruit_ssd1306
import adafruit_dht
import pwmio
import analogio
import keymap  # Importa o mapeamento de teclas

from adafruit_hid.keyboard import Keyboard
from adafruit_hid.consumer_control import ConsumerControl

# === CONFIGURAÇÃO DO DISPLAY OLED ===
i2c = busio.I2C(scl=board.IO9, sda=board.IO8)
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)

# === BUZZER (Feedback Sonoro) ===
buzzer = pwmio.PWMOut(board.IO17, duty_cycle=0, frequency=880)

def beep(t):
    """Toca um beep curto no buzzer."""
    buzzer.duty_cycle = 440
    time.sleep(t)
    buzzer.duty_cycle = 0

# === SENSOR DE TEMPERATURA E UMIDADE (DHT11) ===
dht = adafruit_dht.DHT11(board.IO15)

# === LED RGB (3 LEDs Independentes) ===
led_red = pwmio.PWMOut(board.IO14, frequency=5000, duty_cycle=0)
led_green = pwmio.PWMOut(board.IO13, frequency=5000, duty_cycle=0)
led_blue = pwmio.PWMOut(board.IO12, frequency=5000, duty_cycle=0)

def set_rgb(r, g, b):
    """Define a cor do LED RGB utilizando valores de 0 a 255."""
    led_red.duty_cycle = int((r / 255) * 65535)
    led_green.duty_cycle = int((g / 255) * 65535)
    led_blue.duty_cycle = int((b / 255) * 65535)

def rgb_animation():
    """Executa uma animação inicial no LED RGB."""
    colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255)]
    for color in colors:
        set_rgb(*color)
        time.sleep(0.2)
    set_rgb(0, 0, 0)

# === SENSOR DE LUMINOSIDADE (LDR) ===
ldr = analogio.AnalogIn(board.IO1)

def is_dark():
    """Verifica se o ambiente está escuro com base no valor do LDR."""
    return ldr.value < 20000  # Ajuste conforme necessário

def apply_night_mode():
    """Ativa o modo noturno se o ambiente estiver escuro."""
    if is_dark():
        oled.contrast(10)  # Reduz brilho do OLED
        set_rgb(50, 50, 50)  # Luz RGB fraca
    else:
        oled.contrast(255)
        set_rgb(255, 0, 255)  # Modo normal (roxo)

# === TECLADO E CONTROLE MIDI ===
kbd = Keyboard(usb_hid.devices)
cc = ConsumerControl(usb_hid.devices)

# === BOTÕES DO MACROPAD ===
keys = [
    digitalio.DigitalInOut(board.IO7),  # BT1
    digitalio.DigitalInOut(board.IO6),  # BT2
    digitalio.DigitalInOut(board.IO5),  # BT3
    digitalio.DigitalInOut(board.IO4),  # BT4
    digitalio.DigitalInOut(board.IO3),  # BT5
    digitalio.DigitalInOut(board.IO2)   # BT6
]

for key in keys:
    key.direction = digitalio.Direction.INPUT
    key.pull = digitalio.Pull.UP

# === CARREGAR CONFIGURAÇÃO DE PERFIS ===
PROFILE_FILE = "/profiles.json"

def load_profiles():
    """Carrega os perfis de atalhos de um arquivo JSON e converte para Keycodes."""
    try:
        with open(PROFILE_FILE, "r") as f:
            profiles_raw = json.load(f)
        
        print("JSON antes da conversão:", profiles_raw)  # 🔥 Verificar se já está errado aqui

        profiles_converted = {}
        for profile_name, key_list in profiles_raw.items():
            profiles_converted[profile_name] = [keymap.convert_keys(keys) for keys in key_list]

        return profiles_converted
    except Exception as e:
        print(f"Erro ao carregar perfis: {e}")
        return {"Padrão": [[]] * 6}  # Configuração de fallback


profiles = load_profiles()
profile_names = list(profiles.keys())
current_profile_index = 0
print("Perfis Carregados:", profiles)


# === TROCA DE PERFIL SEGURANDO BT1 ===
def switch_profile():
    """Alterna entre perfis somente se BT1 for segurado por 2 segundos."""
    global current_profile_index
    start_time = time.monotonic()
    
    while not keys[0].value:  # Enquanto BT1 estiver pressionado
        if time.monotonic() - start_time > 2:  # Se segurou por mais de 2s, troca o perfil
            current_profile_index = (current_profile_index + 1) % len(profile_names)
            oled_display()
            beep(0.1)
            while not keys[0].value:  # Aguarda soltar o botão antes de continuar
                pass
            return True  # Indica que o perfil foi trocado

    return False  # Se soltou antes de 2 segundos, executa a macro normalmente

# Armazena o estado dos botões para debounce e repetição automática
last_key_states = [True] * len(keys)  # Começa como True porque os botões estão em Pull-UP
key_press_times = [0] * len(keys)  # Tempo do último acionamento da tecla
repeat_delay = 0.1  # Tempo antes de começar a repetir (segundos)
repeat_rate = 0.005   # Intervalo entre repetições (segundos)

def execute_macro(key_index):
    """Executa o comando configurado para a tecla pressionada, aplicando debounce e repetição."""
    global last_key_states, key_press_times

    current_state = keys[key_index].value  # Lê o estado atual do botão
    now = time.monotonic()  # Obtém o tempo atual

    if current_state == False:  # 🔹 Se o botão está pressionado
        if last_key_states[key_index]:  # 🔹 Foi pressionado pela primeira vez?
            last_key_states[key_index] = False
            key_press_times[key_index] = now  # Registra o tempo da primeira pressão
            
            if key_index == 0:  # Se for BT1, verificar troca de perfil
                if switch_profile():
                    return  

            profile = profile_names[current_profile_index]
            actions = profiles[profile][key_index]

            beep(0.01)
            print(f"⬇ Botão {key_index + 1} pressionado no perfil {profile}: {actions}")

            if isinstance(actions, list) and len(actions) > 0:
                if all(action in keymap.CONSUMER_CONTROL_MAP.values() for action in actions):
                    print(f"🎵 Enviando ConsumerControlCode: {actions[0]}")
                    cc.send(actions[0])
                else:
                    print(f"⌨ Enviando Keycode: {actions}")
                    kbd.send(*actions)

        elif now - key_press_times[key_index] > repeat_delay:  # 🔹 Se passou o tempo do delay inicial
            if (now - key_press_times[key_index]) % repeat_rate < 0.01:  # 🔹 Controla a taxa de repetição
                profile = profile_names[current_profile_index]
                actions = profiles[profile][key_index]

                print(f"🔄 Repetindo comando: {actions}")
                beep(0.01)
                if isinstance(actions, list) and len(actions) > 0:
                    if all(action in keymap.CONSUMER_CONTROL_MAP.values() for action in actions):
                        cc.send(actions[0])
                    else:
                        kbd.send(*actions)

    else:  # 🔹 Se o botão foi solto
        if not last_key_states[key_index]:  # 🔹 Apenas processa se antes estava pressionado
            last_key_states[key_index] = True
            print(f"⬆ Botão {key_index + 1} liberado, soltando teclas")
            kbd.release_all()

def oled_display():
    """Exibe apenas o perfil ativo, temperatura e umidade no OLED."""
    oled.fill(0)
    oled.text(f"Perfil: {profile_names[current_profile_index]}", 0, 0, 1)

    try:
        temp = dht.temperature
        humidity = dht.humidity
        if temp is not None and humidity is not None:
            oled.text(f"T: {temp:.1f}C", 0, 20, 1)
            oled.text(f"U: {humidity:.1f}%", 0, 30, 1)
    except RuntimeError:
        oled.text("Erro DHT", 0, 20, 1)

    oled.show()



rgb_animation()
oled_display()

# === LOOP PRINCIPAL ===
while True:
    apply_night_mode()

    for i, key in enumerate(keys):
        execute_macro(i)  # 🔹 Agora com debounce e repetição automática

    oled_display()
    time.sleep(0.01)  # Pequeno atraso para evitar consumo excessivo de CPU

  • keymap.py – Mapeamento de teclas e comandos multimídia.
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control_code import ConsumerControlCode

# 🔹 Mapeamento de todas as teclas do Keycode
KEYCODE_MAP = {
    "A": Keycode.A,
    "B": Keycode.B,
    "C": Keycode.C,
    "D": Keycode.D,
    "E": Keycode.E,
    "F": Keycode.F,
    "G": Keycode.G,
    "H": Keycode.H,
    "I": Keycode.I,
    "J": Keycode.J,
    "K": Keycode.K,
    "L": Keycode.L,
    "M": Keycode.M,
    "N": Keycode.N,
    "O": Keycode.O,
    "P": Keycode.P,
    "Q": Keycode.Q,
    "R": Keycode.R,
    "S": Keycode.S,
    "T": Keycode.T,
    "U": Keycode.U,
    "V": Keycode.V,
    "W": Keycode.W,
    "X": Keycode.X,
    "Y": Keycode.Y,
    "Z": Keycode.Z,
    "1": Keycode.ONE,
    "2": Keycode.TWO,
    "3": Keycode.THREE,
    "4": Keycode.FOUR,
    "5": Keycode.FIVE,
    "6": Keycode.SIX,
    "7": Keycode.SEVEN,
    "8": Keycode.EIGHT,
    "9": Keycode.NINE,
    "0": Keycode.ZERO,
    "CONTROL": Keycode.CONTROL,
    "ALT": Keycode.ALT,
    "SHIFT": Keycode.SHIFT,
    "TAB": Keycode.TAB,
    "ENTER": Keycode.ENTER,
    "ESCAPE": Keycode.ESCAPE,
    "SPACEBAR": Keycode.SPACEBAR,
    "BACKSPACE": Keycode.BACKSPACE,
    "DELETE": Keycode.DELETE,
    "UP_ARROW": Keycode.UP_ARROW,
    "DOWN_ARROW": Keycode.DOWN_ARROW,
    "LEFT_ARROW": Keycode.LEFT_ARROW,
    "RIGHT_ARROW": Keycode.RIGHT_ARROW,
    "F1": Keycode.F1,
    "F2": Keycode.F2,
    "F3": Keycode.F3,
    "F4": Keycode.F4,
    "F5": Keycode.F5,
    "F6": Keycode.F6,
    "F7": Keycode.F7,
    "F8": Keycode.F8,
    "F9": Keycode.F9,
    "F10": Keycode.F10,
    "F11": Keycode.F11,
    "F12": Keycode.F12
}

# 🎵 Mapeamento de comandos multimídia
CONSUMER_CONTROL_MAP = {
    "VOLUME_INCREMENT": ConsumerControlCode.VOLUME_INCREMENT,
    "VOLUME_DECREMENT": ConsumerControlCode.VOLUME_DECREMENT,
    "MUTE": ConsumerControlCode.MUTE,
    "PLAY_PAUSE": ConsumerControlCode.PLAY_PAUSE,
    "SCAN_NEXT_TRACK": ConsumerControlCode.SCAN_NEXT_TRACK,
    "SCAN_PREVIOUS_TRACK": ConsumerControlCode.SCAN_PREVIOUS_TRACK,
    "BRIGHTNESS_INCREMENT": ConsumerControlCode.BRIGHTNESS_INCREMENT,
    "BRIGHTNESS_DECREMENT": ConsumerControlCode.BRIGHTNESS_DECREMENT
}


print(f"Valor correto de Keycode.ALT: {Keycode.ALT}")  # 🔥 Isso deve imprimir 130!


# 🔹 Função para converter strings em Keycodes ou ConsumerControlCode
def convert_keys(json_keys):
    """Converte os atalhos do JSON para valores corretos de Keycode ou ConsumerControlCode."""
    converted_keys = []
    for key in json_keys:
        print(f"Tentando converter: {key}")  # 🔥 Ver se 'ALT' realmente chega aqui como string

        if isinstance(key, int):
            print(f"⚠️ ERRO: O JSON ainda tem números ({key}), algo está errado!")
        
        if key in KEYCODE_MAP:  
            print(f"✅ Convertendo {key} -> {KEYCODE_MAP[key]}")
            converted_keys.append(KEYCODE_MAP[key])
        elif key in CONSUMER_CONTROL_MAP:
            print(f"🎵 Convertendo {key} -> {CONSUMER_CONTROL_MAP[key]}")
            converted_keys.append(CONSUMER_CONTROL_MAP[key])
        else:
            print(f"⚠️ ERRO: Tecla '{key}' não encontrada no mapeamento!")
    return converted_keys



def reverse_lookup(value):
    """Converte um código numérico de tecla em string legível."""
    for name, code in KEYCODE_MAP.items():
        if code == value:
            return name
    for name, code in CONSUMER_CONTROL_MAP.items():
        if code == value:
            return name
    return f"UNK({value})"  # Retorna um nome desconhecido se não encontrar
  • profiles.json – Arquivo que armazena os perfis de teclas personalizados.
{
    "OBS": [
        ["SHIFT", "T"],  
        ["SHIFT", "1"],  
        ["SHIFT", "2"],  
        ["SHIFT", "P"],  
        ["SHIFT", "R"],  
        ["SHIFT", "S"]   
    ],
    "Midia": [
        ["VOLUME_INCREMENT"],
        ["BRIGHTNESS_DECREMENT"],
        ["BRIGHTNESS_INCREMENT"],
        ["VOLUME_DECREMENT"],  
        ["MUTE"],  
        ["PLAY_PAUSE"]  
    ]
}

Altere os atalhos diretamente no JSON e reinicie o macropad para aplicar as mudanças.

Baixe o código completo: Repositório no GitHub

Como Configurar e Usar seu Macropad DIY

1. Configurar o CircuitPython

  1. Baixe e instale o CircuitPython na Franzininho WiFi LAB01. Use essa ferramenta
  2. Copie o projeto completo para a placa (incluindo as bibliotecas).

2. Criar Perfis Personalizados

Edite o arquivo profiles.json para configurar novos atalhos.

3. Trocar de Perfil

  • Pressione e segure o botão 1 por 2 segundos para alternar entre os perfis configurados.
  • O display OLED exibirá o nome do perfil ativo, a temperatura e a umidade.

4. Testar os Atalhos

  • Pressione os botões e veja os atalhos funcionando no seu computador.
  • Para testar os comandos, acesse um testador de teclas online, como Keyboard Event Viewer.

Conclusão

Este projeto transforma a Franzininho WiFi LAB01 em um Macropad DIY personalizável, permitindo criar atalhos poderosos para produtividade e automação.

Com suporte a perfis dinâmicos, comandos multimídia, OLED interativo e feedback visual/sonoro, este Macropad DIY é uma excelente solução para quem busca eficiência e personalização no dia a dia.

O projeto será melhorado no futuro com a inclusão de mais funcionalidades e automações para ajudar na produtividade diária.

Quais recursos você gostaria de ver nesse projeto?
Deixe seu feedback e sugestões nos comentários.

Quer adquirir sua Franzininho WiFi ou Franzininho WiFi LAB? Entre em contato: contato@embarcados.com.br

Saiba mais

Franzininho WiFi LAB01 – Um Ecossistema Completo para Desenvolvimento em IoT e Sistemas Embarcados

Como programar a Franzininho WiFi: 6 opções para você escolher!

Franzininho WiFi: Pinos de Toque Capacitivo com CircuitPython

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Maker » Faça seu Macropad DIY com a Franzininho WiFi LAB

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: