// Program to load program and data into 16F877a of DLP-2232PB-G
// Program format is assumed to be in gasm format hex file.
// Edit as needed to suit other input data formats.
// Uses linux ftdi_sio kernel driver, libusb, and libftdi
//
// Compiled and linked using
//    cc -Wall -lftdi -o load_16f877a load_16f877a.c


#include <fcntl.h>       // open(), fcntl(), O_RDWR, O_ASYNC, F_SETOWN, F_SETFL
#include <termios.h>     // B230400, CS8, CLOCAL, CREAD, IGNPAR, VTIME, VMIN, TCIFLUSH, TCSANOW
#include <stdio.h>       // printf()
#include <signal.h>      // sigaction(), sigemptyset(), sigaddset(), SIGIO, SA_NODEFER
#include <time.h>        // nanosleep()
#include <string.h>      // bzero()
#include <sys/time.h>    // setitimer(), getitimer(), gettimeofday(), itimerval, ITIMER_REAL 
#include <error.h>       // error()
#include <ftdi.h>        // ftdi_xxx and usb_xxx library functions from libftdi (brings in usb.h)
#include <sys/ioctl.h>   // ioctl()
#include <err.h>         // err()

// Device selection parameters
int     vendor_id = 0x0403;
int     product_id = 0x6010;
char*   description = "DLP-2232PB";
char*   serial_id = "DP40MC4T";  // Test Unit

char*   usb_path = "/dev/bus/usb";
enum    channel {A=0,B=1};
struct  ftdi_context context[2];
char    channel_letter[2] = {'A','B'};
unsigned int bit_mask[9] = {0,1,3,7,15,31,63,127,255};

const int true = 1;
const int false = 0;
unsigned char inbuf[65536+4096], outbuf[65536+4096];
int fd;				     
volatile int signals=0;

struct itimerval timer, start_time, cur_time;
struct timespec remaining_time;
struct timespec delay = {(time_t)0, (long int)1000*1000000};
struct sigaction saio;
struct termios oldtio, newtio;

void ftdi_set_clock_divisor(int divisor);
void set_data_bits_low_byte(unsigned char value, unsigned char directions);
void set_data_bits_high_byte(unsigned char value, unsigned char directions);
void clock_data_bits_out_on_positive_clock_edge_LSB_first(unsigned char nbr_bits, unsigned char byte);
unsigned char clock_data_bits_in_on_negative_clock_edge_LSB_first(unsigned char nbr_bits);
int init_direct();
int deinit_direct();
void reconnect_kernel_drivers();
int usb_device_nbr();
void init_sio();
void deinit_sio();
void signal_handler_IO(int status);
void pause_4_ms();
void enter_program_mode();
void exit_program_mode();
void erase_chip();
void increment_PC(int count);
void read_configuration_memory();
void read_data_memory();
void list_program_memory();
void read_program_memory();
void set_configuration_word(int config_word);
void set_user_id_words(unsigned char id0, unsigned char id1, unsigned char id2, unsigned char id3);
void write_to_data_memory(char *filename);
void write_to_program_memory_gpasm_hex(char *filename);

// The following hex codes are from the 16F877A programming documentation
unsigned char Load_Configuration     = 0x00;
unsigned char Load_Prog_Memory       = 0x02;
unsigned char Load_Data_Memory       = 0x03;
unsigned char Read_Prog_Memory       = 0x04;
unsigned char Read_Data_Memory       = 0x05;
unsigned char Increment_PC           = 0x06;
unsigned char Begin_Erase_Prog_Cycle = 0x08;
unsigned char Bulk_Erase_Prog_Memory = 0x09;
unsigned char Bulk_Erase_Data_Memory = 0x0B;
unsigned char End_Programming        = 0x17;
unsigned char Begin_Prog_Only_Cycle  = 0x18;
unsigned char Chip_Erase             = 0x1F;

unsigned char CLK  = 0x01;
unsigned char DTAO = 0x02;
unsigned char DTAI = 0x04;
unsigned char MCLR = 0x08;
unsigned char PRGM = 0x10;
unsigned char TRIG = 0x80;

unsigned char dir_prog_mode = 0xFB;  // OOOO OIOO
unsigned char dir_to_PIC = 0xFB;     // OOOO OIOO  // Does not read properly if 0xFF because bits 1 & 2 will not track on output
unsigned char dir_from_PIC = 0xF9;   // OOOO OIIO


//  **************** MPSSE Commands ****************
// Clock divisor must be set immediately after MPSSE mode is enabled
// Popular divisors are:  0       6 MHz
//                        1       3 MHz
//                        2       2 MHz
//                        5       1 MHz
//                       59     100 kHz
//                      599      10 kHz
//                     5999       1 kHz
//                    65535     91.553.. Hz   
void ftdi_set_clock_divisor(int divisor){
  unsigned char b[65539];
	if (divisor > 65535){
		printf("Clock divisor of %d exceeds limit of 65536.\n", divisor);
	  exit(-1);
  }
	b[0] = 0x86;
	b[1] = divisor & 0xFF;
	b[2] = divisor >> 8;
	ftdi_write_data(&context[A], b, 3);
}
  
// This should be used to set the initial states and directionality
// of lines 0-7.
// A "High" direction bit means outgoing data; "Low" means incoming data.
void set_data_bits_low_byte(unsigned char value, unsigned char directions){
	outbuf[0] = 0x80;  // Set data bits low byte
	outbuf[1] = value;
	outbuf[2] = directions;
	write(fd, outbuf, 3);
}

// This should be used to set the initial states and directionality
// of lines 8-11
void set_data_bits_high_byte(unsigned char value, unsigned char directions){
	outbuf[0] = 0x82;  // Set data bits low byte
	outbuf[1] = value;
	outbuf[2] = directions;
	write(fd, outbuf, 3);
}

// nbr_bits is the true number, 1-8
// The clock must start at '1' or the command will fail.
// The DO line can start at '0' or '1'; it will be appropriately set before the clock is changed.
//    (That is what is meant by being "clocked out on positive clock edge.")
// The first transition of the clock will be to '0'.  The data is to be read at that negative transition.
// The DO line will be left in whatever state corresponds to the final bit sent (the MSB).
void clock_data_bits_out_on_positive_clock_edge_LSB_first(unsigned char nbr_bits, unsigned char byte){
	outbuf[0] = 0x1A;
	outbuf[1] = nbr_bits - 1;
	outbuf[2] = byte;
	write(fd, outbuf, 3);
}

unsigned char clock_data_bits_in_on_negative_clock_edge_LSB_first(unsigned char nbr_bits){
	int f;
	if (nbr_bits > 8 || nbr_bits == 0){
		printf("nbr_bits is %02X, but must be between 1 and 8.\n", nbr_bits);
		exit(-1);
	}
	outbuf[0] = 0x2E;
	outbuf[1] = nbr_bits-1;
	write(fd, outbuf, 2);
	if ((f = read(fd, inbuf, 1)) <= 0){
		printf("f = %02X from read on command 0x2C\n", f);
		exit(-1);
	}
	inbuf[0] = (inbuf[0] >> (8-nbr_bits)) & bit_mask[nbr_bits];
	return inbuf[0];
}

void clock_data_bytes_in_on_negative_clock_edge_LSB_first(unsigned int nbr_bytes){
  int f, i;
	if (nbr_bytes > 65536 || nbr_bytes == 0){
		printf("%d bytes to clock out is 0 or greater than 65536 limit.\n", nbr_bytes);
	  exit(-1);
  }
  nbr_bytes--;
	outbuf[0] = 0x2C;
	outbuf[1] = nbr_bytes & 0xFF;
	outbuf[2] = nbr_bytes >> 8;
	nbr_bytes++;
	write(fd, outbuf, 3);
	if ((f = read(fd, inbuf, nbr_bytes)) <= 0){
		printf("f = %02X from read on command 0x2C\n", f);
		exit(-1);
	}
	if (f != nbr_bytes){
		printf("Asked for %d bytes, Got back %02X: ", nbr_bytes, f);
  	for (i=0; i<f; i++)
	  	printf("%02X ", inbuf[i]);
	  printf("\n");
	}
}

int init_direct(){
  int f;
  enum channel ch;
  for (ch=A; ch<=B; ch++){
    ftdi_init(&context[ch]);
    ftdi_set_interface(&context[ch], ch+1);
    f = ftdi_usb_open_desc(&context[ch], vendor_id, product_id, description, serial_id); // Test unit
    if(f < 0 && f != -5) {
      fprintf(stderr, "Unable to open of ftdi device - lacking permission, ID's wrong, or may not be plugged in: %d - %s\n",f,context[ch].error_str);
      exit(-1);
    }
    else {
      if(f==-5) {
        fprintf(stderr, "Unable to claim ftdi device: %d - %s\n",f,context[ch].error_str);
        fprintf(stderr, "You may need to unbind (rmmod for module drivers) some other driver (perhaps ftdi_sio).\n");
        exit(-1);
      }
    }
  }
  return 0;
}

int deinit_direct(){
  enum channel ch;
  for (ch=A; ch<=B; ch++){
    ftdi_usb_close(&context[ch]);
    ftdi_deinit(&context[ch]);
  }
  return 0;
}

void reconnect_kernel_drivers(){
  struct usb_device *device;
  char filename[PATH_MAX + 1];
  int fd;
  int ret;
  int interface;
  unsigned int val1, val2;
  struct usb_ioctl {
    int ifno; /* interface 0..N ; negative numbers reserved */
    int ioctl_code; /* MUST encode size + direction of data so the
                     * macros in <asm/ioctl.h> give correct values */
    void *data; /* param buffer (in, or out) */
  };
  struct usb_ioctl command;
  val1 = 3 << 30 | 'U' << 8 | 18 | sizeof(command) << 16;
  val2 = 0 << 30 | 'U' << 8 | 23 | 0 << 16;
  interface = 0;
  command.ifno = interface;
  command.ioctl_code = val2;
  command.data = NULL;
  device = usb_device(context[A].usb_dev);
  snprintf(filename, sizeof(filename) - 1, "%s/%s/%s", usb_path, device->bus->dirname, device->filename);
  fd = open(filename, O_RDWR);
  if (fd < 0){
    printf("Could not open file %s to reconnect kernel drivers.\n",filename);
    exit(-1);
  }
  ret = ioctl(fd, val1, &command);
  if (ret){
    printf("Could not attach kernel driver to interface %d: %d\n", interface, ret);
    err(1, "Could not reconnect ftdi_sio driver");
  }
  close(fd);
}

int usb_device_nbr(){
  DIR *d;
  FILE *f;
  char* sys_path="/sys/bus/usb-serial/devices/";
  char id_path[128];
  char id[32];
  char target_id[32];
  struct dirent *entry;
  d = opendir(sys_path);
  if (!d)
    err(-1,"Could not open directory %s",sys_path); 
  while ((entry = readdir(d)) != NULL){
    if (strncmp(entry->d_name,"ttyUSB",6)==0){
      snprintf(id_path,128,"%s%s%s",sys_path, entry->d_name,"/../../idVendor");
      f = fopen(id_path,"r");
      if (!f)
        err(-1, "%s", id_path);
      fscanf(f,"%s",id);
      fclose(f);
      sprintf(target_id,"%04x",vendor_id);
      if (strncmp(id,target_id,4) !=0)
        continue;
      snprintf(id_path,128,"%s%s%s",sys_path, entry->d_name,"/../../idProduct");
      f = fopen(id_path,"r");
      if (!f)
        err(-1, "%s", id_path);
      fscanf(f,"%s",id);
      fscanf(f,"%s",id);
      fclose(f);
      sprintf(target_id,"%04x",product_id);
      if (strncmp(id, target_id,4) != 0)
        continue;
      snprintf(id_path,128,"%s%s%s",sys_path, entry->d_name,"/../../product");
      f = fopen(id_path,"r");
      if (!f)
        err(-1, "%s", id_path);
      fscanf(f,"%s",id);
      fscanf(f,"%s",id);
      fclose(f);
      if (strncmp(description,id,strlen(description))!=0)
        continue;
      snprintf(id_path,128,"%s%s%s",sys_path, entry->d_name,"/../../serial");
      f = fopen(id_path,"r");
      if (!f)
        err(-1, "%s", id_path);
      fscanf(f,"%s",id);
      fscanf(f,"%s",id);
      fclose(f);
      if (strncmp(serial_id,id,strlen(serial_id))!=0)
        continue;
      snprintf(id_path,128,"%s%s%s",sys_path, entry->d_name,"/../bInterfaceNumber");
      f = fopen(id_path,"r");
      if (!f)
        err(-1, "%s", id_path);
      fscanf(f,"%s",id);
      fscanf(f,"%s",id);
      fclose(f);
      if (strncmp(id,"00",2)!=0)
        continue;
      return atoi(&entry->d_name[6]);
    }
  }
  return -1;
}

void init_sio(){
  int i;
  char device[16];
  int device_nbr;
  sigset_t mask;
  // Setting up signal handler used by read() when doing asynchronous I/O
  saio.sa_handler = signal_handler_IO;
  sigemptyset(&mask);
  sigaddset(&mask, SIGIO);
  saio.sa_mask = mask;
  saio.sa_flags = SA_NODEFER;
  saio.sa_restorer = NULL;
  sigaction(SIGIO, &saio, NULL);
  for (i = 0; i < 20; i++){
    if ((device_nbr = usb_device_nbr()) >= 0){
      snprintf(device,16,"/dev/ttyUSB%d",device_nbr);
      if ((fd = open(device, O_RDWR )) >= 0 )	// If O_ASYNC is set here, hangs read for lack of ownership
        break;
    }
    usleep(50000);  // Allow more time for ftdi_sio to reconnect
  }
  if (device_nbr < 0){
    printf("Unable to locate correct /dev/ttyUSB<n> device\n");
    exit(-1);
  }
  if (fd < 0){
    error(-1, 0, "Could not open %s - Check DLP-2232PB-G connection and power connections.",device);
    exit(-1);
  }
  // At this point only O_RDWR is set and the owner process pid = 0
  fcntl(fd, F_SETOWN, getpid());  // Making this process own the file descriptor allows O_ASYNC to be set!!!
  fcntl(fd, F_SETFL, O_ASYNC );   // Use signals - See man 2 open for explanation
  bzero(&newtio, sizeof(newtio)); // start with a clean slate
  newtio.c_cflag = CS8 | CLOCAL | CREAD;
  newtio.c_iflag = IGNPAR;
  newtio.c_oflag = 0;
  newtio.c_cc[VTIME]	= 0;	// inter-character timer (unused)
  newtio.c_cc[VMIN]	= 1;	// blocking read until 1 character arrives
  cfmakeraw(&newtio);
  tcflush(fd, TCIFLUSH);          // Make sure channel is cleared
  tcsetattr(fd, TCSANOW, &newtio);
}

void deinit_sio(){
//  printf("%d signals received\n",signals);
}

// I can only count on getting at least one signal when a read is ready.
// Subsequent signals might be hidden when the handler is running
void signal_handler_IO(int status) {
  signals++;
}

// Pause is done this way to make sure buffering will not eliminate the pause.
// If done with a program pause, the USB buffering would probably hide it.
void pause_4_ms(){
  int i;
  for (i=0; i < 180; i++)
    set_data_bits_high_byte(i%2, 0x01);
}

// Switch into Program Mode 
void enter_program_mode(){
  set_data_bits_low_byte(TRIG, dir_prog_mode);
  set_data_bits_low_byte(0x00, dir_prog_mode);
  set_data_bits_low_byte(MCLR, dir_prog_mode);
  set_data_bits_low_byte(MCLR | PRGM, dir_prog_mode);
  set_data_bits_low_byte(PRGM, dir_to_PIC);
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
}

void exit_program_mode(){
  set_data_bits_low_byte(PRGM | CLK, dir_prog_mode);
  set_data_bits_low_byte(MCLR, dir_prog_mode);
  set_data_bits_low_byte(CLK, dir_prog_mode);
}

// Erase all FLASH and EEPROM on chip (self-timed)
// Erases the user program memory: 0x0000 - 0x1FFF
//           configuration memory: user ID locations 0x2000 - 0x2003
//                                configuration word 0x2007
//                                       test memory 0x2010 - 0x201F // 0x2000 - 0x201F is also referred to as test memory
//           and user data memory: 0x2100 - 0x21FF
void erase_chip(){
  printf("Erasing entire chip...\n");
  enter_program_mode();
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Chip_Erase);
  pause_4_ms();
  pause_4_ms();
  exit_program_mode();
}

void increment_PC(int count){
  int i;
  for (i=0; i < count; i++)
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Increment_PC);
}

void read_configuration_memory(){
  int word_address;
  unsigned char hiByte, loByte;
  unsigned short word;
  enter_program_mode();
  printf("Configuration Memory starting at 0x2000:");
  // Set clock High and direction to send to PIC at start
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  // The PIC16F87XA Programming Specs say the Load Configuration command as two bytes of data
  // following it, but that seems to be wrong.  I think it only sets the PC to 0x2000.
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Configuration);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0x00);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0x00);
  for (word_address = 0x2000; word_address < 0x2007; word_address++){
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Read_Prog_Memory);
    // Set direction to read from PIC
    set_data_bits_low_byte(PRGM | CLK, dir_from_PIC);
    clock_data_bytes_in_on_negative_clock_edge_LSB_first(2);
    loByte = inbuf[0]; hiByte = inbuf[1];
    word = loByte | (hiByte << 8);
    word = word & 0x7FFF;
    word = word >> 1;
    if (word_address % 4 == 0)
      printf("\n%04X: ", word_address);
    printf("%04X ", word);
    // Set direction to send to PIC
    set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Increment_PC);
  }
  printf("\nConfiguration Word at 0x2007: ");
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Read_Prog_Memory);
  // Set direction to read from PIC
  set_data_bits_low_byte(PRGM | CLK, dir_from_PIC);
  clock_data_bytes_in_on_negative_clock_edge_LSB_first(2);
  loByte = inbuf[0]; hiByte = inbuf[1];
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  word = loByte | (hiByte << 8);
  word = word & 0x7FFF;
  word = word >> 1;
  printf("%04X ", word);
  printf("\n\n");
  exit_program_mode();
}

void read_data_memory(){
  int word_address;
  unsigned char hiByte, loByte;
  unsigned short word;
  FILE *f;
  char* filename = "data_out.hex";
  enter_program_mode();
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  f = fopen(filename, "w");
  if (f <= 0)
    printf("Unable to open file \"%s\" for writing.\n", filename);
  printf("Data EEPROM memory starting at 0x2100:");
  for (word_address=0x2100; word_address < 0x2200; word_address++){
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Read_Data_Memory);
    set_data_bits_low_byte(PRGM | CLK, dir_from_PIC);
    clock_data_bytes_in_on_negative_clock_edge_LSB_first(2);
    loByte = inbuf[0]; hiByte = inbuf[1];
    word = loByte | (hiByte << 8);
    word = word & 0x7FFF;
    loByte = (word >> 1) & 0x00FF;
    if (word_address % 16 == 0)
      printf("\n%04X: ", word_address);
    
    printf("%02X ", loByte);
    fwrite(&loByte, 1, 1, f);
    set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Increment_PC);
  }
  fclose(f);
  printf("\n\n");
  exit_program_mode();
}

void list_program_memory(){
  int word_address;
  unsigned char hiByte, loByte;
  unsigned short word;
  FILE *f;
  char* filename = "16F877A-prog-list-old.txt";
  enter_program_mode();
  f = fopen(filename, "w");
  if (f <= 0)
    printf("Unable to open file \"%s\" for writing.\n", filename);
  printf("\nProgram memory starting at 0x0000");
  // Make sure clock is High and set direction to send to PIC at start
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Prog_Memory);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0xFE);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0x7F);
  for (word_address=0; word_address < 0x1FFF; word_address++){
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Read_Prog_Memory);
    // Change direction to get data from the PIC
    set_data_bits_low_byte(PRGM | CLK, dir_from_PIC);
    clock_data_bytes_in_on_negative_clock_edge_LSB_first(2);
    loByte = inbuf[0]; hiByte = inbuf[1];
    word = loByte | (hiByte << 8);
    word = word & 0x7FFF;
    word = word >> 1;
    if (word_address % 8 == 0){
      if ((word >> 8 ) == 0x3F)
        break;
    }
    fprintf(f, "%04X: %04X\n", word_address, word);
    // Change direction to send to PIC
    set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Increment_PC);
  }
  fclose(f);
  if (word_address == 0)
    printf("Looks like it is all erased - high byte of first word is 0x3F.\n");
  printf("\n");
  exit_program_mode();
}

void read_program_memory(){
  int word_address;
  unsigned char hiByte, loByte;
  unsigned short word;
  int checksum = 0;
  FILE *f;
  char* filename = "a_out.hex";
  enter_program_mode();
  f = fopen(filename, "w");
  if (f <= 0)
    printf("Unable to open file \"%s\" for writing.\n", filename);
  printf("Program memory starting at 0x0000");
  // Make sure clock is High and set direction to send to PIC at start
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Prog_Memory);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0xFE);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0x7F);
  for (word_address=0; word_address < 0x1FFF; word_address++){  // 0x05B0 used for full range
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Read_Prog_Memory);
    // Change direction to get data from the PIC
    set_data_bits_low_byte(PRGM | CLK, dir_from_PIC);
    clock_data_bytes_in_on_negative_clock_edge_LSB_first(2);
    loByte = inbuf[0]; hiByte = inbuf[1];
    word = loByte | (hiByte << 8);
    word = word & 0x7FFF;
    word = word >> 1;
    if (word_address % 8 == 0){
      if ((word >> 8 ) == 0x3F)
        break;
      printf("\n%04X: ", word_address);
      if (word_address != 0)
        fprintf(f,"%02X\n", ((~checksum+1) & 0x00FF));
      fprintf(f,":10%04X00",word_address << 1);
      checksum = 0x10+(word_address >> 7)+((word_address << 1) & 0x00FF) + 0x00;
    }
    printf("%04X ", word);
    fprintf(f,"%02X%02X", word & 0x00FF, word >> 8);
    checksum = checksum + (word & 0x00FF) + (word >> 8);

    // Change direction to send to PIC
    set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Increment_PC);
  }
  fprintf(f,"%02X\n", ((~checksum+1) & 0x00FF));
  fclose(f);
  if (word_address == 0)
    printf("Looks like it is all erased - high byte of first word is 0x3F.\n");
  printf("\n\n");
  exit_program_mode();
}

void set_configuration_word(int config_word){
  unsigned int loByte, hiByte;
  enter_program_mode();
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Configuration);
  // These data will not be used. They just make the Load_Configuration command happy.
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0xFE);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0x7F);
  increment_PC(7);
  // Make sure that unimplemented bits 4,5, and 12 are set
  // Make sure that low-voltage programming bit (bit 7) is set
  config_word |= 0x10B0;
  // Make sure that unused bits 14 & 15 are clear
  config_word &= 0x3FFF;
  printf("Setting configuration word to %04X\n", config_word);
  // Fit 14-bit configuration word into 16-bit data with leading and trailing bits set to 0
  config_word <<= 1;
  loByte = (config_word & 0x00FF);
  hiByte = (config_word >> 8);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Prog_Memory);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, loByte);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, hiByte);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Begin_Erase_Prog_Cycle);
  pause_4_ms();
  exit_program_mode();
}

void set_user_id_words(unsigned char id0, unsigned char id1, unsigned char id2, unsigned char id3){
  int word[4]={id0,id1,id2,id3};
  int i;
  unsigned int loByte, hiByte;
  enter_program_mode();
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  printf("Setting Id words to:%d, %d, %d, and %d\n", id0, id1, id2, id3);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Configuration);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0xFC);
  clock_data_bits_out_on_positive_clock_edge_LSB_first(8, 0x7F);

  for (i=0; i<4; i++){
    word[i] = (word[i] << 1);
    loByte = (word[i] & 0x00FF);
    hiByte = (word[i] >> 8);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Prog_Memory);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(8, loByte);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(8, hiByte);
    if (i != 3)
      increment_PC(1);
  }
  clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Begin_Erase_Prog_Cycle);
  pause_4_ms();
  exit_program_mode();
}

void write_to_data_memory(char *filename){
  int word_address;
  unsigned int loByte, hiByte;
  unsigned char byte;
  FILE *f;
  int bytes_in_EEPROM = 0x100;
  enter_program_mode();
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  f = fopen(filename, "r");
  if (f <= 0)
    printf("Unable to open hex file \"%s\" for reading.\n", filename);
  printf("Writing to data memory from %s:\n", filename);
  printf("0000: ");
  for (word_address = 0; word_address < bytes_in_EEPROM; word_address++){
    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Data_Memory);
    fread(&byte, 1, 1, f);
    loByte = byte << 1;
    loByte = loByte & 0x00FE;
    hiByte = (byte >> 7) & 0x007F;;
    clock_data_bits_out_on_positive_clock_edge_LSB_first(8, loByte);
    clock_data_bits_out_on_positive_clock_edge_LSB_first(8, hiByte);

    printf("%02X ",byte);
    if ((word_address % 0x10) == 0x0F && word_address+1 < bytes_in_EEPROM)
      printf("\n%04X: ", word_address+1);

    clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Begin_Erase_Prog_Cycle);
    pause_4_ms();

    exit_program_mode();
    enter_program_mode();
    increment_PC(word_address);
    increment_PC(1);
  }
  printf("\n");
  fclose(f);
  exit_program_mode();
}

void write_to_program_memory_gpasm_hex(char *filename){
  unsigned int loByte, hiByte;
  FILE *f;
  unsigned int bytesInLine;
  unsigned int byte_address;
  int word_address;
  int word;
  char line[36];  // Must be dimensioned enough to be word-aligned or screws up address
  int i;
  enter_program_mode();
  set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
  f = fopen(filename, "r");
  if (f <= 0)
    printf("Unable to open hex file \"%s\" for reading.\n", filename);
  printf("Writing to program memory from %s:\n", filename);
  do {
    fscanf(f,":%02X", &bytesInLine);
    fscanf(f,"%04X", &byte_address);
    word_address = byte_address/2;
    if (word_address > 0x1FFF)
      break;
    fscanf(f,"00%s\n", (char*)&line);
    // fill out incomplete line with 0x3FFF
    for (i = strlen(line)-2; i < 32; i +=4){
      line[i] = 'F'; line[i+1] = 'F'; line[i+2] = '3'; line[i+3] = 'F';
      if (i == 31)
        line[32] = 0;
    }
    printf("%04X: ", word_address);
    for (i = 0; i < strlen(line)-2; i += 4){
      sscanf(&line[i],"%02X%02X", &loByte, &hiByte);
      word = (hiByte << 8) + loByte;
      printf("%04X ",word);
      word <<= 1;
      loByte = word & 0x00FF;
      hiByte = word >> 8;

      clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Load_Prog_Memory);
      set_data_bits_low_byte(PRGM | CLK, dir_to_PIC);
      clock_data_bits_out_on_positive_clock_edge_LSB_first(8, loByte);
      clock_data_bits_out_on_positive_clock_edge_LSB_first(8, hiByte);
      if (word_address % 8 == 7){
        clock_data_bits_out_on_positive_clock_edge_LSB_first(6, Begin_Erase_Prog_Cycle);
        pause_4_ms();
        exit_program_mode();
        enter_program_mode();
        increment_PC(word_address);
      }
      increment_PC(1);
      word_address++;
    }
    printf("\n");
  }
  while (strlen(line) != 0);
  printf("\n");
  fclose(f);
  exit_program_mode();
}

void prepare_for_xfer(){
    init_direct();
    ftdi_usb_reset(&context[A]);
    ftdi_usb_purge_buffers(&context[A]);
    ftdi_set_bitmode(&context[A], bit_mask[A], BITMODE_RESET);  // Need to RESET to initialize MPSSE to known state
    ftdi_set_bitmode(&context[A], bit_mask[A], BITMODE_MPSSE);
    ftdi_set_clock_divisor(0);
    deinit_direct();
    reconnect_kernel_drivers();
    init_sio();
    set_data_bits_low_byte(0x00, dir_to_PIC);
}

void usage(){
    printf("Program to load 16f877a chip on DLP-2232PB-G Protoboard\n\n"\
           "Usage: load_16f877a [--desc description] [--serial serial_nr]]\n"\
           "                    --read_config | --read_data | --read_program | --read_all\n\n"\
           "       load_16f877a --erase_and_write_all [program_hex_file] [data_hex_file]\n"
           "                    [--desc description] [--serial serial_nr]]\n"\
           "                    [--id_1 id_byte_1] [--id_2 id_byte_2] [--id_3 id_byte_3] [--id_4 id_byte_4]\n\n"\
           "Defaults:  program_hex_file = a.hex    (Intel hex format)\n"\
           "           data_hex_file    = data.hex (256 raw bytes)\n\n"\
           "           id_byte_1    = 0            (0 - 255, Microchip recommends use 0 - 15 only)\n"\
           "           id_byte_2    = 0            (0 - 255, Microchip recommends use 0 - 15 only)\n"\
           "           id_byte_3    = 0            (0 - 255, Microchip recommends use 0 - 15 only)\n"\
           "           id_byte_4    = 0            (0 - 255, Microchip recommends use 0 - 15 only)\n"\
           "           description  = DLP-2232PB   (NULL to bypass testing description)\n"\
           "           serial_nr    = NULL         (NULL to bypass testing serial number)\n\n"\
           "Examples:  load_16f877a --read_program\n"\
           "           load_16f877a --read_all\n"\
           "           load_16f877a --erase_and_write_all\n"\
           "           load_16f877a --erase_and_write_all new_prog.hex new_data.hex\n"\
           "           load_16f877a --erase_and_write_all --id_1 132 --id_2 243 --id_3 31 --id_4 4 --desc DLP-2232PB --serial DP40MC4T\n\n"\
           "The vendor_id = 0603 and product_id = 6010 are assumed.\n"\
           "The chip configuration word will always be set to 0x3FBE.\n\n"\
           "When the program is read, an Intel hex format file named \"a_out.hex\" will be written.\n"\
           "When the EEPROM data is read, a 256-byte binary file named \"data_out.hex\" will be written.\n"\
           );
    exit(-1);
}

int main(int argc, char** argv){
  int i;
  char* prog_file=NULL;
  char* data_file=NULL;
  int id_byte_1=0;
  int id_byte_2=0;
  int id_byte_3=0;
  int id_byte_4=0;
  int writing=false;
  int reading_data=false;
  int reading_prog=false;
  int reading_config=false;
  if (argc <2)
    usage();
  else {
    for (i=1; i<argc; i++){
      if (strncmp(argv[i],"--read_config",13)==0)
        reading_config=true;
      else if (strncmp(argv[i],"--read_data",11)==0)
        reading_data=true;
      else if (strncmp(argv[i],"--read_program",14)==0)
        reading_prog=true;
      else if (strncmp(argv[i],"--read_all",10)==0){
          reading_data=true;
          reading_config=true;
          reading_prog=true;
        }
      else if (strncmp(argv[i],"--erase_and_write_all",19)==0){
        writing = true;
        if (i+1<argc && strncmp(argv[i+1],"--",2)!=0)
          prog_file=argv[++i];
        if (i+1<argc && strncmp(argv[i+1],"--",2)!=0)
          data_file=argv[++i];
      }
      else if (strncmp(argv[i],"--id_1",6)==0)
        id_byte_1 = atoi(argv[++i]);
      else if (strncmp(argv[i],"--id_2",6)==0)
        id_byte_2 = atoi(argv[++i]);
      else if (strncmp(argv[i],"--id_3",6)==0)
        id_byte_3 = atoi(argv[++i]);
      else if (strncmp(argv[i],"--id_4",6)==0)
        id_byte_4 = atoi(argv[++i]);
      else if (strncmp(argv[i],"--desc",6)==0)
        description = argv[++i];
      else if (strncmp(argv[i],"--serial",8)==0)
        serial_id = argv[++i];
      else
        usage();
    }
  }
  prepare_for_xfer();
  if (reading_config)
    read_configuration_memory();
  if (reading_prog)
    read_program_memory();
  if (reading_data)
    read_data_memory();

  if (writing){
    erase_chip();
    set_user_id_words(id_byte_1,id_byte_2,id_byte_3,id_byte_4);
    set_configuration_word(0x3FBE);
    if (prog_file==NULL)
      write_to_program_memory_gpasm_hex("a.hex");
    else
      write_to_program_memory_gpasm_hex(prog_file);
    if (data_file==NULL)
      write_to_data_memory("16F877A-data-good.hex");
    else
      write_to_data_memory(data_file);
  }

  deinit_sio();
  return 0;
}


