Fork me on GitHub

26/01/14

PIC: PWM via Software

PIC: PWM via Software



Vários microcontroladores possuem PWM internos, que são fáceis de configurar e, uma vez inicializado continuará funcionando por conta própria. As vezes você precisa de mais canais PWM ou o microcontrolador não possui canal PWM. Neste post mostrarei como implementar via software vários canais PWM utilizando apenas o TIMER1 do microcontrolador.

Através da interrupção do TIMER1, o valor de uma variável é incrementada e comparada com o valor da largura de pulso que você deseja para um determinado sinal. Se ambos valores forem iguais, a saída do sinal será 1( ou 0 ). Quando a variável estourar ( ex.: > 255 ) a saída do sinal será 0 ( ou 1 ).

No código utilizei apenas três canais, para isso criei três "registros" que configuram cada canal.


typedef struct
{
        union 
        {
           char EN:1;        //Habilita/Desabilita
           char INV:1;       //Inverter
           char:6;
           char DC:8;       //Duty Cycle
        };
} PWM;

PWM PWM1 absolute 0x22;
PWM PWM2 absolute 0x24;
PWM PWM3 absolute 0x26;


o bit EN, habilita ou desabilita o canal.
o bit INV, inverte o sinal. Se "0" o valor do duty cycle será para o nível baixo. Se "1" o valor do duty cycle será para o nível alto.
o registro DC contém o valor do duty cycle do sinal.

CÓDIGO DA BIBLIOTECA
obs.: clock utilizado é de 16MHz
unsigned Timer1 at TMR1L;

typedef struct
{
        union 
        {
           char EN:1;        //Enable
           char INV:1;       //Inverse
           char:6;
           char DC:8;       //Duty Cycle
        };
} PWM;

PWM PWM1 absolute 0x22;
PWM PWM2 absolute 0x24;
PWM PWM3 absolute 0x26;

void Soft_PWM_Init()
{
    PWM1_Channel_Dir = 0;
    PWM2_Channel_Dir = 0;
    PWM3_Channel_Dir = 0;
    PWM1_Channel = 0;
    PWM2_Channel = 0;
    PWM3_Channel = 0;
    
    
    PWM1.EN = 0;
    PWM2.EN = 0;
    PWM3.EN = 0;
    PWM1.INV = 0;
    PWM2.INV = 0;
    PWM3.INV = 0;
    PWM1.DC = 127;
    PWM2.DC = 127;
    PWM3.DC = 127;
    
    T1CON = 1;
    Timer1 = 0xFF38;
    TMR1IE_Bit = 1;
    GIE_Bit = 1;
    PEIE_Bit = 1;
}

void Soft_PWM_Start( char channel )
{
     ((PWM*)&PWM1)[channel].EN = 1;
}

void Soft_PWM_Stop( char channel )
{
     ((PWM*)&PWM1)[channel].EN = 0;
}

void Soft_PWM_Set_Duty( char channel, char DutyCycle )
{
     ((PWM*)&PWM1)[channel].DC = DutyCycle;
}

void Interrupt()
{
static unsigned count = 0;
    if( TMR1IF_Bit )
    {
        TMR1IF_Bit = 0;
        Timer1 = 0xFF38;
        count++;
        
        if( PWM1.EN )
        {
           if( count == PWM1.DC )
           {
               PWM1_Channel = !PWM1.INV;
           }
           else if( count > 255 )
           {
               PWM1_Channel = PWM1.INV;
           }
        }
        
        if( PWM2.EN )
        {
           if( count == PWM2.DC )
           {
               PWM2_Channel = !PWM2.INV;
           }
           else if( count > 255 )
           {
               PWM2_Channel = PWM2.INV;
           }
        }
        
        if( PWM3.EN )
        {
           if( count == PWM3.DC )
           {
               PWM3_Channel = !PWM3.INV;
           }
           else if( count > 255 )
           {
               PWM3_Channel = PWM3.INV;
           }
        }
        
       count &= 0xFF;
    }
}

EXEMPLO:




MikroC PRO PIC
//Habilitar as seguintes bibliotecas:
// - ADC

//MCU: PIC16F877A
//CLOCK: 16MHz

sbit PWM1_Channel at RB0_Bit;
sbit PWM2_Channel at RB1_Bit;
sbit PWM3_Channel at RB2_Bit;
sbit PWM1_Channel_Dir at TRISB0_Bit;
sbit PWM2_Channel_Dir at TRISB1_Bit;
sbit PWM3_Channel_Dir at TRISB2_Bit;

//Copie e cole o código da biblioteca aqui!!!

void main()
{
char i;
    CMCON = 7;
    
    ADC_Init();
    
    Soft_PWM_Init();
    Soft_PWM_Set_Duty( 0, 90 );
    Soft_PWM_Set_Duty( 1, 100 );
    Soft_PWM_Set_Duty( 2, 200 );
    Soft_PWM_Start( 0 );
    Soft_PWM_Start( 1 );
    Soft_PWM_Start( 2 );
    
    while(1)
    {
        for( i = 0; i < 3; i++ )
        {
           Soft_PWM_Set_Duty( i, ADC_Get_Sample(i) >> 2 );
        }
    }
}

13 comentários:

  1. Amigo eu preciso criar 6 canais PWM idênticos, mas com defasamento. Vc teria algum exemplo no CCS pra isso?

    ResponderExcluir
  2. como que eu vou saber qual é o valor do de frequência do pwm.
    Por ex gostaria de um pwm 10 kHz

    PWM_INIT(10000);

    como fica no seu código.

    ResponderExcluir
    Respostas
    1. Não tem como. Como esta gerando sinal via hardware, a frequência sera baixa.

      Excluir
    2. Nem uma base de em que faixa de frequência seria gerado este sinal PWM?

      Excluir
  3. O código abaixo possibilita a defasagem de 120 graus entre dois sinais PWM. utilizando filtros passa baixa, é possível tornar os sinais pwm, em senoides de 60Hz.

    int seno [36] = {127,150,172,192,210,226,239,248,254,255,254,248,239,226,210,192,172,150,127,106,84,64,46,30,17,8,2,0,2,8,17,30,46,64,84,106};
    int seno_b [36] = {254,248,239,226,210,192,172,150,127,106,84,64,46,30,17,8,2,0,2,8,17,30,46,64,84,106,127,150,172,192,210,226,239,248,254,255};
    //int seno_c [36] = {64,46,30,17,8,2,0,2,8,17,30,46,64,84,106,127,150,172,192,210,226,239,248,254,255,254,248,239,226,210,192,172,150,127,106,84};
    void main() {
    trise =0;
    PWM1_Init(10000); PWM2_init(10000);
    PWM1_start(); PWM2_start();
    while(1){
    PWM1_Set_Duty(seno[0]);PWM2_Set_Duty(seno_b[0]); delay_us(463);
    PWM1_Set_Duty(seno[1]);PWM2_Set_Duty(seno_b[1]); delay_us(463);
    PWM1_Set_Duty(seno[2]);PWM2_Set_Duty(seno_b[2]); delay_us(463);
    PWM1_Set_Duty(seno[3]);PWM2_Set_Duty(seno_b[3]); delay_us(463);
    PWM1_Set_Duty(seno[4]);PWM2_Set_Duty(seno_b[4]); delay_us(463);
    PWM1_Set_Duty(seno[5]);PWM2_Set_Duty(seno_b[5]); delay_us(463);
    PWM1_Set_Duty(seno[6]);PWM2_Set_Duty(seno_b[6]); delay_us(463);
    PWM1_Set_Duty(seno[7]);PWM2_Set_Duty(seno_b[7]); delay_us(463);
    PWM1_Set_Duty(seno[8]);PWM2_Set_Duty(seno_b[8]); delay_us(463);
    PWM1_Set_Duty(seno[9]);PWM2_Set_Duty(seno_b[9]); delay_us(463);
    PWM1_Set_Duty(seno[10]);PWM2_Set_Duty(seno_b[10]); delay_us(463);
    PWM1_Set_Duty(seno[11]);PWM2_Set_Duty(seno_b[11]); delay_us(450);
    PWM1_Set_Duty(seno[12]);PWM2_Set_Duty(seno_b[12]); delay_us(463);
    PWM1_Set_Duty(seno[13]);PWM2_Set_Duty(seno_b[13]); delay_us(463);
    PWM1_Set_Duty(seno[14]);PWM2_Set_Duty(seno_b[14]); delay_us(463);
    PWM1_Set_Duty(seno[15]);PWM2_Set_Duty(seno_b[15]); delay_us(463);
    PWM1_Set_Duty(seno[16]);PWM2_Set_Duty(seno_b[16]); delay_us(463);
    PWM1_Set_Duty(seno[17]);PWM2_Set_Duty(seno_b[17]); delay_us(463);
    PWM1_Set_Duty(seno[18]);PWM2_Set_Duty(seno_b[18]); delay_us(463);
    PWM1_Set_Duty(seno[19]);PWM2_Set_Duty(seno_b[19]); delay_us(463);
    PWM1_Set_Duty(seno[20]);PWM2_Set_Duty(seno_b[20]); delay_us(463);
    PWM1_Set_Duty(seno[21]);PWM2_Set_Duty(seno_b[21]); delay_us(463);
    PWM1_Set_Duty(seno[22]);PWM2_Set_Duty(seno_b[22]); delay_us(463);
    PWM1_Set_Duty(seno[23]);PWM2_Set_Duty(seno_b[23]); delay_us(463);
    PWM1_Set_Duty(seno[24]);PWM2_Set_Duty(seno_b[24]); delay_us(463);
    PWM1_Set_Duty(seno[25]);PWM2_Set_Duty(seno_b[25]); delay_us(463);
    PWM1_Set_Duty(seno[26]);PWM2_Set_Duty(seno_b[26]); delay_us(463);
    PWM1_Set_Duty(seno[27]);PWM2_Set_Duty(seno_b[27]); delay_us(463);
    PWM1_Set_Duty(seno[28]);PWM2_Set_Duty(seno_b[28]); delay_us(463);
    PWM1_Set_Duty(seno[29]);PWM2_Set_Duty(seno_b[29]); delay_us(463);
    PWM1_Set_Duty(seno[30]);PWM2_Set_Duty(seno_b[30]); delay_us(463);
    PWM1_Set_Duty(seno[31]);PWM2_Set_Duty(seno_b[31]); delay_us(463);
    PWM1_Set_Duty(seno[32]);PWM2_Set_Duty(seno_b[32]); delay_us(463);
    PWM1_Set_Duty(seno[33]);PWM2_Set_Duty(seno_b[33]); delay_us(463);
    PWM1_Set_Duty(seno[34]);PWM2_Set_Duty(seno_b[34]); delay_us(463);
    PWM1_Set_Duty(seno[35]);PWM2_Set_Duty(seno_b[35]); delay_us(463);
    }
    }

    ResponderExcluir
  4. Qual é a frequência máxima que eu consigo com seu código e como que eu mudo para esse valor
    Há parabéns pelo blog.

    ResponderExcluir
  5. Tiago Henrique, voce poderia postar algum exemplo de aplicaçao + codigo, de algum controle PWM com o Arduino UNO Rev 3?

    ResponderExcluir
  6. Boa Noite!
    Obrigador por dividir os seus conhecimentos conosco.
    Poderia dar um exemplo de um filtro passa baixo para criar o senoides de 60Hz.
    Qual compilador para o código exemplo.
    Atenciosamente
    Carlos Roberto

    ResponderExcluir
  7. Peço desculpas passei pela informação do compilador fica valendo só o exemplo do filtro passa baixo.
    Atenciosamente
    Carlos Roberto

    ResponderExcluir
  8. Eu conseguiria variar de 10Hz a 1.5khz com este programa seu? Obrigado

    ResponderExcluir
  9. Tiago , como faço para um pwm começar em nivel low, e outro right.

    ResponderExcluir

Postagens Relacionadas!!