/**** BootLoader for PIC16F887 

   Ussage:   
      ascii-xfr -s -v -l 100 ./bltest.hex > /dev/ttyUSB0
      
      ascii-xfr is part of 'minicom' package

   Add "uf\n\r" to the first line of .HEX. or use this script:
      echo uf > /dev/ttyUSB$1  
      ascii-xfr -s -v -l 100 ./bltest.hex > /dev/ttyUSB$1
      
   Or add "uf\n\r" and add some dummy characters at end or begin of each line (for 100 ms delay) and use:
      cp ./bltest.hex > /dev/ttyUSB0
      
   For adding characters you can use this:
   sed -i 's/^/pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp/' ./bltest.hex
   sed -i 1i"uf" ./bltest.hex
      
*/

#define ID "$Id: bloader.c 2812 2013-03-10 08:00:23Z kakl $"

#CASE    // Case sensitive compiler

#define  FLASH_BLOCK_SIZE     getenv("FLASH_ERASE_SIZE")/2 // Minimal length of Flash Block Size
#define  RESERVED_BLOCKS      53  // Number of reserved flash blocks for BootLoader
#define  LOADER_RESERVED      (getenv("PROGRAM_MEMORY")-(RESERVED_BLOCKS*FLASH_BLOCK_SIZE)) // begining of BootLoader
#define  BUFFER_LEN_LOD       46  // Length of Working buffer for HEX

#define  ERR_BUFFER_OVERRUN      1  // Error 1 - Buffer Overrun
#define  ERR_CHECKSUM            2  // Error 2 - Bad CheckSum
#define  ERR_TOO_MANY_BYTES      3  // Error 3 - Too many bytes in one line 
#define  ERR_UNSUPORTED_LINETYPE 4  // Error 4 - Unsuported Line type 

#include "bloader.h"
#include <string.h>

#INT_RDA
void rs232_handler() // Test of interrupt
{
   putchar(getc()); // Just echo for test
}

void welcome(void)               // Welcome message
{
   char  REV[50]=ID;       // Buffer for concatenate of a version string

   if (REV[strlen(REV)-1]=='$') REV[strlen(REV)-1]=0;
   printf("\r\n\r\n# BLoader 887 (C) 2013 KAKL\r\n");   // Welcome message
   printf("#%s\r\n",&REV[4]);
}


/*-------------------------------- MAIN --------------------------------------*/
#SEPARATE
void real_main()     // Main of loaded program
{
   int8 i=0;

   i=rs232_errors; // Just for compiler pleasure (supress Warning)
   
   welcome();

   printf("# Reserved: %Lu\r\n", RESERVED_BLOCKS*FLASH_BLOCK_SIZE);
   printf("# FLASH_ERASE_SIZE: %Lu\r\n",getenv("FLASH_ERASE_SIZE"));
   printf("# FLASH_WRITE_SIZE: %Lu\r\n",getenv("FLASH_WRITE_SIZE"));
   
   printf("# Boot Loader Test >>>\r\n# ");
   enable_interrupts(INT_RDA);
   enable_interrupts(GLOBAL);
   while(TRUE)
   {
      printf("%u|",i++);  // Do something
      delay_ms(100);
   }
}


/*------------------- BOOT LOADER --------------------------------------------*/

#BUILD(INTERRUPT=FLASH_BLOCK_SIZE)   // Redirect Interrupt routine above first flash block
#ORG 4,5
void JumpToTheInterrupt()     // Jump to the Interrupt Handler
{ #asm GOTO FLASH_BLOCK_SIZE #endasm }
#ORG 6,FLASH_BLOCK_SIZE-1 {} // First Flash block is reserved


#ORG LOADER_RESERVED,LOADER_RESERVED+FLASH_BLOCK_SIZE-1 auto=0
#SEPARATE
void dummy_main() // Main on the fix position. It will be overwriten by downloaded program reset vector.
{
   real_main();
}

#ORG LOADER_RESERVED+FLASH_BLOCK_SIZE,getenv("PROGRAM_MEMORY")-1 auto=0 default  //Start of BootLoader

#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8,ERRORS)  //RS232 control routine for BootLoader

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

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

   return(result);
}

void assert(int1 Condition, int8 ErrorCode)  // Send error number to the serial line
{
   if(Condition)
   {
      putchar('E');
      putchar(ErrorCode+'0');
      reset_cpu();
   }
}

void pause()
{
   int16 timeout;

   for(timeout=0; timeout<65535; timeout++); // Delay cca 300ms
}

#SEPARATE
void boot_loader()  // Loads a new program
{
/*
:100240001F2999000A300C1E232999006430A4004C
10 = 16 bytes
0249 = address 0x0120 (0x240/2 because of words)
00 = data
...data...
4C = checksum

:00000001FF
00 = 0 bytes
0000 = address 0
01 = END
FF = checksum

http://cs.wikipedia.org/wiki/Intel_HEX
*/
   int  buffidx;
   char buffer[BUFFER_LEN_LOD];  // Buffer for HEX line

   int8  checksum, num_of_bytes, line_type; // Extracted values from HEX line 
   int16 l_addr,h_addr=0;
   int16 addr;             // Address of word in PIC
   int32 next_addr;        // Helper variable for for

   int8  dataidx, i;    // Buffer for program bytes and pointers
   union program_data {
      int8  i8[16];
      int16 i16[8];
   } data;

   disable_interrupts(GLOBAL);
/*   
   putchar('@');  //Start Erase

   //Erase program memory is not necessary.
   {
      int8 i;
      for(i=0;i<32;i++)buffer[i]=0xFF;
   }
   for(addr=FLASH_BLOCK_SIZE;addr<LOADER_RESERVED+FLASH_BLOCK_SIZE;addr+=FLASH_BLOCK_SIZE)
   {
      write_program_memory(addr, &buffer[0], 32);
      putchar('.');
      restart_wdt();
   }
*/
   putchar('!');  //Erase completed

//---WDT
   while(!kbhit()) restart_wdt(); //Wait for HEX
   putc('\r'); putc('\n');
   
   while(TRUE)
   {
//---WDT
      while (getc()!=':') restart_wdt(); // Only process data blocks that starts with ':'
      putchar(':');

      buffidx = 0;  // Read into the buffer until CR is received or buffer is full
      do
      {
         buffer[buffidx] = getc();
         putc(buffer[buffidx]);
      } while ( (buffer[buffidx++] != '\r') && (buffidx < BUFFER_LEN_LOD) );
      assert(buffidx == BUFFER_LEN_LOD, ERR_BUFFER_OVERRUN); // Error 1 - Buffer Overrun
      buffidx--;
      
//---WDT
      restart_wdt();

      checksum = 0;  // Sum the bytes to find the check sum value
      for (i=0; i<(buffidx); i+=2)
      {
         checksum += atoi_b16 (&buffer[i]);
      }
      assert(checksum != 0, ERR_CHECKSUM); // Error 2 - Bad CheckSum

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

      line_type = atoi_b16 (&buffer[6]);
      
      num_of_bytes = atoi_b16 (&buffer[0]);
      assert (num_of_bytes > 16, ERR_TOO_MANY_BYTES);  // Error 3 - Too many bytes in one line 

      addr = make32(h_addr,l_addr);

      addr /= 2;        // PIC16 uses word addresses

      // If the line type is 1 then END
      if (line_type == 1)
      {
         putchar('#');
         reset_cpu();
      }

      assert (line_type != 0, ERR_UNSUPORTED_LINETYPE);  // Error 4 - Unsuported Line type 

      {
         // Read old program memory content
         for (i=0,next_addr=addr;i<8;i++)
            data.i16[i]=read_program_eeprom(next_addr++);
         // Loops through all of the data and stores it in data
         // The last 2 bytes are the check sum, hence buffidx-2
         for (i=8,dataidx=0; i < (buffidx-2); i += 2)
            data.i8[dataidx++]=atoi_b16(&buffer[i]);

         if (addr == 0)
         {
            // Write 8 words to the Loader location (jump to the main())
            addr=LOADER_RESERVED;
            write_program_memory(addr, &data.i8[0], 16);
            putchar('%');
         }
         else
         if ( (addr > 7) && (addr <= (LOADER_RESERVED-16)) ) // Do not overwrite BootLoader
         {
            // Write program
            write_program_memory(addr, &data.i8[0], 16);
            putchar('$');
         }
         else putchar('.'); // Possibly there was prevented write to the location of BootLoader
         putc('\r'); putc('\n');

//---WDT
         restart_wdt();
      }
   }
}


void main()
{
   int8  timeout;

   disable_interrupts(GLOBAL);
   setup_wdt(WDT_2304MS);               // Setup Watch Dog
   setup_adc_ports(NO_ANALOGS);
   setup_adc(ADC_OFF);
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
   setup_timer_1(T1_DISABLED);
   setup_timer_2(T2_DISABLED,0,1);
   setup_comparator(NC_NC_NC_NC);
   setup_vref(FALSE);
   setup_oscillator(OSC_8MHZ|OSC_INTRC);

   for(timeout=0; timeout<255; timeout++) //cca 50s
   {
      if (kbhit())
        if (getc()=='u') // Send "uf" for Update Firmware
        {
          putchar('*');
          if (getc()=='f')
          {
            restart_wdt();
            boot_loader(); // Update Firmware starter
          }
        }
        else break;
      putchar('u'); putchar('f'); putchar('?');
      pause();
      restart_wdt();
   };

   restart_wdt();
   goto_address(LOADER_RESERVED); // Jump to the location where is the jump to the main
}
#ORG default   // End of BootLoader