///////////////////////////////////////////////////////////////////////////
////                    EX_USB_BOOTLOADER.C                            ////
////                                                                   ////
////  This program is an example stand alone USB bootloader.  It uses  ////
////  the communication device class (CDC) that creates a virtual COM  ////
////  port on your PC.  The bootloader contains it's own set of USB    ////
////  and bootloader code in low memory, the high memory will contain  ////
////  the application (and if you are developing a USB application,    ////
////  this will hold it's own USB code).                               ////
////                                                                   ////
////  This program must be loaded into a target chip using a device    ////
////  programmer.  Afterwards this program may be used to load new     ////
////  versions of the application program.  There is a specific        ////
////  protocol used by the bootloader, SIOW.EXE uses this protocol     ////
////  if you use the 'Download Software' option.                       ////
////                                                                   ////
////  Compiling this program gives you a HEX file of just the loader.  ////
////  To load the application you have two choices: a.) use the loader ////
////  to load the application, or b.) merge the application HEX and    ////
////  loader HEX into one production HEX.  The #import() CCS command   ////
////  will help you perfrom the latter.  Be aware that if you do the   ////
////  former the PIC will get it's configuration bit settings (fuses)  ////
////  from the loader and not the application (this bootloader does    ////
////  not change the configuration bits)                               ////
////                                                                   ////
////  To create an application that is compatible with this loader     ////
////  simply #include usb_bootlaoder.h into your application.  This    ////
////  loader does support interrupts in your application.              ////
////                                                                   ////
////  This bootloader is designed to detect pin A4 low on reset (this  ////
////  is the push-button on the CCS USB Development kit).  If A4 is    ////
////  low it starts the USB CDC stack and checks for bootload          ////
////  information over USB.  Otherwise the application program is      ////
////  started.                                                         ////
////                                                                   ////
////  This is a port of ex_bootloader.c, which is a serial bootloader. ////
////  Since CDC makes it easy to port legacy applications, the         ////
////  previous example makes the bulk of this code.                    ////
////                                                                   ////
////  This example will work with the PCH compiler.  The               ////
////  following conditional compilation lines are used to include a    ////
////  valid device for each compiler.  Change the device, clock and    ////
////  RS232 pins for your hardware if needed.                          ////
////                                                                   ////
//// This file is part of CCS's PIC USB driver code.  See USB.H        ////
//// for more documentation and a list of examples.                    ////
////                                                                   ////
///////////////////////////////////////////////////////////////////////////
////                                                                   ////
//// VERSION HISTORY                                                   ////
////                                                                   ////
//// March 5th, 2009:                                                  ////
////   Cleanup for Wizard.                                             ////
////   PIC24 Initial release.                                          ////
////                                                                   ////
///////////////////////////////////////////////////////////////////////////
////        (C) Copyright 1996,2009 Custom Computer Services           ////
//// This source code may only be used by licensed users of the CCS    ////
//// C compiler.  This source code may only be distributed to other    ////
//// licensed users of the CCS C compiler.  No other use,              ////
//// reproduction or distribution is permitted without written         ////
//// permission.  Derivative programs created using this software      ////
//// in object code form are not restricted in any way.                ////
///////////////////////////////////////////////////////////////////////////

//set to 1 to use a PIC's internal USB Peripheral
//set to 0 to use a National USBN960x peripheral
#define __USB_PIC_PERIF__ 1

#if !defined(__PCH__)
 #error This bootloader written for PIC18
#endif

#if __USB_PIC_PERIF__
  //configure a 20MHz crystal to operate at 48MHz, for 4550
  #include <18F4550.h>
  #fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,USBDIV,PLL5,CPUDIV1,VREGEN

  //configure a 8MHz crystal to operate at 48MHz, for 67j50
  //#include <18F67J50.h>
  //#fuses H4_SW,NOXINST,NOWDT,NOSTVREN,NOFCMEN,NOIESO,NOPROTECT,NODEBUG,PLL2,NOCPUDIV
  
  #use delay(clock=48000000)
#else //use the National USBN960x peripheral
  #include <18F452.h>
  #fuses HS,NOWDT,NOPROTECT,NOLVP
  #use delay(clock=20000000)
#endif   //endif check to see which peripheral to use

/////////////////////////////////////////////////////////////////////////////
//
// If you are using a USB connection sense pin, define it here.  If you are
// not using connection sense, comment out this line.  Without connection
// sense you will not know if the device gets disconnected.
//       (connection sense should look like this:
//                             100k
//            VBUS-----+----/\/\/\/\/\----- (I/O PIN ON PIC)
//                     |
//                     +----/\/\/\/\/\-----GND
//                             100k
//        (where VBUS is pin1 of the USB connector)
//
/////////////////////////////////////////////////////////////////////////////
///only ccs's 18F4550 development kit has this pin
#if __USB_PIC_PERIF__ && defined(__PCH__)
 #define USB_CON_SENSE_PIN PIN_B2
#endif

#define LOADER_ISR 0x28
#build(interrupt=LOADER_ISR)

/*
 Configure, then load the bootloader definitions
*/
#define _bootloader
#include <usb_bootloader.h>

// Includes all USB code and interrupts, as well as the CDC API
#include <usb_cdc.h>


/*
 Goto the interrupt vector of the application.
*/
#org 0x08,0x17
void high_isr(void) 
{
   if (bit_test(g_InBootloader,0))
   {
    #ASM
     goto LOADER_ISR
    #ENDASM
   }
   else
   {
    #ASM
     goto APPLICATION_ISR
    #ENDASM
   }
}

#org 0x18,0x27
void low_isr(void) 
{
   if (bit_test(g_InBootloader,0))
   {
    #ASM
     goto LOADER_ISR+0x10
    #ENDASM
   }
   else
   {
    #ASM
     goto APPLICATION_ISR+0x10
    #ENDASM
   }
}

#define ROM_BLOCK_INVALID -1

int32 rom_block_start = ROM_BLOCK_INVALID;

#define EEPROM_ERASE_SIZE  getenv("FLASH_ERASE_SIZE")
int8 rom_block[EEPROM_ERASE_SIZE];

// see rom_w() documentation.  performs the flushing
void rom_w_flush(void)
{
   if (rom_block_start != ROM_BLOCK_INVALID)
   {
      erase_program_eeprom(rom_block_start);     //erase entire block
   
      write_program_memory(rom_block_start, rom_block, sizeof(rom_block));    //write modified block
      
      rom_block_start = ROM_BLOCK_INVALID;
   }
}

// see rom_w() documentation.  performs the writing
void rom_w_block(int32 location, char *src, int16 size)
{
   int32 block_start;
   int16 i,num;

   block_start = location & (~((int32)EEPROM_ERASE_SIZE-1));
      
   i = location - block_start;

   while (size) 
   {
      if (block_start != rom_block_start)
      {
         rom_w_flush();
         
         rom_block_start = block_start;
         
         read_program_memory(block_start, rom_block, sizeof(rom_block));  //read entire block to ram buffer
      }
      

      if (size>(EEPROM_ERASE_SIZE-i)) {num=EEPROM_ERASE_SIZE-i;} else {num=size;}

      memcpy(&rom_block[i], src, num);    //modify ram buffer

      src += num;
      block_start += EEPROM_ERASE_SIZE;
      i = 0;
      size -= num;
   }
}

// Write to Flash ROM.
//
// location - flash program memory address
// src - pointer to data to write
// size - number of bytes to write to flash
//
// Here is the sequence of events:
//   1.) Goes to the beginning of the first erase block for this address
//   2.) Reads n records to ram, where n is the PIC's flash erase size
//   3.) Erases block in flash
//   4.) Modifies block in RAM
//   5.) Writes changed block back to FLASH.  Writes in chunks defined by PIC's flash write size
//   6.) Goes back to step1 if there is still more data to be written
void rom_w(int32 location, char *src, int16 size)
{
   rom_w_block(location, src, size);
   rom_w_flush();
}

#define BUFFER_LEN_LOD 64

#define ACKLOD 0x06
#define XON    0x11
#define XOFF   0x13

// Convert two hex characters to a int8
unsigned int8 atoi_b16(char *s) 
{  
   char c;
   unsigned int8 result = 0;
   int i;

   for (i=0; i<2; i++,s++)  
   {
      c = *s;
      if (c >= 'A')
         result = 16*result + c - 'A' + 10;
      else
         result = 16*result + c - '0';         
   }

   return(result);
}

void load_program(void)
{
   int1  do_ACKLOD, done=FALSE;
   int8  checksum, line_type;
   int16 l_addr,h_addr=0;
   int8 to;
   int32 addr;
   int8  dataidx, i, count;
   int8  data[32];
   int  buffidx;
   char buffer[BUFFER_LEN_LOD];
   
   while (!done)  // Loop until the entire program is downloaded
   {
      usb_task();
      
      if (!usb_cdc_kbhit())
         continue;
         
      buffidx = 0;  // Read into the buffer until 0x0D ('\r') is received or the buffer is full
      to = 250;   //250 milliseconds
      do 
      {
         if (!usb_cdc_kbhit())
         {
            delay_ms(1);
            to--;
            if (!to)
               break;
         }
         else
            to = 250;
         i = usb_cdc_getc();
         buffer[buffidx++] = i;
      } while ( (i != 0x0D) && (i != 0x0A) && (buffidx <= BUFFER_LEN_LOD) );
            
      if (!to)
         continue;

      usb_cdc_putc(XOFF);  // Suspend sender

      do_ACKLOD = TRUE;

      // Only process data blocks that start with ':'
      if (buffer[0] == ':') 
      {
         count = atoi_b16 (&buffer[1]);  // Get the number of bytes from the buffer

         // Get the lower 16 bits of address
         l_addr = make16(atoi_b16(&buffer[3]),atoi_b16(&buffer[5]));

         line_type = atoi_b16 (&buffer[7]);

         addr = make32(h_addr,l_addr);

         checksum = 0;  // Sum the bytes to find the check sum value
         for (i=1; i<(buffidx-3); i+=2)
            checksum += atoi_b16 (&buffer[i]);
         checksum = 0xFF - checksum + 1;

         if (checksum != atoi_b16 (&buffer[buffidx-3]))
            do_ACKLOD = FALSE;
         else
         {
            // If the line type is 1, then data is done being sent
            if (line_type == 1) 
               done = TRUE;
            else if (line_type == 4)
               h_addr = make16(atoi_b16(&buffer[9]), atoi_b16(&buffer[11]));
            else if ((line_type == 0) && (addr >= (int32)APPLICATION_START) && (addr < ((int32)APPLICATION_END)))
            {
                  // Loops through all of the data and stores it in data
                  // The last 2 bytes are the check sum, hence buffidx-3
                  for (i = 9,dataidx=0; i < buffidx-3; i += 2)
                     data[dataidx++]=atoi_b16(&buffer[i]);
                       
                  rom_w_block(addr, data, count);
            }
         }
      }

      if (do_ACKLOD)
         usb_cdc_putc (ACKLOD);

      usb_cdc_putc(XON);
   }

   rom_w_flush();

   usb_cdc_putc (ACKLOD);
   usb_cdc_putc(XON);
   delay_ms(2000);   //give time for packet to flush
   reset_cpu();
}

void main(void) 
{
  #if (defined(__USB_87J50__) && (getenv("FUSE_SET:H4_SW")))
   #byte OSCTUNE = 0xF9B
   #bit PLLEN=OSCTUNE.6
   PLLEN = TRUE;
   delay_ms(250);
  #endif

   //we use PIN_A4 as an event to determine if we should start the USB CDC
   //bootloader.  if it is not low (button is not pressed) then goto the 
   //application, else if is low (button is pressed) then do the bootloader.
   if(!input(PIN_D0))
   {
      g_InBootloader = TRUE;
      usb_cdc_init();
      usb_init();
      while(!usb_enumerated());
      load_program();
   }

   g_InBootloader = FALSE;
   
  #ASM
   goto APPLICATION_START
  #ENDASM
}

