O BlueRetro — PlayStation 1 Custom Edition é um firmware customizado de alta precisão para receptores Bluetooth baseados em ESP32 (DevKit V1 / ESP-WROOM-32) voltado ao console Sony PlayStation 1 (testado em PU-8 late 1-658-467-21; a pinagem é a mesma nas demais revisões).
Baseado no projeto de Jacques Gagnon (BlueRetro), esta edição foi desenvolvida e mantida por Schickacka com o objetivo de implementar calibrações eletromecânicas fiéis às especificações do controle DualShock original (SCPH-1200), robustez de concorrência em tempo real e estabilidade total de feedback em controladores sem fio modernos (DualShock 4 e DualSense).
-
Comportamento do Hardware Original: No controle físico DualShock 1 (SCPH-1200), o motor esquerdo (baixa frequência) é composto por um contrapeso excêntrico de ferro maciço alimentado pela linha de 7.6V do console. Devido ao atrito estático e à inércia mecânica desse conjunto, comandos com sinal PWM inferior a
0x40(64 em decimal) não produzem torque de partida suficiente para girar o motor físico, permanecendo completamente imóvel no hardware com fio original. -
Compatibilidade e Inércia em Controles Paralelos e Modernos: Jogos de PlayStation 1 frequentemente emitem valores residuais contínuos de baixa amplitude (
< 0x40) nos registradores de feedback como ruído de fundo ou nível de linha de base. Em controladores sem fio modernos e controles paralelos equipados com atuadores de 3.7V, esses valores residuais faziam o contrapeso girar continuamente se não fossem filtrados. Adicionalmente, em controles clones/paralelos, a inércia estática e atrito mecânico dos micromotores frequentemente atenuavam ou impediam a percepção física de pulsos muito curtos (1 a 2 quadros, como disparos pontuais de metralhadora em Resident Evil 3 ou passos leves) quando solicitados no piso de ativação mínimo (64). -
Calibração e Remapeamento Ativo (v0.7.5.1): Comandos com valores inferiores a
0x40continuam estritamente tratados como repouso absoluto (0x00), garantindo silêncio cirúrgico entre pulsos. Já a faixa ativa ($\ge 64$ ) é remapeada de forma linear e contínua do intervalo$[64, 255]$ para$[85, 255]$ : $$\text{Power}_{\text{LF}} = \begin{cases} 85 + \left\lfloor \dfrac{(\text{raw} - 64) \times 170 + 95}{191} \right\rfloor, & \text{se } \text{raw} \ge 64 \ 0, & \text{se } \text{raw} < 64 \end{cases}$$ Essa calibração eleva o piso operacional de partida para$85$ (fornecendo torque inicial imediato para vencer o atrito estático em controles paralelos sem atraso perceptível), enquanto preserva intacta a saturação máxima no teto da escala ($\text{raw} = 255 \implies 255$ ) e a proporcionalidade de intensidade em toda a biblioteca do console. -
Ativação do Motor de Alta Frequência: O motor direito (leve/alta frequência) é tratado como sinal binário digital, no qual qualquer byte não nulo aciona saturação máxima (
0xFF), garantindo resposta tátil nítida e imediata para qualquer intensidade de comando emitida pelo jogo.
- Temporizador Watchdog dos Controles Sem Fio: Os controladores Bluetooth modernos (DualShock 4 e DualSense) possuem um temporizador interno de segurança (watchdog) em seus firmwares de aproximadamente 200 ms a 250 ms. Se os motores estiverem ativos e nenhum novo relatório HID Bluetooth for recebido dentro dessa janela, o controle atenua ou desliga a alimentação dos atuadores por proteção térmica e conservação de bateria.
- Sustentação de Eventos Hápticos Prolongados: Em situações de vibração contínua e constante (como disparos sustentados de armas automáticas, aceleração contínua em zebras de pista ou explosões de longa duração), o console mantém os mesmos valores nos registradores de comando quadro a quadro. Na ausência de variação nos dados, a rotina de feedback pausava a transmissão por 300 ms, gerando solavancos e microcortes cíclicos causados pelo desarmamento do watchdog interno do controle.
- Calibração Implementada: A rotina de transmissão Bluetooth (
bt_fb_task) monitora atomicamente o estado ativo de vibração (BT_DEV_RUMBLE_ON). Enquanto qualquer controle conectado estiver vibrando, os pacotes de feedback são retransmitidos a cada 20 ms (50 Hz nominais), por temporizador de software. Não há sincronismo com o vídeo do console: o PS1 opera a 59,94 Hz (NTSC/PAL-M) ou 50 Hz (PAL); o período de 20 ms serve apenas para manter vivo o watchdog interno do controle.
- Quando o jogo entra em pausa ou acessa telas de configuração/inventário, o console chaveia a comunicação serial para o comando
0x43. O driver intercepta imediatamente essa transição e emite um pacote de parada forçada com comprimento zero (data_len = 0), desligando os motores de forma instantânea sem resíduos de rotação.
- Durante transições de tela, telas de carregamento do CD-ROM ou reprodução de vídeos em tela cheia (FMV), determinados jogos suspendem temporariamente a comunicação com as portas de controle por mais de um segundo. O limiar de inatividade para reinicialização (
PS_IDLE_RESET_US) foi estendido para 3,0 segundos, prevenindo que pausas de leitura do disco desarmem o modo analógico, enquanto preserva a capacidade de detectar com precisão quando o console foi reiniciado pelo botão físico de Reset.
- Sincronização na Conexão: Ao ligar o controle no meio de uma partida na qual o console já esteja operando em modo Analógico (
0x73), o controle entra diretamente em Analógico e acende a barra de luz na cor correspondente, sem exigir reinicialização do console ou alternância manual. - Cores padrão do build de release:
- Modo Analógico: vermelho
#6D0000(R=0x6D, ≈43% do vermelho máximo). - Modo Digital: DualShock 4 em cinza
#949190; DualSense com a lightbar apagada (0x000000) e apenas os pontos de jogador acesos.
- Modo Analógico: vermelho
- Modo noturno (opcional): só existe com
CONFIG_BR_PS1_NIGHT_MODE=y(padrãon). Quando ligado, divide as componentes RGB por 2 e usa o brilho mínimo do DS4/DS5.
- O firmware distribui suas cargas entre os dois núcleos do ESP32: o Core 1 é responsável pelo driver baremetal SPI em tempo real (
ps_spi.c), enquanto o Core 0 gerencia a pilha de protocolos Bluetooth e o sistema operacional FreeRTOS (host.c). - As variáveis compartilhadas entre a ISR e as tarefas usam
volatilemais barreiras (__sync_synchronize()) ou operações atômicas (atomic_cas) nos pontos de publicação de estado.volatilesozinho não garante atomicidade de campos multi-byte; por isso os campos de estado são de 8/32 bits alinhados e as transições críticas são publicadas com barreira explícita.
- Um controle Bluetooth sem botão pressionado, com sticks centrados e sem rumble ativo por 10 minutos é desconectado automaticamente (
CONFIG_BR_INACTIVITY_TIMEOUT_MIN;0desativa). - Contador Atômico Thread-Safe: A contagem de inatividade no loop de manutenção (
bt_host_task) utiliza primitivas atômicas nativas da toolchain GCC (__atomic_add_fetche__atomic_store_ncom semântica__ATOMIC_RELAXED) sobre o array de estadodev_idle_sec[BT_MAX_DEV], prevenindo condições de corrida e inconsistências de memória entre tarefas concorrentes do FreeRTOS. - Detecção Normalizada por Amplitude de Eixo: Em vez de limiares brutos fixos (que variavam sensivelmente dependendo da resolução nativa de cada modelo de controle), o limiar de atividade analógica é normalizado com base nos metadados de amplitude (
axis->meta->abs_max / 5), correspondendo a cerca de 20% do curso real (25 unidades na escala de 8 bits). - O console vê o controle desaparecer: jogos que pausam ao perder o controle vão pausar. Basta ligar o controle de novo (botão PS/Home) para reconectar.
- Ajuste em
main/Kconfig.projbuild→ menu PS1 Custom Edition → "Controller inactivity timeout in minutes".
- Resolução Determinística de Nome Bluetooth: Durante a reconexão iniciada pelo próprio controle via paginação (incoming page), o canal de interrupção L2CAP é negociado antes da chegada do pacote de identificação de nome (
BT_HCI_EVT_REMOTE_NAME_REQ_COMPLETE). O driver detecta a transição de tipo genérico (BT_HID_GENERIC) para PlayStation (BT_PS), redefine a flag de inicialização e invoca o inicializador nativo do driver (bt_hid_ps_init), garantindo que o controle nunca fique preso em modo genérico. - Inicialização Imediata (0 ms de espera adicional): Assim que o controle emite seu primeiro relatório de status completo (Report
0x11para DualShock 4 ou Report0x31para DualSense), a rotina de inicialização cancela o temporizador de fallback com segurança (esp_timer_stopantes deesp_timer_delete), marca o dispositivo como pronto, aciona a vibração de confirmação, inicia o piscar da cor do jogador e disponibiliza a porta do console de forma instantânea. - Recuperação de Modo Boot (Report 0x01): Caso o DualShock 4 inicie a transmissão em modo boot genérico (Report
0x01), o firmware emite o comando probe de transição (0xC0,0x07) para colocá-lo imediatamente em modo nativo Bluetooth.
- A verificação do modo de acessórios (
acc_mode & ACC_MEM) no driver SPI baremetal é indexada pelo slot raiz da porta física (port->mt_first_port), assegurando que cartões de memória virtuais ou físicos operem de forma consistente na Porta 2 independentemente da configuração de multitap.
Configuração padrão dos pinos de conexão entre a placa ESP32 DevKit V1 e as portas do console:
| Sinal PlayStation | Pino do Console | GPIO ESP32 | Observação |
|---|---|---|---|
| P1 DATA (MISO) | Pino 1 | GPIO 19 | ESP32 → console |
| P1 COMMAND (MOSI) | Pino 2 | GPIO 32 | Console → ESP32 |
| P1 ATT / DTR | Pino 6 | GPIO 34 | Entrada; pull-up externo de 10 kΩ obrigatório (GPIO34 não tem pull-up interno) |
| P1 CLOCK (SCK) | Pino 7 | GPIO 33 | Console → ESP32 |
| P1 ACK / DSR | Pino 9 | GPIO 21 | ESP32 → console |
| P2 DATA (MISO) | Pino 1 | GPIO 22 | ESP32 → console |
| P2 COMMAND (MOSI) | Pino 2 | GPIO 27 | Console → ESP32 |
| P2 ATT / DTR | Pino 6 | GPIO 5 | Entrada com pull-up interno |
| P2 CLOCK (SCK) | Pino 7 | GPIO 26 | Console → ESP32 |
| P2 ACK / DSR | Pino 9 | GPIO 25 | ESP32 → console |
| GND (Terra) | Pino 4 | GND | Referência comum |
| LED Analógico Porta 1 | — | GPIO 12 | Nível alto em modo analógico |
| LED Analógico Porta 2 | — | GPIO 15 | Nível alto em modo analógico |
| LED Onboard (Status BT) | — | GPIO 2 | LED azul da placa |
Alimentação: não use o pino 5 (3,5 V). Veja "Diretrizes de Alimentação". A fonte da verdade dos GPIOs é
main/wired/ps_spi.c(linhas 97–108); se a tabela e o código divergirem, o código ganha.
Nunca alimente o ESP32 pelo pino 5 (3,5 V) do conector do controle. Essa linha vem da fonte do console, é compartilhada com a lógica das portas de controle e do memory card e é protegida por fusível. O ESP32 pede picos de centenas de mA (rádio Bluetooth em transmissão e calibração de RF no boot) que essa linha não foi dimensionada para fornecer: a tensão afunda, causando brownout reset, e a sobrecarga sostenida abre o fusível — o console perde as portas com fio. Além disso, não há regulador nem proteção entre o pino 5 e o rail de 3,3 V do ESP32, e 3,5 V está no limite superior da faixa de 3,0–3,6 V do chip, sem margem para transitórios.
- Ligue um buck ajustável (ex.: MP1584EN) no pino 3 (7,6 V) e ajuste a saída para 5,0 V (capacidade ≥ 1 A).
- Ligue a saída do buck no pino
VIN/5Vdo ESP32. Não ligue no pino3.3V: o regulador da placa (AMS1117) precisa de pelo menos 4,5 V na entrada. Em placas DevKitC com AMS1117 (máximo 18 V), o próprio upstream aceita 8 V direto noVIN; o buck é preferível porque o LDO dissipa (8 − 5) × I e esquenta. - Instale 470 µF + 100 nF junto ao ESP32 e um fusível de 1 A no ramo de 7,6 V, o mais próximo possível do ponto de derivação.
- O GND do console (pino 4) e o GND do ESP32 devem ser o mesmo nó.
O 7,6 V do conector é a linha não chaveada na maioria das revisões: com o console desligado o ESP32 continua energizado. Se você precisa que ele desligue junto com o console, use o pino 5 (3,5 V) apenas como sinal de enable de um MOSFET / load switch de alta impedância — nunca como fonte de energia.
O pino 5 pode ficar desconectado: ele é saída da console (alimenta a lógica do controle original), não entrada. O ESP32 não precisa dele para nada.
Alimente o ESP32 por USB (5 V) e ligue ao console apenas o GND e as linhas de sinal. É a opção mais segura para bancada e para quem não quer mexer na fonte.
Se o BlueRetro já estiver instalado no console, o procedimento pode ser realizado sem abrir o videogame e sem cabos:
Note
O script de atualização sem fio (ble_ota_update.py) requer a biblioteca Python bleak (pip install bleak).
- Baixe o pacote
BlueRetro_PS1_Custom_v0.7.5.1_Package.zipna aba Releases. - Ligue o PlayStation 1 com os controles Bluetooth desligados.
- Execute o arquivo
flash_bluetooth_ota.bat(Windows). - O script localizará o dispositivo via Bluetooth LE e efetuará a gravação em aproximadamente 50 segundos. O receptor reiniciará automaticamente com o firmware atualizado.
Alternativa via Navegador: Acesse blueretro.io/ota.html no Chrome ou Edge, conecte-se ao BlueRetro e envie o arquivo BlueRetro_hw1_playstation.bin.
- Conecte o ESP32 ao computador via cabo micro-USB.
- Extraia o conteúdo de
BlueRetro_PS1_Custom_v0.7.5.1_Package.zip. - Execute
flash_windows.bate informe a porta serial correspondente (exemplo:COM3,COM4,COM6).
python -m esptool --chip esp32 --port COMx -b 921600 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 4MB --flash_freq 40m 0x1000 bootloader.bin 0x8000 partition-table.bin 0xd000 ota_data_initial.bin 0x10000 BlueRetro_hw1_playstation.bin- Sony DualShock 4 — oficial e clones/paralelos (CUH-ZCT1 e CUH-ZCT2).
- Sony DualSense (PS5) — atuadores de precisão mapeados para baixa e alta frequência.
O firmware foi projetado e calibrado para atender aos padrões de comunicação e feedback utilizados pela biblioteca do PlayStation:
- Efeitos de Pulso e Modulação: Resposta de motores pesados com repouso verdadeiro nos intervalos de baixa intensidade (como batimentos cardíacos, avisos de proximidade e alertas de status).
- Vibração Sustentada Contínua: Disparos prolongados de armas automáticas, aceleração contínua e superfícies acidentadas reduzindo microcortes por watchdog.
- Transições de Cena e Vídeos: Preservação do modo analógico durante pausas de leitura em FMVs e telas de transição.
- Resposta de Impacto: Acionamento agudo em golpes, impactos rápidos e colisões.
Este repositório possui fluxo automatizado de integração contínua (CI) via GitHub Actions que gera os binários a cada commit na branch master. Para compilação local utilizando o framework ESP-IDF v5.5:
# Clonagem recursiva obrigatória para obtenção de submódulos
git clone --recursive https://github.com/Schickacka/BlueRetro.git
cd BlueRetro
# Gerar arquivo de versão do firmware customizado
echo "v0.7.5.1 ps1-custom" > version.txt
# Carregar arquivo de configuração para PlayStation
cp configs/hw1/playstation sdkconfig
# Compilar binários
BR_HW=_hw1 BR_SYS=_playstation idf.py reconfigure buildSe você baixou o
Source.zip(e não clonou com--recursive), rode./scripts/fetch_liblfds.shantes do build: o zip não traz o submódulocomponents/queue_bss/liblfds.
- BlueRetro: Copyright (c) 2019-2025, Jacques Gagnon. Distribuído sob a licença Apache License 2.0.
- PlayStation 1 Custom Edition: Desenvolvido e mantido por Schickacka.
- Todas as alterações de código e adições arquiteturais estão documentadas nos arquivos modificados em conformidade com a seção 4(b) da licença Apache 2.0.
