Introduction
Pulse Width Modulation (PWM) generate an analog output waveform from microcontroller output pin. Enhanced Capture/Compare/PWM (CCP1) peripheral of PIC16F887 is able to generate analog output signal.
![]() |
| Block diagram of PWM module of CCP1 |
DIY PCB Project, CPLD/FPGA Coding with VHDL, Arduino Programming Tutorials, PLC Programming Tutorials, PIC Microcontroller Programming Tutorials and Projects, AVR Microcontroller Programming Tutorials, etc.
Pulse Width Modulation (PWM) generate an analog output waveform from microcontroller output pin. Enhanced Capture/Compare/PWM (CCP1) peripheral of PIC16F887 is able to generate analog output signal.
![]() |
| Block diagram of PWM module of CCP1 |
Servo motor is a type of motor that rotate to specific degree assigned by a controller. Some motor rotates within 180 degree, while others rotate up to 360 degree.
![]() |
| A sample of servo motor from Ali Express |
Supply voltage is typically a +5V DC supply from microcontroller board. Its control signal is a TTL type logic level with a frequency of about 50Hz. Duty time of control signal is between 1.5ms to 2ms that rotate its angle between 0 and 180 degrees.
Controller needs to generate Pulse Width Modulation (PWM) signal with a frequency around 50Hz, with a variation of high time between 1.5ms to 2ms to rotate this motor. Without using PWM module of controller, programmer can create a software PWM routine instead. However this method has a timing latency.
![]() |
| Simulation of this program |
A POT connects to AN0 of PIC16F887 that's channel 0 of ADC module. Controller reads and converts this ADC value into timing value of output PWM signal generates a pin RD0. This pin controls the angle of rotation of servo motor.
MikroC Program:
#define Servo PORTD.RD0 #define Max_Degree 2000 unsigned ADC_Value; unsigned Servo_Value; void main() { int i,j; PORTD=0x00; PORTA=0x00; TRISA=0x01; ANSEL=0x01; TRISD=0x00; OSCCON=0x70; ADC_Init(); while(1) { ADC_Value=ADC_Get_Sample(0); Servo_Value=ADC_Value/4; Servo=1; for(i=0;i<Servo_Value;i++) { delay_us(1); } Servo=0; for(j=0;j<20000-Servo_Value;j++) { delay_us(1); } delay_ms(5); } }
Click here to download this example from GitHub.
Input signal to ADC channel of a microcontroller is other than DC voltage source. It typically range from 0 to 5V DC, as a conventional microcontroller, and voltage reference of ADC module are around +5V. Measuring input voltage within this range is straight forward using a calculation of ADC result and its step voltage size.
![]() |
| A DC +50V DVM using ADC of PIC16F887 MikroC - Program sample |
| Reference circuit from Wikipedia |
Dividing factor depends on value of these two resistor that we can not give its detail and formula here. Using this method we can design a simple digital volt meter (DVM) with a maximum range of +50V DC.
In this example we select Z1 = 100kOhm and Z2 = 10kOhm. This circuit gives an output dividing factor of 0.09 at Vout. So whenever input voltage Vin = 50V then output voltage 50V * 0.09 that is around 5V, which is the maximum input voltage to ADC input channel.
![]() |
| Circuit Diagram |
In this program timer0 is used to schedule ADC reading before it's converted to actual DC input voltage. Using its 8MHz internal oscillator source, timer0 interrupt creates a timer tick for scheduling ADC reading. Whenever the total tick equal to 30 it's a one second timing. Hence ADC reading is activated for every 30 timer0 tick (one second).
However multiplexing display doesn't use this timer tick due total lines of C source code.
MikroC source code:
#define STEP_SIZE 0.0048 #define ONE_SECOND 30 #define RATE 3 #define DIGIT1 PORTA.RA4 #define DIGIT2 PORTA.RA5 #define DIGIT3 PORTA.RA6 #define DIGIT4 PORTA.RA7 unsigned ADC_Value,V_Measure; float Voltage; char int_count; char LED[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; bit ADC_RUN; void interrupt() { if(INTCON.T0IF) { int_count++; if(int_count==ONE_SECOND) { int_count=0; ADC_RUN=1; } } INTCON.T0IF=0; } void PORT_SETUP() { PORTA=0x00; PORTB=0x00; TRISA=0x01; // RA0 IS ANALOG IN PUT TRISB=0x00; // PORTB is OUTPUT ANSEL=0x01; // SELECT AN0 ANSELH=0x00; // OTHERS ARE DIGITAL IO OSCCON|=0x07; // SELECT 8MHz INT RC } void Timer0_Setup(){ OPTION_REG.T0CS=0; // SELECT FOSC/4 OPTION_REG.PSA=0; // SELECT TIMER0 PRESCALER OPTION_REG|=0x07; // SELECT 1:256 PRESCALER } void interrupt_setup() { INTCON.GIE=1; INTCON.T0IE=1; INTCON.T0IF=0; } void Read_Temp () { if(ADC_RUN=1) { ADC_Value=ADC_Get_Sample(0); // READ AN0 delay_ms(10); Voltage=ADC_Value*STEP_SIZE; // Convert to Decimal [Volt] } ADC_RUN=0; } void SSD() { V_Measure=Voltage*10/0.090; // convert Voltage to integer PORTB=LED[V_Measure/100]; DIGIT1=1; delay_ms(RATE); PORTA=0x00; PORTB=LED[(V_Measure%100)/10]|0x80; DIGIT2=1; delay_ms(RATE); PORTA=0x00; PORTB=LED[V_Measure%10]; DIGIT3=1; delay_ms(RATE); PORTA=0x00; PORTB=0x62; DIGIT4=1; delay_ms(RATE); PORTA=0x00; } void main() { ADC_Value=0; int_count=0; ADC_RUN=0; PORT_SETUP(); ADC_Init(); Timer0_Setup(); Interrupt_Setup(); while(1) { Read_Temp(); SSD(); } }