Olá caro leitor, primeiramente esse que vos escreve lhe deseja um excelente 2016. E como primeiro artigo do ano, iremos fechar a nossa pequena série de introdução a controle digital de potência baseada na placa de desenvolvimento STMDiscoveryF334. Assim gostaria de agradecer a STMicroelectronics por ter cedido uma unidade da placa para que pudêssemos explorar conceitos tão interessantes desse belo microcontrolador voltado para controle de energia. No artigo anterior, implementamos o firmware do conversor boost síncrono, usando a infraestrutura fornecida na placa, porém sem qualquer tipo de realimentação envolvida, de forma que o usuário variava a tensão através de um dos botões da placa, e o firmware atuaria no conversor a partir de cálculos utilizados para determinar o regime estacionário do conversor.
A importancia de fechar a malha de controle
Fato, é que o firmware apresentado no artigo anterior possui muito mais utilidade na modelagem inicial do conversor para cálculo dos componentes do que uso em uma aplicação real de digital power. Isso porque a atuação do firmware sobre a infraestrutura do conversor não é periódica e muito menos recorrente, ou seja, se o usuário quiser uma determinada tensão de saída o firmware vai se ajustar no momento em que ele pressionar o botão. Entretanto, se variarmos a carga, ou a tensão de entrada por exemplo, aparecerá um erro entre a tensão selecionada e a real na saída, que nunca será corrigido, apenas com uma nova interferência do usuário. Isso porque o firmware consegue atuar sobre o conversor, mas não “enxerga” o que está de fato ocorrendo em sua saída e fica impedido de tomar qualquer ação caso haja alguma perturbação da condição escolhida no set point.
Fechar a malha de controle tem exatamente esse propósito. Utilizando o ponto de medida de tensão de saída podemos levar essa informação ao microcontrolador, de forma que ele entenda o que está ocorrendo. Como a saída está variando, podendo assim ser programado para atuar periodicamente sobre o conversor e corrigindo eventuais pertubações na condição de set point desejada. Vamos dar novamente mais uma olhada no esquema elétrico dessa seção para localizar o ponto de medida desejado.
Vamos observar o canto superior direito da Figura 1, próximo ao conector de saída CN3. Temos um divisor resistivo formado pelos componentes R52 e R46, e sua saída é enviada para o microcontrolador através do terminal PA3, fornecendo o valor da tensão de saída escalada pela razão entre R52 e R46. Assim agora o microcontrolador já consegue saber o que está acontecendo no conversor e com que intensidade.
O conceito de digital power no conversor síncrono
Como vimos em posts anteriores, o conceito de digital power consiste em substituir a infraestrutura responsável pelo controle de conversores que são tradicionalmente analógicos ou circuitos híbridos discretos, por uma unidade de processamento digital que consiga reproduzir a estratégia de controle, com ganhos de espaço, flexibilidade e uso de algoritmos de controle avançados. No caso do nosso conversor a parte de digital power estava semipronta, pois a unidade de processamento (o microcontrolador) já atuava sobre o conversor e agora já enxerga o que ocorre na saída. Porém ainda falta um elemento para completar a cadeia de controle digital, a figura abaixo nos ajuda a entender o que.

Olhando para a figura inferior que contém um controle completo baseado em digital power, e lembrando dos posts anteriores, fica óbvio saber que o elemento faltante é o compensador, representado pelo bloco PID Filter. A importância desse bloco é justamente executar a estratégia de controle para qual a fonte foi projetada, ou seja, se buscar as condições do set point a partir da diferença entre esse e a saída medida é o fator principal de fechar a malha. Adicionando um compensador colocamos um elemento que vai acelerar essa busca com o mínimo de stress possível imposto ao conversor durante uma pertubação de carga ou na tensão de saída, e esse elemento foi adicionado ao novo firmware.
As modificações no firmware
O firmware modificado pode ser acessado através deste repositório no github, que é basicamente o mesmo do artigo anterior, sendo a base exatamente a mesma, apenas realizei nela alterações necessárias para que o mesmo consiga operar em malha fechada. Além disso a aplicação principal mudou um pouco de perspectiva. No firmware do post anterior, utilizávamos um botão para variar a tensão de saída do conversor. Na versão malha fechada, a interface com o usuário é mais rebuscada, podendo ele conectar um potenciômetro no terminal PA7 (que fica na barra de pinos principal à esquerda na placa) ou um gerador de funções e realmente entrar com valores de tensão. Isso se fez importante para podermos mostrar comportamentos legais do conversor como a resposta a degrau, ou seja, a resposta do conversor a uma variação do set point.
Em boost.c e boost.h, pouca coisa mudou, pois na versão do firmware anterior eles já estavam preparados para rodar como malha fechada (ok, precisei arrumar alguns bugs na rotina do PID Filter), bastando modificar a definição BOOST_CTL_TYPE para 1 em boost.h permitindo a compilação de trechos de código necessários à operação em malha fechada.
Veremos ainda um terceiro arquivo, dpl.S que já existia mas não era adicionado ao firmware pois em malha aberta ele ficava desabilitado. Esse arquivo contém o código em assembly do PID Filter chamado periodicamente como parte da estratégia de controle. O assembly foi escolhido pois essa rotina é chamada dentro de uma interrupção tendo que ser o mais leve possível em termos de overhead, além disso o compilador GNU-ARM não parece ser muito fã de usar o set de instruções DSP do core ARM Cortex-M4, mesmo otimizando o código. Por outro lado o PID Filter final ficou tão enxuto que permitiu o uso das instruções da FPU de forma que o controlador foi feito todo em ponto flutuante.
A estratégia de controle funciona de forma simples, mas bem definida, síncrona e independente do resto do sistema. O HRTIM foi ligeiramente reprogramado de forma que agora ele dispare uma interrupção periódica com tempo igual ao seu período de repetição, dentro da ISR. O mesmo dispara a conversão A/D que também irá gerar uma interrupção, capturando o valor da tensão de saída e a colocando dentro do PID Filter, que calcula quanto de ciclo ativo o conversor deve adicionar ou retirar. O valor é saturado e remapeado para ponto fixo, e colocado no registrador de ciclo ativo do HRTIM que comanda os transistores do conversor implementando a chamada compensação ciclo-a-ciclo, muito similar a obtida (só que por hardware) no firmware de controle do LED de alto brilho usado em posts anteriores.
Assim, chegamos no diagrama de blocos abaixo que representa a execução dos módulos, repare que entre o controle de tempo real, e o de usuário não existe dependência, eles rodam em separado, ao passo que a prioridade de execução é do controle de tempo real do conversor.
O diagrama de blocos da esquerda representa o chamado “background loop”, ou seja, esse é o pedaço reservado da CPU para executar tarefas lentas ou tarefas que não necessitem de um exato ciclo de execução da CPU para completar sua execução, que é o caso da interação com o usuário. A tarefa principal tem por função monitorar o terminal PA7, e capturar a tensão que aparece ali (que pode ser um potenciômetro por exemplo, ou um gerador de função). O valor é escalado para o range do conversor boost, que é entre Vin (configurado por software) e 15,0 V (também configurado por software). Em seguida esse valor é enviado para a malha de controle através de uma função do módulo do conversor. Já o diagrama da direita representa a tarefa de tempo real dessa aplicação, ou seja, que possui execução periódica e executa sempre na mesma quantidade de tempo e que sua interrupção pode ocasionar falha no sistema, por isso sua prioridade é elevada e qualquer dependência do background loop deve ser minimizada ou cortada. Aqui, para conseguir uma base de tempo livre do efeito de Jitter (execução em diferentes pontos dá área de amostragem do sistema), amarramos o gerador de PWM de alta resolução do microcontrolador (pra quem tiver dúvidas sobre seu funcionamento, sugiro que leiam o artigo introdutório da placa discovery, e o primeiro artigo desta série) de forma que a cada ciclo de PWM ocorrido uma interrupção é gerada logo no seu inicio. Assim, obtemos a tensão corrente na saída, e de posse do valor desejado enviamos esses valores ao algoritmo de controle, que nos devolverá quanto devemos atuar sobre o PWM no próximo ciclo de atuação. Além disso a estratégia de controle conta com mecanismo de saturação e escala de valor para o range de PWM, prevenindo estouros ou problemas de wind-up no controlador PID. Ao final, o registrador do PWM é atualizado realizando fisicamente a correção da tensão de saída. Simples não?
O ajuste da malha de controle
Por ser um sistema em malha fechada, é natural que a implementação da estratégia de controle precise ser ajustada às características da planta (ou sistema) a ser controlado. Com o conversor boost não é diferente, o melhor ponto de busca é conseguido carregando corretamente os coeficientes do compensador digital que é executado a cada ciclo da malha de controle. O firmware foi estruturado para que essa parte seja feita apenas uma vez na inicialização do conversor, podendo ser modificado para ajuste em tempo de execução facilmente, e é encapsulado e sem acesso por via das funções públicas do conversor boost digital. Vamos explicar como o ajuste deve ser feito, para tal considere a linha 480 do arquivo boost.c que é exibido a seguir:
//
// SERIE DIGITAL POWER COM A DISCOVERY EMBARCADOS
//
// @file boost.c
// @brief Arquivo contendo modulo de operacao de boost converter, pode rodar em
// modo openloop ou closed loop usando a estrategia voltage mode controller
//
//
#include "stm32f30x.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_comp.h"
#include "stm32f30x_hrtim.h"
#include "stm32f30x_gpio.h"
#include "stm32f30x_dac.h"
#include "stm32f30x_adc.h"
#include "boost.h"
//
// Constantes de configuracao, internas e hardware:
//
//
// Sobre o HRTIM:
//
#define BOOST_PWM_L_PIN GPIO_Pin_10 //diodo sincrono
#define BOOST_PWM_H_PIN GPIO_Pin_11 //low side
#define BOOST_IN_PIN GPIO_Pin_8 //low side
#define BOOST_PWM_PORT GPIOA //
#define BOOST_HRTIM_LOAD_VAL(x) ((x == 0?0:((SystemCoreClock/x) * 64))) //VAlor de recarga do HRTIM
//
// sobre o VFB apenas em modo closed loop:
//
#define BOOST_VFB_PIN GPIO_Pin_3
#define BOOST_VIN_PIN GPIO_Pin_1
#define BOOST_VFB_PORT GPIOA
//
// estados de operacao do conversor boost:
//
#define BOOST_RUN 0x00000001
#define BOOST_HALT 0x00000002
#define BOOST_OFF 0x00000008
//
// Limites de operacao da arimetica usada no controle:
//
#define PID_MAX_VAL 45.0f
#define PID_MIN_VAL -45.0f
//
// Mapeia a saida do controle PID de -32.0f <---> +32.0f para
// Controlador PWM entre 0 - 18K
//
#define MAX_PWM 16384.0f
#define MIN_PWM 16.3f
#define MAP_SCALE ((MAX_PWM - MIN_PWM) / (PID_MAX_VAL - 0.0f))
#define MAP_TO_PWM(x) (uint16_t)((x + 0.1f) * MAP_SCALE)
//
// Rampa de subida do softstart:
//
#define BOOST_RAMP_TIME 20 //Hz
#define BOOST_RAMP_TICKS BOOST_SW_FREQ
#define BOOST_TICKS_PER_TIME (BOOST_RAMP_TICKS / BOOST_RAMP_TIME)
#define BOOST_SOFT_START_PWM_PERCENT ((50.0f/100.0f) * MAX_PWM)
#define BOOST_SOFT_START_PWM_INC (uint16_t)(((BOOST_SOFT_START_PWM_PERCENT) \
/ BOOST_TICKS_PER_TIME) + 1.0f )
#define BOOST_SOFT_START_PWM_VAL (uint16_t)BOOST_SOFT_START_PWM_PERCENT
//
// Variaveis estaticas:
//
static float transferRatio = 0.0f;
static const float vfbScale = 3.1187e-3f; // Scale = (Vref / ADC_Steps) * Vbus to Vfb ratio
static float inVoltage = 0.0f;
static float outVoltage = 0.0f;
static float vfbVal = 0.0f;
static uint16_t dutyCicle = 0;
static uint32_t boostFlags = BOOST_OFF;
static uint32_t adcStep = 0;
static bool inSoftStart = false;
struct loopcontrolwork
{
float *pfeed;
float *perr;
float *kp;
float *ki;
float *kd;
float *integrator;
float *prevError;
float *pOut;
};
//variaveis da malha de controle:
struct loopcontrolwork loopwork;
float kp = 0.0f;
float ki = 0.0f;
float kd = 0.0f;
float integrator = 0.0f;
float prevError = 0.0f;
float output = 0;
//
// forward references as funcoes usadas internamente:
//
#if BOOST_CTL_TYPE == 1
extern void DigitalPowerLoopFunc(void);
void DigitalPowerLoopSetCoef(float kp, float ki, float kd);
#endif
void Boost_HW_SetAnalog(void);
void Boost_HW_SetPwm(void);
void Boost_HW_SetInterrupts(void);
void Boost_HW_SetDutyCicle(uint16_t dc);
void HRTIM1_TIMB_IRQHandler(void);
void ADC1_2_IRQHandler(void);
//
// codigos de erro do buck converter:
//
typedef enum
{
knoError = 0,
koutVoltageTooLow = -1,
koutVoltageTooHigh,
kboostIsDiscontinuous,
}errorcode;
//
// Funcoes internas:
//
//
// Boost_HW_SetAnalog
//
void Boost_HW_SetAnalog(void)
{
GPIO_InitTypeDef analogIo, vin;
analogIo.GPIO_Mode = GPIO_Mode_AN;
analogIo.GPIO_Pin = BOOST_VFB_PIN;
vin.GPIO_Mode = GPIO_Mode_AN;
vin.GPIO_Pin = BOOST_VIN_PIN;
//Aciona clock do port onde esta localizado o A/D
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
//Inicializa o pino de IO desejado:
GPIO_Init(BOOST_VFB_PORT, &analogIo);
GPIO_Init(BOOST_VFB_PORT, &vin);
//Inicializa clock do ADC:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
// Configura o A/D:
ADC_InitTypeDef adcInit;
ADC_CommonInitTypeDef adcCommon;
ADC_StructInit(&adcInit);
ADC_CommonStructInit(&adcCommon);
//
// o adc vai rodar no modo mais simples, single conversion + 1 regular channel:
//
adcCommon.ADC_Clock = ADC_Clock_SynClkModeDiv1;
adcCommon.ADC_Mode = ADC_Mode_Independent;
adcInit.ADC_ExternalTrigEventEdge = ADC_ExternalTrigInjecEventEdge_None;
adcInit.ADC_AutoInjMode = ADC_AutoInjec_Disable;
adcInit.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
adcInit.ADC_DataAlign = ADC_DataAlign_Right;
adcInit.ADC_Resolution = ADC_Resolution_12b;
adcInit.ADC_NbrOfRegChannel = 1;
// Prepara o sequencer:
ADC_DeInit(ADC1);
ADC_CommonInit(ADC1, &adcCommon);
ADC_Init(ADC1, &adcInit);
ADC_RegularChannelSequencerLengthConfig(ADC1,1);
ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1,ADC_SampleTime_1Cycles5 );
// ADC Pronto para rodar.
//ADC_VoltageRegulatorCmd(ADC1, ENABLE);
uint32_t i = 0;
//Aguarda o vreg estabilizar.
for( i = 0 ; i < 0x7FFF; i++);
ADC_Cmd(ADC1, ENABLE);
}
//
// Boost_HW_SetPwm()
//
void Boost_HW_SetPwm(void)
{
GPIO_InitTypeDef gpioL, gpioH, gpioIn;
gpioL.GPIO_Pin = BOOST_PWM_L_PIN;
gpioL.GPIO_Mode = GPIO_Mode_AF;
gpioL.GPIO_OType = GPIO_OType_PP;
gpioL.GPIO_Speed = GPIO_Speed_Level_3;
gpioH.GPIO_Pin = BOOST_PWM_H_PIN;
gpioH.GPIO_Mode = GPIO_Mode_AF;
gpioH.GPIO_OType = GPIO_OType_PP;
gpioH.GPIO_Speed = GPIO_Speed_Level_3;
gpioIn.GPIO_Pin = BOOST_IN_PIN;
gpioIn.GPIO_Mode = GPIO_Mode_OUT;
gpioIn.GPIO_OType = GPIO_OType_PP;
gpioIn.GPIO_Speed = GPIO_Speed_Level_3;
//Aciona o clock do HRTIM:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_HRTIM1, ENABLE);
RCC_HRTIM1CLKConfig(RCC_HRTIM1CLK_PLLCLK);
//Aciona o pino que queremos o HRTIM:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
GPIO_Init(BOOST_PWM_PORT, &gpioL);
GPIO_PinAFConfig(BOOST_PWM_PORT, GPIO_PinSource10,GPIO_AF_13);
GPIO_Init(BOOST_PWM_PORT, &gpioH);
GPIO_PinAFConfig(BOOST_PWM_PORT, GPIO_PinSource11,GPIO_AF_13);
GPIO_Init(BOOST_PWM_PORT, &gpioIn);
GPIO_ResetBits(BOOST_PWM_PORT, BOOST_IN_PIN);
//COnfigura o HRTIM para obter-se uma saida pushpull em pwmH e L:
// OBS usamos o timer B
HRTIM1->HRTIM_MASTER.MCR = 0x00000000; //desliga o HRTIM
HRTIM1->HRTIM_MASTER.MPER = BOOST_HRTIM_LOAD_VAL(BOOST_SW_FREQ); //Periodo do master timer
HRTIM1->HRTIM_TIMERx[1].TIMxCR = 0x00000000 | ( 1 << 3); //MOdo de operacao como output compare e em push-pull
HRTIM1->HRTIM_TIMERx[1].PERxR = BOOST_HRTIM_LOAD_VAL(BOOST_SW_FREQ); //TPWM em hertz acertado
HRTIM1->HRTIM_TIMERx[1].CMP1xR = MAP_TO_PWM(32.0f); //Duty cicle minimo
HRTIM1->HRTIM_TIMERx[1].SETx1R = (1 << 2); //acerta evento de set
HRTIM1->HRTIM_TIMERx[1].RSTx1R = (1 << 3); //e evento de reset
//HRTIM1->HRTIM_TIMERx[1].SETx2R = (1 << 2);
//HRTIM1->HRTIM_TIMERx[1].RSTx2R = (1 << 3);
HRTIM1->HRTIM_TIMERx[1].DTxR = (250 << 16) | (250 << 0) | (0x01 << 10); //acerta o dead time para 20ns (obtido experim.)
HRTIM1->HRTIM_TIMERx[1].OUTxR = (1 << 8); //acerta o sistema de push pull
HRTIM1->HRTIM_COMMON.OENR = (1 << 2) | (1 << 3); //habilita as saidas PWM.
HRTIM1->HRTIM_MASTER.MCR = 0x003F0008; //Habilita o master timer
}
//
// Boost_HW_SetInterrupts()
//
void Boost_HW_SetInterrupts(void)
{
//Desliga o HRTIM1 e ADC1 como fonte de interrupcao:
NVIC_DisableIRQ(HRTIM1_TIMB_IRQn);
NVIC_DisableIRQ(ADC1_2_IRQn);
//Aciona interupcao por reset (ela que sera o trigger para disparar
//a conversao A/D)
HRTIM_ITConfig(HRTIM1, 0x01, HRTIM_TIM_IT_RST, ENABLE);
//Aciona a interrupcao do ADC:
ADC_ITConfig(ADC1, ADC_IT_EOC,ENABLE);
}
//
// Boost_HW_SetDutyCicle()
//
void Boost_HW_SetDutyCicle(uint16_t dc)
{
HRTIM1->HRTIM_TIMERx[1].CMP1xR = dc;
}
//
// BoostError()
//
void BoostError(errorcode err)
{
//
// Eh uma funcao interna mas util para debug
// se cair aqui o programa pede um halt da fonte
// e deixa o errorcode visivel para debug.
if(err != knoError)
{
BoostHalt();
while(err);
}
}
//
// DigitalPowerLoopSetCoef()
//
#if BOOST_CTL_TYPE == 1
void DigitalPowerLoopSetCoef(float _kp, float _ki, float _kd)
{
integrator = 0.0f;
prevError = 0.0f;
//prepara o set de coeficientes:
kp = _kp;
ki = _ki;
kd = _kd;
//liga a array de estados nos nodes da funcao de controle:
loopwork.pOut = &output;
loopwork.perr = &outVoltage;
loopwork.pfeed = &vfbVal;
loopwork.kp = &kp;
loopwork.ki = &ki;
loopwork.kd = &kd;
loopwork.integrator = &integrator;
loopwork.prevError = &prevError;
}
#endif
//
// DigitalPowerSaturate()
//
#if BOOST_CTL_TYPE == 1
void DigitalPowerSaturate(void)
{
//satura resultado:
if(output > PID_MAX_VAL) output = PID_MAX_VAL;
if(output < PID_MIN_VAL) output = PID_MIN_VAL;
//satura integrador:
if(integrator > PID_MAX_VAL) integrator = PID_MAX_VAL;
if(integrator < PID_MIN_VAL) integrator = PID_MIN_VAL;
//satura erro anterior:
if(prevError > PID_MAX_VAL) prevError = PID_MAX_VAL;
if(prevError < PID_MIN_VAL) prevError = PID_MIN_VAL;
}
#endif
//
// HTRIM ISR:
//
void HRTIM1_TIMB_IRQHandler(void)
{
#if BOOST_CTL_TYPE == 1
if(true != inSoftStart)
{
//Roda a malha de controle:
DigitalPowerLoopFunc();
//Satura:
DigitalPowerSaturate();
if(output < 0.0f) output = 0.0f;
//Prepara o dutycicle para correcao no proximo ciclo:
dutyCicle = MAP_TO_PWM(output);
}
else
{
//
// Se o flag de subida suave estiver ativo, ignora a malha de
// controle:
//
dutyCicle += BOOST_SOFT_START_PWM_INC;
if(dutyCicle > BOOST_SOFT_START_PWM_VAL)
{
inSoftStart = false;
}
}
#endif
//Dispara a leitura do conversor A/D:
ADC_StartConversion(ADC1);
//Atualiza novo DutyCicle calculado previamente:
Boost_HW_SetDutyCicle(dutyCicle);
//Limpa o flag de interrupcao:
HRTIM_ClearITPendingBit(HRTIM1, 0x01, 0xFFFFFFFF);
}
//
// ADC1 ISR:
//
void ADC1_2_IRQHandler(void)
{
//Limpa fonte de interupcao:
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
//Toma o resultado da conversao A/D
vfbVal = vfbScale * (float)ADC_GetConversionValue(ADC1);
}
//
// conversor boost, funcoes publicas:
//
//
// BoostInit()
//
void BoostInit(float inputVoltage, float outputVoltage)
{
//
// Checa se a relacao Vo/Vi eh valida:
//
if(inputVoltage > outputVoltage)
{
errorcode err = koutVoltageTooLow;
BoostError(err);
}
//
// Checa se a tensao de saida desejada eh valida:
//
if(outputVoltage > BOOST_MAX_VOLTAGE)
{
errorcode err = koutVoltageTooHigh;
BoostError(err);
}
#if BOOST_CTL_TYPE == 0
//
// Determina o duty cicle
// nota, estamos trabalhando com um buck em mondo continuo
// Vo/Vi = 1 / (1 - D)
transferRatio = 1.0f - (inputVoltage / outputVoltage);
dutyCicle = (uint16_t)(transferRatio * (float)BOOST_HRTIM_LOAD_VAL(BOOST_SW_FREQ));
inVoltage = inputVoltage;
outVoltage= outputVoltage;
#else
//Malha fechada, seleciona o setpoint inicial
//E ignora a relacao VO/VI, sera compensado automaticamente.
//E configura o PID:
inVoltage = inputVoltage;
outVoltage = outputVoltage;
if(inVoltage > BOOST_MAX_VOLTAGE) inVoltage = BOOST_MAX_VOLTAGE;
DigitalPowerLoopSetCoef(15.0, 0.80, 0.1);
#endif
//
// Inicializa o hw do boost converter mas sem operar:
//
Boost_HW_SetAnalog();
Boost_HW_SetPwm();
Boost_HW_SetInterrupts();
//
// Sistema inicialmente em halt:
//
BoostHalt();
boostFlags = BOOST_HALT;
}
//
// BoostStart()
//
void BoostStart(void)
{
#if BOOST_CTL_TYPE == 0
Boost_HW_SetDutyCicle(dutyCicle);
#else
const float tmp = 0.0f;
dutyCicle = MAP_TO_PWM(tmp);
Boost_HW_SetDutyCicle(dutyCicle);
#endif
//
// Com o dutycicle pre calculado da um trigger no pwm
//
HRTIM1->HRTIM_MASTER.MCR = 0x003F0008;
Boost_HW_SetInterrupts();
//Libera VIN:
GPIO_SetBits(BOOST_PWM_PORT, BOOST_IN_PIN);
boostFlags = BOOST_RUN;
NVIC_SetPriority(HRTIM1_TIMB_IRQn, 250);
NVIC_SetPriority(ADC1_2_IRQn, 251);
NVIC_EnableIRQ(HRTIM1_TIMB_IRQn);
NVIC_EnableIRQ(ADC1_2_IRQn);
}
//
// BoostVoltageChange()
//
void BoostVoltageChange(float newVoltage)
{
// Checa se a relacao Vo/Vi eh valida:
//
if(inVoltage > newVoltage)
{
errorcode err = koutVoltageTooLow;
BoostError(err);
}
//
// Checa se a tensao de saida desejada eh valida:
//
if(newVoltage > BOOST_MAX_VOLTAGE)
{
errorcode err = koutVoltageTooHigh;
BoostError(err);
}
#if BOOST_CTL_TYPE == 0 //apenas calcula o dc em caso de malha aberta
//
// Recalcula parametros do boost:
//
transferRatio = 1.0f - (inVoltage / newVoltage);
dutyCicle = (uint16_t)(transferRatio * (float)BOOST_HRTIM_LOAD_VAL(BOOST_SW_FREQ));
//Malha fechada, o PID se encarrega disso:
outVoltage = newVoltage;
//
// Modifica tensao de saida:
//
Boost_HW_SetDutyCicle(dutyCicle);
#else
//COntrole em malha fechada, apenas modifica o setpoint
//E ignora a relacao VO/VI, sera compensado por SW.
outVoltage = newVoltage;
#endif
}
//
// BoostHalt()
//
void BoostHalt(void)
{
if(boostFlags & BOOST_HALT)
{
//
// Ja esta inoperante.
//
return;
}
//
// derruba o master hrtim:
//
HRTIM1->HRTIM_MASTER.MCR = 0;
//Derruba VIN
GPIO_ResetBits(BOOST_PWM_PORT, BOOST_IN_PIN);
//
// derruba as interupcoes:
//
NVIC_DisableIRQ(HRTIM1_TIMB_IRQn);
NVIC_DisableIRQ(ADC1_2_IRQn);
boostFlags = BOOST_HALT;
inSoftStart = false;
dutyCicle = 0;
}
Nessa linha ocorre a chamada da função DigitalPowerLoopSetCoef. Esta recebe três parâmetros convenientemente chamados de KP, KI, e KD, que fazem referência ao conhecido conjunto de coeficientes do compensador Proporcional-Integral-Derivativo, ou PID. Como já mencionado antes, ele é o elemento central da estratégia de controle do nosso conversor, e foi inteiramente implementado em assembly para aproveitar ao máximo possível a unidade de ponto flutuante do Cortex-M4 que está na discovery a fim de garantir bom desempenho e baixa penalidade de execução. Mas voltando aos coeficientes, esses são quem devem ser ajustados para garatir que as perturbações ocorridas na tensão de saída sejam compensadas o mais rápido possível, mantendo também baixo erro de estado estacionário e estabilidade incondicional, ou seja, em qualquer situação de comportamento do sistema. Postulado isso, vamos ver o que acontece com a malha de controle sem qualquer ajuste, com um ganho proporcional (KP) igual a 1.0 sob um degrau gerado no set point, vejam a imagem abaixo:
Como podemos ver, após um degrau no setpoint (traço verde), sem o ajuste a malha de controle praticamente não atua, causando esse erro de estado estacionário enorme, além da velocidade de correção que está em perfeita incógnita, pois não vimos sequer a tensão de saída (traço amarelo variar). Vamos melhorar um pouco a correção agora aumentando o ganho proporcional para um valor que encotrei previamente, em torno de 25.0. Vejamos o que acontece:
Com o ganho ja obtemos uma melhora na resposta, veja que os sinais param quase no mesmo valor ao entrar em regime estacionário, a velocidade de correção está suficientemente boa a partir do momento que o tempo morto é respeitado (lembre – se que temos pelo menos 10 ms ou mais até que uma nova tensão no A/D seja capturada), mas o erro de estado estacionário ainda permanece seguida de uma pequena oscilação de baixa amplitude. Para melhorar um pouco mais vamos usar a ação integral para tentar eliminar o erro estacionário, assim, vamos iniciar com mais um valor que obtive experimentalmente, 0.729.
Vejam que o erro de estado estacionário ficou bem minimizado, com o valor de realimentação muito próximo ao set point. Um problema que pode ocorrer com os ganhos proporcional e integral com valor elevado é a aparição do fenômeno de sobressinal, ou seja, é quando a tensão de saída vai pra um valor acima do desejado e oscila por um tempo até que a ação integral consiga surtir efeito e a oscilação diminua. Vamos experimentalmente colocar o ganho proporcional e integral em valores altos, 50.0 e 25.0 respectivamente. Vamos ver o resultado:
Esse é o cuidado que devemos ter ao elevar os ganhos proporcional e integral. Se mal dimensionados podem levar o sistema para essa oscilação desagradável da imagem, e o sistema à instabilidade permanente. Acredite, com esses valores você vai escutar um chiado bem desagradável. Vamos agora aplicar as três ações obtidas experimentalmente, que deram um ajsute final bem interessante. Vejam essa última reposta a degrau com KP, KI e KD sendo respectivamente 8.5, 0.729 e 43.93:
Vejam que além de melhorar problemas com overshoot, praticamente eliminamos o efeito de zona morta, tendo compesação tão logo haja uma variação do conversor A/D ou alguma perturbação na tensão de saída. Essa resposta pode ser melhorada mais ainda caso o usuário conheça bem o modelo da planta do conversor usado. E por falar em modelo, creio que o leitor agora vá me questionar de onde eu tirei tais valores para ajuste dos coeficientes do algoritmo de controle. Bem posso dizer que o modelo por espaço de estados (que pode ser lido rapidamente aqui) já esteja de cor em minha cabeça, porém modelar sistemas não lineares como conversores de potência está fora do escopo da série (que está mais pra algo introdutório). Porém é possível utilizar uma técnica empírica que ajuda a extrair os coeficientes de forma experimental, com ela cheguei a valores muito próximos do obtido pela simulação com MatLab. Trata-se do método Ziegler – Nichols, explicar de onde ele vem também está fora do escopo da série, o que é importante citar é que esse método fornece um bom ajuste inicial como ponto de partida para uma melhoria mais fina nos coeficientes, ou como no nosso caso podemos ter a feliz coincidência dos valores caírem bem próximos aos de coeficientes obtidos analiticamente. Trata – se de um método intuitivo, para tal considere a Tabela abaixo:

E abaixo listamos como fazer o uso dessa tabela. Veja que os coeficientes resultantes dela podem ser inseridos diretamente na chamada da função DigitalPowerLoopSetCoef em boost.c (linha 480). O termo dT que aparece em KI e KD trata-se de uma normalização para seu uso em sistemas discretos (como no nosso caso), então seu valor pode ser obtido tomando o inverso da frequência de chaveamento configurada no firmware (veja boost.h), assim prossigamos:
- Primeiramente, com o sistema em funcionamento, aumente o ganho KP até que o sistema comece a ter em sua saída uma oscilação sem amortecimento e constante, lembre – se de deixar KI e KD zerados;
- De posse desse valor, ele será o parâmetro Kc que pode ser usado na Tabela 1. Para busca do ganho proporcional de acordo com o tipo de compensador desejado;
- Ainda sem ajustar a malha, volte ao sistema que ainda está oscilando, meça com o osciloscopio ou frequencímetro o valor do período dessa oscilação. Esse valor é o parâmetro Pc que também poderá agora ser usado na tabela 1;
- Use Kc e Pc para buscar os valores de KP, KI, KD (ou a combinação que desejar de acordo com o compesador necessário) com o uso da tabela 1, calcule os valores e coloque diretamente na chamada da função DigitalPowerLoopSetCoef em boost.c (linha 480).
A partir desse ponto o controlador estará ajustado de forma razoável, e o usuário poderá brincar com os coeficientes afim de obter a melhor resposta. É sábio lembrar que esse método é o mais básico para ajuste de compensador, outros métodos analíticos podem ser empregados sempre com problemas com os tradicionais root locus, e por resposta em frequência (esse meu favorito para uso em digital power).
O conversor boost em funcionamento
Bem agora que já ajustamos nosso conversor, nada melhor que ver um vídeo da aplicação em ação, reparem que o traço verde é a tensão de entrada escalada e o traço amarelo a tensão de saída escalada. Em determinado ponto colocarei a ponta de prova onde o leitor irá ver a tensão de saída real na carga. Aliás convêm falar que a carga usada possui uma impedância de 100 ohms com uma capacitância de 10 nF para gerar uma condição de inrush da nossa planta, afim de mostrar nosso conversor próximo a aplicações do mundo real. Submeti o conversor a entradas de diferentes formas de onda para verificar sua resposta dinâmica, o resultado é bem legal, e pode ser visto no video a seguir:
Conclusão
Bem aqui chegamos ao fim de mais uma série cobrindo o mundo as vezes místico do controle digital aplicado em conversores de energia. Foi uma série introdutória, o assunto é bem extenso, mas conseguimos atingir o objetivo de dar uma visão de como funciona para eventualmente cobrirmos tópicos mais complexos. O kit fornecido pela STMicroelectronics atendeu muito bem em ilustrar os experimentos aqui desenvolvidos além de, ao mesmo tempo, mostrar quais inovações a ST vem trazendo para o campo de digital power como seu poderoso HRTIM para geração de PWM de altíssima resolução. Conseguimos em torno de 15bits operando a uma elevada frequência de chaveamento digna de circuitos integrados analógicos, além disso os comparadores ultra rápidos combinados com o DAC permitiram realizar um controle de malha fechada totalmente por hardware para o LED de alto brilho feita nos artigos anteriores. A ST também se superou no quesito conversor A/D, provendo um que converte a taxas impressionantes de até 5 Milhões de amostras por segundo permitindo o uso das configurações mais simples até mesmo em um software um pouco mais agressivo como o controlador de tempo real do conversor boost. Enfim, uma solução interessante e com custo competitivo ante a outras fabricantes de semicondutores especializadas em digital power. A série termina, mas a discussão está aberta, e a sugestão que deixo ao leitor é que utilize os conhecimentos obtidos e tente realizar um conversor buck-boost sem inversão afim de prover um poderoso regulador de tensão para qualquer situação. Com a discovery da ST a coisa fica mais fácil pois o hardware do conversor está prontinho, bastando pensar no firmware. Bem caro leitor, por enquanto é isso, aguardo você nas próximas séries e artigos, afinal 2016 está só começando.
Referências
Manual de hardware da placa discovery STM32F334
ERICKSON, W. Robert – Fundamentals of Power Electronics , 2001
AN4885 – High brightness LED dimming using the STM32F334 Discovery kit
OpenSTM32 – System Workbench for STM32
Manual de referência do STM32F334
Repositório no github com o firmware de controle boost closed loop





