Zéfiros: Estabilização & Ondas
O desenho ideal da ponte giroscópio–batimento: modulação respiratória Ondas, feed-forward síncrono ao golpe, ressonância com bloqueio de fase e modulação aeroelástica do ganho PID — e um mapa honesto do que o firmware atual realmente liga.
Linhagem: OrniFlight e um Ondas simplificado
Ondas não é uma invenção do PteronautOS. A família de modulação de onda — controlo de cadência respiração-pausa, ferocidade de forma de batida, dwell assimétrico e o acoplamento aeroelástico da carga alar à autoridade PID — vive e evolui no OrniFlight projeto, cuja wiki e simulador carregam os algoritmos avançados (incluindo a servo-efficiency table ).
O PteronautOS incorpora uma implementação parcialmente simplificada: a mesma geometria matemática de respiração, escalada para um ESP8285 com um MPU6050 e até nove servos. Onde o OrniFlight simula aeroelástica contínua e explora famílias de envelope completas offline, o PteronautOS tem de decidir em ticks de 4 ms, num núcleo de 80 MHz, com a pilha de rádio ExpressLRS a partilhar o mesmo silício. Cada ganho neste artigo existe em ambos os projetos; as constantes de firmware abaixo são o subconjunto aéreo.
Fluxo do sinal — Zéfiros para Ornithopter
A ponte do giroscópio ao batimento alar é deliberadamente curta — quatro ficheiros, sem alocação dinâmica, sem filas:
MPU6050 raw (I2C, 1 kHz register reads)
▼
Zephyrus::update() Mahony AHRS → roll°, pitch°, yawRate °/s
▼ dual PID: roll→0°, pitch→0°, yawRate→0°/s
4 raw pitch terms exposed pitchPTerm · pitchITerm · pitchDTerm · pitchErrorRate
▼
ZephyrusFilter.h (250 Hz) copies terms onto ornithopter.gyroPitch*
▼
Ornithopter::_computeServoMixer() / _computeGearboxMixer()
▼ Ondas P → phase · PD → dwell · I → asymmetry · SSFF → next stroke
_f[] → funcMap → wing / crest-rudder / tail servos (µs)
Os quatro termos brutos são escritos em Zephyrus::update() imediatamente após o PID de arfagem calcular a sua correção:
pitchPTerm = ZEPHYR_PID_PITCH_KP * pitchErr; // raw proportional
pitchITerm = _pidPitch.integrator; // accumulated error
pitchDTerm = ZEPHYR_PID_PITCH_KD * _pidPitch.lastDerivative; // low-pass filtered D
pitchErrorRate = _pidPitch.lastDerivative; // °/s, for SSFF
Os quatro são protegidos por uma armadilha NaN (se qualquer entrada for NaN, os quatro são zerados) para que um frame I²C corrompido nunca chegue à aritmética do mixer. Sete campos em Ornithopter recebem os dados da ponte:
| Campo | Fonte Zephyrus | Consumido por |
|---|---|---|
gyroPitchPTerm
|
pitchPTerm
|
Ondas P → avanço de fase (canal de cadência) |
gyroPitchITerm
|
pitchITerm
|
Ondas I → assimetria de batida (canal de equilíbrio) |
gyroPitchDTerm
|
pitchDTerm
|
Ondas D → razão de dwell (canal de ferocidade) |
gyroPitchErrorRate
|
pitchErrorRate
|
Acumulação de meia-batida SSFF |
gyroRudderCorrection
|
rudderCorrection
|
Offset µs do leme de crista (kernels servo e caixa) |
gyroAileronCorrection
|
rollCorrection
× gearbox gain
|
Caixa: PID de rolagem → superfícies aileron da cauda V |
gyroElevatorCorrection
|
pitchCorrection
× gain ×
aeroGainScale
|
Caixa: PID de arfagem → superfícies elevon / profundor |
Modulação Ondas — respiração de três canais
Ondas (português: "ondas") é o sistema de respiração de três canais que mapeia os termos brutos P, I e D do PID de arfagem em três dimensões ortogonais da forma de onda de batimento. É um ciclo proprioceptivo: o sentido de arfagem do giroscópio molda diretamente o movimento da asa, e o movimento da asa muda a arfagem que o giroscópio sente.
Cadência — P → avanço de fase
Um erro de arfagem positivo (nariz para cima) avança a fase do oscilador, encurtando a meia-batida atual; um erro negativo atrasa-a. A lei ideal escala a mola do oscilador:
_osc.kGainMod = 1.0f + gyroPitchPTerm * aeroGainScale * cadenceGain * 0.00005f;
// clamped to [0.5, 2.0] — phase can at most double or halve
O advance() do oscilador consome-o como error = kGain · kGainMod · cadenceTarget − kDamp · cadence. Faixa de afinação 0–100, padrão 20.
Ferocidade — mistura PD no dwell
Os termos P e D são misturados num único sinal de ferocidade que aguça ou suaviza o dwell da forma de onda:
ferocitySignal = (gyroPitchPTerm * ferocityPGain * 0.00015f
+ gyroPitchDTerm * ferocityDGain * 0.0003f) * aeroGainScale;
// clamped to [-0.5, 0.5], added to both stroke and return ferocity
Com ferocityPGain = 0 (o padrão) o comportamento é só D — mudanças rápidas de arfagem produzem batidas mais aguçadas enquanto o voo estável permanece sinusoidal.
Equilíbrio — I em assimetria
O termo integral desloca o centro da batida: um viés persistente de nariz para cima fortalece a batida descendente, um viés persistente de nariz para baixo fortalece a ascendente.
float iBias = gyroPitchITerm * aeroGainScale * balanceGain * 0.0001f;
// clamped to [-3.0, 3.0]; added to strokeFer, subtracted from returnFer
O equilíbrio é o canal mais desestabilizador quando alto — o guia de afinação ideal mantém-no baixo (5–15) e adiciona-o por último. A aplicação de ferocidade, combinando comando do piloto, Ondas e viés SSFF, mantém a autoridade do piloto sobre a janela completa [1.0, 8.0]:
strokeFer = pilot(stroke) + ferocitySignal + iBias + _ssffFerocityUpBias
returnFer = pilot(return) + ferocitySignal − iBias + _ssffFerocityDownBias
Modelagem da onda e mistura PD
A forma de onda em si segue a família de batidas OrniFlight: cada metade usa a sua própria ferocidade, um limiar de reversão partilhado mantém as asas esquerda/direita em fase, e uma mistura de forma por perfil transmuta continuamente uma batida plana de dwell/plateau num caminho piramidal arredondado. O explorador interativo abaixo renderiza a geometria comandada para qualquer par de ferocidade descida/subida — a mesma curva que o mixer pede a cada tick.
A ferocidade controla tanto o carácter como o tempo de meio-batimento. Internamente, o valor 0–100 da WebUI mapeia para 0–8. Maior ferocidade de descida encurta a fracção do ciclo da descida; maior ferocidade de retorno encurta a fracção do retorno. O tempo restante é transferido para a metade oposta — não há pausa em zero.
O controlo Forma da Ferocidade por perfil selecciona o carácter de alta ferocidade. A 0% favorece uma forma finita de patamar/quadrada. A 100% favorece um percurso piramidal arredondado. Cada metade continua a usar a sua própria ferocidade, pelo que uma metade de baixa ferocidade permanece alongada e sinusoidal, enquanto uma metade de alta ferocidade se aproxima de uma travessia diagonal directa.
A inclinação de descida e de subida (±100) desloca o centro de cada meio-batimento ao longo do seu próprio eixo início–fim — o mesmo espelhamento por meio-batimento da ferocidade. Positivo antecipa o batimento: a asa atinge a velocidade máxima mais cedo, aumentando o impulso. Negativo desloca-o para o fim: o batimento gasta o seu movimento no final, diminuindo o impulso. A deformação é monótona e preserva os extremos, sendo exatamente uma mistura assimétrica quadrado↔triângulo, nunca um salto de posição.
O acoplamento de forma de empuxo do throttle mistura permanência e centro ântero-posterior num só controle. Aceleração total quadra o batimento E adianta os dois meios-batimentos — a velocidade de pico chega antes tanto na descida quanto na subida; em marcha lenta ambos ficam neutros. O único escalar de agressão move permanência e centro juntos, de modo que as duas asas e os dois meios-batimentos andam em uníssono — autoridade de empuxo, não de rolagem ou arfagem.
O acoplamento aileron→skew front-loada uma asa enquanto atrasa a outra — torque de roll no eixo do skew, o gémeo espelho do skew de throttle. Stick à esquerda soma skew na asa esquerda e remove-o na direita; stick à direita faz o oposto. Usar junto ou em vez do diferencial de amplitude do leme.
Slew é um boost ou travão transitório na taxa de um stick. Dar gás front-loada brevemente AMBOS os meios-batimentos, cortar gás late-loada brevemente AMBOS; a taxa filtrada por passa-baixo (τ = 0,10 s) injeta o impulso, que depois decai assim que o stick repousa. Dois acoplamentos: slew de taxa de throttle (simétrico, empuxo) e slew de taxa de aileron (diferencial, roll).
O slew gain do Zephyrus aplica a mesma ideia transitória ao leme: uma mudança rápida de atitude amplia brevemente a correção do leme (µs), passa-baixo (τ = 0,12 s) e limitada a ±80 µs. Aguça a resposta a mudanças rápidas de atitude sem aumentar o ganho estático.
Matriz de eixos de controlo
O stick de leme não tem autoridade de guinada no misturador de asas: os seus botões diferenciais (amplitude e ferocidade) constroem a mesma assimetria esquerda/direita que a inclinação do aileron — e essa assimetria é rolamento. A guinada verdadeira existe apenas no leme de crista autónomo (Zephyrus, apenas giroscópio).
| Entrada de controlo | Parâmetro da onda | Efeito de voo |
|---|---|---|
| Stick de aileron | skew — diferencial, esquerda/direita |
Rolamento |
| Stick de leme | amplitude + ferocidade — diferencial, esquerda/direita |
Rolamento |
| Guinada — giroscópio Zephyrus | Leme de crista | Guinada (autónoma) |
Direção de voo & slew
Ciclo completo
Cada metade esticada para largura igual
Ler combinações extremas
Uma combinação 7 / 1 produz claramente uma descida curta e pontiaguda e um retorno sinusoidal longo. Valores exactos e quase máximos exigem cuidado: a lei de duração actual usa a quantidade restante abaixo da ferocidade 8. Consequentemente, 8 / 0 atribui apenas cerca de 0,125% do ciclo à descida, enquanto até 8 / 6 atribui cerca de 0,5%. A posição permanece contínua, mas a velocidade e a aceleração pedidas podem tornar-se fisicamente inatingíveis.
Amortecimento de âncora (k₂)
O amortecimento do oscilador é exposto como uma âncora afinável. A lei base é uma mola amortecida em direção ao alvo de cadência:
kDamp = 10.0f + anchorGain; // anchorGain 0 → k₂=10 (tight)
error = kGain * kGainMod * cadenceTarget
- kDamp * cadence;
Com anchorGain = 0 (padrão) o amortecimento é apertado — convergência rápida para a cadência comandada. Valores maiores elevam k₂ até 110 no ganho 100, travando o ritmo com mais força e rejeitando perturbações. Faixa de afinação 0–100.
Ressonância — bomba com bloqueio de fase
A ressonância é um amplificador lock-in travado em fase: multiplica a taxa de erro de arfagem por sin(fase do oscilador) e acumula — reforçando a batida exatamente quando uma correção é mais eficaz, amortecendo-a quando lutaria contra a asa.
_resonanceAccum += gyroPitchErrorRate * sinf(_osc.phase)
* resonanceGain * 0.01f * dt;
_resonanceAccum *= expf(-dt / 0.15f); // leaky integrator, τ = 0.15 s
clamp(_resonanceAccum, -2.0f, 2.0f);
O valor acumulado é injetado na ferocidade de batida e de retorno para o próximo frame. Com resonanceGain = 0 (padrão) todo o caminho está desativado. O decaimento com fuga impede acumulação descontrolada durante perturbação sustentada.
SSFF — feed-forward síncrono ao golpe
O feed-forward síncrono de batida é filosoficamente diferente de todos os termos PID: corrige a próxima meia-batida a partir do que o giroscópio mediu durante a atual. Em cada cruzamento de zero do seno de batimento (reversão da asa), a taxa média de erro de arfagem da meia-batida concluída torna-se um viés na ferocidade da metade oposta.
// zero-cross detected: finished one half-stroke, starting the next
meanError = _ssffAccumError / count;
bias = meanError * ssffGain * 0.00001f; // clamped ±2.0
// upstroke just ended → bias feeds the downstroke (power stroke)
// downstroke just ended → bias feeds the upstroke (recovery)
O viés de subida é aplicado à batida descendente (corrigindo o erro acumulado na ascendente anterior); o viés de descida à ascendente. Quando ssffGain cai para zero, os viés são explicitamente limpos para que uma desativação na WebUI nunca deixe valores obsoletos na forma de onda. O acumulador é de 32 bits e reiniciado por meia-batida — a 250 Hz precisaria de ~24 dias de batimento contínuo para transbordar, o que não é uma preocupação prática.
Modulação aeroelástica do ganho PID
Toda a modulação Ondas e SSFF é escalada por um coeficiente que sabe se o ornitóptero está batendo ou planando — modulação de ganho aeroelástica. No kernel servo (forma de onda) o seletor é a porta de batimento com a sua histerese de 50 µs:
aeroGainScale = isFlapping ? (aeroFlapCoeff * 0.01f)
: (aeroGlideCoeff * 0.01f);
No kernel de caixa o seletor é o estado do motor (armed && throttleNorm > 0.1f). Padrões:
| Coeficiente | Padrão | Faixa | Quando ativo |
|---|---|---|---|
aeroGlideCoeff
|
40 | 0–100 | Asas paradas — planagem, ou motor de caixa desligado |
aeroFlapCoeff
|
40 | 0–100 | Asas batendo — ou motor de caixa a funcionar |
Escalar para 0–1 torna o slider 0–100 a autoridade do giroscópio sobre a asa: a 100 o giroscópio detém totalmente a modulação, a 0 está desligado. Autoridade por kernel:
| Autoridade | Kernel servo (forma de onda) | Kernel de caixa |
|---|---|---|
| Leme de crista | rolagem + guinada combinadas | só guinada (rolagem/arfagem vão para as pernas) |
| Forma de onda da asa | Modulação de respiração completa Ondas / SSFF | — |
| Ailerons de perna (cauda V) | — | PID de rolagem → lado aileron da mistura elevon |
| Elevons de perna / profundor | — | PID de arfagem × aeroGainScale → lado profundor |
As correções PID de caixa são limitadas a ±250 µs (ZEPHYR_GEARBOX_CLAMP_US) antes da mistura; o caminho do leme de crista é limitado a ±200 µs. Um frame de atraso é inerente: aeroGainScale é calculado no mixer de caixa mas consumido pela próxima zephyrusUpdate() — 4 ms a 250 Hz, negligenciável para servos mecânicos.
Painel WebUI e endpoints de configuração
O painel do ornitóptero mostra dois grupos de estabilização quando a build inclui Zephyrus. Sliders Ondas & SSFF (0–100): Cadência P→Fase, Ferocidade D→Dwell, Ferocidade P→Dwell, Equilíbrio I→Assimetria, Âncora k₂, Ressonância, Ganho SSFF. Grupo aeroelástico: Coeficiente de Planagem e Coeficiente de Batimento. Todos os valores persistem através de POST /pteronautos/config e voltam por GET /pteronautos/state:
{
"ornithopter": {
"cadence_gain": 20.0, "ferocity_d_gain": 20.0, "balance_gain": 10.0,
"ferocity_p_gain": 0.0, "anchor_gain": 0.0, "resonance_gain": 0.0,
"ssff_gain": 0.0,
"aero_glide_coeff": 40.0, "aero_flap_coeff": 40.0,
"aero_gain_scale": 0.4
}
}
aero_gain_scale é calculado e somente leitura. O POST aceita atualizações parciais e limita cada campo a [0, 100] no servidor.
Guia de ajuste
A sequência ideal de teste em voo — cada passo é um voo, não um arrasto de slider:
- Zere todos os ganhos. Voe só manual; a aeronave tem de estar estável e previsível antes de o giroscópio ganhar um voto.
- Ative o leme de crista do Zephyrus (PID rolagem+guinada). Verifique que o leme resiste a um balanço suave da asa antes de qualquer coisa tocar na forma de onda.
- Cadência (P→Fase) a partir de 10. Demasiado alto = asas aos solavancos, caça de fase; demasiado baixo = nada. Certo = asas sentem-se "ligadas" à arfagem.
- Ferocidade D (D→Dwell) a partir de 10. Demasiado alto = a forma de onda oscila entre aguçado e suave; certo = mudanças rápidas de arfagem aguçam a batida, voo estável permanece sinusoidal.
- Ferocidade P (opcional) a partir de 5 — mistura a autoridade P no dwell.
- Equilíbrio (I→Assimetria) por último, baixo. Este é o canal mais desestabilizador; só 5–15. Contraria deriva persistente, nunca solavancos transitórios.
- SSFF a partir de 5, só depois de cadência e ferocidade assentarem. Demasiado alto = sobrecorreção e flutter de meia-batida; certo = amortecimento visível da amplitude de oscilação de arfagem.
- Coeficientes aeroelásticos por último. Afine a planagem primeiro (asas paradas), depois o batimento. Mantenha o giroscópio um copiloto autoritário, não um piloto nervoso.
Estado da implementação e inconsistências em aberto
Mapa honesto, firmware atual (Ornithopter.cpp @ 72c63020). As leis ideais acima são o design; o código contém a superfície completa de parâmetros, as constantes, os sliders da WebUI e o shim de ponte — mas um bloco de andaime de teste em voo desliga atualmente a maior parte do giroscópio do kernel servo:
// Ornithopter.cpp:269-275 (inside the flapping branch)
#ifdef ZEPHYRUS_ENABLED
// STEP 7: ALL gyro values hard-zeroed (NaN guard test)
_osc.kGainMod = 1.0f; // cadence channel OFF
gyroRudderCorrection = 0.0f; // crest rudder OFF while flapping
gyroAileronCorrection = 0.0f;
gyroElevatorCorrection= 0.0f;
#endif
Cometido com o trabalho de i18n como um teste de guarda NaN "STEP 7" e nunca revertido, este bloco significa que o comportamento atual do kernel servo é:
- Cadência, Ferocidade-PD, Equilíbrio, Ressonância, SSFF — todos presentes como parâmetros e na ponte, mas os seus locais de consumo (
kGainMod,ferocitySignal,iBias,resonanceBias) estão fixados a zero ou reiniciados a cada frame (Ornithopter.cpp:282–284, 311–313, 354). - A estabilização do leme de crista é zerada dentro do ramo de batimento, pelo que a correção do leme atua atualmente apenas no ramo de planagem — o oposto do que o voo precisa (a maioria das perturbações chega a meio da batida).
- O kernel de caixa é a exceção viva. Lê
gyroAileronCorrection/gyroElevatorCorrection/gyroRudderCorrectione aplica-os com limites (linhas 436–451) — o bloco de zeragem STEP-7 vive apenas no kernel servo.
Isto não é uma crítica à matemática — é um estado conhecido e nomeado: os módulos de estabilização estão compilados, parametrizados, em ponte, e à espera de que o andaime STEP-7 seja removido e substituído por uma política NaN real (as guardas Validatio já dão aos dados do MPU um caminho limpo). Até lá, trate o guia de afinação Ondas/SSFF/ressonância acima como a especificação pronta para voo, verificada contra Zephyrus.cpp:729–737 e OrnithopterWaveform.h, mas ainda não aérea.
O registo de design autoritativo permanece STABILIZATION.md ; o seu projeto irmão OrniFlight mantém a investigação ONDAS continuamente avançada.