• PicŪ Basic




  • Using the CWrite command on 18F devices

    Using the CWrite command, Proton allows you to take advantage of the self-modifying program memory feature on newer micros such as the 18F series. Having read the manual, you might assume that it's as simple as pointing at a location in program memory and writing your bytes. Sadly, as I discovered myself, it's a bit more tricky than that!

    There are a couple of things that it's important to understand about flash memory. Firstly, you can't just erase or write individual bytes as you please, it needs to be done in blocks. Secondly, writing to flash memory is actually a case of clearing the bits you don't want, rather than setting those that you do. As such, a block erase command changes the state of all bits in that block to '1', and the subsequent write command changes any bits that do not need to be set to a '0'. This is all down to the way charge is applied to the gates that make up the memory, but we'll not get too hung up on that here.

    While the Proton manual doesn't make much mention of it, you need to check your datasheet and find out the erase and write block sizes for your chosen microcontroller. In the case of an 18F45K20, the erase block size is 64 bytes, while the write block size is 32 bytes. To all intents, that means that even if you want to write a single byte into flash, you'll need to erase 64 bytes. This isn't so bad if you want to write exactly 64 bytes, or you can afford to store one byte and waste the remaining 63, but you'll still have to complete the entire 32 byte write block as a minimum - even if all you do is fill it with zeros! Note that proton doesn't actually execute the write until you've written enough bytes with the CWrite command to fill the entire write block. As the erase block size is going to be the same or greater in length than the write block, we'll just concentrate on the length of the write block.

    In practice, writing to flash means first backing up the contents of the block you are about to erase into RAM, adding your new data, and then writing the old and new contents of the block back into flash. In my application, I needed to save some relatively long text strings into memory. Some of them were long enough that they would even span multiple 64-byte blocks, although they all varied in length. I needed some easy way to deal with this whilst not having to worry about picking write addresses that began at the very start of an erase or write block. By taking advantage of the fact that integer division doesn't return the remainder, it's pretty easy to calculate the start address of an erase block. To that end, I came up with the following code:

    Code:
    Symbol blockSize = 64 ;Set your erase block size in bytes here
    
    
    Dim memTemp[blockSize] As Byte
    Dim writeAddress As Word
    Dim currentAddress As Word
    Dim startFirstBlock As Word
    Dim startLastBlock As Word
    Dim startCurrentBlock As Word
    Dim blockCount As Word
    dim textLength as word
    Dim byteCount As Byte
    Dim stringCount As Byte
    Dim dataByte As Byte
    
    
    FlashWrite:
    Pop writeAddress                                                                                 ;get the address in program memory in which to write first byte
    stringCount = 0
    textLength = Len(textString)
    startFirstBlock = (writeAddress / blockSize)                                                     ;work out start address of first erase block
    startLastBlock = (writeAddress + textLength) / blockSize                                         ;work out start address of last erase block
    For blockCount = startFirstBlock To startLastBlock                                               ;repeat for as many blocks as textLength spans
        startCurrentBlock = blockCount * blockSize                                                   ;get the start address of the current block
        For byteCount = 0 To (blockSize - 1)                                                         ;repeat for each byte in the current block
            currentAddress = startCurrentBlock + byteCount                                           ;get the address of the current byte within the block
            If currentAddress >= writeAddress And currentAddress <= (writeAddress + textLength) Then ;if reached write location and not finished writing string
                memTemp[byteCount] = textString[stringCount]                                         ;save the new string data in RAM
                Inc stringCount                                                                      ;move onto next byte in string
            Else                                                                                     ;if not writing new string to RAM   
                memTemp[byteCount] = CRead currentAddress                                            ;save old contents of program memory
            EndIf     
        Next
        CErase startCurrentBlock                                                                     ;erase the current block
        For byteCount = 0 To (blockSize - 1)                                                         ;repeat for each byte in the current block
            currentAddress = startCurrentBlock + byteCount                                           ;get the address of the current byte within the block
            CWrite currentAddress, [memTemp[byteCount]]                                              ;write the bytes to flash
        Next    
    Next
    Return
    To use, first set the symbol at the beginning of the code to the size of your PICs erase block. Dimension a string variable named textString and make it long enough to hold whatever data you want to write to flash. Load up textString with data and be sure to null-terminate the string. Call the routine and pass it the address in program memory where you want to write the first byte of data:

    Code:
    GoSub FlashWrite[yourProgramCodeAddressHere]
    For the sake of example, let's say you want to write a 20 byte string starting at program code address 60, and your erase block size is 64 bytes. As this spans two blocks, the code will automatically take care of writing the data across the two blocks, backing up any existing data and writing it back after the erase. The only bytes to be overwritten will be from the start address to the start address plus the length of textString. The code also works just fine for data that doesn't span multiple blocks, even down to writing a single byte.