Wednesday, September 29, 2010

PIC16F887/877 programming in C Tutorial 9 (Timers Interrupts)

Timers Interrupt:
Now lets see the interrupt of timers. As we know Timer0 will generate an interrupt when the TMR0 register overflows from FFh to 00h. The T0IF interrupt flag bit of the INTCON register is set every time the TMR0 register overflows, regardless of whether or not the Timer0 interrupt is enabled. The T0IF bit must be cleared in software. The Timer0 interrupt enable is the T0IE bit of the INTCON register. However we will use TMR0IF bit for interrupt programming.

The Timer1 register pair (TMR1H:TMR1L) increments to FFFFh and rolls over to 0000h. When Timer1 rolls over, the Timer1 interrupt flag bit of the PIR1 register is set. To enable the interrupt on rollover, you must set these bits:
• Timer1 interrupt enable bit of the PIE1 register
• PEIE bit of the INTCON register
• GIE bit of the INTCON register
The interrupt is cleared by clearing the TMR1IF bit in the Interrupt Service Routine.

For interrupt programming we have to create ISR (interrupt service routine); so that when an interrupt will occur ISR will take care of it. So, to use timers interrupt; first we have to enable the global interrupt by setting the GIE bit of intcon register, then the interrupts of the respective timer.

Code:  
unsigned cnt, cnt1, cnt2;

void interrupt() {
  if (TMR2IF_bit) {
    cnt2++;                    // increment counter
    TMR2IF_bit = 0;           // clear TMR2IF
    TMR2 = 0;
    if (cnt2 >= 122) {
      PORTc = ~PORTc;         // Toggle PORTc LEDs
      cnt2 = 0;                // Reset cnt
    }
   
  }
  if (TMR0IF_bit) {
    cnt++;                 // increment counter
    TMR0IF_bit = 0;        // clear TMR0IF
    TMR0   = 0;
    if (cnt >= 122) {         //1 sec delay
      PORTd = ~PORTd;      // Toggle PORTd LEDs
      cnt = 0;             // Reset cnt
    }
   
  }
  if(tmr1if_bit){
      tmr1if_bit=0;
      cnt1++;
      tmr1h=128;
      tmr1l=0;
      if(cnt1>=36){
           porta=!porta;
           cnt1=0;
        }
    }
 
 
}

void main() {
  OPTION_REG = 0x84;       // Assign prescaler to TMR0 32
 ////////////// commect for 877///////////////
  ansel=0;
  anselh=0;
  c1on_bit=0;
  c2on_bit=0;
///////////////////////////////////////
  TRISc = 0;               // PORTc is output
  PORTc = 0xFF;            // Initialize PORTc
  TRISd = 0;               // PORTd is output
  PORTd = 0xFF;            // Initialize PORTd
  TRISa = 0;               // PORTa is output
  PORTa = 1;            // Initialize PORTa
  TMR0  = 0;              // Timer0 initial value
  INTCON = 0xA0;           // Enable TMRO interrupt
  cnt = 0;                 // Initialize cnt

  t1con=1;
  tmr1if_bit=0;
  tmr1h=128;
  tmr1l=0;
  tmr1ie_bit=1;
  cnt1=0;
  intcon=192;
 
  cnt2 = 0;                    // initialize cnt
  PORTc = 0xFF;               // Initialize PORTc
  TRISc =   0;                // PORTc is output
  T2CON = 7;               // Timer2 settings
  TMR2  =   0;                // Initialize Timer2 register
  TMR2IE_bit = 1;             // enable interupt
  INTCON = 0xC0;              // Set GIE, PEIE

}

This code toggle the bits of port a, b and d when the interrupts of timer 1, 0, and 2 respectively. The time duration is about 0.5 sec, 1 sec, and 2 sec.

void interrupt() is the isr taking care of interrupts; handling interrupts and take decision according to them.

Schematic:

Wednesday, September 15, 2010

PIC16F887/877 programming in C Tutorial 8 (PWM)

PWM:
The PWM mode generates a Pulse-Width Modulated signal on the CCPx pin. The duty cycle, period and resolution are determined by the following registers:
• PR2
• T2CON
• CCPRxL
• CCPxCON
In Pulse-Width Modulation (PWM) mode, the CCP module produces up to a 10-bit resolution PWM output on the CCPx pin. Since the CCPx pin is multiplexed with the PORT data latch, the TRIS for that pin must be cleared to enable the CCPx pin output driver.

PWM Period:
The PWM period is specified by the PR2 register of Timer2. The PWM period can be calculated using the formula
                             PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)
                             Note: TOSC = 1/FOSC

When TMR2 is equal to PR2, the following three events occur on the next increment cycle:
• TMR2 is cleared
• The CCPx pin is set. (Exception: If the PWM duty cycle = 0%, the pin will not be set.)
• The PWM duty cycle is latched from CCPRxL into CCPRxH.

PWM Duty Cycle:
The PWM duty cycle is specified by writing a 10-bit value to multiple registers: CCPRxL register and DCxB<1:0> bits of the CCPxCON register. The CCPRxL contains the eight MSbs and the DCxB<1:0> bits of the CCPxCON register contain the two LSbs. CCPRxL and DCxB<1:0> bits of the CCPxCON register can be written to at any time. The duty cycle
value is not latched into CCPRxH until after the period completes (i.e., a match between PR2 and TMR2 registers occurs). While using the PWM, the CCPRxH register is read-only.

         Pulse Width = (CCPRxL:CCPxCON<5:4>) • TOSC • (TMR2 Prescale Value)

                  Duty Cycle Ratio (CCPRxL:CCPxCON<5:4>) / 4(PR2 + 1)

PWM Resolution:
The resolution determines the number of available duty cycles for a given period. For example, a 10-bit resolution will result in 1024 discrete duty cycles, whereas an 8-bit
resolution will result in 256 discrete duty cycles. The maximum PWM resolution is 10 bits when PR2 is 255.
                                     Resolution = log[4(PR2 + 1)] / log(2)  bits

Setup for PWM Operation:
The following steps should be taken when configuring the CCP module for PWM operation:
1. Disable the PWM pin (CCPx) output drivers as an input by setting the associated TRIS bit.
2. Set the PWM period by loading the PR2 register.
3. Configure the CCP module for the PWM mode by loading the CCPxCON register with the appropriate values.
4. Set the PWM duty cycle by loading the CCPRxL register and DCxB<1:0> bits of the CCPxCON register.
5. Configure and start Timer2:
• Clear the TMR2IF interrupt flag bit of the PIR1 register.
• Set the Timer2 prescale value by loading the T2CKPS bits of the T2CON register.
• Enable Timer2 by setting the TMR2ON bit of the T2CON register.
6. Enable PWM output after a new PWM cycle has started:
• Wait until Timer2 overflows (TMR2IF bit of the PIR1 register is set).
• Enable the CCPx pin output driver by clearing the associated TRIS bit.

Note: This is only for 887. For Enhanced PWM Mode please refer to pic16F887 datasheet.

Lets do all this things in easy way by using in-build mikroc library for pwm.
Code:
Lets write the code to generate PWM on pins RC1 & RC2. And switches RA0-RA3 to increase/decrease PWM duty cycle.

unsigned short current_duty, current_duty1;

void InitMain() {
//using 887 and 8MHz xtal//////////////
  ANSEL  = 0;                         // Configure AN pins as digital
  ANSELH = 0;
  C1ON_bit = 0;                       // Disable comparators
  C2ON_bit = 0;

  PORTA = 255;
  TRISA = 255;                        // configure PORTA pins as input
  PORTC = 0;                          // set PORTC to 0
  TRISC = 0;                          // designate PORTC pins as output
  PWM1_Init(1000);                    // Initialize PWM1 module at 1KHz
  PWM2_Init(2000);                    // Initialize PWM2 module at 2KHz
}

void main() {
  InitMain();
  current_duty  = 16;                 // initial value for current_duty
  current_duty1 = 16;                 // initial value for current_duty1

  PWM1_Start();                       // start PWM1
  PWM2_Start();                       // start PWM2
  PWM1_Set_Duty(current_duty);        // Set current duty for PWM1
  PWM2_Set_Duty(current_duty1);       // Set current duty for PWM2

  while (1) {                         // endless loop
    if (RA0_bit) {                    // button on RA0 pressed
      Delay_ms(40);
      current_duty++;                 // increment current_duty
      PWM1_Set_Duty(current_duty);
     }

    if (RA1_bit) {                    // button on RA1 pressed
      Delay_ms(40);
      current_duty--;                 // decrement current_duty
      PWM1_Set_Duty(current_duty);
     }

    if (RA2_bit) {                    // button on RA2 pressed
      Delay_ms(40);
      current_duty1++;                // increment current_duty1
      PWM2_Set_Duty(current_duty1);
     }

    if (RA3_bit) {                    // button on RA3 pressed
      Delay_ms(40);
      current_duty1--;                // decrement current_duty1
      PWM2_Set_Duty(current_duty1);
     }

    Delay_ms(5);                      // slow down change pace a little
  }
}

PWM1_Init();Initializes the PWM module with duty ratio 0. Parameter freq is a desired PWM frequency in Hz.
PWM1_Set_Duty(); Sets PWM duty ratio. Parameter duty takes values from 0 to 255, where 0 is 0%, 127 is 50%, and 255 is 100% duty ratio. Other specific values for duty ratio can be calculated as (Percent*255)/100.
PWM1_Start(); Starts PWM.


Schematic:

Thursday, September 9, 2010

PIC16F887/877 programming in C Tutorial 7 (LCD Moving Display n Custom Characters)

LCD; Moving Display & Custom Character:
You can display your custom characters by using mikroc custom character tool. Go to tools>>customer character then make the character you want to display. After this click on 'generate code'. It will generate a customchar function just copy and paste above the main function. customchar has two parameters; row and position in that row. Just pass these parameters where you want to see your custom character on LCD.
You can also move the text, left and right, on LCD screen; by using the LCD commands. _LCD_SHIFT_LEFT and   _LCD_SHIFT_RIGHT are the commands. There are various other commands in mikroc, i forget to tell you in lcd tutorial, these are:
LCD Commands
Now lets write a code to display and move the custom character, back and forth, (of your choice) on LCD screen.

Code:

// LCD module connections
sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D4 at RB0_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D7 at RB3_bit;

sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;
// End LCD module connections

char txt[] = "MOVING TEXT";
char txt1[] = "EEW";
char i;                              // Loop variable
const char character[] = {0,31,4,10,17,17,14,0};

void CustomChar(char pos_row, char pos_char) {
  char i;
    Lcd_Cmd(64);
    for (i = 0; i<=7; i++) Lcd_Chr_CP(character[i]);
    Lcd_Cmd(_LCD_RETURN_HOME);
    Lcd_Chr(pos_row, pos_char, 0);
}

void main(){
///////// comment for 877////////////////////////////////////////
  ANSEL  = 0;                        // Configure AN pins as digital I/O
  ANSELH = 0;
  C1ON_bit = 0;                      // Disable comparators
  C2ON_bit = 0;
////////////////////////////////////////////////////////////////
  Lcd_Init();                        // Initialize LCD

  Lcd_Cmd(_LCD_CLEAR);               // Clear display
  Lcd_Cmd(_LCD_CURSOR_OFF);          // Cursor off
  Lcd_Out(1,8,txt);                 // Write text in first row
  Lcd_Out(2,11,txt1);                 // Write text in second row
  CustomChar(2,14);
  CustomChar(2,10);

  // Moving text
  for(i=0; i<8; i++) {               // Move text to the right 4 times
    Lcd_Cmd(_LCD_SHIFT_RIGHT);
    Delay_ms(200);
  }

  while(1) {                         // Endless loop
    for(i=0; i<8; i++) {             // Move text to the left 7 times
      Lcd_Cmd(_LCD_SHIFT_LEFT);
      Delay_ms(200);
    }

    for(i=0; i<8; i++) {             // Move text to the right 7 times
      Lcd_Cmd(_LCD_SHIFT_RIGHT);
      Delay_ms(200);
    }
  }
}

After you specify the LCD connections paste the code of your custom character you wanna display. Then initialize the LCD, display text (if you want), and display your custom char by passing the positions parameter.  

Now to move text just use for loop and LCD command LEFT/RIGHT, where you want to move. Number of iterations depends on you. Once you have done this, use a endless loop and apply the for loop to move text in back forth. That is it!!.

Schematic:
moving text

PIC16F887/877 programming in C Tutorial 6 (Keypad Interfacing)

Keypad Interfacing:
This tutorial is about the interfacing of 4x3(4 rows & 3 columns) keypad with micro-controller using library routine of mikroc. Using these functions it is much easy to program. The basic technique is,  make the coloums as i/p and drive the rows making them o/p, this whole procedure of reading the keyboard is called scanning.

In order to detect which key is pressed from the matrix, we make row lines low one by one and read the coloums. Lets say we first make Row1 low, then read the columns. If any of the key in row1 is pressed will make the corrosponding column as low i.e if second key is pressed in Row1, then column2 will give low. So we come to know that key 2 of Row1 is pressed. This is how scanning is done.

So to scan the keypad completely, we need to make rows low one by one and read the columns. If any of the button is pressed in a row, it will take the corrosponding column to a low state which tells us that a key is pressed in that row. If button 1 of a row is pressed then Column 1 will become low, if button 2 then column2 and so on...

The above whole algorithm is in Keypad_Key_Click() function, we are going to use, ones we have key just compare it the ASCII value to get the right key.  

Code:
Lets write a program that display the pressed key and quantity (how many times the key is pressed), on LCD using mickroc routines.  

unsigned short kp, cnt, oldstate = 0;
char txt[4];

// Keypad module connections
char  keypadPort at PORTD;
// End Keypad module connections

// LCD module connections
sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D4 at RB0_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D7 at RB3_bit;

sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;
// End LCD module connections

void main() {
  cnt = 0;                                 // Reset counter
  Keypad_Init();                           // Initialize Keypad
 ///////////// comment for 877/////////////////////////////////////
  ANSEL  = 0;                              // Configure AN pins as digital I/O
  ANSELH = 0;
  c1on_bit=0;
  c2on_bit=0;
/////////////////////////////////////////////////////////////////
  Lcd_Init();                              // Initialize LCD
  Lcd_Cmd(_LCD_CLEAR);                     // Clear display
  Lcd_Cmd(_LCD_CURSOR_OFF);                // Cursor off

  Lcd_Out(1, 1, "Key Pressed :");                 // Write message text on LCD
  Lcd_Out(2, 1, "Times: ");

  do {
    kp = 0;                                // Reset key code variable

    // Wait for key to be pressed and released
    do
            kp = Keypad_Key_Click();             // Store key code in kp variable
    while (!kp);
   // Prepare value for output, transform key to it's ASCII value
    switch (kp) {

      case  1: kp = 49; break; // 1
      case  2: kp = 50; break; // 2
      case  3: kp = 51; break; // 3
      case  4:                              //  kp = 65; break; // A for 4x4 pad
      case  5: kp = 52; break; // 4
      case  6: kp = 53; break; // 5
      case  7: kp = 54; break; // 6
      case  8:                              //kp = 66; break; // B for 4x4 pad
      case  9: kp = 55; break; // 7
      case 10: kp = 56; break; // 8
      case 11: kp = 57; break; // 9
      case 12:                             //kp = 67; break; // C for 4x4 pad
      case 13: kp = 42; break; // *
      case 14: kp = 48; break; // 0
      case 15: kp = 35; break; // #
      //case 16: kp = 67; break; // D for 4x4 pad
    }

    if (kp != oldstate) {                  // Pressed key differs from previous
      cnt = 1;
      oldstate = kp;
      }
    else {                                 // Pressed key is same as previous
      cnt++;
      }

    Lcd_Chr(1, 15, kp);                    // Print key ASCII value on LCD


    byteToStr(cnt, txt);                   // Transform counter value to string
    Lcd_Out(2, 12, txt);                   // Display counter value on LCD
  } while (1);
}

First specify the keypad connections; same thing we did in last tutorial, and LCD connections. Then initialize the keypad routine by using Keypad_Init() function, initialize the lcd and display the appropriate messages. Now once the key is pressed and the value is store in kp, by using Keypad_Key_Click() function, we have to compare the key number to its ASCII code to get the right key. Switch(kp) is used for this purpose.

Once we get the right key we compare it with oldstate; if Pressed key differs from previous, assign the cnt with one and update the oldstate. And if the Pressed key is same as previous, cnt is incremented (to count the number of times this key is pressed).

After this we want to display the pressed key and number of times on LCD. To display ASCII value, kp, we use 'Lcd_Chr' function. On the other hand cnt is a short to display it we have to convert it into a string; 'byteToStr' function is do the job. It converts input byte to a string. The output string has fixed width of 4 characters including null character at the end. txt[4] is declared for this purpose. For the next key the whole steps are repeated. 

Schematic:
keypad connection diagram

Wednesday, September 8, 2010

PIC16F887/877 programming in C Tutorial 5 (LCD Interfacing)

LCD Interfacing:
In this tutorial i will show you how to interface 16x2 LCD with micro-controller. 16x2 means there are two rows and each row contain maximum 16 characters. 
For more detail refer to the LCD datasheet, which you are using.
Basic Connection:
Applies 5v to pin 2 and gnd to pins 1 & 5. Use variable resistor at pin 3 to set contrast. Pins 7 to 14 are the data pins,, used to send/rec data. Pin 6 is of enable; every time when you write to lcd you should have to give high to low, to this pin. pin 4 is register select pin use to give commands like clear, home etc.  

In this tutorial i will interface lcd in 4-bit instead of 8-bit, so we only required four data pins.

Code:
Lets write a code that will display the motor status and its direction; it will be fun!!!!

Requirements:
Design a motor controller circuit using l298 and display its status on lcd. LCD is connected to portb and motor controller circuit is at portd. Required two switches to change motor direction; if both are open/close the motor should remain off. Also the switches are connected to portd.

// LCD module connections
sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D4 at RB0_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D7 at RB3_bit;

sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;

// End LCD module connections
char txt3[] = "Motor Direction";
char txt2[] = "Clock Wise ";
char txt1[] = "Counter C.W";
char txt[]  = "Motor Off  ";

void main() {
/////// comment for 877//////////////////
     ansel=0;
     anselh=0;
    
     c1on_bit=0;
     c2on_bit=0;
 ///////////////////////////////////////
     trisd0_bit=0;
     trisd1_bit=0;
     trisd2_bit=0;
    
     trisd3_bit=1;                 // CW dir
     trisd4_bit=1;                  //CCW dir

     Lcd_Init();                    // lcd ini
     Lcd_Cmd(_LCD_CLEAR);           //lcd clear
     Lcd_Cmd(_LCD_CURSOR_OFF);
     while(1){

      Lcd_Out(1,1,txt3);             //1row 1col
      delay_ms(500);
      if(rd3_bit){
        Lcd_Out(2,1,txt2);
        rd1_bit=0;
        rd0_bit=1;
      }
       
      if(rd4_bit){
        Lcd_Out(2,1,txt1);
        rd0_bit=0;
        rd1_bit=1;
      }
       
      if(rd3_bit & rd4_bit){             //invalid selction
        lcd_out(2,1,txt);
        rd0_bit=0;                       //to stop motor
        rd1_bit=0;
      }
       
      if(!rd3_bit & !rd4_bit){           //inv sec
        lcd_out(2,1,txt);
        rd0_bit=0;
        rd1_bit=0;
      }
      }

}


This is the simple code according to out requirement. To interface the LCD you must have to specify the port (port i/o), as well as the sfr of that port,  you are using 'sbit LCD_RS at RB4_bit' and 'sbit LCD_RS_Direction at TRISB4_bit' doing this job. As we are using LCD in four bit so, just specify the four data pins (pins 11 to 14 of LCD), E and RS pins.

txt1 to txt3 are the character array containing the message you want to display. Pins 0, 1, and 2 of portD is configure as o/p whereas pins 3, and 4 as i/p. L298 is connected to the pins0-2 and switches are at pins 3 and 4.

After setting the pins for LCD we used Lcd_Init(); a mikroc lcd library function, to initialize the LCD. Lcd_Cmd(char) is used to send commands to LCD like clear screen, curs0r off etc.
Lcd_Out(char row, char column, char *text); Prints text on Lcd starting from specified position; 1, 1 means first row and first column. Then a simple logic off switches and their functions.

L298 Connections:
Important thing is the connection of l298, as we are using out1 & out2 so we have to use in1 & in2, also have to enable ena pin for these selections. Use a 10 ohm resistor to connect between sensa pin and ground. Apply 9/12v to VCC, pin 9, depending on your motor voltage and 5v at pin 4, VC. To change to direction just change the bit value of in1 and in2; or in other words swap these values. If the both have same value, 0 or 1, motor will not rotate; as potential is same, current can't flow!!.

Schematic:
motor controller