View on Github
			
/**
 * @file nvm_pic24_dspic30f_dspic33.c
 * @author Sebastien CAUX (sebcaux)
 * @copyright UniSwarm 2020-2023
 *
 * @date March 20, 2020, 08:32 AM
 *
 * @brief NVM (Non Volatile Memory) support drivers for dsPIC30F, dsPIC33FJ,
 * dsPIC33EP, dsPIC33EV, PIC24F, PIC24FJ, PIC24EP and PIC24HJ
 */

#include "nvm.h"

#include <archi.h>

#if !defined(NVM_FLASH_PAGE_BYTE) || NVM_FLASH_PAGE_BYTE == 0
#    warning "No flash on the current device or unknown device"
#endif

void nvm_writeDoubleWord(nvm_addr addr, const char *data);

/**
 * @brief Reads a defined number of bytes flash memory
 * @param addrs address in bytes of the page to read
 * @param ramBuffer array of read data
 * @param size number of words to read
 */
ssize_t nvm_read(nvm_addr addr, char *data, size_t size)
{
    uint16_t offset, i = 0;
    size_t sizeRemaining;
    ssize_t size_read;

    addr >>= 1;
    TBLPAG = addr >> 16;
    offset = addr;
    sizeRemaining = size;  // set the number of value to read

    while (sizeRemaining >= 3)  // read if there is more than 3 bytes to read
    {
        uint16_t data16h, data16l;
        data16h = __builtin_tblrdh(offset);  // read of high word
        data16l = __builtin_tblrdl(offset);  // read of low word

        data[i++] = (char)data16l;
        data[i++] = (char)(data16l >> 8);
        data[i++] = (char)data16h;

        offset += 2;
        sizeRemaining -= 3;

        if (offset == 0x0000)
        {
            TBLPAG++;  // TBLPAG value switch
        }
    }

    if ((sizeRemaining < 3) && (sizeRemaining > 0))  // read if there is less than 3 bytes to read
    {
        uint16_t data16l;
        data16l = __builtin_tblrdl(offset);
        data[i++] = (char)data16l;
        if (sizeRemaining == 2)
        {
            data[i++] = (char)(data16l >> 8);
        }
    }
    size_read = i;
    return size_read;
}

/**
 * @brief Writes 2 instrusction words in flash memory
 * @param addraddr of the page to write
 * @param data array of the data to write (2 * three bytes)
 */
void nvm_writeDoubleWord(nvm_addr addr, const char *data)
{
    unsigned char *udata = (unsigned char *)data;

    NVMCON = 0x4001;  // Memory double-word program operation
    TBLPAG = 0xFA;    // write latch upper addr

    __builtin_tblwtl(0, (((uint16_t)udata[1]) << 8) + udata[0]);
    __builtin_tblwth(0, (uint8_t)udata[2]);  // load write latches

    __builtin_tblwtl(2, (((uint16_t)udata[4]) << 8) + udata[3]);
    __builtin_tblwth(2, (uint8_t)udata[5]);  // load write latches

    NVMADRL = addr;
    NVMADRH = addr >> 16;  // set target write address

    __builtin_disi(6);      // Disable interrupts for NVM unlock
    __builtin_write_NVM();  // unlock and wait until WR = 0
    while (NVMCONbits.WR == 1)
    {
        ;
    }
}

/**
 * @brief Writes a defined number of bytes in flash memory
 * @param addr address of the page to write
 * @param data array of the data to write
 * @param size size of the data to write in number of bytes
 */
ssize_t nvm_write(nvm_addr addr, const char *data, size_t size)
{
    uint32_t currentAddr;
    size_t sizeRemaining, i;
    ssize_t size_write;
    char newData[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

    addr >>= 1;
    currentAddr = addr;
    sizeRemaining = size;  // set the number of value to read

    if ((currentAddr & 0x0003) >= 2)  // if the address is not aligned
    {
        if (sizeRemaining < 3)  // if less than 3 bytes to write
        {
            for (i = 0; i < sizeRemaining; i++)
            {
                newData[i + 3] = data[i];
            }
            sizeRemaining = 0;
        }
        else  // if 3 bytes or more to write
        {
            for (i = 0; i < 3; i++)
            {
                newData[i + 3] = data[i];
            }
            sizeRemaining -= 3;
        }

        currentAddr = currentAddr & 0xFFFFFFC;
        nvm_writeDoubleWord(currentAddr, newData);

        currentAddr += 4;
        data += 3;
    }

    while (sizeRemaining > 6)  // if at least 6 bytes to write
    {
        nvm_writeDoubleWord(currentAddr, data);
        currentAddr += 4;
        data += 6;
        sizeRemaining -= 6;
    }

    if (sizeRemaining > 0)  // if less than 6 bytes to write
    {
        newData[3] = 0xFF;
        newData[4] = 0xFF;
        newData[5] = 0xFF;

        for (i = 0; i < sizeRemaining; i++)
        {
            newData[i] = data[i];
        }
        sizeRemaining = 0;
        nvm_writeDoubleWord(currentAddr, newData);
    }
    size_write = (size - sizeRemaining);
    return size_write;
}

/**
 * @brief Read a whole page in flash memory
 * @param addr address of the page to read
 * @param data array of the data to read
 */
ssize_t nvm_readPage(nvm_addr addr, char *data)
{
    uint32_t pageAddr;
    size_t pageSize;
    ssize_t size_read;

    pageAddr = addr & NVM_FLASH_PAGE_MASK;      // align the address with top of page
    pageSize = (NVM_FLASH_PAGE_BYTE >> 2) * 3;  // calculate the exact number of byte
    // only 3 out of 4 bytes are useful because of phantom byte

    size_read = nvm_read(pageAddr, data, pageSize);

    return size_read;
}

/**
 * @brief Erases a page of flash memory
 * @param addraddr of the page to read
 */
ssize_t nvm_erasePage(nvm_addr addr)
{
    ssize_t size_erase;

    addr >>= 1;
    NVMADR = addr & 0xF800;  // filter supposed to be on the other register
    NVMADRU = addr >> 16;    // set target write addrof general segment

    NVMCON = 0x4003;
    size_erase = NVM_FLASH_PAGE_BYTE;

    __builtin_disi(6);      // Disable interrupts for NVM unlock
    __builtin_write_NVM();  // unlock and wait until WR = 0
    while (NVMCONbits.WR == 1)
    {
        ;
    }

    return size_erase;
}

/**
 * @brief Writes a whole page in flash memory
 * @param addr address of the page to write
 * @param data array of the data to write
 */
ssize_t nvm_writePage(nvm_addr addr, const char *data)
{
    uint32_t pageAddr;
    size_t pageSize;
    ssize_t size_write;

    addr >>= 1;
    pageAddr = addr & NVM_FLASH_PAGE_MASK;      // align the address with top of page
    pageSize = (NVM_FLASH_PAGE_BYTE >> 2) * 3;  // calculate the exact number of byte
    // only 3 out of 4 bytes are useful because of phantom byte

    nvm_erasePage(pageAddr);  // the page needs to be erase before any writing
    size_write = nvm_write(pageAddr, data, pageSize);

    return size_write;
}

/**
 * @brief transform an address to page number
 * @param addr address of the page
 */
uint16_t nvm_pageNumber(nvm_addr addr)
{
    uint16_t pageNum = addr >> NVM_FLASH_PAGE_SHIFT;
    return pageNum;
}

/**
 * @brief transform a page number to an address
 * @param pageNum number of the page
 */
uint32_t nvm_pageAddress(uint16_t pageNum)
{
    uint32_t pageAddr = (uint32_t)pageNum << NVM_FLASH_PAGE_SHIFT;
    return pageAddr;
}

bool nvm_ready(void)
{
    return (NVMCONbits.WR != 0);
}

void nvm_waitForReady(void)
{
    while (NVMCONbits.WR != 0)
    {
        ;
    }
}