picstick

An AVR based programming adapter for PIC microcontrollers.

/** @file icsp.c
 * 
 * This part of the firmware implements the ICSP interface by bitbanging some
 * GPIO pins.
 *
*/

#include <avr/io.h>
#include <util/delay.h>

#include "icsp.h"


// Startup bit sequence to enter programming mode is cleverly MCHIP in ascii.
#define ICSP_STARTUP_KEY "MCHP"

// ICSP Timings
#define ICSP_DELAY_ENTH 250
#define ICSP_DELAY_CKL 1
#define ICSP_DELAY_CKH 1
#define ICSP_DELAY_DLY 3
#define ICSP_DELAY_ERAB 8600    // Bulk erase time takes max 8.4 ms
#define ICSP_DELAY_ERAR 3000    // Row erase time is max 2.8 ms
#define ICSP_DELAY_PINT_PM 3000 // Program memory internal timed takes max 2.8ms
#define ICSP_DELAY_PINT_CW 5800 // Configuration word internally timed takes max 5.6 ms



// Change pin states.
#define icsp_pins_outputs() (ICSP_DDR |= (ICSP_PIN_MCLR | ICSP_PIN_CLK | ICSP_PIN_DAT))
#define icsp_pins_inputs()  (ICSP_DDR &= ~(ICSP_PIN_MCLR | ICSP_PIN_CLK | ICSP_PIN_DAT))
#define icsp_pins_low()     (ICSP_PORT &= ~(ICSP_PIN_MCLR | ICSP_PIN_CLK | ICSP_PIN_DAT))
#define pin_low(pin)    (ICSP_PORT &= ~(pin))
#define pin_high(pin)    (ICSP_PORT |= (pin))


// Internal functions.
static void icsp_write (unsigned char data);


void
icsp_init (void)
{
    // Our target state for our three ICSP pins are inputs without pullup.
    // This is the default state for AVRs.
    icsp_pins_inputs();
}


void
icsp_enable (void)
{
    // To enter program mode, we set MCLR low and shift in the 32 bit startup key
    
    icsp_pins_outputs();    // Our pins are in an input state.
    icsp_pins_low();        // Set all pins low, including MCLR.

    _delay_us(ICSP_DELAY_ENTH); // Wait Entry Hold Time period.

    int i, j;
    for (i=0; i < 4; i++) { // Start with the most significant byte
        for (j=7; j >= 0; j--) { // And the most significant bit

            pin_high(ICSP_PIN_CLK); // CLK High

            // Determine the next data bit in the startup sequence.
            if ((ICSP_STARTUP_KEY[i]) & (1 << j))
            {
                // Transmit a 1
                pin_high(ICSP_PIN_DAT);
            }
            else
            {
                // Transmit a 0
                pin_low(ICSP_PIN_DAT);
            }

            _delay_us(ICSP_DELAY_CKH); // Wait a Clock High period.

            // CLK low, this will cause the connected chip to latch the data.
            pin_low(ICSP_PIN_CLK);

            _delay_us(ICSP_DELAY_CKL); // Wait a Clock low period.
        }
    }
}

void
icsp_disable (void)
{
    // Exits programming mode

    pin_high(ICSP_PIN_MCLR); // Set MCLR high to exit programming mode.

    _delay_us(10);  // Wait for a bit.

    icsp_pins_inputs();     // Configure pins as inputs.
}

void
icsp_command (unsigned char command)
{
    icsp_write(command);
}

void
icsp_payload (unsigned int data)
{
    // A data payload is 24 bits long:
    // (7)  - Start + Padding 0s
    // (16) - Data bits
    // (1)  - Stop 0s
    union {
        unsigned char bytes[3];
        long number;
    } payload;
    
    // We can craft this payload by shifting the data left by one.
    payload.number = data;
    payload.number = payload.number << 1;

    // Now we can write our payload by writing the 3 low bytes.
    icsp_write(payload.bytes[2]);
    icsp_write(payload.bytes[1]);
    icsp_write(payload.bytes[0]);
}


unsigned int
icsp_read (void)
{
    signed char i;
    unsigned int word = 0;

    // Configure our DAT pin as input
    ICSP_DDR &= ~(ICSP_PIN_DAT);


    // Clock out 9 clock dummy clocks
    for (i=0; i<9; i++)
    {
        pin_high(ICSP_PIN_CLK);
        _delay_us(ICSP_DELAY_CKH);
        pin_low(ICSP_PIN_CLK);
        _delay_us(ICSP_DELAY_CKL);
    }

    // Clock out 14 cycles and read the data bits on a CLK fall
    for (i = 13; i > -1; i--)
    {
        pin_high(ICSP_PIN_CLK);     // CLK High

        _delay_us(ICSP_DELAY_CKH);  // Wait for chip to latch the next bit

        pin_low(ICSP_PIN_CLK);      // ClK Low

        // Record data state.
        if (ICSP_PIN & ICSP_PIN_DAT)
        {
            word |= (1U << i);
        }

        _delay_us(ICSP_DELAY_CKL);  // Wait a clock low period
    }

    // Clock out our stop bit totalling 24 bits
    pin_high(ICSP_PIN_CLK);
    _delay_us(ICSP_DELAY_CKH);
    pin_low(ICSP_PIN_CLK);
    _delay_us(ICSP_DELAY_CKL);

    // Set DAT pin back to output
    ICSP_DDR |= ICSP_PIN_DAT;

    // return result
    return word;
}



static void
icsp_write (unsigned char data)
{
    for (int bit = 7; bit >= 0; bit--) { // Start with the MSb

        pin_high(ICSP_PIN_CLK); // CLK High

        // Determine the next data bit in the command.
        if (data & (1 << bit))
        {
            // Transmit a 1
            pin_high(ICSP_PIN_DAT);
        }
        else
        {
            // Transmit a 0
            pin_low(ICSP_PIN_DAT);
        }
        
        _delay_us(ICSP_DELAY_CKH);

        pin_low(ICSP_PIN_CLK); // CLK Low

        _delay_us(ICSP_DELAY_CKL);
    }
}