Filtro Sallen-Key com a LaunchPad LAUNCHXL-F28379D: MPC convencional com o modelo ARX, em Ts = 1 ms
Universidade Federal do Pará
2026-10-02
SK07MPCNumerico e o ensaio na placa.Modelo: Identificação do Sallen-Key · Placa: Placa LAUNCHXL-F28379D.
A cada amostra \(k\) (a cada 1 ms):
No instante \(k\) (linha vertical): o passado é medido; o futuro é previsto para o plano escolhido. O plano não precisa ser perfeito — só o 1º movimento é aplicado, e o plano é refeito na amostra seguinte.
Modelo da identificação do Sallen-Key, \(T_s\) = 1 ms, identificado com a PRBS:
\[ y(k) = a_1\,y(k-1) + a_2\,y(k-2) + b_1\,u(k-1) + b_2\,u(k-2) + c \]
Subtraindo a equação em \(k-1\) da equação em \(k\), o termo constante \(c\) some:
\[ \Delta y(k) = a_1\,\Delta y(k-1) + a_2\,\Delta y(k-2) + b_1\,\Delta u(k-1) + b_2\,\Delta u(k-2) \]
com \(\Delta y(k) = y(k) - y(k-1)\) e \(\Delta u(k) = u(k) - u(k-1)\).
Planta com ganho 5 % maior que o do modelo. Prevendo os valores de \(y\), sobra um erro permanente de 30 mV; prevendo as variações, a saída chega exatamente à referência — o modelo nunca é perfeito, e isso não importa no regime.
\[ J = \sum_{j=1}^{N_p}\big[r(k) - \hat y(k+j)\big]^2 \;+\; \lambda\sum_{i=0}^{N_u-1}\Delta u(k+i)^2 \]
| \(N_p\) (amostras) | horizonte | \(t_s\) (ms) | sobressinal (%) | maior u (V) |
|---|---|---|---|---|
| 10 | 10 ms | 71 | 18,3 | 1,70 |
| 20 | 20 ms | 27 | 1,0 | 1,67 |
| 40 | 40 ms | 44 | 0,0 | 1,50 |
| 60 | 60 ms | 49 | 0,0 | 1,46 |
| 100 | 100 ms | 52 | 0,0 | 1,44 |
| \(N_u\) | \(t_s\) (ms) | sobressinal (%) | multiplicações por iteração |
|---|---|---|---|
| 1 | 73 | 0,0 | 302 |
| 2 | 68 | 0,0 | 364 |
| 4 | 49 | 0,0 | 488 |
| 8 | 39 | 2,3 | 736 |
Escolha: \(\lambda\) = 3 — acomodação ≈ 50 ms sem sobressinal (a especificação do planejamento e dos ensaios P e PI).
Exemplo com 2 movimentos, no instante em que a referência sobe: cada iteração desce a tigela. Os primeiros passos fazem quase todo o trabalho; o resto é ajuste fino.
| iterações por amostra | \(t_s\) (ms) | maior diferença de u para o ótimo (mV) | multiplicações | fração de 1 ms |
|---|---|---|---|---|
| 10 | 67 | 92 | 4.880 | 10 % |
| 20 | 61 | 61 | 9.760 | 20 % |
| 50 | 53 | 18 | 24.400 | 49 % |
| 100 | 50 | 3 | 48.800 | 98 % |
| até convergir (até 258) | 49 | 0 | 125.904 | 252 % |
Cada iteração simula o modelo 60 passos e calcula 4 derivadas: ≈ 490 multiplicações ≈ 2 000 ciclos ≈ 10 µs (≈ 4 ciclos por multiplicação, medido no SK08).
Com o ruído do ADC (1,9 mV), \(y\) continua limpo, mas \(u\) fica ruidoso: a previsão parte da medida. Desvio de \(u\) em regime: 52 mV (λ = 3), 40 mV (λ = 30). Um \(\lambda\) maior reduz o ruído, ao custo de uma resposta mais lenta.
SK07MPCNumericomain.cconst ParametrosMPC p = {
.modelo = { // ARX, Ts = 1 ms (identificacao do Sallen-Key)
.a1 = 1.60918827f, .a2 = -0.62250084f,
.b1 = 0.01096776f, .b2 = 0.00237673f,
},
.Np = 60, // horizonte de previsao: 60 ms
.Nu = 4, // movimentos planejados
.lambda = 3.0f, // peso de du
.nIter = 50, // iteracoes do gradiente por amostra
.uMin = 0.0f, // limites do atuador [V]
.uMax = 1.7f,
};Na inicialização: a resposta ao degrau \(s_j\) do modelo e o tamanho do passo do gradiente.
Simula o modelo nas variações, a partir das medidas, para o plano dU:
static void Preve(void)
{
float dyA = y0m - y1m, dyB = y1m - y2m; // dy(k), dy(k-1) medidos
float duAnt = u1 - u2, yy = y0m; // du(k-1); parte de y(k)
for (j = 0U; j < Np; j++) {
du = (j < Nu) ? dU[j] : 0.0f; // depois de Nu, u constante
dy = a1 * dyA + a2 * dyB + b1 * du + b2 * duAnt;
duAnt = du; dyB = dyA; dyA = dy;
yy += dy; yp[j] = yy; // soma as variacoes
}
}static void IteracaoGradiente(float r)
{
Preve(); // y^ para o plano atual
for (i = 0U; i < Nu; i++) { // derivada de J/2 em du_i
g = lambda * dU[i];
for (j = i; j < Np; j++)
g -= s[j - i] * (r - yp[j]); // s: resposta ao degrau
grad[i] = g;
}
for (i = 0U; i < Nu; i++)
dU[i] -= passo * grad[i]; // desce a tigela
}float MPC(float r, float y)
{
y2m = y1m; y1m = y0m; y0m = y; // medidas
for (i = 0U; i + 1U < Nu; i++) dU[i] = dU[i + 1U];
dU[Nu - 1U] = 0.0f; // parte do plano anterior
for (it = 0U; it < nIter; it++) IteracaoGradiente(r);
u = u1 + dU[0]; // so o 1o movimento
if (u < uMin) u = uMin; // saturacao do atuador
if (u > uMax) u = uMax;
u2 = u1; u1 = u;
return u;
}O CPU Timer 1 conta ciclos de CPU (5 ns); a struct desempenho aparece no Watch:
| Campo | O que mede |
|---|---|
inicializacao |
resposta ao degrau e passo do gradiente — uma vez |
calculoMax |
MPC() inteiro: previsões e gradiente |
lacoMax |
a interrupção inteira (ADC, MPC, DAC, gravação) |
ocupacaoMax |
lacoMax em % de 1 ms |
estouros |
amostras em que o cálculo passou de 1 ms |
Por amostra, tV[k] grava o tempo de cálculo em µs.
Device_cal e -O2-O2) para medir tempos realistas;Device_init chama uma rotina de calibração da TI que altera um registrador da CPU (XAR1) sem restaurá-lo;-O2, o compilador guardava ali o endereço da rotina de amostragem: o programa travava na primeira amostra (Interrupt_illegalOperationHandler);placa.c: Ts e o endereço da rotina ficam em variáveis volatile durante o Device_init.rV, uV, yV e tV para dados/DadosMPCConvencional01.csv;desempenho: calculoMax, lacoMax, ocupacaoMax e estouros;nIter (10, 20, 50, 100) e ver o efeito na resposta e no tempo — até onde cabe em 1 ms?Firmware: ProjetosSK/SK07MPCNumerico/; réplica em Python: analise/convencional.py.