Saturday, June 3, 2017

Generating PWM Signal Using PIC Micro-controller

Hello Everyone, few days back i am working on a project which requires controlling the speed of a motor depending upon certain conditions, in this i have to increase or decrease the power delivered to motor, based on the input condition from user, and while doing that i learned about the PWM, Pulse Width Modulation technique, this is the technique of controlling the amount of power delivered to an electronic load by turning on-off digital signal.
I think most of you guys are aware of PWM and some of you might wanted to use the PWM.
So in this post i will use PIC18F micro-controller and use it's internal PWM Module (CCP Module) to generate a PWM signal, and then i will further control the duty cycle of this signal based on the input from the keypad connected to the PIC micro-controller.
I will use simulation environment for this post, but that doesn't means that the project will not work on real hardware, as i have tested this module in a complex project, which is little bit harder for me to share, and that's why i extracted the PWM module from that project to share with you guys.
The following is the simulation diagram of this simple project.
Simulation Diagram
The following components are present in this simulation diagram.

  • PIC18F45K50 micro-controller, this is used for generating PWM, you can use any PIC16F or PIC18F, the same code can be used with little or no changes.
  • 16X2 Lcd, is used to display the duty cycle values, this is working in 4-bit mode and that too using busy flag check, click here to read all the post related to lcd.
  • 2 Push Buttons, one for increasing and one for decreasing the duty cycle value, click here to read about keypad interfacing.
  • Plus we have frequency counter and oscilloscope to view the PWM signal
So in this small project, we have lots of things to learn.
To make things simpler, i have made four functions, and using that you can easily configure the PWM-1 module of the PIC, you can use the same functions for other PWM modules of your PIC. So these functions are as follow:

boolean PWM1_Init(const u32_t frequency);
void PWM1_Set_Duty(u8_t duty_ratio);
void PWM1_Start();
void PWM1_Stop();

Now we will look into these functions one by one.

1) Initialization of PWM Module

boolean PWM1_Init(const u32_t frequency);

This function takes desired PWM frequency as input argument and configure the PR2 register and determine the timer prescaler required, if unsuccessful this function returns false.
The following formula is used for calculation of PR2 register.

/*
 * Important Formulas
 * PWM Period = [(PR2)+1]*4*TOSC*(TMR2 Prescale Value)
 * TOSC = 1/FOSC
 * PWM Period = [(PR2)+1]*4*(TMR2 Prescale Value)/FOSC
 * PWM Frequency = FOSC/([(PR2)+1]*4*(TMR2 Prescale Value))
 * PR2+1 = FOSC/(PWM Frequency*4*TM2 Prescale Value)
 * PR2 = FOSC/(PWM Frequency*4*TM2 Prescale Value) - 1
 * 
 */

2) Configuring Duty Cycle

void PWM1_Set_Duty(u8_t duty_ratio);

This function takes duty cycle value in percentage and produce the desired duty cycle signal on the PWM pin. This function configures the CCPR1L register and CCP1CON (5th and 4th bit). The formula used is as follow:
/*
 * Pulse Width = (CCPRxL:CCPxCON<5:4>*TOSC*(TMR2 Prescale Value)
 * 
 * Duty Cycle Ratio = Pulse Width/ PWM Period
 * Duty Cycle Ratio = (CCPRxL:CCPxCON<5:4>*TOSC*(TMR2 Prescale Value)
 *                    ------------------------------------------------
 *                        [(PR2)+1]*4*TOSC*(TMR2 Prescale Value)
 * Duty Cycle Ratio = (CCPRxL:CCPxCON<5:4>/([(PR2)+1]*4)
 */


3) Starting the PWM Signal

void PWM1_Start();

This function will start the PWM Signal on the PWM pin, which is CCP1 pin in this case.

4) Stopping the PWM Signal

void PWM1_Stop();

This function will stop the PWM Signal on the PWM pin, which is CCP1 pin in this case.

Have a look at this video, to see the complete simulation.

The following images shows the PWM signal on Oscilloscope and Duty Cycle value on LCD.
41% Duty Cycle and 2KHz Frequency
41% Duty Cycle
25% Duty Cycle and 2KHz Frequency
25% Duty Cycle
39% Duty Cycle and 2KHz Frequency
39% Duty Cycle
The main part of this project is as follow, and to download the complete code and simulation file click here.

/*
 * File:   main.c
 * Author: Embedded Laboratory
 *
 * Created on June 3, 2017, 10:24 PM
 */
#include "config.h"
#include "lcd_16x2.h"
#include "keypad.h"
#include "pwm.h"

// Tasks
Task_s lcdTask        = { TRUE,   1000u,   0u };
Task_s keyTask        = { TRUE,   30u,    0u };

static char lcd_msg[LCD_BUFFER_LEN] = { 0 };

static u8_t keyPress = 0;

void Initialize_IO( void );

void main()
{
  const u32_t frequency = 2000u;
  u8_t duty_cycle = 50u;
  Initialize_IO ();
  
  PWM1_Init (frequency);
  PWM1_Set_Duty (duty_cycle);
  PWM1_Start ();
  
  LCD_Print_Line (0, (char*)"Embedded");
  LCD_Print_Line (1, (char*)"      Laboratory");
  LCD_Update ();
  
  while(1) 
  {
    // Get Key Task
    // if( millis() - keyTask.timestamp > keyTask.period )
    {
      keyPress = getKey ();
      if (keyPress!=NO_KEY && keyPress < MAX_KEY_SIZE )
      {
        keyPress = KeyMap[keyPress-1];
      }
    }
    
    // LCD Update Task
    if( (millis() - lcdTask.timestamp > lcdTask.period)
         || (keyPress == UP_KEY || keyPress == DOWN_KEY) )
    {
      lcdTask.timestamp = millis();
      if ( keyPress ==  UP_KEY )
      {
        LCD_Print_Line (0, (char*)"DUTY CYCLE:");
        if ( duty_cycle < 100u )
        {
          duty_cycle++;
          PWM1_Stop ();
          PWM1_Set_Duty (duty_cycle);
          PWM1_Start ();
        }
        sprintf (lcd_msg, "%d", duty_cycle);
        LCD_Print_Line (1, lcd_msg);
      }
      else if( keyPress == DOWN_KEY )
      {
        LCD_Print_Line (0, (char*)"DUTY CYCLE:");
        if (duty_cycle > 0 )
        {
          duty_cycle--;
          PWM1_Stop ();
          PWM1_Set_Duty (duty_cycle);
          PWM1_Start ();
        }
        sprintf (lcd_msg, "%d", duty_cycle);
        LCD_Print_Line (1, lcd_msg);
      }
      LCD_Update ();
    }
  }
}

void Initialize_IO( void )
{
  // Select 16MHz Internal Oscillator
  OSCCONbits.IRCF = 0x07;             // From 1MHz to 16MHz
  
  Initialize_Keypad ();
  InitTimer0 ();
  LCD_Init ();
}

5 comments:

  1. Finally I found the mikroC's like PWM1_* funcs for MPLAB X and an awesome post regarding PWM. Thanks for sharing it! :-)

    ReplyDelete
    Replies
    1. I am glad you like it.
      Yes i normally try to keep functions like mikroc.

      Delete
  2. Hi, this is very good guide thanks and can I ask you for sine wave system guide pls cause every one need it in this days please.

    ReplyDelete
    Replies
    1. Thanks.
      You can use RC circuit to generate sine wave after this setup.
      But with this approach you will not have range on complete coverage.
      You can use DAC to generate sine wave and apart from this there are several waveform generator ics available, you can use them also.

      Delete
  3. Its good sir but can you give me programming code of 8051 mc for this same project?

    ReplyDelete