Sei sulla pagina 1di 19

ermicroblog

Microcontrollers and Electronics Project Blog

Home

About

Copyright and Disclaimer

e-Books

Contact Us

Blog Entry
AdChoices

Micro Blog PWM Circuit Pic Micro

Search This Site

Register

PIC18 Pulse W idth Modulation (PW M) DC Motor Speed Controller with the
RPM Counter Project
Decem ber 9, 2009 by rwb, under Microcontroller.

Search
Custom Search

Future Post
C ontrolling the Motor is one of
interesting topics in the embedded
world especially for the robotics
enthusiasts, on the next post we will
learn the basic of motor electronic
circuit as well as how to control it with
microcontroller.

Equipped with sophisticated Enhanced C apture/C ompare/PWM (EC C P) peripheral the Microchip
PIC 18F14K50 microcontroller could produce up to four PWM channels output. The enhanced PWM (Pulse
Width Modulation) mode in EC C P 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
PIC 18F14K50 pins when it configured in pulse steering mode. In this tutorial we will take advantage of
PIC 18F14K50 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
216 LC D.

Therefore don't miss it, stay tune on


this blog !

The PWM and RPM Counter Project


On this project we will use the HITEC C PRO PIC 18 MC U 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 PIC 18 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 lets list
down all the project interesting features:

Subscribe
Posts |

C omments

Using Advanced 8-bit Microchip PIC 18F14K50 microcontroller with PIC Jazz 20PIN development
board

converted by W eb2PDFConvert.com

Driving the HD44780U 216 LC D 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 LC D
Use the PIC 18F14K50 external interrupt and 16-bit TIMER0 counter to measure the RPM and
display it on the LC D.

Feature Product
PIC Jazz 20PIN Board

PIC Jazz 20PIN Board

Categories
Select C ategory

Blogroll
ermicro shop
ermicroblog Amazon Store
ermicroblog on YouTube
ermicroblog video on Metacafe

Archives
Select Month

AdChoices

Micro DC Motor
Make Blog
LCD Display

The following is the C code that makes this thing happens:

/* ***************************************************************************
** File Name
: pwmrpm.c
** Version
: 1.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.63PL3
** IDE
: Microchip MPLAB IDE v8.40
** Programmer : PICKit2
** Last Updated : 28 Nov 2009
** ***************************************************************************/
#include <pic18.h>
/*
**
**
**
**
**

PIC18F14K50 Configuration Bit:


FCMDIS
CPUDIV_0
RCIO

- Fail-Safe Clock Monitor disabled


- No CPU System Clock divide
- Internal RC Oscillator

converted by W eb2PDFConvert.com

**
**
**
**
**
**
**
**
**
*/

PLLDIS
- PLL is under software control
---------------------------------------------------------------------BORDIS
- Brown-out Reset disabled in hardware and software
WDTDIS
- WDT is controlled by SWDTEN bit of the WDTCON register
---------------------------------------------------------------------MCLREN
- MCLR pin enabled, RE3 input pin disabled
---------------------------------------------------------------------XINSTDIS - Disable extended instruction set (Legacy mode)
LVPDIS
- Single-Supply ICSP disabled

__CONFIG(1,
__CONFIG(2,
__CONFIG(3,
__CONFIG(4,
__CONFIG(5,
__CONFIG(6,
__CONFIG(7,

FCMDIS & CPUDIV_0 & RCIO & PLLDIS);


BORDIS & WDTDIS);
MCLREN);
XINSTDIS & LVPDIS);
0xFFFF);
0xFFFF);
0xFFFF);

// 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;
char sdigit[6]={'0','0','0','0','0','\0'};
/* Delay Function */
#define FOSC 16000000UL // Using Internal Clock of 16 MHz
#define delay_us(x) { unsigned char _dcnt; \
_dcnt = (x)/(24000000UL/FOSC)|1; \
while(--_dcnt != 0) continue; \
}
void delay_ms(unsigned int cnt)
{
unsigned char i;
do {
i = 5;
do {
delay_us(164);
} while(--i);
} while(--cnt);
}
// PIC18 High-priority Interrupt Service
void interrupt high_isr(void){
static unsigned char pulse_state=0;
unsigned int rpm_timer;
if (TMR0IF) {
rpm_value = 0;
TMR0IF=0;
}

// Check for TIMER0 Overflow Interrupt


// Reset the RPM Value
// Clear TIMER0 interrupt flag

if (INT0IF){
switch(pulse_state) {
case 0:
TMR0H = 0;
TMR0L = 0;
pulse_state=1;
break;
case 1:
rpm_timer=TMR0L;
rpm_timer+=(TMR0H << 8);

// Check for External INT0 Interrupt


// First Low to High Pulse
// Zero the high byte in TMR0H Buffer
// Clear 16-bit TIMER0 Counter
// Second Low to High Pulse
// Get the first 8-bit TIMER0 Counter
// Get the last 8-bit TIMER0 Counter

// Calculate RPM = 60 x (1/Period)


// RPM Value = 60000 (1 / (0.032 ms x rpm_timer))
rpm_value = (int) (60000.0 / (0.032 * rpm_timer));
pulse_state=0;
}
INT0IF = 0;

// Clear INT0 interrupt flag

}
}
/*
** LCD Routine
** LCD Data RB7,RB6,RB5,RB4
** LCD Control: RC7 -> E-Enable, RC6 -> RS-Register Select, R/W-Always 0
*/
void LCD_putcmd(unsigned char data,unsigned char cmdtype)
{
// Put the Upper 4 bits data
PORTB = data & 0xF0;
RC6=0;
// RS = 0
RC7=1;
// E = 1
converted by W eb2PDFConvert.com

// E=0; write data


RC7=0;
delay_us(1);
// Delay 1us for 16 MHz Internal Clock
// cmdtype = 0; One cycle write, cmdtype = 1; Two cycle writes
if (cmdtype) {
// Put the Lower 4 bits data
PORTB = (data & 0x0F) << 4;
RC6=0;
// RS = 0
RC7=1;
// E = 1
// E=0; write data
RC7=0;
delay_us(1); // Delay 1us for 16 MHz Internal Clock
}
delay_ms(5);

// Wait for busy flag (BF)

}
void LCD_putch(unsigned char data)
{
// Put the Upper 4 bits data
PORTB = data & 0xF0;
RC6=1;
// RS = 1
RC7=1;
// E = 1
// E=0; write data
RC7=0;
delay_us(1); // Delay 1us for 16 MHz Internal Clock
// Put the Lower 4 bits data
PORTB = (data & 0x0F) << 4;
RC6=1;
// RS = 1
RC7=1;
// E = 1
// E=0; write data
RC7=0;
delay_ms(5);

// Wait for busy flag (BF)

}
void LCD_init(void)
{
// Wait for more than 15 ms after VCC rises to 4.5 V
delay_ms(30);
// Send Command 0x30
LCD_putcmd(0x30,LCD_1CYCLE);
// Wait for more than 4.1 ms
delay_ms(8);
// Send Command 0x30
LCD_putcmd(0x30,LCD_1CYCLE);
// Wait for more than 100 us
delay_us(200);
// Delay 250us for 16 MHz Internal Clock ;
// Send Command 0x30
LCD_putcmd(0x30,LCD_1CYCLE);
// Function set: Set interface to be 4 bits long (only 1 cycle write).
LCD_putcmd(0x20,LCD_1CYCLE);
// Function set: DL=0;Interface is 4 bits, N=1; 2 Lines, F=0; 5x8 dots font)
LCD_putcmd(0x28,LCD_2CYCLE);
// Display Off: D=0; Display off, C=0; Cursor Off, B=0; Blinking Off
LCD_putcmd(0x08,LCD_2CYCLE);
// Display Clear
LCD_putcmd(0x01,LCD_2CYCLE);
// Entry Mode Set: I/D=1; Increament, S=0; No shift
LCD_putcmd(0x06,LCD_2CYCLE);
// Display On, Cursor Off
LCD_putcmd(0x0C,LCD_2CYCLE);
}
void LCD_puts(const char *s)
{
while(*s != 0) {
// While not Null
if (*s == '\n')
LCD_putcmd(LCD_NEXT_LINE,LCD_2CYCLE); // Goto Second Line
else
LCD_putch(*s);

converted by W eb2PDFConvert.com

s++;
}
}
// Implementing integer value from 0 to 65530
char *num2str(unsigned int number,unsigned char start_digit)
{
unsigned char digit;
if (number > 65530) number = 0;
digit = '0';
while(number >= 10000)
{
digit++;
number -= 10000;
}

// Start with ASCII '0'


// Keep Looping for larger than 10000
// Increase ASCII character
// Subtract number with 10000

sdigit[0]='0';
// Default first Digit to '0'
if (digit != '0') sdigit[0]=digit; // Put the first digit
digit = '0';
while(number >= 1000)
{
digit++;
number -= 1000;
}

// Start with ASCII '0'


// Keep Looping for larger than 1000
// Increase ASCII character
// Subtract number with 1000

sdigit[1]='0';
// Default Second Digit to '0'
if (digit != '0') sdigit[1]=digit; // Put the Second digit
digit = '0';
while(number >= 100)
{
digit++;
number -= 100;
}

// Start with ASCII '0'


// Keep Looping for larger than 100
// Increase ASCII character
// Subtract number with 100

sdigit[2]='0';
// Default Second Digit to '0'
if (digit != '0') sdigit[2]=digit; // Put the Second digit
digit = '0';
while(number >= 10)
{
digit++;
number -= 10;
}

// Start with ASCII '0'


// Keep Looping for larger than 10
// Increase ASCII character
// Subtract number with 10

sdigit[3]='0';
// Default Second Digit to '0'
if (digit != '0') sdigit[3]=digit; // Put the Second digit
sdigit[4]='0' + number;
return(sdigit + start_digit);
}
void main(void)
{
unsigned char motor_stat,duty_cycle;
OSCCON=0x70;

/* Select 16 MHz internal clock */

// Initial PORT
TRISA = 0x30;
TRISC = 0x01;
PORTC = 0x00;
TRISB = 0x00;
PORTB = 0x00;
TRISB = 0x00;
ANSEL = 0x08;
ANSELH = 0x00;

//
//
//
//
//
//
//
//

Input for RA4 and RA5


Set RC0 as Input, RC<7:1> on PORTC as Output
Initial Port C
Set PORTB as Output
Initial Port B
Set All on PORTB as Output
Set PORT AN3 to analog input
Set PORT AN8 to AN11 as Digital I/O

// Initial LCD using 4 bits data interface


LCD_init();
LCD_puts("PICJazz 20-PIN\n");
// Init ADC
ADCON0=0b00001101;
ADCON1=0b00000000;
ADCON2=0b00101011;

// ADC port channel 3 (AN3), Enable ADC


// Use Internal Voltage Reference (Vdd and Vss)
// Left justify result, 12 TAD, Select the FRC for 16 MHz

// Init TIMER0: Period: 4 x Tosc x Prescale for each counter


// Tosc = 1/16 Mhz = 0.0000000625
// TIMER0 Period: 4 x 0.0000000625 x 128 = 0.000032 Second = 0.032 ms
T0CON = 0b10000110; // TIMER0 Enable, use 16-bit timer and prescale 1:128
TMR0H = 0;
// Zero the high byte in TMR0H Buffer
TMR0L = 0;
// Clear 16-bit TIMER0 Counter
TMR0IE = 1;
// Enable TIMER0 Overflow Interrupt

converted by W eb2PDFConvert.com

// Set the External Interrupt on INT0 (RC0) Port


INT0IE = 1;
// Enables the INT0 external interrupt
INTEDG0 = 1;
// Interrupt on rising edge
// Init PWM for Single Output
CCP1CON=0b00001100; // Single PWM mode; P1A, P1C active-high; P1B, P1D active-high
CCPR1L=0;
// Start with zero Duty Cycle
PSTRCON=0b00000100; // 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.0000000625
// PWM Period = 4 x 0.0000000625 x 201 x 4 = 0.000201
// PWM Frequency = 1/PWM Period = 1/0.000201 = 4.975 kHz
T2CON=0b00000101;
// Postscale: 1:1, Timer2=On, Prescale = 1:4
PR2=200;
// Frequency: 4.975 kHz
TMR2=0;
// Start with zero Counter
// Initial Variable used
rpm_value=0;
motor_stat=0;
// Motor Off Condition
duty_cycle=0;
// 0 Duty Cycle

// Now Enable the Interrupt


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

converted by W eb2PDFConvert.com

The PIC18 Pulse Steering PWM mode


The heart of the PIC 18F14K50 pulse steering PWM mode is rely on the TIMER2 peripheral, where it used
as the basic counter generator for the PWM signal. The TIMER2 counter clock (TMR2) is supplied by
selectable prescale clock, this prescale circuit will divide the system clock by 1, 4 or 16 respectively. 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, then the TMR2 counter
register value will be reset to 0.

At the same time the value of TMR2 counter register is also being compared to the CCPR1L register value
(actually with the CCPR1H register value, since the CCPR1H equal to CCPR1L than we could say
CCPR1L), when the TMR2 reach the CCPR1L value than the PWM peripheral circuit will reset the CCP1
output (logical 0) and when the TMR2 counter register equal to the PR2 register value than it will set the
CCP1 output (logical 1). Therefore by changing the PR2 value we could change the PWM period and this
mean changing the PWM frequency as well. 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; and applying all these values to the
formula above, 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.000.000) x 201 x 4 = 0.000201 second

converted by W eb2PDFConvert.com

Therefore the PWM frequency is:


PWM frequency = 1 / 0.000201 = 4.975 kHz
The T2CON (TIMER2 C ontrol) register is used select the postscale (T2OUTPS<3:0>), activate the TIMER2
peripheral (TMR2ON) and set the prescale clock used by the TMR2 counter register. BY setting the
T2CKPS1=0 and T2CKPS0=1 in the T2CON register we select the 1:4 prescale; and by setting the
TMR2ON to logical 1 we activate the TIMER2 peripheral.

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.0000000625
// PWM Period = 4 x 0.0000000625 x 201 x 4 = 0.000201
// PWM Frequency = 1/PWM Period = 1/0.000201 = 4.975 kHz
T2CON=0b00000101;
// Postscale: 1:1, Timer2=On, Prescale = 1:4
PR2=200;
// Frequency: 4.975 kHz
TMR2=0;
// Start with zero Counter
By setting P1M1=0 and P1M0=0 bits in the CCP1CON register we select the single output PWM; setting
the CCP1M3=1, CCP1M2=1, CCP1M1=0 and CCP1M0=0 in the CCP1CON register we select the PWM
mode with P1A, P1C active-high; P1B, P1D active-high.

On this tutorial we just set the additional 2 LSB extended bits (DC1B1 and DC1B0) to all zero (logical 0)
for the CCPR1L register (10-bit wide). We start by setting the CCPR1L to zero mean we start with zero
duty cycle (no PWM output yet). In single PWM mode we could select the PWM output to PIC 18F14K50
RC3 output port by setting the STRC bit on PSTRCON (Pulse Steer C ontrol) register to the logical 1
while other bits is set to logical 0. The following is the C code:

// Init PWM for Single Output


CCP1CON=0b00001100; // Single PWM mode; P1A, P1C active-high; P1B, P1D active-high
CCPR1L=0;
// Start with zero Duty Cycle
PSTRCON=0b00000100; // Enable PIC Pulse Steering PWM on RC3 Port
By applying the analog value read from the 10K trimport connected to the PIC 18F14K50 RA4 pins, we
could easily varying the PWM duty cycle by changing the voltage divider output formed by the 10K
trimport. The following C code shows how we use the PIC 18F14K50 microcontroller ADC peripheral to
change the PWM duty cycle; for more information about using the ADC peripheral on PIC 18 families you
could read my previous posted blog PIC 18 Microcontroller Analog to Digital C onverter with Microchip C 18
C ompiler:

// Init ADC
ADCON0=0b00001101;
ADCON1=0b00000000;
ADCON2=0b00101011;
...
...

// ADC port channel 3 (AN3), Enable ADC


// Use Internal Voltage Reference (Vdd and Vss)
// Left justify result, 12 TAD, Select the FRC for 16 MHz

if (motor_stat) {
GODONE=1;
while (GODONE) continue; // Wait conversion done
duty_cycle=ADRESH;
// Get the High byte ADC 8-bit result
} else {
duty_cycle=0;
}

converted by W eb2PDFConvert.com

// Assign duty cycle to the PWM CCPR1L register


CCPR1L = duty_cycle;
The enhanced PWM feature on the PIC 18 families actually is almost identical to the PIC 16 families series,
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 C ontroller.
The RPM Sensor
To count the DC motor rotation per minute (RPM), I decided to use the infra red reflective object sensor
Junye JY209-01 or you could replace it with Fairchild QRE00034; as this type of sensor will make sensing
the DC motor rotation become easier.

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; 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 PIC 18F14K50
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; and
the voltage across 470 Ohm resistor will drop to zero volt (logical 0).
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; T is the generated pulse period in second.
RPM (Rotation per Minute) = Frequency x 60
The following pictures show in detail of how I put the PS2 Playstation dual shock DC motor, the Tamiya
racing car tire with the white sticker and the infra red reflective object sensor for this project.

converted by W eb2PDFConvert.com

The PIC18 External Interrupt


As I mention before that in order to calculate the DC motor RPM, we could simply calculate the period of
the pulse generated by the RPM sensor shown above. One of the methods to measure the pulse period is
to use the PIC 18F14K50 microcontroller EC C P (Enhanced C apture/C ompare/PWM) peripheral in the
capture mode to calculate the period; 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). The capture
interrupt will be generated every rising edge of the pulse (or falling edge), 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.
Unfortunately we could not use the Microchip PIC 18F14K50 microcontroller EC C P peripheral as this
peripheral has already being used to generate the PWM signal for the DC motor in our project, but using
the same principal we could make use of the PIC 18F14K50 external interrupt peripheral on pin INT0
(RC0) or INT1 (RC1). This external interrupt peripheral will generate interrupt on every rising edge (or
falling edge) of the pulse; therefore by combining it with the 16-bit TIMER0 counter mode now we could
calculate the RPM as shown on the following diagram.

converted by W eb2PDFConvert.com

As shown on the above picture, first we have to activate the PIC 18F14K50 microcontroller external
interrupt and configure it to detect the pulse rising edge; next we configure the TIMER0 peripheral for the
RPM period counter.

By setting the TMR0E and INT0IE bits to logical 1 on the PIC 18F14K50 microcontroller interrupt control
register (INTCON) and TMT0ON bits to logical 1 on the TIMER0 control register (T0CON), we activate
both the TIMER0 and External peripherals. Selecting the 1:256 prescale value we could calculate the time
required to increase the TIMER0 16-bit counter.
TIMER0 Clock period = 4 x Tosc x TMR2 prescale value second
TIMER0 C lock period = 4 x (1/16.000.000) x 128 = 0.000032 second = 0.032 ms
This mean the TIMER0 counter required 0.032 ms to increase the TMR0L and TMR0H registers counter
value by one. The following C code shows the PIC 18F14K50 microcontroller external interrupt and TIMER0
peripherals initialization:

// Init TIMER0: Period: 4 x Tosc x Prescale for each counter


// Tosc = 1/16 Mhz = 0.0000000625
// TIMER0 Period: 4 x 0.0000000625 x 128 = 0.000032 Second = 0.032 ms
T0CON = 0b10000110; // TIMER0 Enable, use 16-bit timer and prescale 1:128
TMR0H = 0;
// Zero the high byte in TMR0H Buffer
TMR0L = 0;
// Clear 16-bit TIMER0 Counter
TMR0IE = 1;
// Enable TIMER0 Overflow Interrupt
// Set the External Interrupt on INT0 (RC0) Port
INT0IE = 1;
// Enables the INT0 external interrupt
INTEDG0 = 1;
// Interrupt on rising edge
By setting the INTEDG0 to logical 1 on INTCON2 (interrupt C ontrol 2) register we choose the Rising
Edge detection. 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;
converted by W eb2PDFConvert.com

unsigned int rpm_timer;


if (TMR0IF) {
rpm_value = 0;
TMR0IF=0;
}

// Check for TIMER0 Overflow Interrupt


// Reset the RPM Value
// Clear TIMER0 interrupt flag

if (INT0IF){
switch(pulse_state) {
case 0:
TMR0H = 0;
TMR0L = 0;
pulse_state=1;
break;
case 1:
rpm_timer=TMR0L;
rpm_timer+=(TMR0H << 8);

// Check for External INT0 Interrupt


// First Low to High Pulse
// Zero the high byte in TMR0H Buffer
// Clear 16-bit TIMER0 Counter
// Second Low to High Pulse
// Get the first 8-bit TIMER0 Counter
// Get the last 8-bit TIMER0 Counter

// Calculate RPM = 60 x (1/Period)


// RPM Value = 60000 (1 / (0.032 ms x rpm_timer))
rpm_value = (int) (60000.0 / (0.032 * rpm_timer));
pulse_state=0;
}
INT0IF = 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.032 x TIMER0 Counter (TMR0H:TMR0L) millisecond
The RPM value is the frequency of rotation measured in minute (60 second), therefore the DC motor RPM
value could be calculated as the following formula:
rpm_value = (1 / Pulse Period) in second x 60 = 60000.0 / 0.032 x rpm_timer
The rpm_timer variable contains the 16-bit TIMER0 counter value, while the global rpm_value contain
the RPM value of the DC motor.
The PICKit2 Logic Analyzer
To check the RPM counter accuracy I simply use one of the useful feature of the Microchip PIC Kit2
programmer, where we could use it as the powerful Logic Analyzer tools to debug serial communication
buses such as UART, SPI and I2C . This time we will use it to analyze the RPM pulse produce by the infra
red reflective object sensor circuit above and connected the output directly to the PIC 18F14K50
microcontroller RC0 input port and the PIC Kit2 channel 3 inputs to measure the RPM pulse period.

The PIC Kit2 Logic Analyzer tool could be used by running the PIC Kit2 programmer Version v2.61 and
selecting the Logic Tools from the Tools menu; set the Rising Edge trigger on the channel 3 and 100 ms
Sample Rate; next press the RUN button. After the pulse appears check the C ursor checkbox to activate
the X and Y horizontal bar to measure the pulse period.
As shown on the above picture, the channel 3 on the PIC Kit2 logic analyzer tool show that the measured
converted by W eb2PDFConvert.com

pulse frequency is about 77.52 Hz; this mean the RPM is about 4651 (77.52 x 60) which is close enough
to the RPM calculated value 4641 displayed on the LC D at 72% PWM duty cycle.
The 216 LCD Display
To display both of the PWM duty cycle and RPM value, I used the Hitachi HD44780U or the equivalent
microcontroller 216 LC D with back light LED in 4-bit data mode. Most of the LC D function C routine I use
in this project is taken from my previous posted blog AVR LC D Thermometer Using ADC and PWM Poject;
where you could read more information about the principal of how to drive this kind of display. The
following is the list of C function for driving the LC D:
LC D_putch() function is used to display single character on the LC D
LC D_putcmd() function is used to send LC D command (e.g. clear the LC D, move to second row,
etc)
LC D_init() function is used to initialized the 216 LC D; this function will initialized the 216 LC D
into 4-bit data mode
LC D_puts() function is used to display a string on the LC D
num2str() function is used to convert a numeric value to a string, we use this function to display
numeric value on the LC D.
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. After doing the LC D, ADC , TIMER0, External Interrupt and PWM/TIMER2 peripherals setup. After
enabling the high priority interrupt and activating the global interrupt the code enter the for(;;) endless
loop. Inside this loop; first we read the users switch, this switch is attached to the PIC 18F14K50
microcontroller input port RA5 and work as the toggle switch to run or stop the DC motor:

if (RA5 == 0) { // Read Switch


delay_ms(1);
if (RA5 == 0) { // Read again for Simple Debounce
motor_stat ^= 0x01;
}
}
The PIC 18F14K50 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.

if (motor_stat) {
GODONE=1;
while (GODONE) continue; // Wait conversion done
duty_cycle=ADRESH;
// Get the High byte ADC 8-bit result
} else {
duty_cycle=0;
}
// Assign duty cycle to the PWM CCPR1L register
CCPR1L = duty_cycle;
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_2CYCLE);
// LCD Home
LCD_puts("Duty Cycle: "); LCD_puts(num2str((int)((duty_cycle/255.0) * 100.0),3));
LCD_puts(" %");
LCD_putcmd(LCD_NEXT_LINE,LCD_2CYCLE); // Goto Second Line
LCD_puts("RPM: "); LCD_puts(num2str(rpm_value,1));
Downloading and Running the Code
After compiling and simulating your code hook up your PIC Kit2 programmer to the PIC Jazz 20PIN
development and learning board IC SP port turn power on. From the MPLAB IDE menu select
Programmer -> Select Programmer -> Pickit2 it will automatically configure the connection and
display it on the PIC kit2 tab Output windows; now you are ready to down load the code from MPLAB IDE
menu select Programmer -> Program; this will down load the HEX code into the PIC Jazz 20PIN board
with the Microchip PIC 18F14K50 microcontroller on it.
Now its time to run your PWM and RPM counter code, where you could enjoy this following video showing
all of the process that weve been going through.

converted by W eb2PDFConvert.com

The Final Though


Obviously the Microchip PIC 18 PWM peripheral feature is almost identical to its 8-bit PIC 16 little brother
families, therefore upgrading from the PIC 16 to PIC 18 could be done almost without changing the code.
In this project we also take advantage of the infra red reflective object sensor to measure the RPM, where
we dont have to make special modification on the wheel in order to make this sensor to sense the
rotation; all we need is just a piece of white sticker attached to the wheel.
This project also shows us an example of how we use many of the advanced PIC 18F14K50 microcontroller
peripherals at the same time, which will help to shape our understanding of how we could utilize all of
these peripherals in the future project.

Bookmarks and Share

Related Posts
The 2009 Year End Notes
Using Maxim DS1307 Real Time C lock with Atmel AVR Microcontroller
Basic Servo Motor C ontrolling with Microchip PIC Microcontroller
H-Bridge Microchip PIC Microcontroller PWM Motor C ontroller
AVR Twinkle Twinkle Using PWM Project

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

#1

C omment by lawrence.
Hi I would appreciate it if you could send the code in assembly
(*.asm ) as i am not familiar c or if there is a method to
change the code then i would appreciate it and i am using the
pic 16f887A mplab v8.1 using a bootloader connected via
rs232.
please help me out

converted by W eb2PDFConvert.com

29.03.10

#2

C omment by rwb.
For this project I program it only on C language not
assembler, but if you familiar with the PIC assembler you
could use similar principle to manipulate the PIC
microcontroller registers as described on this blog. As I also
use direct register manipulation to achieve this project
purpose (i.e. PWM and ADC ).

31.03.10

#3

C omment by lawrence.
well i have tried to change tmr0 as it is 16-bit on the 18f but 8bit on the 16f etc so do you mind if you can post a C code but
this using the famous outdated 16f877a please help me out as
I am struggling to get it right using the pic 16f877 believe me i
have manipulated the timer and ADC to 10 bit and did the
justification but still struggling.

31.03.10

#4

C omment by rwb.
My suggestion is to try the microcontroller peripherals once at
the time start with the ADC then continue with the PWM,
External Interrupt and TIMER0.

01.06.10

#5

C omment by embed84.
Hi i am a student embedded systems, and im working on my
first microchip project
cool, i wonder if i can use the
void interrupt high_isr(void); function on a pic18f4550 to read
the rotation of my robot wheels. im using two QSE113
transistors. the pulses will be used to make a rotation of 45
degrees. is it possible?

01.06.10

#6

C omment by rwb.
Although its possible to do that but for controlling DC motor
for fix 45 degree rotation will be quite hard as you need to
apply special circuit and logic for the DC motor break system.
For precision position its better to use the DC stepper motor.

14.06.10

#7

C omment by wangui_esther.
hello. am very new to this site, and i cant believe what i was
missing.
Anyway, i want to do a small project so am doing a bit of
research.I want to move a machine table which will have two
axes, x and y. And i will require to control the speed of the
motors for the two axes.
what am wondering , so far like in this tutorial, the motors are
very small in size. obviously my application will require a
stronger motor. what kind of micro controllers can i use for
such? and do i use atmels microcontrollers or PIC s? thanks

14.06.10

#8

C omment by rwb.
You could use Atmel AVR or Microchip PIC , because both of
this microcontrollers could be use to control the DC motor
speed with their PWM feature.

24.06.10

#9

converted by W eb2PDFConvert.com

C omment by wangui_esther.
hi, rwb. the particular motor available to me is the stepper
motor from RS labelled RS 34-3755 4.3A/PH.
Is it possible for PIC 18F14K50 to drive this motor since in this
tutorial you use a much smaller motor?
Also you have worked with dc motors and the servo motor
from the blogs ive seen. is it possible to control the speed of a
servo motor using the micro controllers pwm facility? I am
asking so i can know what microcontroller and programmer to
buy. Thanks

24.06.10

#10

C omment by rwb.
The stepper motor is different from the ordinary DC motor,
therefore to drive the stepper motor you have to use different
circuit and logic. When you want to drive a bigger DC motor
just change the motor driver circuit and using the Darlington
Transistor or better use the high current MOSFET. The
PIC 18F14K50 microcontroller practically could drive any kind
of motor but we have to use a suitable motor driver circuit in
order do it. For more information of how to drive bigger
current you could read my previous posted blog Using
Transistor as Switch (http://www.ermicro.com/blog/?
p=423).
The standard servo motor usually has a fixed speed, we only
can control the servo motor direction through PWM not the
speed. For more information of how to drive the servo motor,
you could read my previous posted blog Basic Servo Motor
Controlling with Microchip PIC Microcontroller
(http://www.ermicro.com/blog/?p=771)

28.06.10

#11

C omment by wangui_esther.
Thanks so much rwb. I am definitely going to be needing
advice on coding for stepper motors. But i am going to try out
with dc motors first Oonce i get all the equipment together so
ill at least have a clue on working with microcontrollers.
Something i meant to ask is, is it possible to vary the speed of
stepper motors and is it possible to achieve positional
accuracy with dc motors say move it by 150 cm then stop?

28.06.10

#12

C omment by rwb.
Yes you could vary the speed of stepper motor, remember
that the stepper motor torque will decrease as it speed
increase. Yes you could achieve quite accurate position by
adding the wheel encoder to the DC motor.

01.07.10

#13

C omment by wangui_esther.
hi rwb. now, i have an atmega 64 chip, avr studio, an stk 500
programmer and stepper motor RS 34-3755 4.3A/PH. i want to
make the motor move and control its speed. the thing is i
have never built any circuit before and apart from the
examples in the internet i have not written any code. where do
i start?

01.07.10

#14

C omment by rwb.
The first thing you need is a good reference for the stepper
motor, fortunately Atmel has a good application note and the
example code for it (AVR446: Linear speed control of stepper

converted by W eb2PDFConvert.com

motor); then you have to start to program your AVR


microcontroller with a simple application such as the blinking
LED in order to get acquainted with the AVR development
environment, you could read my previous posted blog Using
Transistor as Switch (http://www.ermicro.com/blog/?
p=423) for the example. The next step build your stepper
motor driver circuit and program the AVR to rotate this
stepper motor.

01.07.10

#15

C omment by wangui_esther.
thanks. i will do just that.

16.10.10

#16

C omment by iriana.
Hii rbw, Im doubt and curious so I would like to ask in this
case of the rpm sensor. Will the diameter of the wheel affect
the rpm value? Likewise, larger wheel will have a lower rpm
value compared to that is by smaller wheel? Due to the every
beam received by the photo-transistor with diffs circumference
of the wheel.
If it is yes, so what do you think for getting the real RPM
value of a motor? Thank you so much if you would like to give
me some advices

16.10.10

#17

C omment by rwb.
No, the diameter of the wheel wont effect the rpm value
measured by the sensor. Since we simply count the white
marker (i.e. the white sticker) received by the photo-reflector
sensor from one complete rotation of the wheel (1 rotation = 1
pulse).

16.10.10

#18

C omment by iriana.
Thank you rwb.

02.09.11

#19

C omment by aor777.
Hi,
As per the PIC 18F14K50 datasheet it can support 4 channel
PWM. what i learn from your tutorial is that all the four
channels will output identical PWM waveform. Please correct
me if i am wrong. Also, tell me how to output 4 different PWM
signals controlled using 4 different potentiometers. Thanks in
advance.

02.09.11

#20

C omment by rwb.
The datashet didnt mention 4 channels, but it say 4 PWM
outputs from one Enhanced C apture/C ompare/PWM (EC C P)
module. This EC C P module is specially designed for controlling
the H-Bridge motor. To have 4 different PWM output with 4
potentiometers you need to have 4 independent PWM
generator.

04.10.11

#21

C omment by muslihen19.
Hi rwb,

converted by W eb2PDFConvert.com

where can i download HI-TEC H C PRO PIC 18 MC U Family(Lite)


Version 9.63PL3 compiler, can u give the link? i already
compile it using HC PIC 18-Lite-9.66 but it still got error

04.10.11

#22

C omment by rwb.
You should be able to compile the project using Microchip HITEC H C C ompiler for PIC 18 MC U Lite (free version) which you
could download it from Microchip official website
(www.microchip.com).

01.12.11

#23

C omment by David.
This is an excellent tutorial Mr. RWB. I thank you. It is
tempting to wish for a recipe that fills every need exactly, but
then we do not learn. Your tutorials are far more than a
recipe. I appreciate very much how well written they are.
One question: In the Project Basic schematic above, you show
a resistor labeled 4K7. I am not familiar with that notation
for resistors (I have seen it elsewhere in your tutorials). Do
you mean 47K? Or?
This is good stuff man, you have made my day.

02.12.11

#24

C omment by rwb.
Thanks David, the 4K7 notation is similar to 4.7K Ohm or 4700
Ohm.

04.09.12

#25

C omment by JamTheMan.
Hi rwb,
I was wondering if it was possible to change the direction of
rotation of the DC Motor using this design.
And if there is another way of driving the motor other than
using an H-Bridge?
Thanks in advance.

04.09.12

#26

C omment by rwb.
In order to change the DC motor direction you need to change
the DC motor power supply polarity, the H-Bridge is the circuit
that could do this function. If you dont want to use the HBridge circuit, You could use a simple DPDT (Double Pole
Double Through) relay to do this function and you only need 1
transistor and 1 output port to change the DC motor direction.

04.09.12

#27

C omment by JamTheMan.
I will check the DPDT out, sounds promising.
Thanks again rwb. This post has been such a great help.

24.08.13

#28

C omment by kik0.
Hi I have to C ontrol DC motor whit PIC 18f8490 and i have one

converted by W eb2PDFConvert.com

question. My PIC has a LC D Driver. The motors senzor for


RPM product 2500 pulses on one turnig on the rotor. I
undurstand how to set the timer on counter mode and count
pulses but how can I turn them in RPM and show them on the
displey. Its i litle hard to me. The displey is
https://www.olimex.com/Products/_resources/LC D/LC D449STK2.pdf Thanks

24.08.13

#29

C omment by kik0.
And one more question

how can i make to stop and start

the motor whit one button

25.08.13

#30

C omment by rwb.
You could get an idea from this project PIC 18 Pulse Width
Modulation (PWM) DC Motor Speed C ontroller with the RPM
C ounter Project.

Copyright 2008-2016 By erm icro. Powered by W ord Press.

converted by W eb2PDFConvert.com

Potrebbero piacerti anche