Friday, October 15, 2004

PIC16F84

Introduction:

This page contains some basic information about programming the PIC16F84. I provide circuit diagrams at the top of most of the C files.

Note: I've added a "putting it all together" section below. The code in that section is cleaner than in the preceding sections, so if you're not completely new at this, I'd skip right to that section.

What You Need:

  • A programmer. There are lots of options, and you can cheap out if you like. I purchased PICALL (assembled) from www.qkits.com. Qkits is an excellent resource for any electronic project. For more information about the PICALL programmer, visit www.picallw.com. I chose this programmer because it'll program just about anything. Once you've got the PICALL hardware, you need to download the latest software from www.picallw.com. If you're buying this programmer, do yourself a favour and buy a ZIF socket and a power source from the same distributor as the programmer.
  • A compiler. There are lots of options. I decided to cheap out and went with PICC LITE from HI-TECH Software. You could just program in assembler, and I'd love to, but the truth is:
    1. no one else will understand your code.
    2. You won't understand your code two years from now.
    3. as cool as assembler is, it's time you suck it up and write in c.
  • Hardware. ca.digikey.com or www.digikey.com if you're not in Canada is the best there is. They have everything you can imagine, for good prices and fast delivery. I placed an order at 2:00pm on Wednesday and it arrived in Kingston (shipped from Winnipeg) at 2:30pm the next day.
  • Examples. All the examples in this section are written in C for PICC LITE and compiled for the PIC16F84. All of these examples use the PICC LITE include pic.h. This just defines constant labels for the pins, and configuration fuses, etc. You should be able to port this code to another compiler without much difficulty. One final note is on the delay routines. They are for a fixed 4MHz clock.

Simple Projects

Control a 16x2 Character LCD

Serial Communications (rs232) With A max232 Level Converter

Serial Communications (rs232) Without A Level Converter

Putting it all together:

The following application combines rs232 and LCD control in the simplest way I could imagine. The code was initially very similar to the above projects, but I have removed all the fat and redundancies. That implies that the above code isn't as clean as it could be, so if you're feeling brave, you might want to skip all of the above and head straight to this project. Here's what it looks like.

Controlling LCD With PIC16F84

Controlling LCD With PIC16F84 Close-Up

The green board is the PICALL programmer, and the blue board is the project. The red and white switch block allows me to safely program in place (also called ICSP or ICP). When the switches are "down" (as in the pictures) Vss, Vdd and reset pins are connected normally (as in the project spec). When the switches are "up" Vss, Vdd and reset pins are connected to the yellow, black, and white wires of my ICSP connector leading to the PICALL programmer (the blue wire is unused). Note that the ICSP connector's green and red wires connect to RB7 and RB6, and these pins are unused in the project. This particular switch block is a 76stc04, where each switch controls three pins and 2 of the 3 are connected in a given position. If you want to simplify things, I'd suggest you leave out the ICSP stuff until later.

Other than that, we have the reset pin hooked up to a pushbutton (top left), we have a simple rs232 connection using two resistors and an old mouse cord (bottom left), and we have the LCD and it's contrast controlling POT (right). You can't see some of the LCD wires because they run under it, but it's all specified in diagrams in the C files.

You need to wire your board, program your chip, and open a hyperterminal connection (Start > All Programs > Accessories > Communications > Hyperterminal, [COM1, 9600 baud, 8 data bits, no parity, 1 stop bit, no flow control]). Then press and release the reset button. "Hello:" should appear in Hyperterminal, and the LCD should be blank. The first two characters you type in hyperterminal will echo back, and then the LCD will display the custom logo. The next two characters you type in hyperterminal will echo back doubled, and the LCD will go blank. The next character you type into hyperterminal will echo and appear on the LCD. The next character you type will cause the program to loop back to the beginning, effectively a reset.

Below are all the source files necessary for this project. Only main.c includes pic.h and I believe that the only dependencies in the entire project on that file are constructs: PORTA, PORTB, and the CONFIG fuses.

main.c

/* IMPORTANT: I don't know if it's necessary, but I put 10k resistors on
 * the transmit and receive pins of the pic, just to be safe, and it works fine.
 */

/*                     PIC16F84
 *                   .-----------.
 *   TRANSMIT white -|RA2     RA1|- 
 *   RECEIVE orange -|RA3     RA0|- 
 *                  -|RA4    OSC1|- XT CLOCK pin1, 27pF to GND
 *      PUSH BUTTON -|MCLR   OSC2|- XT CLOCK pin2, 27pF to GND
 *              GND -|Vss     Vdd|- +5v
 *           LCD D4 -|RB0     RB7|- ICSP
 *           LCD D5 -|RB1     RB6|- ICSP
 *           LCD D6 -|RB2     RB5|- LCD EN
 *           LCD D7 -|RB3     RB4|- LCD RS
 *                   '-----------'       
 *
 *  PUSH BUTTON = PIN - BUTTON - GND
 *                PIN - 10k - +5v
 */

/*  rs232 interface (serial port communications) without a level converter.
 *  Therefore microcontroller pins are connected DIRECTLY to the serial cable.
 *  Therefore inverted (from standard) logic is used.
 *  
 *  PIC PIN01 PORTA2 - TRANSMIT (receive at PC end of serial cable) white
 *  PIC PIN02 PORTA3 - RECEIVE (transmit at PC end of serial cable) orange
 */

/*  looking head on at the female connector of a serial mouse coard:
 *  i.e. the end that plugs into your pc
 *
 *  5 4 3 2 1      =      blue    null    orange      white    null
 *   9 8 7 6       =          null    null      yellow     null
 *
 *  the following are loose wires on the other end of the coard.
 *  i.e. where it would have been soldered to the mouse circuit board.
 *
 *  5 = blue   = gnd   = GND
 *  3 = orange = PC_td = PIC PIN02 PORTA3
 *  2 = white  = PC_rd = PIC PIN01 PORTA2
 *  7 = yellow = rts   = unused
 */

#include <pic.h>
#include "delay.c"
#include "lcd.c"
#include "rs232.c"

__CONFIG(WDTDIS & XT & UNPROTECT);

main(void) 
{
  unsigned int i = 0;
  unsigned char c;

  TRISA = 0b00001000; 
  TRISB = 0b00000000; 

  rs232_initialize();
  lcd_initialize(); 

  rs232_send_string("Hello: ");
  for (i=0;i<2;i++) 
  {
    rs232_echo_char();
  }

  lcd_draw_logo();

  for (i=0;i<2;i++) 
  {
    c = rs232_echo_char();
    rs232_send_char(c);
  }

  lcd_clear_and_home();

  for (i=0;i<2;i++) 
  {
    c = rs232_echo_char();
    lcd_write_char(c);
  }
}

rs232.c

/* IMPORTANT: I don't know if it's necessary, but I put 10k resistors on
 * the transmit and receive pins of the pic, just to be safe, and it works fine.
 */

/*  rs232 interface (serial port communications) without a level converter.
 *  Therefore microcontroller pins are connected DIRECTLY to the serial cable.
 *  Therefore inverted (from standard) logic is used.
 *  
 *  PIC PIN01 PORTA2 - TRANSMIT (receive at PC end of serial cable) white
 *  PIC PIN02 PORTA3 - RECEIVE (transmit at PC end of serial cable) orange
 */

/*  looking head on at the female connector of a serial mouse coard:
 *  i.e. the end that plugs into your pc
 *
 *  5 4 3 2 1      =      blue    null    orange      white    null
 *   9 8 7 6       =          null    null      yellow     null
 *
 *  the following are loose wires on the other end of the coard.
 *  i.e. where it would have been soldered to the mouse circuit board.
 *
 *  5 = blue   = gnd   = GND
 *  3 = orange = PC_td = PIC PIN02 PORTA3
 *  2 = white  = PC_rd = PIC PIN01 PORTA2
 *  7 = yellow = rts   = unused
 */

/* If you're changing the pinout of the code, these vars are the only changes you have to make.
 * Plus, you'll have to change the TRIS in your main program.
 */
static volatile bit RS232_TD @ (unsigned)&PORTA*8+2;
static volatile bit RS232_RD @ (unsigned)&PORTA*8+3;

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

/**********************************************************************************************
 * Initialize the transmit pin, 
 * by ensuring it is low for an entire message cycle.
 */
void rs232_initialize()
{
  RS232_TD = 0;
  delay_ms(1);
}

/**********************************************************************************************
 * Send a single char out the transmit pin.
 */
void rs232_send_char(unsigned char c) 
{
  c = ~c;

  // start bit

  RS232_TD = 1;
  delay_us(100);

  // 8 data bits

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  RS232_TD = (c & 1);
  c = c >> 1;
  delay_us(98);

  // stop bit

  RS232_TD = 0;
  delay_us(100);
}

/**********************************************************************************************
 * Send a string of chars out the transmit pin.
 */
void rs232_send_string(const char * s)
{
  while(*s)
  { rs232_send_char(*s++); }
}

/**********************************************************************************************
 * Get a single char from the receive pin.
 * Then send that char over the transmit pin.
 */
unsigned char rs232_echo_char() 
{
  unsigned char c = 0;

  // start bit

  while (RS232_RD == 0) { continue; }
  delay_us(125);

  // 8 data bits

  if (RS232_RD == 0) { c = c | 0b00000001; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b00000010; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b00000100; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b00001000; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b00010000; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b00100000; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b01000000; }
  delay_us(97);

  if (RS232_RD == 0) { c = c | 0b10000000; }
  delay_us(97);

  // stop bit

  delay_us(100);

  rs232_send_char(c);

  return c;
}

lcd.c

/*     16x2 character LCD
 *     .----------------.
 *     |  pin15         |- unused
 *     |  pin16         |- unused
 *     |                |
 *     |  pin01      GND|- GND 
 *     |  pin02      +5v|- +5v 
 *     |  pin03 Contrast|- 4.7k pot 
 *     |  pin04       RS|- PORTB4
 *     |  pin05       RW|- GND 
 *     |  pin06       EN|- PORTB5
 *     |  pin07    Data0|- GND 
 *     |  pin08    Data1|- GND
 *     |  pin09    Data2|- GND
 *     |  pin10    Data3|- GND
 *     |  pin11    Data4|- PORTB0
 *     |  pin12    Data5|- PORTB1
 *     |  pin13    Data6|- PORTB2
 *     |  pin14    Data7|- PORTB3
 *     '-----------------'   
 *
 *  LCD interface.
 *  This code will interface to a standard LCD controller like the Hitachi HD44780.
 *  This code is for the standard 14 pin connector, and 4-bit mode is assumed.
 */

/* If you're changing the pinout of the code, these vars are the only changes you have to make.
 * Plus, you'll have to change lcd_write_byte(), where it explicitly puts 4 bits onto portb
 * Plus, you'll have to change the TRIS in your main program.
 */
static bit LCD_RS  @ ((unsigned)&PORTB*8+4);  // Register select
static bit LCD_EN  @ ((unsigned)&PORTB*8+5);  // Enable

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

#define LCD_STROBE  ((LCD_EN = 1),(LCD_EN=0))

/**********************************************************************************************
 * Write one byte to the LCD.
 */
void lcd_write_byte(unsigned char c)
{
  PORTB = (PORTB & 0xF0) | (c >> 4);
  LCD_STROBE;
  PORTB = (PORTB & 0xF0) | (c & 0x0F);
  LCD_STROBE;
  delay_us(40);
}

/**********************************************************************************************
 * Clear and home the LCD. 
 */
void lcd_clear_and_home(void)
{
  LCD_RS = 0;  // control
  lcd_write_byte(0x01);
  delay_ms(2);
}

/**********************************************************************************************
 * Go to the specified position.
 */
void lcd_goto(unsigned char pos)
{
  LCD_RS = 0;  // control
  lcd_write_byte(0x80+pos);
}

/**********************************************************************************************
 * Write one char to the LCD.
 */
void lcd_write_char(char c)
{
  LCD_RS = 1;  // characters
  lcd_write_byte(c);
}

/**********************************************************************************************
 * Write a string of chars to the LCD.
 */
void lcd_write_string(const char * s)
{
  LCD_RS = 1;  // characters
  while(*s)
  { lcd_write_byte(*s++); }
}

/**********************************************************************************************
 * Clear and home the LCD,
 * then write a string of chars to the LCD.
 */
void lcd_overwrite_string(const char * s)
{
  lcd_clear_and_home();
  LCD_RS = 1;  // characters
  while(*s)
  { lcd_write_byte(*s++); }
}

/**********************************************************************************************
 * Write a string of chars to the LCD 
 * starting at the beginning of the second line of the display.
 */
void lcd_write_string_at_second_line(const char * s)
{
  lcd_goto(64);
  LCD_RS = 1;  // characters
  while(*s)
  { lcd_write_byte(*s++); }
}

/**********************************************************************************************
 * Load the custom characters into cgram.
 * Also performs Clear and Home.
 */
void lcd_load_custom_characters(void)
{
  LCD_RS = 0;  // control
  lcd_write_byte(0b01000000);  // cgram address #0
  LCD_RS = 1;  // characters

  // pattern is xxxbbbbb

  // arrow right
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000010);
  lcd_write_byte(0b00011111);
  lcd_write_byte(0b00000010);
  lcd_write_byte(0b00000100);

  // arrow left
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001000);
  lcd_write_byte(0b00011111);
  lcd_write_byte(0b00001000);
  lcd_write_byte(0b00000100);

  // arrow up
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001110);
  lcd_write_byte(0b00010101);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001100);
  lcd_write_byte(0b00011000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  // node
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00001110);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00001110);

  // marked node
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00001110);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00010101);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00001110);

  // line
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00011111);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  // corner bottom left
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000110);
  lcd_write_byte(0b00000011);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  // corner bottom right
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001100);
  lcd_write_byte(0b00011000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  lcd_clear_and_home();
}

/**********************************************************************************************
 * Initialize the LCD.
 */
void lcd_initialize(void)
{
  LCD_RS = 0;  // control
  delay_us(100);
  lcd_write_byte(0x28);  // 4 bit mode, 1/16 duty (2 lines), 5x8 dots
  lcd_write_byte(0x0C);  // display on, blink off, cursor off
//lcd_write_byte(0x0F);  // display on, blink on, cursor on
  lcd_write_byte(0x06);  // entry mode

  lcd_load_custom_characters();
}

/**********************************************************************************************
 * Display a pattern of custom characters from cgram. 
 * Also performs Clear and Home.
 */
void lcd_draw_logo(void)
{
   // 0 = arrow right
   // 1 = arrow left
   // 2 = arrow up
   // 3 = node
   // 4 = marked node
   // 5 = line
   // 6 = corner bottom left
   // 7 = corner bottom right

   lcd_clear_and_home();

   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(3);

   lcd_goto(64);

   lcd_write_char(' ');
   lcd_write_char(6);
   lcd_write_char(5);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(2);
   lcd_write_char(' ');
   lcd_write_char(6);
   lcd_write_char(0);
   lcd_write_char(4);
   lcd_write_char(1);
   lcd_write_char(7);
   lcd_write_char(4);
   lcd_write_char(1);
   lcd_write_char(7);
}

delay.h

#define delay_us(x) { unsigned char cnt; cnt = (x)/(3)|1; while(--cnt != 0) continue; }

extern void delay_ms(unsigned char);

extern void delay_s(unsigned char);

delay.c

/* These routines assume a 4MHz clock.
 */

#include "delay.h"

/* Delay for the specified number of miliseconds.
 */
void delay_ms(unsigned char cnt)
{
  unsigned char i;
  do 
  {
    i = 4;
    do { delay_us(250); } while(--i);
  } 
  while(--cnt);
}

/* Delay for the specified number of seconds.
 */
void delay_s(unsigned char cnt)
{
  unsigned char i;
  do 
  {
    i = 4;
    do { delay_ms(250); } while(--i);
  } 
  while(--cnt);
}

MAIN.HEX

:10000000830100308A0004288301412A831294006E
:10001000951B112815088A001408940A0319950ADB
:1000200082008313151883171408940A84000008AB
:100030000800483465346C346C346F343A342034FE
:0200400000348A
:1003340083128F0006168312282A831205110130B6
:100344008312BD2983128F000612803E8312282A4D
:10035400831206120130831228220230BD2983122F
:100364008C0004308D00FA308312BD218D0BB52929
:100374008C0BB32908008312900004309100533091
:100384009200920BC329910BC129900BBF2908003D
:100394008312061221308F008F0BCE29283083124E
:1003A40028220C30282206302822792BE229100832
:1003B40095000F088F0A0319900A0620831203235D
:1003C4008312100895000F0806200038031908004E
:1003D400D9298312AA210616F7290D0895000C08BD
:1003E4008C0A03198D0A062028220D0895000C0892
:0C03F4000620003803190800EF298312CE
:1004000006160B2A0D0895000C088C0A03198D0A94
:100410000620831228220D0895000C0806200038BB
:1004200003190800022A40308312A4210616202A4C
:100430000D0895000C088C0A03198D0A0620282245
:100440000D0895000C080620003803190800182A2A
:1004500083129000100E0F3993000608F039130430
:1004600086008616861210080F3993000608F039A8
:1004700013048600861686120D309100910B3E2AD9
:10048000080083128D018E010830831685008601D5
:1004900083129F21CA2119308F0000309000D8218B
:1004A0008D018E017E228D0A03198E0A00300E0204
:1004B000023003190D02031C522ABF228D018E0146
:1004C0007E228C0003238D0A03198E0A00300E024F
:1004D000023003190D02031C602AAA218D018E012E
:1004E0007E228C009A218D0A03198E0A00300E029A
:1004F000023003190D02031C702A042883128F0195
:10050000851D802A29309000900B842A851D0F14A8
:1005100021309000900B8A2A851D8F142130900085
:10052000900B902A851D0F1521309000900B962A74
:10053000851D8F1521309000900B9C2A851D0F166C
:1005400021309000900BA22A851D8F16213090003B
:10055000900BA82A851D0F1721309000900BAE2A12
:10056000851D8F1721309000900BB42A2130900008
:10057000900BB82A0F08831203230F080800831278
:10058000AA2100309A2103309A2105309A210530A2
:100590009A2105309A2100309A2103309A210030A7
:1005A0009A2103309A2105309A2105309A21003092
:1005B0009A2103309A2105309A2105309A2103307F
:1005C0009A214030A42120309A2106309A2105300A
:1005D0009A2100309A2103309A2105309A21023065
:1005E0009A2120309A2106309A2100309A21043035
:1005F0009A2101309A2107309A2104309A21013042
:100600009A2107309A29831291009109051521300A
:100610009200920B092B110C031C102B0515112BAA
:10062000051103108312910C21309200920B162BAE
:10063000110C031C1D2B05151E2B05110310831215
:10064000910C21309200920B232B110C031C2A2BAE
:1006500005152B2B051103108312910C21309200EC
:10066000920B302B110C031C372B0515382B051161
:1006700003108312910C21309200920B3D2B110C30
:10068000031C442B0515452B051103108312910CF7
:1006900021309200920B4A2B110C031C512B051593
:1006A000522B051103108312910C21309200920BF2
:1006B000572B110C031C5E2B05155F2B0511031026
:1006C0008312910C21309200920B642B110C031CAD
:1006D0006B2B05156C2B051103108312910C213027
:1006E0009200920B712B051121309200920B762B08
:1006F00008008312061240308312282206160030AA
:1007000028220030282200302822043028220230FB
:1007100028221F30282202302822043028220030CC
:1007200028220030282200302822043028220830D5
:1007300028221F30282208302822043028220430A2
:1007400028220E30282215302822043028220C308E
:100750002822183028220030282200302822003099
:10076000282200302822003028220E302822113082
:10077000282211302822113028220E302822003061
:10078000282200302822003028220E302822113062
:10079000282215302822113028220E30282200303D
:1007A0002822003028220030282200302822003061
:1007B00028221F302822003028220030282204302E
:1007C000282204302822043028220430282206302F
:1007D000282203302822003028220030282204302A
:1007E00028220430282204302822043028220C3009
:1007F0002822183028220030282200302822AA2956
:02400E00F93F78
:00000001FF
{ "loggedin": false, "owner": false, "avatar": "", "render": "nothing", "trackingID": "UA-36983794-1", "description": "This is a tutorial to help a person who has little or no experience get started with the 1684.", "page": { "blogIds": [ 222 ] }, "domain": "holtstrom.com", "base": "\/michael", "url": "https:\/\/holtstrom.com\/michael\/", "frameworkFiles": "https:\/\/holtstrom.com\/michael\/_framework\/_files.4\/", "commonFiles": "https:\/\/holtstrom.com\/michael\/_common\/_files.3\/", "mediaFiles": "https:\/\/holtstrom.com\/michael\/media\/_files.3\/", "tmdbUrl": "http:\/\/www.themoviedb.org\/", "tmdbPoster": "http:\/\/image.tmdb.org\/t\/p\/w342" }