// NOPPP.C (Revised) - M. Covington 1997, 1998, 1999
// Software for the "No-Parts Pic Programmer"
// Inspired by David Tait's TOPIC; compatible therewith.

// HISTORY OF MODIFICATIONS
// Converted to LINUX by Claus Fuetterer in 1999
// Watchdog-bug removed, C. Fuetterer, June 2000

#define BANNER " NOPPP - \"No-Parts\" PIC Programmer"
#include <stdio.h>
#include <stdlib.h>
#include "io_ports.h"
#include <linux/lp.h>
#include <ctype.h>
#include <string.h>
#define IBUFLEN 128
#define LPT1_BASE  (0x378)             /* where LPT1: is on most h/w */

/* These assume that 'base' == LPT1_BASE or equiv. */
#define lp_data   (base)               /* write to this to change D0-D7 */
#define lp_status (base+1)             /* read from this to get status */
#define lp_ctl    (base+2)             /* write to change the ctl lines */

int base = LPT1_BASE;

typedef unsigned int  word;
typedef unsigned char byte, bit;

#define PIC16C84 1
#define PIC16F84 2                                         
#define PIC16F83 3
int     DEVICE = 0;          // which PIC we're programming

#define PROGRAM 1
#define VERIFY  0            // desired action in main programming loop

char    FNAME[80];          // name of file currently loaded

char    CHOICE = 0;          // user's most recent menu choice

void errmsg(char *s) {
  puts(s);
  getchar();
}

// *********************************************************************
// I/O UTILITIES
// *********************************************************************

#define KBUFSIZE 256

char buf[KBUFSIZE];          // keyboard input buffer

char* cleanctrl(char *s) {   // truncates string at first ctrl char,
  int i=0;                   // cleaning up ^M or ^J left behind by fgets
  while (s[i] >= ' ') i++;
  s[i] = 0;
  return s;
}

char* getstring() {                              // read string
  fgets(buf,KBUFSIZE-1,stdin);
  cleanctrl(buf);
  return buf;
}

int getint()  { return atoi(getstring()); };     // read integer

char getchoice(char *s)  {                       // read menu choice
  // prints prompt, then reads letters, uppercasing,
  // until a letter that is in s is found
  printf("\n\nYour choice (%s): ",s);
  do {
    CHOICE = getchar();
    CHOICE = toupper(CHOICE);
    // toupper evals arg twice, so arg can't be getch()
  }
  while ((CHOICE == ',') || (strchr(s,CHOICE) == NULL));
  return CHOICE;
}


// *********************************************************************
// TIMING
// *********************************************************************
               
void delay(int k) {      
  // Delay at least k microseconds, possibly a good bit more.
  // k must be less than 27304.
  // Minimum delay on a 25-MHz 386 is about 100 microseconds;
  // on a 133-MHz Pentium, about 18 microseconds.
  
  // Uses system timer 2.  
  // When running in a DOS box under OS/2, set HW_TIMER ON in DOS settings. 
  
#define IODELAY (in_byte(0x61))   // allow time for timer to respond
               
  unsigned int w;
  unsigned char lo,hi;
  
  out_byte(0x61, (in_byte(0x61) & 0xFD) | 1);  // spkr off, tmr 2 gate on
  w = (unsigned int)(k*1.2);
  out_byte(0x43, 0xB0);                   // tmr 2 mode 0 2-byte load
  IODELAY;
  out_byte(0x42, (unsigned char)w);       // low byte
  IODELAY;
  out_byte(0x42, (unsigned char)(w>>8));  // high byte
  IODELAY;
  do {
    out_byte(0x43,0x80);          // latch timer count
    IODELAY;
    lo = in_byte(0x42);           // discard low byte
    IODELAY;
    hi = in_byte(0x42);           // get high byte  
    IODELAY;
  }  
  while ((hi & 0x80) == 0);    // wait for a 1 there, signifying rollover
  return;
} 
// *********************************************************************
// PARALLEL PORT HARDWARE INTERFACE
// *********************************************************************

// Uses two-wire serial data communication through printer port.
// Pin  1, STROBE, is serial clock;
// Pin 14, AUTOFD, is serial data out to PIC;
// Pin 11, BUSY,   is serial data in from PIC;
// Pin 17, SLCTIN, is low when writing, high to provide pull-up when reading;
// Pin  2, D0,     is lowered to apply Vpp.

// SLCTIN and BUSY are tied together for pull-up and for hardware detection.
// (In current versions this is done through diodes or gates.)

// SLCTIN is an open-collector output with pull-up.
// If it is pulled down, some printer ports will latch it down.
// Accordingly, it and all the other control bits are asserted 
// every time they are needed.

byte BITS = 0x0F;

// Procedures to set and clear the data lines

void datawritable() {                               // SLCTIN, AUTOFD down
  BITS |=  0x0A; out_byte(lp_ctl,BITS); 
}            
void datareadable() {                               // SLCTIN, AUTOFD up
  BITS &= ~0x0A; out_byte(lp_ctl,BITS);
}

void datadown()     { BITS |=  0x02; out_byte(lp_ctl, BITS); } // AUTOFD down
void dataup()       { BITS &= ~0x02; out_byte(lp_ctl, BITS); } // AUTOFD up

void clockdown()    { BITS |=  0x01; out_byte(lp_ctl, BITS); } // STROBE down
void clockup()      { BITS &= ~0x01; out_byte(lp_ctl, BITS); } // STROBE up

void vppon() { 
  BITS &= ~0x04;
  out_byte(lp_ctl, BITS);                           // INIT down, D0 down
  out_byte(lp_data,   0   );
} 

void vppoff() { 
  BITS |= 0x04;
  out_byte(lp_ctl, BITS);                           // INIT up,   D0 up
  out_byte(lp_data,   1   );
} 


bit  datain() { return(((~(byte)in_byte(lp_status)) & 0x80) >> 7); }


void allpinslow() {
  vppoff();
  datawritable();
  datadown();
  clockdown();
  BITS = 0x0F;
  out_byte(lp_data, BITS);
}  


bit  detecthardware() {           // True if BUSY and SLCTIN are tied together.
  datawritable(); // SLCTIN down
  dataup();       // AUTOFD up
  delay(10);
  if (datain() == 1) return(0);
  datareadable(); // SLCTIN up
  dataup();       // AUTOFD up
  delay(10);
  if (datain() == 0) return(0);
  return(1);
}
// *********************************************************************
// PIC COMMUNICATION ROUTINES
// *********************************************************************

void sendbit(bit b) {                  // Sends out 1 bit to PIC
  if (b) dataup(); else datadown();
  clockup();
  delay(1);                            // tset1
  clockdown();                         // data is clocked into PIC on this edge
  delay(1);                            // thld1
  datadown();                          // idle with data line low
}

bit recvbit() {                        // Receives a bit from PIC
  bit b;
  clockup();
  delay(1);                            // tdly3
  clockdown();                         // data is ready just before this
  b = datain();
  delay(1);                            // thld1 
  return b;
}

void sendcmd(byte b) {                 // Sends 6-bit command from bottom of b
  int i;
  datawritable();
  delay(2);                            // thld0
  for (i=6; i>0; i--) {
    sendbit((bit)(b & 1));
    b = b >> 1;
  }
  delay(2);                            // tdly2
}

void senddata(word w) {                // Sends 14-bit word from bottom of w
  int i;
  datawritable();
  delay(2);                            // thld0
  sendbit(0);                          // one garbage bit
  for (i=14; i>0; i--) {
    sendbit((bit)(w & 1));             // 14 data bits
    w = w >> 1;
  }
  sendbit(0);                          // one garbage bit
  delay(2);                            // tdly2
}

word recvdata() {                      // Receives 14-bit word, lsb first
  int i;
  bit b;
  word w = 0;
  datareadable();                      // SLCTIN up for pull-up
  delay(2);                            // thld0
  recvbit();                           // one garbage bit
  for (i=0; i<14; i++) {
    b = recvbit();
    w = w | ((word)b << i);            // 14 data bits
  }
  recvbit();                           // another garbage bit;
  delay(2);                            // tdly2
  return w;
}

// *********************************************************************
// PIC PROGRAMMING ALGORITHMS
// *********************************************************************

// PIC MEMORY MAP

// The PIC16F84 has four programmable memory areas
// (plus data RAM, which is not programmable).
// Config memory is only 1 byte, but is treated like the others.

#define PBASE      0                   // Base address of each memory
#define IBASE      0x2000
#define CBASE      0x2007
#define DBASE      0x2100

#define PSIZEMAX   1024                // Max size of each memory
#define ISIZEMAX   4
#define CSIZEMAX   1
#define DSIZEMAX   64

word    PSIZE      = PSIZEMAX;         // Actual size, can be set lower
word    ISIZE      = ISIZEMAX;         // for particular CPUs
word    CSIZE      = CSIZEMAX;
word    DSIZE      = DSIZEMAX;

word    PMEM[PSIZEMAX];                // Arrays representing the memories
word    IMEM[ISIZEMAX];
word    CMEM[CSIZEMAX];
word    DMEM[DSIZEMAX];

word    PUSED      = 0;                // Number of valid words in array
word    CUSED      = 0;
word    IUSED      = 0;
word    DUSED      = 0;
                        
#define PMASK      0x3fff              // Which bits are used in each word
word    CMASK      = 0x001f;           //  (CMASK depends on processor)
#define IMASK      0x3fff
#define DMASK      0x00ff
                        
word    DEFAULTCONFIG  =  0x1B;        // Initialization for config word

void cleararrays () {                  // Mark the memory arrays as empty
  PUSED =
  IUSED =
  CUSED =
  DUSED = 0;
}

bit  stuffarray (word address,         // Stuff data into a memory array.
                 word array[],         // Returns true if successful.
                 word base,
                 word size,
                 word *used,
                 word data[],
                 int  count) {
  int i;
  if (address-base+count-1 > size) {
    printf("Invalid address: %04XH\n",address+count-1);
    return 0;
  }
  for (i=0; i<count; i++) {
    array[i+address-base] = data[i];
    if (*used < address-base+count) *used = address-base+count;
  }
  return 1;
}

// PIC PROGRAMMING COMMANDS

byte LOADCONFIG        = 0;
byte LOADPROGRAM       = 2;
byte READPROGRAM       = 4;
byte INCREMENTADDRESS  = 6;
byte BEGINPROGRAMMING  = 8;
byte LOADDATA          = 3;
byte READDATA          = 5;
byte ERASEPROGRAM      = 9;
byte ERASEDATA         = 11;

// PROGRAMMING PROCEDURES

void vppreset() {                      // reset PIC, apply Vpp
  vppoff();
  datawritable();
  datadown();
  clockdown();
  delay(25000);
  vppon();
  delay(25000);
}

void progcycle(byte cmd, word arg) {   // send a command and an argument,
  sendcmd(cmd);                        // then program EPROM accordingly
  senddata(arg);
  sendcmd(BEGINPROGRAMMING);
  delay(20000); // PIC errata sheet says 10ms is typical, not guaranteed
}

bit programall(
// Program a memory from appropriate array.
// Returns true if successful.
             int  mode,                // program or verify
             word mask,                // to throw away irrelevant bits
             byte writecommand,        // command to write this memory
             byte readcommand,         // command to read this memory,
             word array[],             // which memory array we're using
             word base,                // its base address
             word used) {              // number of valid words in array
  word i,w;
  switch(mode) {
    case PROGRAM: puts("programming..."); break;
    case VERIFY:  puts("verifying..."); break;
  }
  for (i=0; i<used; i++) {
    printf("%04X\b\b\b\b",i+base);     // display addr and backspace
    if (mode==PROGRAM)
      progcycle(writecommand,(array[i] & mask));
    sendcmd(readcommand);
    w = (recvdata() & mask);
    if (w != (array[i] & mask)) {
      printf("Failed at %04X: Expecting %04X, found %04X.\n",
                                         i+base, (array[i] & mask), w);
      return 0;
    }
    sendcmd(INCREMENTADDRESS); 
  }
  return 1;
}


// *********************************************************************
// HEX FILE LOADER
// *********************************************************************

// This is for Intel INHX8M (8-bit merged) hex files only.
// These files use two bytes for each word (low, then high).
// All addresses are doubled, i.e., 0x2001 is encoded as 0x4002,
// so that addresses increment at the same rate as the byte count.

bit validhexline(char *s) {            // Gross syntax and checksum check.
  byte cksum = 0;                      // For all HEX formats, not just 8M.
  int bytecount;
  int i, b;
  if (s[0] != ':') return(0);          // Initial colon
  sscanf(s+1,"%2x",&bytecount);
  if (bytecount > 32) return(0);       // Valid byte count
  cksum = bytecount;
  i = 3;
  bytecount = bytecount+3;
  while (bytecount>0) {
    bytecount--;
    sscanf(s+i,"%2x",&b);
    cksum = cksum+b;                   // Compute checksum
    i = i+2;
  }
  sscanf(s+i,"%2x",&b);
  cksum = -cksum;
  if (cksum == b) return 1;            // Test checksum
  return 0;
}

void loadhexfile(FILE *f) {            // Loads a hex file into memory arrays
  char s[256];
  word i,lo,hi;
  word linetype = 0;   // 0 for data, 1 for end of file
  word wordcount;      // number of 16 bit words on this line
  word address;        // address where they begin
  word data[8];        // 16 bytes = 8 words max. per line of hex

  cleararrays();

  while((!feof(f)) && (linetype != 1)) {
    fgets(s,255,f);
    cleanctrl(s);

    if (!validhexline(s)) {            // Syntax check
      s[40] = 0;                       // Truncate invalid line for display
      if (s[0] != ':') {
        printf("Invalid line (skipped): '%s'...\n",s);
        continue;
      }
      else {
        printf("Unable to decode line:  '%s'...\n",s);
        goto bailout;
      }
    }

    sscanf(s+1,"%2x",&wordcount);      // Parse the line - Intel Hex8M
    wordcount = wordcount/2;           // (double bytes, addresses doubled)
    sscanf(s+3,"%4x",&address);
    address = address/2;
    sscanf(s+7,"%2x",&linetype);

    if (linetype==1) goto finished;

    for (i=0; i<wordcount; i++) {           // Grab the data
      sscanf(s+(9+4*i),"%2x%2x",&lo,&hi);
      data[i] = (hi << 8) | lo;
    }

                                            // Place in appropriate array
    if (address >= DBASE) {
      if (!stuffarray(address,DMEM,DBASE,DSIZE,&DUSED,data,wordcount))
        goto bailout;
    }
    else if (address >= CBASE) {
      if (!stuffarray(address,CMEM,CBASE,CSIZE,&CUSED,data,wordcount))
        goto bailout;
    }
    else if (address >= IBASE) {
      if (!stuffarray(address,IMEM,IBASE,ISIZE,&IUSED,data,wordcount))
        goto bailout;
    }
    else {
      if (!stuffarray(address,PMEM,PBASE,PSIZE,&PUSED,data,wordcount))
        goto bailout;
    }
  } // while

finished:
  printf("Program memory loaded: %5d word(s)\n",PUSED);
  printf("Configuration loaded:  %5d word(s)\n",CUSED);
  printf("ID memory loaded:      %5d word(s)\n",IUSED);
  printf("Data memory loaded:    %5d byte(s)\n",DUSED);
  return;

bailout:
  cleararrays();
  errmsg("Unable to load file.");
  FNAME[0] = 0;
  return;
}


// *********************************************************************
// USER INTERFACE
// *********************************************************************

void banner() {
  puts("---------------------------------------");
  puts(BANNER);
  puts(" Michael A. Covington");
  puts(" converted to LINUX by Claus Fuetterer");
  puts(" Version 0.2_lx of " __DATE__ " " __TIME__);
  puts("---------------------------------------");
}

void statusline() {
  printf("-----------------------------------------------------\n");
  printf("status: ");
  switch (DEVICE) {
    case PIC16C84: printf("PIC16C84, PSIZE = 1024, CMASK = 0x001F;  \n"); break;
    case PIC16F84: printf("PIC16F84, PSIZE = 1024, CMASK = 0x3FF3;  \n"); break;
    case PIC16F83: printf("PIC16F83, PSIZE =  512, CMASK = 0x3FF3;  \n"); break;
    default:       printf("no PIC chosen;  ");
  }
  printf("HEX-PROGRAM: %s\n",FNAME);
  printf("-----------------------------------------------------\n");
}


void selectport() {
  puts("Apply power to programmer now.\n");
  puts("If your programmer has adjustable Vcc,");
  errmsg("set it to 5.0 volts and press RETURN...");

  if (!detecthardware()) {
    puts("Caution: Programmer hardware not found!\n\n");
    puts("With some versions of the circuit and some");
    puts("parallel ports, this may be normal.\n");
    puts("If you are sure you have chosen");
    puts("parallel port 1, press RETURN to proceed.");
    puts("To cancel program, press Ctrl-C.");
    errmsg(" ");
  }
}  

void troubleshoot() {
  puts("Ensure programmer is powered up now,");
  puts("with Vcc set to 5.0 V (if adjustable)");
  puts("and no PIC in the socket.");
  errmsg(" ");
  
  allpinslow();
//  banner();
  puts("TEST A\n");
  puts("Connect negative voltmeter lead to pin 5");
  puts("of PIC socket and check the following voltages:\n");
  puts("  Socket pin 4       < 0.8 V");
  puts("  Socket pin 12      < 0.8 V");
  puts("  Socket pin 13      < 0.8 V");
  puts("  Socket pin 14      4.75 to 5.25 V");
  puts("  Junction of");
  puts("   D1, D2, and R1    < 0.8 V");
  errmsg(" ");
  
  vppon();
  clockup();
  dataup();
//  banner();
  puts("TEST B\n");
  puts("With negative voltmeter lead still");
  puts("connected to pin 5 in the PIC socket,");
  puts("check the following voltages:\n");
  puts("  Socket pin 4       12.0 - 14.0 V");
  puts("  Socket pin 12      > 4.0 V");
  puts("  Socket pin 13      > 4.0 V");
  puts("  Junction of");
  puts("   D1, D2, and R1    < 0.8 V");
  errmsg(" ");                    
  
  vppoff();
  clockdown();
  datareadable();  // AUTOFD, SLCTIN high
  //banner();
  //puts("TEST 3\n");
  //puts("With negative voltmeter lead still");
  //puts("connected to pin 5 in the PIC socket,");
  //puts("verify that pin 13 > 4.0 V.\n");
  //puts("Next, insert a 470-ohm resistor into the socket");
  //puts("connecting pin 13 to pin 5 and verify that");
  //puts("pin 13 drops to < 2.0 V with the resistor in place.\n");
  //errmsg("Then remove the resistor.");                    
          
  allpinslow();          
  datawritable();  // SLCTIN down
  dataup();        // AUTOFD up
//  banner();
  puts("TEST C\n");
  puts("With negative voltmeter lead still");
  puts("connected to pin 5 in the PIC socket,");
  puts("verify that the junction of D1, D2, and R1");
  puts("is < 0.8 V.");
  errmsg(" ");                    

  allpinslow();          
  datareadable();  // SLCTIN up
  dataup();        // AUTOFD up
//  banner();
  puts("TEST D\n");
  puts("With negative voltmeter lead still");
  puts("connected to pin 5 in the PIC socket,");
  puts("verify that the junction of D1, D2, and R1");
  puts("is now > 4 V.\n");
  errmsg(" "); 
  
//  banner();
  puts("This completes the voltage test sequence.\n");
  puts("You should also check the cable from PC to programmer.");
  puts("It should be relatively short (under 2 feet) and have");
  puts("all necessary pins connected (serial cables don't).");
  errmsg(" ");
  
  allpinslow();                    
//  exit(0);
}  
  
  
void load() {
  FILE *f;
  printf("File to load: ");
  strcpy(FNAME,getstring());
  f = fopen(FNAME,"r");
  if (f == NULL) {
    errmsg("Unable to open file.");
    FNAME[0] = 0;
    return;
  }
  loadhexfile(f);
  fclose(f);
  errmsg("Loading complete.");

  if (CUSED == 0) {
//    banner();
    puts("Caution: HEX file did not contain a configuration word.\n");
    puts("The following settings will be used:\n");
    puts("  RC oscillator");
    puts("  Watchdog timer disabled");
    puts("  Power-up timer enabled");
    puts("  Code not read-protected\n");
    errmsg("You can specify other settings in the assembler.");
  } 
  else if (CMEM[0] != (CMEM[0] & CMASK)) {
//    banner();
    puts("Caution: Configuration word appears to contain invalid bits.\n");
    puts("Your program may have been assembled for a different");
    puts("type of PIC.  Check device selection carefully.");
    errmsg("");
  }
  
}

void selectdevice() {
  statusline();
  puts("Devices supported:\n");
  puts("  C   PIC16C84");
  puts("  F   PIC16F84"); 
  puts("  3   PIC16F83\n\n");
  puts("  T   Test the programmer circuit\n");

  CHOICE = getchoice("C,F,3,T");
  switch(CHOICE) {
    case 'C': 
      DEVICE = PIC16C84; 
      PSIZE  = 1024;
      CMASK  = 0x001F;
      DEFAULTCONFIG = 0x001B;
      break;
    case 'F': 
      DEVICE = PIC16F84; 
      PSIZE  = 1024;
      CMASK  = 0x3FF3;
      DEFAULTCONFIG = 0x3FFF;
      break;
    case '3':
      DEVICE = PIC16F83;
      PSIZE  = 512;
      CMASK  = 0x3FF3;
      DEFAULTCONFIG = 0x3FFF;
      break;
    case 'T':
      troubleshoot();
  }
}

void erase() {
  int i;
  vppreset();
  printf("Commanding PIC to erase ID, configuration, ");
    sendcmd(LOADCONFIG);
    senddata(0x3FFF);
    for (i=7; i>0; i--) sendcmd(INCREMENTADDRESS);
    sendcmd(1);
    sendcmd(7);
    sendcmd(BEGINPROGRAMMING);
    delay(20000);
    sendcmd(1);
    sendcmd(7);
  printf("program, ");
    progcycle(ERASEPROGRAM,0x3FFF);    // is the data word necessary?
  printf("data...");
    progcycle(ERASEDATA,0x3FFF);       // is the data word necessary?
  allpinslow();
  puts(" Done.");
//  waitkey();
}

void program(int mode) {
  word i;
  if (PUSED+IUSED+CUSED+DUSED == 0) {
    printf("Load a file first.\n");
    goto finish;
  }

  vppreset();

  printf("Program memory: ");
  if (!programall(mode,PMASK,LOADPROGRAM,READPROGRAM,PMEM,PBASE,PUSED))
    goto finish;

  sendcmd(LOADCONFIG);      // from here on we're in config/ID memory
  senddata(DEFAULTCONFIG);  // loadconfig requires an arg, here it is
  
  printf("ID memory: ");
  if (!programall(mode,IMASK,LOADPROGRAM,READPROGRAM,IMEM,IBASE,IUSED))
    goto finish;

  for (i=0; i < CBASE-IBASE-IUSED; i++)
    sendcmd(INCREMENTADDRESS);      // get to config memory

  printf("Configuration memory: ");
  if (!programall(mode,CMASK,LOADPROGRAM,READPROGRAM,CMEM,CBASE,CUSED))
    goto finish;

  vppreset();   // Reset address counter in PIC to 0

  printf("Data memory: ");
  if (!programall(mode,DMASK,LOADDATA,READDATA,DMEM,DBASE,DUSED))
    goto finish;

  puts("Programming complete.\n\n");
  puts("For production-grade work, you should now verify");
  puts("the PIC at the maximum and minimum values of Vcc.");

finish:
  allpinslow();
}

  
  
  
void queryexit() {
//  banner();
  allpinslow();
//  puts("Are you sure you want to exit?");
//  CHOICE = getc(stdin);
//  if (CHOICE == 'Y') { 
//    banner();
    errmsg("You may remove the PIC from the socket now.");
    exit(0);
//  }
}


void menu() {
  statusline();
  allpinslow(); // Clean up after aberrant routines if any
  puts("  L  Load HEX file");
  puts("  S  Select type of PIC");
  puts("  E  Erase PIC");
  puts("  P  Program PIC");
  puts("  V  Verify PIC\n");
  puts("  X  Exit program");
  CHOICE = getchoice("L,S,E,P,V,X");
  switch(CHOICE) {
    case 'L': getc(stdin);load(); break;
    case 'S': selectdevice(); break;
    case 'E': erase(); break;
    case 'P': program(PROGRAM); break;
    case 'V': program(VERIFY); break;   
    case 'X': queryexit();
  }
}



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

void main() 
{
  banner();
  FNAME[0] = 0;   // no file is presently loaded
  open_io();
  selectport();
  allpinslow();
  selectdevice();  // mandatory               
  puts("You may insert the PIC in the socket now.");
  errmsg("Be very careful not to insert it backward.");
  while (1) {menu();}

  close_io();
}
