You are on page 1of 18

PIC18 Pulse Width Modulation (PWM) DC Motor Speed Controller with the RPM Counter Project

December 9, 2009 by rwb, under Microcontroller.

Equipped with sophisticated Enhanced Capture/Compare/PWM (ECCP) peripheral the Microchip PIC18F14K50 microcontroller could produce up to four PWM channels output. The enhanced PWM (Pulse Width Modulation) mode in ECCP peripheral is capable to drive the full bridge DC Motor circuit directly both in forward or reverse direction. It also could generate single PWM output on the selectable PIC18F14K50 pins when it configured in pulse steering mode. In this tutorial we will take advantage of PIC18F14K50 pulse steering mode to drive the DC Motor and at the same time we will build the RPM (Rotation per Minute) counter to observe the PWM effect on the DC Motor speed and display it on the 2×16 LCD.

The PWM and RPM Counter Project On this project we will use the HITEC C PRO PIC18 MCU Family Version 9.63PL3 and Microchip MPLAB IDE version 8.40 as our development tools platform. This project also serves as the learning tools of how to use many of the Microchip PIC18 advanced peripherals simultaneously to accomplish the project goal. You could see the complete project demonstrated on the video at the end of this tutorial; Ok now let’s list down all the project interesting features:

•Using Advanced 8-bit Microchip PIC18F14K50 microcontroller with PICJazz 20PIN development board

.•Driving the HD44780U 2×16 LCD in 4-bit data mode •Use DC Motor taken from discarded dual shock PS2 Playstation joystick and the Tamiya racing car tire for measuring the DC Motor RPM •Simple and easy to build RPM sensor with the infra red reflective object sensor •Use the ADC peripheral to read the trimport value for adjusting the DC Motor Speed and display the PWM duty cycle on the LCD •Use the PIC18F14K50 external interrupt and 16-bit TIMER0 counter to measure the RPM and display it on the LCD.

__CONFIG(3.Fail-Safe Clock Monitor disabled ** CPUDIV_0 .Internal RC Oscillator ** PLLDIS .No CPU System Clock divide ** RCIO .The following is the C code that makes this thing happens: /* *************************************************************************** ** File Name : pwmrpm. __CONFIG(4.63PL3 ** IDE : Microchip MPLAB IDE v8.MCLR pin enabled. RE3 input pin disabled ** ---------------------------------------------------------------------** XINSTDIS . FCMDIS & CPUDIV_0 & RCIO & PLLDIS).Single-Supply ICSP disabled */ __CONFIG(1.40 ** Programmer : PICKit2 ** Last Updated : 28 Nov 2009 ** ***************************************************************************/ #include <pic18.Disable extended instruction set (Legacy mode) ** LVPDIS . XINSTDIS & LVPDIS).Brown-out Reset disabled in hardware and software ** WDTDIS . BORDIS & WDTDIS). MCLREN). .0 ** Description : PIC18 Pulse Width Modulation with RPM Counter ** Author : RWB ** Target : PICJazz 20PIN Board: PIC18F14K50 ** Compiler : HI-TECH C PRO PIC18 MCU Family(Lite) Version 9. __CONFIG(2.WDT is controlled by SWDTEN bit of the WDTCON register ** ---------------------------------------------------------------------** MCLREN .PLL is under software control ** ---------------------------------------------------------------------** BORDIS .h> /* ** PIC18F14K50 Configuration Bit: ** ** FCMDIS .c ** Version : 1.

// LCD Definition #define LCD_HOME 0x02 #define LCD_NEXT_LINE 0xC0 #define LCD_CLEAR 0x01 #define LCD_1CYCLE 0 #define LCD_2CYCLE 1 // RPM Counter Variable volatile unsigned int rpm_value.'0'. pulse_state=0. \ _dcnt = (x)/(24000000UL/FOSC)|1. 0xFFFF).'0'.unsigned char cmdtype) . if (TMR0IF) { // Check for TIMER0 Overflow Interrupt rpm_value = 0.'\0'}.032 * rpm_timer)). char sdigit[6]={'0'. } while(--i). // Clear 16-bit TIMER0 Counter pulse_state=1.0 / (0. unsigned int rpm_timer. do { i = 5.'0'.RB5. do { delay_us(164). /* Delay Function */ #define FOSC 16000000UL // Using Internal Clock of 16 MHz #define delay_us(x) { unsigned char _dcnt.'0'. R/W-Always 0 */ void LCD_putcmd(unsigned char data. } while(--cnt). __CONFIG(6. // Get the last 8-bit TIMER0 Counter // Calculate RPM = 60 x (1/Period) // RPM Value = 60000 (1 / (0. // Get the first 8-bit TIMER0 Counter rpm_timer+=(TMR0H << 8). // Clear TIMER0 interrupt flag } if (INT0IF){ // Check for External INT0 Interrupt switch(pulse_state) { case 0: // First Low to High Pulse TMR0H = 0. RC6 -> RS-Register Select. case 1: // Second Low to High Pulse rpm_timer=TMR0L.RB4 ** LCD Control: RC7 -> E-Enable. 0xFFFF). // Zero the high byte in TMR0H Buffer TMR0L = 0. // Reset the RPM Value TMR0IF=0. } INT0IF = 0. 0xFFFF). // Clear INT0 interrupt flag } } /* ** LCD Routine ** LCD Data RB7.RB6. \ while(--_dcnt != 0) continue.032 ms x rpm_timer)) rpm_value = (int) (60000. \ } void delay_ms(unsigned int cnt) { unsigned char i.__CONFIG(5. break. __CONFIG(7. } // PIC18 High-priority Interrupt Service void interrupt high_isr(void){ static unsigned char pulse_state=0.

delay_us(1). // Send Command 0x30 LCD_putcmd(0x30. // RS = 1 RC7=1. One cycle write. // RS = 1 RC7=1. write data RC7=0. // Wait for more than 4. // Send Command 0x30 LCD_putcmd(0x30. // RS = 0 RC7=1. // Send Command 0x30 LCD_putcmd(0x30. // Delay 250us for 16 MHz Internal Clock . RC6=1. RC6=0.LCD_1CYCLE). // Delay 1us for 16 MHz Internal Clock // cmdtype = 0. // E = 1 // E=0.Interface is 4 bits. write data RC7=0. RC6=0. 5x8 dots font) .LCD_1CYCLE). // Delay 1us for 16 MHz Internal Clock // Wait for busy flag (BF) } void LCD_putch(unsigned char data) { // Put the Upper 4 bits data PORTB = data & 0xF0. // RS = 0 RC7=1. // Function set: DL=0. // E = 1 // E=0. // E = 1 // E=0. write data RC7=0.LCD_1CYCLE). cmdtype = 1. // Delay 1us for 16 MHz Internal Clock // Put the Lower 4 bits data PORTB = (data & 0x0F) << 4. Two cycle writes if (cmdtype) { // Put the Lower 4 bits data PORTB = (data & 0x0F) << 4.5 V delay_ms(30). RC6=1. LCD_putcmd(0x20. write data RC7=0. } delay_ms(5). // Wait for more than 100 us delay_us(200). 2 Lines. delay_ms(5). // Function set: Set interface to be 4 bits long (only 1 cycle write). N=1. F=0.LCD_1CYCLE).1 ms delay_ms(8). delay_us(1). delay_us(1). // Wait for busy flag (BF) } void LCD_init(void) { // Wait for more than 15 ms after VCC rises to 4. // E = 1 // E=0.{ // Put the Upper 4 bits data PORTB = data & 0xF0.

while(number >= 1000) { digit++. C=0.LCD_2CYCLE).LCD_2CYCLE). Increament. while(number >= 10) { digit++. S=0. if (digit != '0') sdigit[2]=digit. } sdigit[1]='0'. B=0. // Display Clear LCD_putcmd(0x01. No shift LCD_putcmd(0x06. } sdigit[3]='0'. number -= 1000. s++. // Display On.LCD_2CYCLE). while(number >= 10000) { digit++. number -= 10. } sdigit[2]='0'. Display off.LCD_2CYCLE). digit = '0'. } void LCD_puts(const char *s) { while(*s != 0) { // While not Null if (*s == '\n') LCD_putcmd(LCD_NEXT_LINE. if (number > 65530) number = 0. // Entry Mode Set: I/D=1.LCD_2CYCLE). // Goto Second Line else LCD_putch(*s). digit = '0'. Blinking Off LCD_putcmd(0x08. // Display Off: D=0.LCD_putcmd(0x28. if (digit != '0') sdigit[1]=digit. digit = '0'. // Start with ASCII '0' // Keep Looping for larger than 10000 // Increase ASCII character // Subtract number with 10000 // // // // Default first Digit to '0' Put the first digit Start with ASCII '0' Keep Looping for larger than 1000 // Increase ASCII character // Subtract number with 1000 // // // // Default Second Digit to '0' Put the Second digit Start with ASCII '0' Keep Looping for larger than 100 // Increase ASCII character // Subtract number with 100 // // // // Default Second Digit to '0' Put the Second digit Start with ASCII '0' Keep Looping for larger than 10 // Increase ASCII character // Subtract number with 10 // Default Second Digit to '0' . if (digit != '0') sdigit[0]=digit. digit = '0'. number -= 100. Cursor Off LCD_putcmd(0x0C. Cursor Off.unsigned char start_digit) { unsigned char digit. } } // Implementing integer value from 0 to 65530 char *num2str(unsigned int number. } sdigit[0]='0'.LCD_2CYCLE). while(number >= 100) { digit++. number -= 10000.

// Input for RA4 and RA5 TRISC = 0x01.000201 = 4. // Postscale: 1:1. // Use Internal Voltage Reference (Vdd and Vss) ADCON2=0b00101011. LCD_puts("PICJazz 20-PIN\n"). // TIMER0 Enable. Select the FRC for 16 MHz // Init TIMER0: Period: 4 x Tosc x Prescale for each counter // Tosc = 1/16 Mhz = 0. // Init ADC ADCON0=0b00001101. P1B. // Initial Port B TRISB = 0x00. 12 TAD.if (digit != '0') sdigit[3]=digit. // Motor Off Condition duty_cycle=0.000032 Second = 0.032 ms T0CON = 0b10000110. // Set PORTB as Output PORTB = 0x00.0000000625 // TIMER0 Period: 4 x 0. Enable ADC ADCON1=0b00000000. P1D active-high CCPR1L=0.0000000625 x 128 = 0. // Enable High Priority Interrupt GIEH = 1.0000000625 x 201 x 4 = 0. Timer2=On.) { if (RA5 == 0) { // Read Switch . // Interrupt on rising edge // Init PWM for Single Output CCP1CON=0b00001100. // Set All on PORTB as Output ANSEL = 0x08. // Single PWM mode. Prescale = 1:4 PR2=200. // Enables the INT0 external interrupt INTEDG0 = 1.975 kHz TMR2=0. RC<7:1> on PORTC as Output PORTC = 0x00. // Put the Second digit sdigit[4]='0' + number. // Set PORT AN8 to AN11 as Digital I/O // Initial LCD using 4 bits data interface LCD_init(). P1C active-high. // Clear 16-bit TIMER0 Counter TMR0IE = 1. return(sdigit + start_digit). motor_stat=0. // Enable PIC Pulse Steering PWM on RC3 Port // PWM Period = 4 x Tosc x (PR2 + 1) x TMR2 Prescale Value // Tosc = 1/16 Mhz = 0. // Global Interrupt Enable (High Priority) for(. // Enable TIMER0 Overflow Interrupt // Set the External Interrupt on INT0 (RC0) Port INT0IE = 1. /* Select 16 MHz internal clock */ // Initial PORT TRISA = 0x30.975 kHz T2CON=0b00000101.. } void main(void) { unsigned char motor_stat. // Zero the high byte in TMR0H Buffer TMR0L = 0. // Set RC0 as Input. // Initial Port C TRISB = 0x00. OSCCON=0x70.000201 // PWM Frequency = 1/PWM Period = 1/0. // 0 Duty Cycle // Now Enable the Interrupt IPEN = 1. // ADC port channel 3 (AN3). // Start with zero Duty Cycle PSTRCON=0b00000100. use 16-bit timer and prescale 1:128 TMR0H = 0. // Frequency: 4.0000000625 // PWM Period = 4 x 0. // Set PORT AN3 to analog input ANSELH = 0x00. // Start with zero Counter // Initial Variable used rpm_value=0. P1A. // Left justify result.duty_cycle.

// Wait conversion done duty_cycle=ADRESH. } } if (motor_stat) { GODONE=1. LCD_puts(" %"). // Get the High byte ADC 8-bit result } else { duty_cycle=0. LCD_puts(num2str((int)((duty_cycle/255. LCD_putcmd(LCD_NEXT_LINE.0) * 100.0). // Goto Second Line LCD_puts("RPM: ").LCD_2CYCLE). // Put the delay here delay_ms(10). // Display the Information on the LCD LCD_putcmd(LCD_HOME. if (RA5 == 0) { // Read again for Simple Debounce motor_stat ^= 0x01. LCD_puts(num2str(rpm_value.delay_ms(1). // LCD Home LCD_puts("Duty Cycle: "). while (GODONE) continue. } } /* EOF: pwmrpm.3)).c */ .LCD_2CYCLE). } // Assign duty cycle to the PWM CCPR1L register CCPR1L = duty_cycle.1)).

. 4 or 16 respectively. The TIMER2 counter clock (TMR2) is supplied by selectable prescale clock. The prescale could be selected by assigning the T2CKPS1 and T2CKPS0 bits in the T2CON register. The TMR2 register value is continuously compared to the PR2 register which determine the TOP value of the TMR2 counter register. When the TMR2 register value reach the PR2 value. where it used as the basic counter generator for the PWM signal. this prescale circuit will divide the system clock by 1.The PIC18 Pulse Steering PWM mode The heart of the PIC18F14K50 pulse steering PWM mode is rely on the TIMER2 peripheral. then the TMR2 counter register value will be reset to 0.

// Postscale: 1:1.975 kHz .000) x 201 x 4 = 0. since the CCPR1H equal to CCPR1L than we could say CCPR1L). Prescale = 1:4 PR2=200.At the same time the value of TMR2 counter register is also being compared to the CCPR1L register value (actually with theCCPR1H register value.0000000625 x 201 x 4 = 0.000201 = 4. and by setting the TMR2ON to logical “1” we activate the TIMER2 peripheral. Therefore by changing the PR2 value we could change the PWM period and this mean changing the PWM frequency as well. and applying all these values to the formula above.000201 // PWM Frequency = 1/PWM Period = 1/0. Timer2=On.000201 = 4. // Frequency: 4. The PWM period could be calculated using this following formula: PWM period = 4 x Tosc x ( PR2 + 1) x (TMR2 prescale value) second Where Tosc is the system clock period in second PWM frequency = 1 / PWM Period Hz By assigning the PR2 register with 200 and select the prescale to 4. activate the TIMER2 peripheral (TMR2ON) and set the prescale clock used by the TMR2 counter register. when the TMR2 reach the CCPR1Lvalue than the PWM peripheral circuit will reset the CCP1 output (logical “0“) and when the TMR2 counter register equal to thePR2 register value than it will set the CCP1 output (logical “1“).000.0000000625 // PWM Period = 4 x 0.975 kHz T2CON=0b00000101. BY setting the T2CKPS1=0 and T2CKPS0=1 in the T2CON register we select the 1:4 prescale.975 kHz The T2CON (TIMER2 Control) register is used select the postscale (T2OUTPS<3:0>).000201 second Therefore the PWM frequency is: PWM frequency = 1 / 0. we could determine the PWM frequency for our DC Motor base on the internal system oscillator of 16 MHz as follow: PWM period = 4 x (1 / 16. The following is the C code to initialize the TIMER2 peripheral: // PWM Period = 4 x Tosc x (PR2 + 1) x TMR2 Prescale Value // Tosc = 1/16 Mhz = 0.

P1D active-high. for more information about using the ADC peripheral on PIC18 families you could read my previous posted blog PIC18 Microcontroller Analog to Digital Converter with Microchip C18 Compiler: // Init ADC ADCON0=0b00001101. // Start with zero Counter By setting P1M1=0 and P1M0=0 bits in the CCP1CON register we select the single output PWM. Enable ADC ADCON1=0b00000000. we could easily varying the PWM duty cycle by changing the voltage divider output formed by the 10K trimport.TMR2=0. P1D active-high CCPR1L=0. On this tutorial we just set the additional 2 LSB extended bits (DC1B1 and DC1B0) to all zero (logical “0“) for the CCPR1Lregister (10-bit wide). // ADC port channel 3 (AN3). In single PWM mode we could select the PWM output to PIC18F14K50 RC3 output port by setting the STRC bit on PSTRCON (Pulse Steer Control) register to the logical “1” while other bits is set to logical “0“.. P1C active-high. // Get the High byte ADC 8-bit result } else { duty_cycle=0. // Use Internal Voltage Reference (Vdd and Vss) ADCON2=0b00101011. if (motor_stat) { GODONE=1. while (GODONE) continue. // Wait conversion done duty_cycle=ADRESH. .. The following C code shows how we use the PIC18F14K50 microcontroller ADC peripheral to change the PWM duty cycle. CCP1M2=1. CCP1M1=0 and CCP1M0=0 in the CCP1CON register we select the PWM mode with P1A.. P1C active-high. P1B. setting the CCP1M3=1. We start by setting the CCPR1L to zero mean we start with zero duty cycle (no PWM output yet). .. } // Assign duty cycle to the PWM CCPR1L register CCPR1L = duty_cycle. P1A. // Single PWM mode. Select the FRC for 16 MHz . // Start with zero Duty Cycle PSTRCON=0b00000100. 12 TAD. The following is the C code: // Init PWM for Single Output CCP1CON=0b00001100. P1B. // Left justify result. // Enable PIC Pulse Steering PWM on RC3 Port By applying the analog value read from the 10K trimport connected to the PIC18F14K50 RA4 pins.

as this type of sensor will make sensing the DC motor rotation become easier.The enhanced PWM feature on the PIC18 families actually is almost identical to the PIC16 families series. the Tamiya racing car tire with the white sticker and the infra red reflective object sensor for this project. T is the generated pulse period in second. . I decided to use the infra red reflective object sensor Junye JY209-01 or you could replace it with Fairchild QRE00034. The RPM Sensor To count the DC motor rotation per minute (RPM). The infra red reflective object sensor work by simply emitting the infra red beam and when it encounter the white object surface than the infra red beam will be reflected back to the phototransistor. and the voltage across 470 Ohm resistor will drop to zero volt (logical “0“). therefore you could read more about the enhanced PWM feature if you want to drive the H-bridge DC motor circuit from my previous posted blog H-Bridge Microchip PIC Microcontroller PWM motor Controller. next the phototransistor and the 2N3904 transistor which formed the Darlington pair will start to conduct and will generate enough voltage across the 470 Ohm resistor to be considered by the PIC18F14K50 microcontroller build in Schmitt trigger RC0 input port as the logical “1“. When the infra red beam encounters the black tire surface than both of the phototransistor and 2N3904 transistor will turn off. Therefore by timing the generated pulse period by the infra red reflective object sensor we could easily calculate the RPM using this following formula: Frequency = 1/T Hz. RPM (Rotation per Minute) = Frequency x 60 The following pictures show in detail of how I put the PS2 Playstation dual shock DC motor.

.

. therefore by combining it with the 16-bit TIMER0 counter mode now we could calculate the RPM as shown on the following diagram. therefore by knowing the exact TIMER1 or TIMER3 counter clock time period and get the timer 16-bit counted value between the two rising edge pulse we could calculate the RPM. in the capture mode we could easily use the 16-bit TIMER1 or TIMER3 to count the pulse period by feeding the pulse directly to the CCP1 pins (RC5). but using the same principal we could make use of the PIC18F14K50 external interrupt peripheral on pin INT0 (RC0) or INT1 (RC1). One of the methods to measure the pulse period is to use the PIC18F14K50 microcontroller ECCP (Enhanced Capture/Compare/PWM) peripheral in the capture mode to calculate the period. we could simply calculate the period of the pulse generated by the RPM sensor shown above.The PIC18 External Interrupt As I mention before that in order to calculate the DC motor RPM. Unfortunately we could not use the Microchip PIC18F14K50 microcontroller ECCP peripheral as this peripheral has already being used to generate the PWM signal for the DC motor in our project. This external interrupt peripheral will generate interrupt on every rising edge (or falling edge) of the pulse. The capture interrupt will be generated every rising edge of the pulse (or falling edge).

As shown on the above picture. we activate both the TIMER0 and External peripherals. By setting the TMR0E and INT0IE bits to logical “1” on the PIC18F14K50 microcontroller interrupt control register (INTCON) and TMT0ON bits to logical “1” on the TIMER0 control register (T0CON).000032 second = 0. Selecting the 1:256 prescale value we could calculate the time required to increase the TIMER0 16-bit counter.000) x 128 = 0. next we configure the TIMER0 peripheral for the RPM period counter.000. TIMER0 Clock period = 4 x Tosc x TMR2 prescale value second TIMER0 Clock period = 4 x (1/16.032 ms . first we have to activate the PIC18F14K50 microcontroller external interrupt and configure it to detect the pulse rising edge.

pulse_state=0.032 ms T0CON = 0b10000110.0 / 0. while the global rpm_value contain the RPM value of the DC motor. case 1: // Second Low to High Pulse rpm_timer=TMR0L. // Get the last 8-bit TIMER0 Counter // Calculate RPM = 60 x (1/Period) // RPM Value = 60000 (1 / (0. // Reset the RPM Value TMR0IF=0. The PICKit2 Logic Analyzer To check the RPM counter accuracy I simply use one of the useful feature of the Microchip PICKit2 programmer. // Interrupt on rising edge By setting the INTEDG0 to logical “1” on INTCON2 (interrupt Control 2) register we choose the Rising Edge detection. // Get the first 8-bit TIMER0 Counter rpm_timer+=(TMR0H << 8). SPI and I2C. // Zero the high byte in TMR0H Buffer TMR0L = 0. // Clear INT0 interrupt flag } } By resetting the TIMER0 counter on the first rising edge external interrupt and reading back the TIMER0 counter on the second rising edge external interrupt we could easily calculate the pulse period Pulse Period = 0. // Clear 16-bit TIMER0 Counter pulse_state=1. // Enable TIMER0 Overflow Interrupt // Set the External Interrupt on INT0 (RC0) Port INT0IE = 1. // Zero the high byte in TMR0H Buffer TMR0L = 0. This time we will use it to analyze the RPM pulse produce by the infra red .032 ms x rpm_timer)) rpm_value = (int) (60000. // Enables the INT0 external interrupt INTEDG0 = 1. The RPM value is being calculated inside the interrupt service routine as shown on this following C code: // PIC18 High-priority Interrupt Service void interrupt high_isr(void){ static unsigned char pulse_state=0.0000000625 x 128 = 0. therefore the DC motor RPM value could be calculated as the following formula: rpm_value = (1 / Pulse Period) in second x 60 = 60000.032 * rpm_timer)). } INT0IF = 0. break. // TIMER0 Enable.0 / (0.This mean the TIMER0 counter required 0.032 x TIMER0 Counter (TMR0H:TMR0L) millisecond The RPM value is the frequency of rotation measured in minute (60 second).0000000625 // TIMER0 Period: 4 x 0.000032 Second = 0. // Clear TIMER0 interrupt flag } if (INT0IF){ // Check for External INT0 Interrupt switch(pulse_state) { case 0: // First Low to High Pulse TMR0H = 0.032 ms to increase the TMR0L and TMR0H registers counter value by one. The following C code shows the PIC18F14K50 microcontroller external interrupt and TIMER0 peripherals initialization: // Init TIMER0: Period: 4 x Tosc x Prescale for each counter // Tosc = 1/16 Mhz = 0. // Clear 16-bit TIMER0 Counter TMR0IE = 1. use 16-bit timer and prescale 1:128 TMR0H = 0. if (TMR0IF) { // Check for TIMER0 Overflow Interrupt rpm_value = 0. unsigned int rpm_timer.032 x rpm_timer The rpm_timer variable contains the 16-bit TIMER0 counter value. where we could use it as the powerful Logic Analyzer tools to debug serial communication buses such as UART.

The PICKit2 Logic Analyzer tool could be used by running the PICKit2 programmer Version v2.52 Hz. The 2×16 LCD Display To display both of the PWM duty cycle and RPM value.g. Most of the LCD function C routine I use in this project is taken from my previous posted blog AVR LCD Thermometer Using ADC and PWM Poject. the channel 3 on the PICKit2 logic analyzer tool show that the measured pulse frequency is about 77. clear the LCD. where you could read more information about the principal of how to drive this kind of display.52 x 60) which is close enough to the RPM calculated value 4641 displayed on the LCD at 72% PWM duty cycle. this function will initialized the 2×16 LCD into 4-bit data mode •LCD_puts() function is used to display a string on the LCD •num2str() function is used to convert a numeric value to a string. we use this function to display numeric value on the LCD. . next press the RUN button. move to second row. I used the Hitachi HD44780U or the equivalent microcontroller 2×16 LCD with back light LED in 4-bit data mode. set the Rising Edge trigger on the channel 3 and 100 ms Sample Rate. The following is the list of C function for driving the LCD: •LCD_putch() function is used to display single character on the LCD •LCD_putcmd() function is used to send LCD command (e.61 and selecting the Logic Tools from the Tools menu. After the pulse appears check the Cursor checkbox to activate the X and Y horizontal bar to measure the pulse period. this mean the RPM is about 4651 (77. etc) •LCD_init() function is used to initialized the 2×16 LCD. As shown on the above picture.reflective object sensor circuit above and connected the output directly to the PIC18F14K50 microcontroller RC0 input port and the PICKit2 channel 3 inputs to measure the RPM pulse period.

After doing the LCD. Downloading and Running the Code After compiling and simulating your code hook up your PICKit2 programmer to the PICJazz 20PIN development and learning board ICSP port turn power on. External Interrupt and PWM/TIMER2 peripherals setup. Then next we display the duty cycle and the RPM value on the 2x16 LCD: // Display the Information on the LCD LCD_putcmd(LCD_HOME. LCD_puts(" %"). if (motor_stat) { GODONE=1.LCD_2CYCLE).3)). // Goto Second Line LCD_puts("RPM: "). The PIC18F14K50 microcontroller ADC peripheral than read the analog input on channel 3 (RA4) port and assigned the value to the CCPR1L register where it used to control the PWM duty cycle. After enabling the high priority interrupt and activating the global interrupt the code enter the for(.. where you could enjoy this following video showing all of the process that we’ve been going through.) endless loop. now you are ready to down load the code from MPLAB IDE menu select Programmer -> Program. ADC. // Wait conversion done duty_cycle=ADRESH.1)). LCD_puts(num2str((int)((duty_cycle/255. . From the MPLAB IDE menu select Programmer -> Select Programmer -> Pickit2 it will automatically configure the connection and display it on the PICkit2 tab Output windows. LCD_putcmd(LCD_NEXT_LINE.LCD_2CYCLE). } // Assign duty cycle to the PWM CCPR1L register CCPR1L = duty_cycle.0) * 100. LCD_puts(num2str(rpm_value.Inside the C Code The C program begins by selecting the 16 MHz internal clock and setting all the I/O ports used on this project. // Get the High byte ADC 8-bit result } else { duty_cycle=0. this switch is attached to the PIC18F14K50 microcontroller input if (RA5 == 0) { delay_ms(1). Now its time to run your PWM and RPM counter code. TIMER0. this will down load the HEX code into the PICJazz 20PIN board with the Microchip PIC18F14K50 microcontroller on it. while (GODONE) continue. Inside this loop.0). // LCD Home LCD_puts("Duty Cycle: "). first we read the user’s switch. if (RA5 == 0) { motor_stat ^= } } port RA5 and work as the toggle switch to run or stop the DC motor: // Read Switch // Read again for Simple Debounce 0x01.