picstick

An AVR based programming adapter for PIC microcontrollers.

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

#include "uuart.h"
#include "icsp.h"

#include "commands.h"


unsigned char input_buffer[INPUT_BUFFER_SIZE];
int recv_size;


// Status Flags
#define STATUS_DISCONNECTED 0
#define STATUS_CONNECTED 1
#define STATUS_PROGRAM 2

unsigned char cmd_is(const char *cmd)
{
    if (memcmp(input_buffer, cmd, strlen(cmd)) == 0) {
        return 1;
    }
    return 0;
}

void cmd_resp(const char *resp)
{
    uuart_print((char *)resp);
    uuart_tx_byte(SERIAL_CMD_SEP);
}

void cmd_resp_error(unsigned char *msg, unsigned char msg_len)
{
    uuart_print(SERIAL_CMD_ERROR);
    uuart_tx_byte(SERIAL_CMD_SEP);

    for (int i=0; i < msg_len; i++)
    {
        uuart_tx_byte(msg[i]);
    }
}

unsigned char cmd_hello(void)
{
    cmd_resp(SERIAL_CMD_HELLO);
    return STATUS_CONNECTED;
}

unsigned char cmd_bye(void)
{
    cmd_resp(SERIAL_CMD_BYE);
    return STATUS_DISCONNECTED;
}

unsigned char cmd_start(void)
{
    icsp_enable();
    cmd_resp(SERIAL_CMD_OK);
    return STATUS_PROGRAM;
}

unsigned char cmd_stop(void)
{
    icsp_disable();
    cmd_resp(SERIAL_CMD_OK);
    return STATUS_CONNECTED;
}

unsigned char cmd_addr(void)
{
    uuart_rx_bytes(input_buffer, 2);
    unsigned int address = (input_buffer[0] << 8) | input_buffer[1];
    icsp_command(ICSP_CMD_ADDR_LOAD);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(address);
    cmd_resp(SERIAL_CMD_OK);
    return STATUS_PROGRAM;
}

unsigned char cmd_word(void)
{
    uuart_rx_bytes(input_buffer, 5);
    if (input_buffer[2] != ':') {
        cmd_resp_error(input_buffer, 5);
        return STATUS_PROGRAM;
    }
    unsigned int address = (input_buffer[0] << 8) | input_buffer[1];
    unsigned int word = (input_buffer[3] << 8) | input_buffer[4];

    // Load address
    icsp_command(ICSP_CMD_ADDR_LOAD);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(address);
    _delay_us(ICSP_DELAY_DLY);

    // Write word
    icsp_command(ICSP_CMD_LOAD_DATA);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(word);
    _delay_us(ICSP_DELAY_DLY);

    // Begin write command
    icsp_command(ICSP_CMD_START_INT);

    // Wait at least half of the time required for internal timed programming.
    // The time it takes to respond and such should cover the other half
    _delay_us(ICSP_DELAY_PINT_CW/2);

    // verify data

    // Return response
    cmd_resp(SERIAL_CMD_OK);

    return STATUS_PROGRAM;
}

unsigned char cmd_row(void)
{
    // Get address
    recv_size = uuart_rx_bytes(input_buffer, 3);
    if ((recv_size != 3) || (input_buffer[2] != ':')) {
        cmd_resp_error(input_buffer, recv_size);
        return STATUS_PROGRAM;
    }
    unsigned int address = (input_buffer[0] << 8) | input_buffer[1];
    
    // Get row
    recv_size = uuart_rx_bytes(input_buffer, 128);
    if (recv_size != 128) {
        cmd_resp_error(input_buffer, recv_size);
        return STATUS_PROGRAM;
    }

    // Load address
    icsp_command(ICSP_CMD_ADDR_LOAD);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(address);
    _delay_us(ICSP_DELAY_DLY);

    // Write 63 words of data
    unsigned int word;
    unsigned char offset;
    for (offset=0; offset < 126; offset += 2) {
        // Write word
        word = (input_buffer[offset] << 8) | input_buffer[offset+1];
        icsp_command(ICSP_CMD_LOAD_DATA_INC);
        _delay_us(ICSP_DELAY_DLY);
        icsp_payload(word);
        _delay_us(ICSP_DELAY_DLY);
    }

    //Write 64th word without incrementing address
    word = (input_buffer[offset] << 8) | input_buffer[offset+1];
    icsp_command(ICSP_CMD_LOAD_DATA);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(word);
    _delay_us(ICSP_DELAY_DLY);

    // Begin write command
    icsp_command(ICSP_CMD_START_INT);
    
    // Hope our serial transfer takes longer than 4ms. (Usually does)
    // _delay_us(ICSP_DELAY_PINT_PM);

    // verify data

    // Return response
    cmd_resp(SERIAL_CMD_OK);

    return STATUS_PROGRAM;
}

unsigned char cmd_erase(void)
{
    recv_size = uuart_rx_bytes(input_buffer, 2);
    if (recv_size != 2) {
        cmd_resp_error(input_buffer, recv_size);
    }

    unsigned int address = (input_buffer[0] << 8) | input_buffer[1];
    unsigned char cmd = ICSP_CMD_ERASE_ROW;
    unsigned int delay_len = ICSP_DELAY_ERAR;

    // Bulk erase the whole device
    if (address == SERIAL_CMD_ERASE_ALL) {
        address = 0x8000;
        cmd = ICSP_CMD_ERASE_BULK;
        delay_len = ICSP_DELAY_ERAB;
        
    }
    // Bulk erase user flash
    else if (address == SERIAL_CMD_ERASE_FLASH) {
        address = 0x0000;
        cmd = ICSP_CMD_ERASE_BULK;
        delay_len = ICSP_DELAY_ERAB;
    }

    icsp_command(ICSP_CMD_ADDR_LOAD);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(address);
    _delay_us(ICSP_DELAY_DLY);
    icsp_command(cmd);
    if (delay_len == ICSP_DELAY_ERAB)
    {
        _delay_us(ICSP_DELAY_ERAB);
    }
    else
    {
        _delay_us(ICSP_DELAY_ERAR);
    }

    cmd_resp(SERIAL_CMD_OK);
    return STATUS_PROGRAM;
}

unsigned char cmd_read(void)
{
    recv_size = uuart_rx_bytes(input_buffer, 2);
    if (recv_size != 2) {
        cmd_resp_error(input_buffer, recv_size);
    }

    unsigned int address = (input_buffer[0] << 8) | input_buffer[1];

    icsp_command(ICSP_CMD_ADDR_LOAD);
    _delay_us(ICSP_DELAY_DLY);
    icsp_payload(address);
    _delay_us(ICSP_DELAY_DLY);
    icsp_command(ICSP_CMD_READ_DATA);
    _delay_us(ICSP_DELAY_DLY);
    unsigned int word = icsp_read();

    // Return response
    cmd_resp(SERIAL_CMD_OK);
    uuart_tx_byte((word >> 8));
    uuart_tx_byte(word & 0xFF);
    return STATUS_PROGRAM;
}


unsigned char handle_command(void)
{
    recv_size = uuart_rx_bytes_until(':', input_buffer, INPUT_BUFFER_SIZE);

    // Greeting
    if (cmd_is(SERIAL_CMD_HELLO))
        return cmd_hello();

    else if (cmd_is(SERIAL_CMD_BYE))
        return cmd_bye();

    else if (cmd_is(SERIAL_CMD_START))
        return cmd_start();

    else if (cmd_is(SERIAL_CMD_STOP))
        return cmd_stop();

    // else if (cmd_is(SERIAL_CMD_ADDR)) This doesnt really need to be a command
    //     return cmd_addr();        since we specify address in all the others    
    
    else if (cmd_is(SERIAL_CMD_WORD))
        return cmd_word();
    
    else if (cmd_is(SERIAL_CMD_ROW))
        return cmd_row();
    
    else if (cmd_is(SERIAL_CMD_ERASE))
        return cmd_erase();
    
    else if (cmd_is(SERIAL_CMD_READ))
        return cmd_read();

    uuart_print("UNKOWN:");
    uuart_tx_bytes(input_buffer, recv_size);
    return 0;
}