Archive

Archive for the ‘pic18f bootloader’ Category

Set up required to program Pic18f4620 via Bootloader

September 18, 2013 21 comments

STEP 1

Your PIC must first be programmed with this bootloader. If you don’t have a preprogrammed PIC you  can download the hex file from here and program it using a pickit2 programmer by doing the following:

  • Connect the pickit2 to the pic18f4620 and the pickit2 to your computer as shown here.
  • Download, install and open the pickit2 software application
  • Select File->Import Hex. Browse to the location of the hex file you downloaded and select the file.
  • Click on the ‘Write’ button (left middle of the application)
  • Your PIC is now programmed with the bootloader

STEP 2

Build this circuit using the pic with bootloader programmed on to it and connect the usb-to-serial converter to your computers usb socket. (Make sure that the drivers for the usb-to-serial converter are installed on your computer – see prolific’s website for instructions on installing drivers for the PL2303HX)

circuit

STEP 3
Enter bootloader mode by holding down button 2 while clicking button 1 (The reset button). The LED will light up when the pic is in bootloader mode and your pic is ready to be programmed via the bootloader.

STEP 4

Download serialpicprog.exe (here is sourceode) and build.bat and save them in the same location as the code you wish to program on to the pic. Note: in order to use these files the xc8 compiler must be installed on your computer. Here’s some example code from Ted Burke that you can use for testing:

//
// PIC18F4620 example program
// Written by Ted Burke (ted.burke@dit.ie)
// Last update 19-9-2013 -by david.dorran@dit.ie
//          MCLRE set ON
//          RB6 used as output rather than RD0

#include <xc.h>

#pragma config OSC=INTIO67,MCLRE=ON,WDT=OFF,LVP=OFF,BOREN=OFF

int main(void)
{
    // Make RB6 a digital output
    TRISB = 0b10111111;

    while(1)
    {
        LATBbits.LATB6 = 1; // Set pin RB6 high
        _delay(125000);     // 0.5 second delay
        LATBbits.LATB6 = 0; // Set pin RB6 low
        _delay(125000);     // 0.5 second delay
    }
}

STEP 5

Using the DOS prompt, change into the directory where you saved the files in STEP 4 and run the following command “build mycode.c” (replace ‘mycode.c’ with the name of the file you wish to program on to the pic using the bootloader).

If you used the example given above the LED in the circuit will flash at 1Hz.

Advertisement

PIC18f4620 Bootloader

September 18, 2013 Leave a comment
//A bootloader designed  for the microchips pic18f4620. 
//Compile using xc8 --chip=18F4620 --ROM=0-bff bootloader.c. Download bootloader.hex to the pic using a pickit2. (see http://robosumo.wordpress.com/2013/02/28/recommended-software-to-install-on-your-own-laptop/ if you're not familiar with this)
//
//To enter programming mode hold RB7 low during reset. RB6 will be set high indicating the bootloader is ready to program. 
//The most strightforward way to program the device is to:
//   1. Build the circuit shown at .
//   2. Download build.bat and serialprog.exe from https://dadorran.wordpress.com.
//   3. Make sure that you have the xc8 compiler installed on your PC (download at http://www.microchip.com/mplabxc8windows)
//   5. Write your code in a file called test.c. (see http://robosumo.wordpress.com/2013/02/28/recommended-software-to-install-on-your-own-laptop/ for example code and download instructions)
//   6. At the DOS prompt type "build test.c"
//
//See https://dadorran.wordpress.com (search for pic18f4620 bootloader) for more details
//By david.dorran@dit.ie

#include <xc.h>
#include <flash.h>
#include <usart.h>
#include <stdio.h>

#pragma config OSC=INTIO67,MCLRE=ON,WDT=OFF,LVP=OFF,BOREN=OFF

//where the application program will reside in memory
#define START_PROG_ADDR 0x0c00


#asm
PSECT intcode 
	GOTO START_PROG_ADDR + 0x8
PSECT intcodelo 
	GOTO START_PROG_ADDR + 0x18
#endasm
void restart(void);
void jump_to_application(void);
int main(void)
{
	int BOOT_serial_data_recieved;
	BOOT_serial_data_recieved = EEPROM_READ(0x00); 
	EEPROM_WRITE(0x00, 0); 
	 
	TRISB = 0b10111111;
	LATD = 0;
	LATBbits.LATB6= 0; //just used for debugging
    if(PORTBbits.RB7 == 0 || BOOT_serial_data_recieved == 21)
    {     
		LATBbits.LATB6= 1; //just used for debugging
        int  k=0, i=0, address_and_checksum_recieved=0, first_address_recieved=0;
        int  checksum, sum=0;
        unsigned long int  address=0;
        unsigned char uart_recieve_buffer[64]; 
        unsigned char flash_contents[64]; 
        unsigned char Orig_OSCCON; 

		
		LATDbits.LATD0 = 1; //just used for debugging
        Orig_OSCCON = OSCCON;
        OSCCON = 0b01110000; //TO DO reset OSCCON back to default after programming
		OSCTUNEbits.PLLEN = 1;
		//open usart - baud rate 115200
		OpenUSART(USART_TX_INT_OFF & USART_RX_INT_OFF & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_CONT_RX & USART_BRGH_HIGH, 16);
		INTCONbits.GIE = 0; //disable global interrupts 
		INTCONbits.PEIE = 0; //disable peripheral interrupts 
		
        while(PIR1bits.RCIF == 0 || BOOT_serial_data_recieved == 21);
        if(RCREG == 21  || BOOT_serial_data_recieved == 21) 
        {
            TXREG = 22;
            while (!TXSTAbits.TRMT);     
        }
        else
        {
           restart();
        }
        //Expect to recieve data via serial line in the following way:
        // 1. First two bytes (lower address followd by upper address) indicating the start address for a block of 64 bytes.
        // 2. Third byte is checksum (modulo 256 sum of all 64 data bytes)
        // 3. 64 bytes of data
        // NOTE: if an adress of 0x000 is recieved there is no more data and the code jumps to the new program code 
        k=0;
        while(1)
        {
            
            //wait until data is recieved on serial rx
            while(PIR1bits.RCIF == 0);
            if(!address_and_checksum_recieved)
            {
                k = k +1;
                if(k<3 && !address_and_checksum_recieved)
                {
                    address = address+(unsigned int) (RCREG<<(8*(k-1)));
                    TXREG = address>>(8*(k-1));
                    while (!TXSTAbits.TRMT);  

                    if(address == 0 && k == 2)
                    {
						//done receiving data and programming the PIC since the address value is set to 0. 
                        OSCCON = Orig_OSCCON;
						OSCTUNEbits.PLLEN = 0;
						
						jump_to_application(); 
                    }
                    if(k==2 && address != START_PROG_ADDR && !first_address_recieved)
                    {
                        first_address_recieved = 1;
                    }
                } 
                if(k==3 && !address_and_checksum_recieved)
                {
                    checksum = RCREG;
                    TXREG = RCREG;
                    while (!TXSTAbits.TRMT);  
                    address_and_checksum_recieved = 1;
                    k = 0;
                }
            }
            else
            {
                uart_recieve_buffer[k] = RCREG;
                sum = sum + uart_recieve_buffer[k];
                k = k + 1;
                if(k == 64) //received 64 bytes - ready to write to flash memory
                {   
                    EraseFlash(address,address+63);
                    WriteBlockFlash(address, 1, uart_recieve_buffer ); 
                    ReadFlash(address, 64, flash_contents);
                    for(i=0;i<64;i++)
                    {
                        if(flash_contents[i] != uart_recieve_buffer[i])
                        {
                            TXREG = 2;
                            while (!TXSTAbits.TRMT);   
                            restart();
                        }
                    }                    
                    
                    if((sum & 255) != checksum)
                    {
                        //checksum doesn't match
                        TXREG = 1;
                        while (!TXSTAbits.TRMT);   
                        restart();
                    }
                    else
                    {
                        TXREG = 0;
                        while (!TXSTAbits.TRMT);   
                    }
                    k = 0;
                    sum =0;
                    address_and_checksum_recieved = 0;
                    address = 0;
                }
            }
                
        }
    }
    else
	{						
		jump_to_application();
    }
    return(0);
    
}

void jump_to_application(void)
{
		//redirect the interrupt vectors to allow application program use vectors
		LATBbits.LATB6= 0;
        #asm
            goto START_PROG_ADDR 
        #endasm	
}


void restart(void)
{
	#asm 
		goto start
	#endasm
}

serialpicprog.c

September 18, 2013 1 comment
// By David Dorran (david.dorran@gmail.com)
// Parses pic18f .hex file and sends program data via serial port to a pic programmed with the bootloader 
// available from (https://dadorran.wordpress.com)
// 
// 
// Acknowledgements:
// pic18f HEX file format described nicely at http://www.kanda.com/blog/microcontrollers/pic-microcontrollers/pic-hex-file-format/
// Serial comms code nabbed from http://batchloaf.wordpress.com (Ted Burke)
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <windows.h>
#include <time.h>

#define BOOTLOADER_MEMORY 0xc00
#define BAUD_RATE_VALUE CBR_115200
#define BAUD_RATE 115200
int lineLen(char *line);
int hextodec(char *line, int index, int len);
void hex_format_error(char *str, int linenum);
void serialCommsError(char *str);

//variables for serial comms
HANDLE hSerial;
DCB dcbSerialParams = {0};
COMMTIMEOUTS timeouts = {0};
void closeSerial(void);
void openSerial();
void serialWrite(char *data, int numBytes);
void serialRead(char *data, int numBytes);

int main(int argc, char *argv[])
{
    FILE *f;
    char line[100];
    char msg[30];
    int mem[1024][64];
    int memfilled[1024][64]; //indicates which memory locations have data stored
    int k, i,sum, temp1, temp2;
    int skip_line =0; //EEPROM DATA and CONFIG data are skipped at the moment (TO DO: update to handle this rather than skip)
    int flash_block_address, Type, numBytes,Address, LowerAddress, UpperAddress, numChars, HexEndReached=0, LineNum=1;
    long int checksum;
    char bytes_to_send[100];
    char bytes_received[100];
    DWORD bytes_written;
	if (argc > 1)
	{
		f = fopen(argv[1], "r");
	}
	else
	{
		printf("usage: serialprogpic input.hex\n\nNote:input.hex can be any filename. It is the hexfile outputted by the xc8 compiler.");
        return(0);
	}    
    //clear the mem array
    for(k=0;k< 1024;k++)
    {
        for(i=0;i<64;i++) mem[k][i]=0;          
        for(i=0;i<64;i++) memfilled[k][i]=0;          
    }   
    //parse the hex file
    while(!feof(f))
    {
        fscanf(f, "%[^\n]\n", line);

        numChars = lineLen(line);
        if(numChars < 11)//make sure each line contains the min number of characters
        {
            hex_format_error("A minimum of 11 characters are expected on each line.", LineNum);
        }
        if(line[0] != ':')
        {
            hex_format_error("The first character in each line should be ':'.", LineNum);
        }
        
        numBytes = hextodec(line,1,2);
        Address = hextodec(line,3,4);
        Type = hextodec(line,7,2);
        
        if(Type == 0 && skip_line == 0)//This means the line contains flash memory data
        {
            if(HexEndReached > 0)
            {
                sprintf(msg, "Hex file data was found in the hex file after and 'end of code' line was encountered at line %d. ", HexEndReached );
                hex_format_error(msg, LineNum);
            }
            if(numChars != 11 + numBytes*2)
            {
                hex_format_error("The number of data bytes available does not equal the number specified.", LineNum);
            }
            for(k=0;k<numBytes;k++)
            {
                flash_block_address =  Address - fmod(Address, 64);
                flash_block_address = flash_block_address/64;
                mem[flash_block_address][(int) fmod(Address, 64) + k] = hextodec(line,9+k*2,2);
                memfilled[flash_block_address][(int) fmod(Address, 64) + k] = 1;  
            }
        }
        if(skip_line == 1)
        {        
            if(strcmp(line, ":08000000FF08191EFF8381FFB8")) //config bits associated with #pragma config OSC=INTIO67,MCLRE=ON,WDT=OFF,LVP=OFF,BOREN=OFF.
            {
                printf("The bootloader only allows the pic to be configured using:\n #pragma config OSC=INTIO67,MCLRE=ON,WDT=OFF,LVP=OFF,BOREN=OFF.");
                return(0);        
            }
            skip_line = 0;
        }
        if(Type == 1)
        {
            if(HexEndReached > 0) //two end of datas reached
            {
                hex_format_error("This is the second 'end of code' sequence found. Only one permitted in each hex file", LineNum);
            }
            
            HexEndReached = LineNum;
        }
        if(Type == 4)
        {
            //Type 4 are configuration bits or EPPROM data (OR USER ID ??)
            //The following line is config/eeprom data (we'll skip ir for now - not sure how to set confiuration bits though code at the moment)
            if(Address != 0) 
            {
                hex_format_error("The address for type 4 should be 0.", LineNum);
            }
            temp1 = hextodec(line,9,4);
            //temp will be 48 if its a config line and 240 if its a EEPROM line
            if(temp1 == 48)
            {
                skip_line = 1;
            }
            else if (temp1 == 240)
            {
                skip_line = 1;
                printf("The bootloader doesn't handle EEPROM data yet.");
                return(0);
            }
            else
            {
                hex_format_error("Type 4 data should only be either 240 (EEPROM DATA) or 48 (CONFIG DATA)", LineNum);
            }
        }
        //print out line for debugging
        /*k = 0;
        while(line[k] != '\0')
        {
            printf("%c", line[k]);
            k = k + 1;
        }
        printf("\n\n");*/
        LineNum++;
    }
    fclose(f);
    
    //get the data ready for sending to serial port. Send the data in blocks of 64 because flash can only be written in blocks of 64. 
       
    openSerial();

    printf("Verified serial communication with the PIC.\n");
    for(k=0;k< 1024;k++)
    {
        checksum =0, sum=0;
        // check each block of 64 to see if any data has to be written
        for(i=0;i<64;i++)
        {
            if(memfilled[k][i] == 0) mem[k][i] = 255;
            checksum = checksum + mem[k][i];            
            sum = sum + memfilled[k][i];
        }
        //there is data to be written. Data is written in blocks of 64 bytes
        if(sum > 0)
        {
            if(k*64 < BOOTLOADER_MEMORY)
            {
                printf("k = %d.This program would overwrite the booloader which occupies flash memory from 0-%xh. This is not permitted. If you are using xc8 then recompile with the option --CODEOFFSET=%xh, to ensure that that the bootloader is not overwritten.", k, BOOTLOADER_MEMORY, BOOTLOADER_MEMORY);
                //return(0);
            } 
            Address = k*64;
            LowerAddress = fmod(Address, 256);
            UpperAddress = (Address - LowerAddress) >> 8;
            checksum = fmod(checksum, 256);
            
            bytes_to_send[0] = LowerAddress;
            bytes_to_send[1] = UpperAddress;
            bytes_to_send[2] = checksum;
            
            serialWrite(bytes_to_send, 3);   
            serialRead(bytes_received, 3);
                   
            //confirm data sent was received back 
            if(bytes_received[0] != bytes_to_send[0] || bytes_received[1] != bytes_to_send[1] || bytes_received[2] != bytes_to_send[2])
            {
                serialCommsError("Error - Serial comms error. Expected values not recieved during data transfer.\n A valid connection with the PIC had been established earlier, therefore you are conencted to the correct COM port. There may have been a break in serial connections or data may have been corrupted during transfer. Please reset the PIC while RB7 is held low, check serial connections and try again.");
            }
               
            printf("\n\nProgramming Flash Memory Addresses %04X - %04X  with the following 64 bytes of machine code\n", Address, Address+63 );
            for(i=0;i<64;i=i+2)
            {
                if(fmod(i,16) == 0)
                {
                    printf("\n");
                }
                printf(" %02X%02X ",  mem[k][i+1], mem[k][i]);
                bytes_to_send[i] = (char) mem[k][i];
                bytes_to_send[i+1] = (char) mem[k][i+1];
            }
            if(checksum > 255)
            {
                printf("Programming Error - checksum = %d. Checksum should be less that 255", checksum);
                return(0);
            }
            printf("\n checksum is %d\n",checksum); 
            //send address in two bytes (lower address followed by upper address), one byte checksum followed by 64 bytes of data       
            //wait for ok (or ok at very end)? 
            serialWrite(bytes_to_send, 64);
            serialRead(bytes_received, 1);
            if(bytes_received[0] == 1)
            {
                serialCommsError("Error - Serial comms error. Checksums did not match. \n A valid connection with the PIC had been established earlier, therefore you are conencted to the correct COM port. There may have been a break in serial connections or data may have been corrupted during transfer. Please power off the PIC to reset, check serial connections and try again.");
            }
            else if(bytes_received[0] == 2)
            {
                //serialRead(bytes_to_send, 1); //make use of bytes_to_send seeing that we're about to exit
                printf("Could not write to flash memory location %04X\n", Address);
                serialCommsError("Error -  Unable to write to flash memory. ");
            }
            else if(bytes_received[0] != 0)
            {
                //serialRead(bytes_to_send, 1); //make use of bytes_to_send seeing that we're about to exit
                printf("Could not erase flash memory location %04X. Address info  %04X .\n Memory needs to earased before program data can be written.", Address, bytes_received[0] );
                serialCommsError("Error -  Unable to write to flash memory. ");
            }
                    
        }
    }
    //Send a couple of zeros to indicate to the PIC that programming is complete and it should jump to the application 
    bytes_to_send[0] = 0;
    bytes_to_send[1] = 0;  
    serialWrite(bytes_to_send, 2);       
    
    closeSerial();
    
}

void hex_format_error(char *str, int linenum)
{
   // printf("\n\nHEX FORMATTING ERROR\n\n\tThe hex file inputted is not in the expected format. \n\n\t See http://www.kanda.com/blog/microcontrollers/pic-microcontrollers/pic-hex-file-format/\n\n\t\t\"Each line should be in the format :BBAAAATT[DDDDDDDD]CC\n\n\t\twhere \n\t\t: is start of line marker\n\t\tBB is number of data bytes on line\n\t\tAAAA is address in bytes\n\t\tTT is type. 00 means data, 01 means EOF and 04 means extended address\n\t\tDD is data bytes, number depends on BB value\n\t\tCC is checksum (2s-complement of number of bytes+address+data)\"\n\nError on line %d : %s", linenum, str);
    printf("\n\nHEX FORMATTING ERROR\n\n\t See http://www.kanda.com/blog/microcontrollers/pic-microcontrollers/pic-hex-file-format/\n\n\t\t");
    printf("\n\nError on line %d : %s", linenum, str);
    exit(0);
}

int lineLen(char *line)
{
    int k=0;
    while(line[k] != '\0') k++;
    return(k);
}
int hextodec(char *line, int index, int len)
{
    int temp1, sum = 0,  k;
    
    for(k=0;k<len;k++)
    {
        temp1 = line[index+len-1-k];
        if(temp1 < 64) temp1 = temp1 - 48;
        else temp1 = temp1 - 55;
       
        temp1 = temp1*pow(16, k);
        sum = sum + temp1;
    }
    return sum;    
}

void serialWrite(char *data, int numBytes)
{
    DWORD bytes_written;
    if(!WriteFile(hSerial, data, numBytes, &bytes_written, NULL))
    {
        serialCommsError( "Error - could not send data to the serial port. Closing the COM port\n");
    } 
}
void closeSerial(void)
{
    // Close serial port
    printf( "Closing serial port...");
    if (CloseHandle(hSerial) == 0)
    {
        printf( "Error - Could not close serial port\n");
        exit(0);
    }
    printf( "OK\n");
}
void openSerial()
{
    char dev_name[MAX_PATH] = "";
    int n;
    int portNum = 0;
    char portPath[20];
    int scan_max = 40;
    int scan_min = 1;
    char bytes_to_send[1];
    char bytes_received[1];
    DWORD bytes_read;
    
    // Scan for an available COM port in _descending_ order
    printf("\nAttempting to download program to the PIC via serial (com) port...\n Scanning COM Ports ...");
    for (n = scan_max ; n >= scan_min ; --n)
    {
        // Try next device
        sprintf(dev_name, "\\\\.\\COM%d", n);
        hSerial = CreateFile(dev_name, GENERIC_READ|GENERIC_WRITE, 0, 0,
                                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
        if (hSerial!=INVALID_HANDLE_VALUE)
        {
            bytes_to_send[0] = 21; //send 21 out and expect to recieve 22 back 
            
            dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
            if (GetCommState(hSerial, &dcbSerialParams) == 0)
            {
                serialCommsError( "Error getting device state\n");
            }
            dcbSerialParams.BaudRate = BAUD_RATE_VALUE;
            dcbSerialParams.ByteSize = 8;
            dcbSerialParams.StopBits = ONESTOPBIT;
            dcbSerialParams.Parity = NOPARITY;
            if(SetCommState(hSerial, &dcbSerialParams) == 0)
            {
                serialCommsError("Error setting device parameters\n");
            }
         
            // Set COM port timeout settings
            timeouts.ReadIntervalTimeout = 50;
            timeouts.ReadTotalTimeoutConstant = 50;
            timeouts.ReadTotalTimeoutMultiplier = 10;
            timeouts.WriteTotalTimeoutConstant = 50;
            timeouts.WriteTotalTimeoutMultiplier = 10;
            if(SetCommTimeouts(hSerial, &timeouts) == 0)
            {
                serialCommsError( "Error setting timeouts\n");
            }
            serialWrite(bytes_to_send, 1);
            
            
            ReadFile(hSerial, bytes_received, 1, &bytes_read, NULL);
            
            if((int) bytes_received[0] == 22)
            {
                portNum = n;
                break;
            } // stop scanning COM ports
        }
    }   
    if(portNum == 0)
    {
        serialCommsError( "Could not establish communications with the PIC.\nYou may connected to the incorrect COM port. \nIf you are certain you are using the correct COM port then:\n\t1. Reset the PIC (set MCLRE low or turn off power) \n\t2. ensure that the PIC is in programming mode i.e. ensure that RB7 is held low during reset)\n\t3. check serial connections \n\t4. try again!\n\n");    
    }
    printf("Connected to PIC through COM%d\n", portNum);

}

void serialRead(char *data, int numBytes)
{
    int data_recieved = 0, num_received;
    float wait_time_expired = 0;;
    DWORD bytes_read;
    clock_t start_time, finish_time;
    start_time = clock();
    finish_time = clock();
    
    while(!wait_time_expired && !data_recieved)
    {
        ReadFile(hSerial, data, 3, &bytes_read, NULL);
        finish_time = clock();
        if( (finish_time-start_time)/CLOCKS_PER_SEC > 2 + numBytes*1/BAUD_RATE)
        {
            wait_time_expired = 1;
            serialCommsError("Error - Serial comms error. Time out waiting for a response from the PIC.\nYou may connected to the incorrect COM port. \nIf you are certain you are using the correct COM port then:\n\t1. turn off power to PIC to reset \n\t2. ensure that the PIC is in programming mode (RB7 is held low during reset)\n\t3. check serial connections \n\t4. try again!\n\n");
            exit(0);
        }
        if (bytes_read != numBytes) continue;
        data_recieved = 1;
    }
    
}

void serialCommsError(char *str)
{  
    printf("\n%s", str);
    CloseHandle(hSerial);
    exit(0);
}