Initial Input Output Routines

copyright, Peter H. Anderson, Baltimore, MD, Jan, '99


Introduction.

This discussion is intended to illustrate basic concepts in programming the PIC16F84 using C in the context of lighting LEDs in response to switches.

Configuration.



			PIC16F84

	Inputs				Outputs
							LED
	    S7
GRD  -----X---- RB7 (term 13)	RB3 (term 9) -----330-->|---- GRD

            S6
GRD  -----X---- RB6 (term 12)	RB2 (term 8) -----330-->|---- GRD

            S5
GRD  -----X---- RB5 (term 11)	RB1 (term 7) -----330-->|---- GRD


				RB0 (term 6) -----330-->|---- GRD

Note that normally open switches are connected to PORTB7, 6 and 5. These may simply be wires which are either open or connected to ground. The PIC16F84 includes the ability to turn on weak pullup resistors on PORTB inputs. Thus, when a switch is open, the PIC input sees +5VDC through a weak pull-up (100K) which it interpets as a logic one. When at ground, the PIC sees a logic zero.

Four LEDs are connected to outputs RB3, 2, 1 and 0. Note that I opted to configure them such that they are on when the PIC output is a logic one. My only reason for doing so is to avoid the confusion associated with driving a load with a logic zero as is required with TTL. Unlike TTL, the CMOS technology associated with the PIC permits outputs to source sufficient current to light an LED.

Note that we have 5V LEDs in a variety of colors that include the 330 Ohm series current limiting resistor which greatly simplifies assembly.

Program LED_1.C.

This program uses a single LED on RB0 and a single switch on RB7. The idea is to flash the LED when switch S7 is at ground.

// LED_1.C
//
// Continually flashes LED on RB.0 if switch on RB.7 is at logic zero.
//
// Build project with LED_1.C and LCD_F84.C.
//
// copyright, P. H. Anderson, Jan, '99


#include <pic.h>
#include "lcd_f84.h"	// for delay routine

#define LED_DIR TRISB0
#define SW_DIR TRISB7

#define IN 1
#define OUT 0

#define LED_PIN RB0
#define SW_PIN RB7

void main(void)
{
   while(1)	// continually
   {
      CLRWDT();
      RBPU = 0;		// enable internal pullups on PORTB
      LED_DIR = OUT;
      SW_DIR = IN;
      if (SW_PIN)		// if the switch is at logic one
      {
         LED_PIN = 0;  	// turn the LED off
      }
      else
      {
         LED_PIN = 1;	// otherwise, flash LED one time
         lcd_delay_ms(200);
         LED_PIN = 0;   
         lcd_delay_ms(200);
      }
   }
}

Discussion.

Include file PIC.H includes various definitions of the PICs registers and bits in those registers; e.g., PORTB, RB0, TRISB, TRISB0.

The state of TRISB controls the direction of the PORTB bits. A logic one in a bit position configures the PIC such that it is an input and a zero as an output.

Thus, RB0 is made an output and RB7 an input;

     TRISB0 = 0;
     TRISB7 = 1;
However, I have used various #defines such that the code appears a bit more readable;
     LED_DIR = OUT
     SW_DIR = IN
The importance of doing this, however, extends beyond readability. It allows flexability in modifying or reusing your code in another application. Embedding such statements as
     RB0 = 1
in your code makes it all that more difficult when the LED is relocated say to PORTA, bit 1, or the entire program is mapped over to a PIC12C671 where the LED is on IO GP4. By using the #define, the transition might be as simple as changing the #define to
     #define LED GP4
The CLRWDT() is defined in pic.h as;
     #define CLRWDT()  asm(" clrwdt")
The asm operator permits the insertion of a single line of assembly code.

The weak pullups on the PORTB pins that are used as inputs are configured using;

     RBPU = 0;
Note that function lcd_delay_ms is implimented in Program LCD_F84.C which is a collection of routines used primarily to interface with a PIC-n-LCD serial LCD. It just happens that the various routines associated with controlling the PIC-n-LCD require this type of time delay and thus I decided to keep it in the LCD_F84.C program.

In compiling program LED_1.C, the compiler needs to know about this function. This is handled;

     extern void lcd_delay_ms(unsigned int t);
with the extern indicating that it is implemented externally to LED_1.C.

This statement appears in file "lcd_f84.h".

Note that the program operates in a continual loop (while(1)). Housekeeping tasks of configuring for weak pullups, making RB7 an input and RB0 an output are performed. The switch is then read and if at zero, the LED is flashed one time.

In theory, the housekeeping chores, need only be performed once as illustrated below;

void main(void)
{
   RBPU = 0;		// enable internal pullups on PORTB
   LED_DIR = OUT;
   SW_DIR = IN;

   while(1)	// continually
   {
      if (SW_PIN)
      ...
The advantage in placing them within the loop is that life isn't always perfect. Noise may well cause one of the TRIS bits to change, but, if it is in the loop, this is corrected. If it is outside the loop, it is corrected only when the processor is reset.

Of course, including them in the loop means that time is consumed in executing them. But, in this application, the nominal 10 usecs it rquires to execute these housekeeping chores is insignificant when compared with the task of turning an LED on and off for 200 msecs.

As a rule of thumb it is wise to assume that if anything can go wrong, it will and structure your program such that is self correcting.

Program LED_2.C.

This program builds on LED_1.C so as to provide four different flash rates based on switches on inputs RB6 and RB5.

Note that the speed (or flash rate) is;

    speed = (PORTB >> 5) &0x03;	// RB6 and RB5
With the compiler I used, the entire port is defined as PORTB and the individual bits as RB7, RB6, etc. The entire tri-state register is TRISB and the individual bits as TRISB7, TRISB6, etc.

Note that in using the above statement, I have violated my own guidance in not embedding hardware specific instructions in the code. I could have used;

     #define SPEED ((PORTB>>5) & 0x03)
and then used;
   speed = SPEED; 
in the program. If I later used the code on a PIC12C672 where the flash rate is GP3 and GP0, the same code could be used with a minor change in the #define statement;
     #define SPEED ((GP3 << 1) | (GP0))
Not to get too far afield of program LED_2, the code could be structured such that it could be compiled for a PIC12C672 or PIC16F84.
     #ifdef PIC12C672	// if PIC12C672 is defined
     #define SPEED  ((GP3 << 1) | (GP0))
     #else
     #define SPEED ((PORTB>>5) & 0x03)
     #endif
In program LED_2.C, a switch statement is used to map the states of RB6 and RB5 into a delay_time. A word of caution. In C, "switch" is a key word as thus cannot be used as the name of a function or variable. I have that error more times than I care recall.
// LED_2.C
//
// Flashes LED on RB0 if switch on RB7 is at logic zero
// Flash rate is determined by switches on RB6 and RB5.
//
// Uses switch statement to map switch setting into delay time.
//
// Build with LED_2.C and LCD_F84.C
//
// P. H. Anderson, Baltimore, MD, Jan, '99

#include <pic.h>
#include "lcd_f84.h"	// for delay routine

#define LED_DIR TRISB0
#define S5_DIR TRISB5	// speed bits
#define S6_DIR TRISB6
#define S7_DIR TRISB7	// enable

#define IN 1
#define OUT 0

#define LED_PIN RB0
#define S7_PIN RB7

void main(void)
{
   unsigned char speed;
   unsigned int delay_time;

   while(1)	// continually
   {
      CLRWDT();
      RBPU = 0;		// enable internal pullups on PORTB
      LED_DIR = OUT;
      S5_DIR = IN;
      S6_DIR = IN;
      S7_DIR = IN;

      speed = (PORTB >> 5) &0x03;	// RB6 and RB5

      switch(speed)
      {
         case 0:  delay_time=50;
                  break;
         case 1:  delay_time=100;
                  break;
         case 2:  delay_time=200;
                  break;
         case 3:  delay_time=500;
                  break;
      } // end of switch

      if (S7_PIN)		// if the switch is at logic one
      {
         LED_PIN = 0;  	// turn the LED off
      }
      else
      {
         LED_PIN = 1;	// otherwise, flash LED one time
         lcd_delay_ms(delay_time);
         LED_PIN = 0;   
         lcd_delay_ms(delay_time);
      }
   }
}
Program LED_3.C.

This program performs the same function as LED_2.C.

However, a constant variable array is used to map the settings of RB6 and RB5 to a delay time. The concept of a constant array is discussed in the context of program LED_4.C.

// LED_3.C
//
// Flashes LED on RB0 if switch on RB7 is at logic zero
// Flash rate is determined by switches on RB6 and RB5.
//
// Uses const array to map switch setting into delay time.
//
// Build with LED_3.C and LCD_F84.C
//
// P. H. Anderson, Baltimore, MD, Jan, '99

#include <pic.h>
#include "lcd_f84.h"	// for delay routine

#define LED_DIR TRISB0
#define S5_DIR TRISB5	// spped bits
#define S6_DIR TRISB6
#define S7_DIR TRISB7	// enable

#define IN 1
#define OUT 0

#define LED_PIN RB0
#define S7_PIN RB7

void main(void)
{
   const unsigned int speeds[4]={50, 100, 200, 500};
   unsigned int delay_time;
   unsigned char speed;
   
   while(1)	// continually
   {
      CLRWDT();
      RBPU = 0;		// enable internal pullups on PORTB
      LED_DIR = OUT;
      S5_DIR = IN;
      S6_DIR = IN;
      S7_DIR = IN;

      speed = (PORTB >> 5) &0x03;	// RB6 and RB5
      delay_time = speeds[speed];
     
      if (S7_PIN)		// if the switch is at logic one
      {
         LED_PIN = 0;  	// turn the LED off
      }
      else
      {
         LED_PIN = 1;	// otherwise, flash LED one time
         lcd_delay_ms(delay_time);
         LED_PIN = 0;   
         lcd_delay_ms(delay_time);
      }
   }
}
Program LED_4.C.

In this program four LEDs on RB3::RB0 are flashed in a pattern determined by switches on RB6 and RB5. In the previous routine, the settings of RB6 and RB5 were mapped into a delay_time using a switch statement. In this routine, the setting is mapped into one of four arrays of patterns. The pattern is sequentially output at a fixed flash rate.

Note that the four arrays are declared as;

     const unsigned char patt[5];
Constant variables are implemented in program memory using the RETLW technique for a lookup table. Constant variables may only be intialized. That is, the values cannot later be modified;
	patt[4] = 0x0f;	// invalid statement
If the four arrays used in this program had been declared in the form;
     unsigned char patt[5];
they would be implemented in RAM and C would use the FSR register for indirectly addressing each element. Note that RAM varaibles may be changed. The disadvantage is that RAM is limited in a PIC and a few strings may well use or array patterns may well exhaust it.
// LED_4.C
//
// Flashes LEDs on RB3::RB0 if switch on RB7 is at logic zero
// Flash pattern is determined by switches on RB6 and RB5.
//
// RB6 RB5	Pattern
//
//   0   0      Up
//   0   1      Down
//   1   0	Outside in
//   1   1      Inside out
//
// Uses const arrays to implement patterns
//
// Build with LED_4.C and LCD_F84.C
//
// P. H. Anderson, Baltimore, MD, Jan, '99

#include <pic.h>
#include "lcd_f84.h"	// for delay routine

#define LED_DIR TRISB0
#define S5_DIR TRISB5	// spped bits
#define S6_DIR TRISB6
#define S7_DIR TRISB7	// enable

#define IN 1
#define OUT 0

#define LED_PIN RB0
#define S7_PIN RB7

void main(void)
{

   const unsigned char up[5]={0x00, 0x01, 0x03, 0x07, 0x0f};
   const unsigned char down[5]={0x00, 0x0f, 0x07, 0x03, 0x01};
   const unsigned char out_in[3]={0x00, 0x81, 0x42};
   const unsigned char in_out[3]={0x03, 0x42, 0x81}; 

   unsigned char patt_type, n;

   while(1)	// continually
   {
      CLRWDT();
      RBPU = 0;		// enable internal pullups on PORTB
      LED_DIR = OUT;
      S5_DIR = IN;
      S6_DIR = IN;
      S7_DIR = IN;

      patt_type = (PORTB >> 5) &0x03;	// RB6 and RB5
         
      if (S7_PIN)		// if the switch is at logic one
      {
         PORTB = 0;  	// turn all of the LEDs off
      }
      else
      {
         switch(patt_type)
         {
            case 0:  for (n=0; n<5; n++)
                     {
                        PORTB = up[n];
                        lcd_delay_ms(200);
                     }
                     break;

           case 1:  for (n=0; n<5; n++)
                     {
                        PORTB = down[n];
                        lcd_delay_ms(200);
                     }
                     break;

           case 2:  for (n=0; n<3; n++)
                     {
                        PORTB = out_in[n];
                        lcd_delay_ms(200);
                     }
                     break;

           case 3:  for (n=0; n<3; n++)
                     {
                        PORTB = in_out[n];
                        lcd_delay_ms(200);
                     }
                     break;
         } // end of switch
      } // end of else
   } // of while(1)   
} // end of main
Program LED_5.C.

This program is functionally the same as LED_4.C. However and additional constant array is used to indicate the number of elements in each of the four array patterns;

   const unsigned char patt_num_ele[4]={5, 5, 3, 3};
In addition an array of pointers to type constant unsigned char is declared;
   const unsigned char *patts[4];
It is important to note that this array is implemented in RAM and thus may be modified. In this case, each of the elements are set to the address of each of the constant arrays;
   patts[0] =  up;
   patts[1] =  down;
   patts[2] =  out_in;
   patts[3] =  in_out;
Thus, the RB6 and RB5 switches are read;
   patt_type = (PORTB >> 5) &0x03;	// RB6 and RB5
The particular array to output then is;
   patts[patt_type]
and the number of patterns in that array is;
   patt_num_ele[patt_type]
These are passed to a function;
   void out_patts(const unsigned char p[], unsigned char num_ele);
which sequentially outputs num_ele patterns in the designated array.
// LED_5.C
//
// Flashes LEDs on RB3::RB0 if switch on RB7 is at logic zero
// Flash pattern is determined by switches on RB6 and RB5.
//
// RB6 RB5	Pattern
//
//   0   0      Up
//   0   1      Down
//   1   0	Outside in
//   1   1      Inside out
//
// Uses const arrays to implement patterns
//
// Build with LED_5.C and LCD_F84.C
//
// P. H. Anderson, Baltimore, MD, Jan, '99

#include <pic.h>
#include "lcd_f84.h"	// for delay routine

#define LED_DIR TRISB0
#define S5_DIR TRISB5	// speed bits
#define S6_DIR TRISB6
#define S7_DIR TRISB7	// enable

#define IN 1
#define OUT 0

#define LED_PIN RB0
#define S7_PIN RB7

void out_patts(const unsigned char p[], unsigned char num_ele);

void main(void)
{

   const unsigned char *patts[4];
   const unsigned char up[5]={0x00, 0x01, 0x03, 0x07, 0x0f};
   const unsigned char down[5]={0x00, 0x0f, 0x07, 0x03, 0x01};
   const unsigned char out_in[3]={0x00, 0x81, 0x42};
   const unsigned char in_out[3]={0x03, 0x42, 0x81}; 

   const unsigned char patt_num_ele[4]={5, 5, 3, 3};

   unsigned char patt_type;
   
   patts[0] =  up;
   patts[1] =  down;
   patts[2] =  out_in;
   patts[3] =  in_out;

   while(1)	// continually
   {
      CLRWDT();
      RBPU = 0;		// enable internal pullups on PORTB
      LED_DIR = OUT;
      S5_DIR = IN;
      S6_DIR = IN;
      S7_DIR = IN;

      patt_type = (PORTB >> 5) &0x03;	// RB6 and RB5
         
      if (S7_PIN)		// if the switch is at logic one
      {
         PORTB = 0;  	// turn all of the LEDs off
      }
      else
      {
         out_patts(patts[patt_type], patt_num_ele[patt_type]);
      }
  
   } // end of while(1)
} // end of main


void out_patts(const unsigned char p[], unsigned char num_ele)
{
   unsigned char n;
   for (n=0; n<num_ele; n++)
   {
      PORTB = p[n];
      lcd_delay_ms(200);
   }
}
Summary.

This discussion has provided an overview of C in the context of simple flashing of LEDs.

For the compiler I used (High Tech PIC C) TRISB and PORTB is used to address the entire direction and port registers. TRISB0, TRISB1, etc and RB0, RB1, etc are used for addressing specific bits. Setting RBPU to zero causes the weak pullup resistors to be present on PORTB inputs.

The use of #defines is suggested to make code readable and transportable.

Assume that what can go wrong, will. When possible, structure your code such that such house keeping chores as setting the direction register and the like are constantly performed.

Looping using the "while" and "for" and decision making using "if", "else" and "switch" been presented in various examples.

The concept of an array of constant variables was presented.