News:

PROTON pic BASIC Compilers for PIC, PIC24, dsPIC33

Main Menu

Self Flash 18F27Q83

Started by Trikkitt, Nov 26, 2025, 01:14 AM

Previous topic - Next topic

Trikkitt

I'm trying to write a self-programming routine for the 18F27Q83. The datasheet is pretty clear how to do it.  The problem I'm trying to figure out is how to access the specific bank of RAM to load the buffer ready to write it to the program memory page.

I've got a routine to erase the page, and a routine to write the page.  The missing bit is that I need to load the GPR bank number 55 with the data to be written to that page of program memory.  But I can't see how through the compiler I can do that.  I'm trying to avoid writing one word at a time as I suspect that will take a very long to complete, as this is intended to flash most of the program memory.  So programming a bank at a time makes sense. Any ideas how I can access and load that specific RAM bank with the data?  I was thinking that maybe there is a way to map a variable to that specific area of RAM?

I don't need to read what is already there because this is downloading a new version of the software and writing.  My intention is to locate this self-programming routine at the end of the program memory, and then just rewrite the bottom section as needed.  The code below is the only relevant bit so far and is untested.  The whole section will have interrupts disabled, which is why there is no specific interrupts off line within the code below.

Dim PageAddr as word
dim ProgPage[128] as word

ErasePage:
  NVMADRU=PageAddr.highbyte
  NVMADRH=Pageaddr.LowByte
  nvmcon1=%00000110 ; Erase page operation
  NVMLOCK = 0x55
  NVMLOCK = 0xAA
  set nvmcon0.0
  while nvmcon0.0=1
  Wend
  clear nvmcon1 ' Clear the operation command
  return

WritePage:
  NVMADRU=PageAddr.highbyte
  NVMADRH=Pageaddr.LowByte
  clear nvmadrl
  nvmcon1=%00000101 ; Write page operation
  NVMLOCK = 0x55
  NVMLOCK = 0xAA
  set nvmcon0.0
  while nvmcon0.0=1
  Wend
  clear nvmcon1 ' Clear the operation command
  return


JonW

#1
You don't need to access the RAM banks manually; the compiler will handle it.  All you need to do is write to the NVMADR, and the compiler will do all of the work.  Here is a complete working include for a 16F15376 that shows how to access the nvm.  It should be easy to port.  The Loadlatch procedure loads the required address registers; the compiler handles everything else.


' P16F15376NVM.INC
'---------------
' CAN READ AND WRITE TO ANY SINGLE REGISTER IN THE DFM
' CAN READ THE DIA FOR CONSTANTS ETC
' EXAMPLE OF USE

' VAR = FLASHREAD(ADDRESS)          ' READS THE DFM AREA
' VAR = FLASHREADCFG(ADDRESS)       ' READS THE DIA AREA
' FLASHWRITE(ADDRESS, DATA)         ' WRITES TO A SINGLE LOCATION IN DFM

'----------------------------------------------------------------------------------------------
'----------------------------------------------------------------------------------------------
'NVMCONTROL

SYMBOL  NVMREGS     = NVMCON1.6             ' 1 = DIA, 0 = PFM
SYMBOL  NVMLWLO     = NVMCON1.5             ' LOAD WRITE LATCHES ONLY
SYMBOL  NVMFREE     = NVMCON1.4             ' PROGRAM FLASH MEMORY ERASE ENABLE
SYMBOL  NVMWRERR    = NVMCON1.3             ' PROGRAM/ERASE ERROR FLAG
SYMBOL  NVMWREN     = NVMCON1.2             ' PROGRAM/ERASE ENABLE BIT
SYMBOL  NVMWR       = NVMCON1.1             ' 1 = INITIATES A WRITE
SYMBOL  NVMRD       = NVMCON1.0             ' 1 = INITIATES A READ ON THE NEXT CYCLE

'NVM PROCS FOR 16F15376

(********************************************************************************************
* Title     :  FLASHREADMEM                                                                 *
* Input     :  FLASH ADDRESS                                                                *
* Output    :  FLASH CONTENT                                                                *
* Notes     :  READS FLASH MEM                                                              *
********************************************************************************************)
Proc FLASHREAD(ADDR As WORD),WORD
      NVMREGS = 0                             ' POINT TO FLASH
      NVMADRL = ADDR.Byte0                    ' LOAD THE POINTERS
      NVMADRH = ADDR.Byte1
      NVMRD = 1                               ' START THE READ
      NOP
      RESULT.HIGHBYTE = NVMDATH               ' RETURN THE DATA
      RESULT.LOWBYTE = NVMDATL
      RESULT = RESULT & %0011111111111111
EndProc

(********************************************************************************************
* Title     :  READFLASHCFG   MMMMMM                                                        *
* Input     :  FLASH ADDRESS                                                                *
* Output    :  FLASH CONTENT                                                                *
* Notes     :  READS DIA (CONFIG) AREA                                                      *
********************************************************************************************)
Proc FLASHREADCFG(ADDR As WORD),WORD
      NVMREGS = 1                             ' POINT TO CONFIG
      NVMADRL = ADDR.Byte0                    ' LOAD THE POINTERS
      NVMADRH = ADDR.Byte1
      NVMRD = 1                               ' START THE READ
      NOP
      RESULT.HIGHBYTE = NVMDATH               ' RETURN THE DATA
      RESULT.LOWBYTE = NVMDATL
EndProc

(********************************************************************************************
* Title     :  UNLOCKDFM (UNLOCK THE DATA FLASH)                                            *
* Input     :  FLASH ADDRESS                                                                *
* Output    :  FLASH CONTENT                                                                *
* Notes     :  26/27K42                                                                     *
********************************************************************************************)
Proc UNLOCKDFM()
        NVMCON2 = $55                         ' Required Writes
        NVMCON2 = $AA                         ' Required Wrtits
        NVMWR = 1                             ' Set WRITE CONTROL BIT
        NOP
        NOP
EndProc

(********************************************************************************************
* Title     :  FLASHWRITE                                                                   *
* Input     :  FLASH ADDRESS, BYTE TO WRITE                                                 *
* Output    :  NONE                                                                         *
* Notes     :  UPDATE 1 BYTE IN DFM (CAN WRITE TO ANY LOCATION)                             *
*********************************************************************************************)
Proc FLASHWRITE(ADDR As WORD, B2LOAD As WORD)

     Dim b_ROWs          As word              ' NEED A TEMPORY BYTE TO STORE THE ADDR.BYTE0
     DIM b_BYTE          as byte
     DIM b_LDL1          as byte
     Dim b_FLASH[32]     as word

     B_ROWs = (ADDR/32)                       ' GET THE 32 BYTE BOUNDARY ROW
     b_rows = b_rows * 32                     ' GET START ADDRESS
     b_byte = addr - (b_rows)                 ' GET BYTE INDEX TO REPLACE


     FOR B_LDL1 = 0 to 31                     ' READ FLASH INTO RAM
         B_FLASH[B_LDL1] = FLASHREAD(B_ROWs + B_LDL1)
     Next

     B_FLASH[B_BYTE] = B2LOAD                 ' UPDATE THE BYTE TO CHANGE

     ERASEROW(B_ROWs)                         ' ERASE THE 32 BYTE BLOCK (ROW)

     FOR B_LDL1 = 0 to 30                     ' RELOAD LATCHES
        LOADLATCH(B_ROWS,B_FLASH[B_LDL1],1)
        INC B_ROWS
     Next
        LOADLATCH(B_ROWS,B_FLASH[31],0)       ' FINAL LATCH AND WRITE
     WHILE NVMWR = 1:Wend                     ' WAIT FOR WRITE TO OCCUR IF IN A FAST LOOP
     NVMWREN = 0                              ' DISABLE WRITES
EndProc

(********************************************************************************************
* Title     :  LOADLATCH                                                                    *
* Input     :  FLASH ADDRESS, WORD TO LOAD, LATCH OR WRITE                                  *
* Output    :  NONE                                                                         *
* Notes     :  UPDATE 1 BYTE IN DFM (CAN WRITE TO ANY LOCATION)                             *
*********************************************************************************************)
proc LOADLATCH(ADDR AS WORD,DAT AS WORD, LATCH AS BIT)
        dim  b_GIESTORE AS BIT

        b_GIESTORE = INTCON.7
        NVMDATH = DAT.HIGHBYTE
        NVMDATL = DAT.LOWBYTE
        NVMADRH = ADDR.HIGHBYTE
        NVMADRL = ADDR.LOWBYTE

        NVMREGS = 0
        NVMLWLO = LATCH
        NVMFREE = 0
        NVMWREN = 1
        NOP
        UNLOCKDFM()
        INTCON.7 = b_GIESTORE    ' RETURN INT STATUS
EndProc

(********************************************************************************************
* Title     :  ERASEROW                                                                     *
* Input     :  FLASH ADDRESS                                                                *
* Output    :  NONE                                                                         *
* Notes     :  ERASES 32BYTE ROW AT ADDR                                                    *
*********************************************************************************************)
Proc   ERASEROW(ADDR As word)   ' KEEP THIS PROC SEPERATE SO WE CAN ERASE ANY 64-BYTE BLOCK

       NVMADRL = ADDR.Byte0
       NVMADRH = ADDR.Byte1

       NVMREGS = 0              ' ACCESS FLASH MEMORY
       NVMWREN = 1              ' ENABLE WRITE TO MEM
       NVMFREE = 1              ' ERASE BLOCK
       UNLOCKDFM()              ' UNLOCK DFM
       Nop                      ' REQUIRED
       WHILE NVMFREE = 1:WEND
       NVMWREN = 0              ' DISABLE WRITE TO MEM

EndProc

Trikkitt

Quote from: JonW on Nov 26, 2025, 09:37 AMYou don't need to access the RAM banks manually; the compiler will handle it.  All you need to do is write to the NVMADR, and the compiler will do all of the work.  Here is a complete working include for a 16F15376 that shows how to access the nvm.  It should be easy to port.  The Loadlatch procedure loads the required address registers; the compiler handles everything else.

Thanks - this looks like it writes one word at a time.  When I was looking at the data sheet there are two write methods, one is to write one word at a time which I suspect is what this does from a quick scan.  The feature I was trying to use is to write to the whole page (all 128 words) via one command.  That second method does require loading the data to be written into the specific RAM bank.  I thought there would be a way to tell the compiler to map a variable to a specific memory location, but I can't see any way to do that. 

For now I'll give the single word writing a try - at least then I'll have a way to update my code OTA.  Just worried about how long it'll take!

RGV250

Hi,
QuoteI thought there would be a way to tell the compiler to map a variable to a specific memory location, but I can't see any way to do that.
I think you use "at" something like Dim Variable as word at 0x4000

I hardly use it so not sure if that is the correct syntax.

Bob

Trikkitt


Thanks, that looks like it has potential.  Just need to give it a test now to validate it!

trastikata

Hello, try this:

Device = 18F27Q83                       
Declare Xtal = 4                               

Declare Auto_Heap_Arrays = On
Dim wPageBuffer[128] As Word At 0x3700         'PIC18Fx7Q83 - BANK 55 = 0x3700 / PIC18Fx6Q83 - BANK 37 = 0x2500
Dim wPageAddress As Word At NVMADRH

Main:
    WriteFlashPage(316)
End
   
'wAddress = FLASH page to be erased
'Result: 0 - page write success
'        1 - erase error   
'        2 - write error
Proc WriteFlashPage(wPageAddress As wPageAddress), Byte
    Dim bTempGIE As Byte       
    Result = 0

    bTempGIE = INTCON0          'Save INTCON0 to temporary Variable
    INTCON0.7 = 0               'Disable GIE 
    'Erase page
    NVMCON1 = 0x06              'Set the page erase command
    NVMLOCK = 0x55              'Unlock Sequence 0
    NVMLOCK = 0xAA              'Unlock Sequence 1
    NVMCON0.0 = 1               'Start page erase
    While NVMCON0.0 = 1 : Wend  'Wait for the erase operation to complete
    If NVMCON1.7 = 1 Then       'Verify erase operation success 
        NVMCON1.7 = 0           'Clear NVM Erase/Write Error
        Result = 1              'Set Result to Erase Error
        INTCON0 = bTempGIE      'Restore INTCON0 from temporary variable
        NVMCON1 = 0             'Disable writes to memory 
        ExitProc                'Exit
    EndIf
    'Write page
    NVMCON1 = 0x05              'Set the page write command
    NVMLOCK = 0x55              'Unlock Sequence 0
    NVMLOCK = 0xAA              'Unlock Sequence 1                     
    NVMCON0.0 = 1               'Start page write
    While NVMCON0.0 = 1 : Wend  'Wait for the write operation to complete
    If NVMCON1.7 = 1 Then       'Verify write operation success 
        NVMCON1.7 = 0           'Clear NVM Erase/Write Error
        Result = 2              'Set Result to Write Error
        INTCON0 = bTempGIE      'Restore INTCON0 from temporary variable
        NVMCON1 = 0             'Disable writes to memory 
        ExitProc                'Exit
    EndIf
    INTCON0 = bTempGIE          'Restore INTCON0 from temporary variable
    NVMCON1 = 0                 'Disable writes to memory 
EndProc   
   


Trikkitt


I ran the following code, then read the memory back using my programmer and it had updated!  So this is spot on for what I needed.  Thanks everyone who posted... :)

Dim PageAddr as word
dim ProgPage[128] as word at 0x3700
dim countw as word
dim ccc as byte

SelfProgram:
  ' Turn off all interrupts for all of this section.
  clear intcon0
  clear countw
  for ccc = 0 to 127
    inc countw
    progpage[ccc]=countw
  Next
  pageaddr=10
  gosub erasepage
  gosub writepage
  pageaddr=11
  countw=512
  for ccc = 0 to 127
    inc countw
    progpage[ccc]=countw
  Next
  gosub erasepage
  gosub writepage
  return

ErasePage:
  NVMADRU=PageAddr.highbyte
  NVMADRH=Pageaddr.LowByte
  nvmcon1=%00000110 ; Erase page operation
  NVMLOCK = 0x55
  NVMLOCK = 0xAA
  set nvmcon0.0
  while nvmcon0.0=1
  Wend
  clear nvmcon1 ' Clear the operation command
  return

WritePage:
  NVMADRU=PageAddr.highbyte
  NVMADRH=Pageaddr.LowByte
  clear nvmadrl
  nvmcon1=%00000101 ; Write page operation
  NVMLOCK = 0x55
  NVMLOCK = 0xAA
  set nvmcon0.0
  while nvmcon0.0=1
  Wend
  clear nvmcon1 ' Clear the operation command
  return



JonW

It updates one word but it reads and writes the entire block. You would just need to transfer your array in the same way it reads and writes.

Trikkitt

Quote from: JonW on Nov 26, 2025, 07:51 PMIt updates one word but it reads and writes the entire block. You would just need to transfer your array in the same way it reads and writes.


As I understand it from the datasheet, you can only erase in fixed blocks.  However writing you can write one word at a time or write the entire block in one go using the method I've shown in my code where it writes a specific memory bank straight to the program flash storage as a single write operation.  Rather than having to write each word individually.  It might be a difference between the 18F part i'm using, but it can only update a block in one go from a specific memory bank, there is no way to control the RAM location it uses when writing a block in one operation, at least that I could see.