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"

// 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))


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_msb (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.

    unsigned char i, j;
    for (i=0; i < 4; i++) { // Start with the most significant byte
        unsigned char bit = 0x80;
        for (j=8; 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]) & bit)
            {
                // 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);

            bit = bit >> 1;

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

void
icsp_enable_lsb (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.

    signed char i, j;
    for (i=3; i >= 0; i--) { // Start with the least significant byte
        unsigned char bit = 0x1;
        for (j=8; j > 0; j--) { // And the least significant bit

            pin_high(ICSP_PIN_CLK); // CLK High

            // Determine the next data bit in the startup sequence.
            if ((ICSP_STARTUP_KEY[i]) & bit)
            {
                // 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);

            bit = bit << 1;

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

    // 33rd clock at the end for some reason
    pin_high(ICSP_PIN_CLK); // CLK High
    _delay_us(ICSP_DELAY_CKH + 2); // Wait a Clock High period.
    pin_low(ICSP_PIN_CLK);
    _delay_us(ICSP_DELAY_CKL); // Wait a Clock low period.
}

void
icsp_disable (void)
{
    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 data)
{
    unsigned char bit = 0x80;
    for (char i = 8; i > 0; i--) { // Start with the MSb

        pin_high(ICSP_PIN_CLK); // CLK High

        // Determine the next data bit in the command.
        if (data & 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

        bit = bit >> 1;

        _delay_us(ICSP_DELAY_CKL);
    }
}

void
icsp_payload (unsigned long data)
{
    // A data payload is 24 bits long:
    // (1)  - Start bit (0)
    // (22) - Data bits
    // (1)  - Stop bit (0)
    // We can craft this payload by shifting the data left by one.
    data = (data << 1UL) & 0x7FFFFFUL;

    unsigned long bit = 0x800000UL;
    for (char i = 24; i > 0; i--) { // Start with the MSb

        pin_high(ICSP_PIN_CLK); // CLK High

        // Determine the next data bit in the command.
        if (data & 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

        bit = bit >> 1;

        _delay_us(ICSP_DELAY_CKL);
    }
}


unsigned long
icsp_read (void)
{
unsigned long payload = 0;

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

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

        payload = payload << 1;

        _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)
        {
            payload |= 1;
        }

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

    // Set start/stop bits to 0 (They should be, but not guaranteed)
    payload &= 0x7FFFFE;

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

    return payload;
}


void
icsp_short_command (unsigned char data)
{
    unsigned char bit = 0x1;
    for (char i=0; i < 6; i++) { // Start with the LSb

        pin_high(ICSP_PIN_CLK); // CLK High

        // Determine the next data bit in the command.
        if (data & 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

        bit = bit << 1;

        _delay_us(ICSP_DELAY_CKL);
    }
}

void
icsp_short_payload (unsigned int data)
{
    // A data payload is 16 bits long:
    // (1)  - Start bit (0)
    // (14) - Data bits
    // (1)  - Stop bit (0)
    // We can craft this payload by shifting the data left by one.
    data = (data << 1) & 0x7FFE;

    unsigned int bit = 0x1;
    for (char i = 0; i < 16; i++) { // Start with the LSb

        pin_high(ICSP_PIN_CLK); // CLK High

        // Determine the next data bit in the command.
        if (data & 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

        bit = bit << 1;

        _delay_us(ICSP_DELAY_CKL);
    }
}


unsigned int
icsp_short_read (void)
{
unsigned int payload = 0;

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

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

        payload = payload << 1;

        _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)
        {
            payload |= 1;
        }

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

    // Set start/stop bits to 0 (They should be, but not guaranteed)
    payload &= 0x7FFE;

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

    return payload;
}