flipflop

A lightweight serial bootloader for PIC16F1 devices.

#include <xc.h>

#include "nvm.h"
#include "uart.h"

// NVM unlock sequence. This will set the WR bit and write any data.
static void nvm_unlock (void);

int
nvm_read_words (unsigned int address, int len, unsigned char * buff)
{
    if (address > 0x7FFF)
    {
        // Set bit to indicate were reading config/ID/DIA/EEPROM
        NVMCON1bits.NVMREGS = 1;
    }
    else
    {
        // Clear bit to indicate were reading flash
        NVMCON1bits.NVMREGS = 0;
    }

    // Convert # of words to bytes
    len *= 2;
    for (int word = 0; word < len; word += 2)
    {
        // Load address into NVMADR registers
        NVMADRH = address >> 8;
        NVMADRL = address & 0xFF;

        // Set bit to initiate read
        NVMCON1bits.RD = 1;

        // Data is available the very next cycle.
        buff[word] = NVMDATH;
        buff[word+1] = NVMDATL;

        // Increment address
        address++;
    }

    return len;
}

void
nvm_write_row (unsigned int address, unsigned char *row_data)
{
    // Before writing we have to erase the row
    nvm_erase(address);

    // Clear bit to indicate we're writing to PFM
    NVMCON1bits.NVMREGS = 0;

    // Enable NVM writes
    NVMCON1bits.WREN = 1;

    // Set flag to only load data latches
    NVMCON1bits.LWLO = 1;

    // Load starting address into NVMADR registers
    NVMADRH = (address >> 8);
    NVMADRL = (address & 0xFF);

    // Load 63 words into the data latches
    for (unsigned char word = 0; word < 63; word++)
    {
        // Load word into NVM registers
        NVMDATH = *row_data++;
        NVMDATL = *row_data++;

        // Initiate write sequence
        nvm_unlock();

        // Increment word address
        address++;
        NVMADRH = (address >> 8);
        NVMADRL = (address & 0xFF);
    }

    // Clear flag to enable flash write
    NVMCON1bits.LWLO = 0;

    // Load final word
    NVMDATH = *row_data++;
    NVMDATL = *row_data;

    // Initiate write sequence. This will write the entire row to flash.
    nvm_unlock();
}


void
nvm_write (unsigned int address, unsigned int word)
{
    // Set bit to indicate we're writing to config
    NVMCON1bits.NVMREGS = 1;

    // Enable NVM writes
    NVMCON1bits.WREN = 1;

    // Load address into NVMADR registers
    if ((address >> 8) == 0xF0)
    {
        // Writing EEPROM
        NVMADRH = 0x70;
    }
    else
    {
        // Writing CONFIG/ID
        NVMADRH = 0;
    }
    NVMADRL = (address & 0xFF);

    // Load word into NVM registers
    NVMDATH = word >> 8;
    NVMDATL = word & 0xFF;

    // Initiate write sequence
    nvm_unlock();
}

void
nvm_erase (unsigned int address)
{
    if (address > 0x7FFF)
    {
        // Set bit to indicate we're reading config/ID/DIA
        NVMCON1bits.NVMREGS = 1;
        // address = address & 0xFF;
    }
    else
    {
        // Clear bit to indicate we're reading flash
        NVMCON1bits.NVMREGS = 0;
    }

    // Enable writes
    NVMCON1bits.WREN = 1;

    // Set FREE bit to indicate erasure
    NVMCON1bits.FREE = 1;

    // Load address
    NVMADRH = (address >> 8);
    NVMADRL = (address & 0xFF);

    // Initiate erase
    nvm_unlock();

    // Disable write
    NVMCON1bits.WREN = 0;
}

static void
nvm_unlock (void)
{
    // The unlock sequence must not be interrupted, so we disable interrupts.
    // unsigned char gie_bit = PIE0bits.GIE;
    // PIE0bits.GIE = 0;

    // Step 1. Write 0x55 to NVMCON2
    NVMCON2 = 0x55;

    // Step 2. Write 0xAA to NVMCON2
    NVMCON2 = 0xAA;

    // Step 3. Set WR bit of NVMCON1
    //  This will start the write.
    NVMCON1bits.WR = 1;

    // Unlock sequence complete, we may reenable interrupts.
    // PIE0bits.GIE = gie_bit;
}


// EOF //