Search code examples
cassemblyarduinoavravr-gcc

figure out flash access AVR ATmega2560


EDIT: update learning assembly: ok so this function causes no compiler warnings and they should all be turned on now. Are my constraint values correct for these inputs and outputs?

void boot_page_erase(address_t Addr)
{
  Addr = Addr << 1;
  uint16_t reg_z;
  uint8_t  cmderase = CMD_PAGEERASE;
  
  __asm volatile
  (
    "MOVW %[z],      %[addr]  \n\t"   //copies into ZH and ZL
    "OUT  %i[rampz], %C[addr] \n\t"  //%i means SRAM, %C means get 3rd byte of [addr]
    "IN   %[spmcsr], %[eras] \n\t"
    "spm \r\n"
      //output operends
    :[z]  "=&z" (reg_z)  //  
      //input operends
    :[addr] "r" (Addr),   [rampz]  "n" (RAMPZ),
     [spmcsr] "n" (SPMCSR), [eras] "M" (cmderase)  
  );                               
}

// FILL TEMP PAGE BUFFER
//set address in Z-pointer
//data in R1:R0 __tmp_reg__
//Write 0b00000001 to SPMCSR
//call spm
void loadTempPage(uint8_t* buffer, int noBytes, address_t Addr)
{
  Addr = Addr << 1;
  uint16_t reg_z, word1;
  for(int n=0; n<noBytes; n+=2)
  {
    word1 = buffer[n] | (buffer[n+1] << 8));    
    __asm volatile 
    ( 
      "MOVW %[z],         %[addr]\n\t"  //copies into ZH and ZL
      "OUT  %i[rampz],    %C[addr]\n\t" //%i ram, %C means get 3rd byte of [addr]
      "MOVW __tmp_reg__,  %[data]\n\t"  //load r0 and r1
      "IN   %[spmcsr],    %[cmd] \n\t"  //set SPMCSR
      "spm\n\t"
        //output operends
      :[z]  "=&z" (reg_z)
        //input operends 
      :[addr] "r" (Addr), [rampz] "n" (RAMPZ), 
       [data] "r" (word1),
       [spmcsr] "n" (SPMCSR), [cmd] "M" (CMD_ENABLEBIT)    
    );
    boot_spm_busy_wait();
    Addr +=2;
  }
}//page fill done


EDIT: update, @emacs drives me nuts I understand more from your example and I found the reference for AVR-libc section on inline access to assembly instructions. https://www.nongnu.org/avr-libc/user-manual/inline_asm.html

I still can't find what the "%i" meaning is on the "OUT" statement.

Also why does someone mark things negative when someone just has a honest question and is learning? Seriously people.

uint16_t readFlash1Word(const address_t Addr)
{
  uint16_t result, reg_z; 
  __asm
  ( 
    "MOVW %[z],      %[addr]\n\t"   //copies into ZH and ZL
    "OUT  %i[rampz], %C[addr]\n\t"  //%i means??, %C means get 3rd byte of [addr]
    "ELPM %A[res], Z+\n\t"          //read flash, %A= put in low byte of res, increment 
    "ELPM %B[res], Z+\n\t"          //read flash, %A= put in low byte of res, increment 
    :[z]  "=&z" (reg_z), [res]  "=r" (result) //output operends
    :[addr] "r" (Addr), [rampz] "n" (RAMPZ)     //input operends
  );
  return result;
}

original below I'm trying to understand the flash access using C and assembly commands. I've been reading through the code of several bootloader programs like optiboot to get an idea how this works. I'm trying to write a flashloader program that would be placed in the boot segment but not pointed to by the hardware fuses. Something that could be called from the main application when signaled to do so.

#define SPM_REG         SPMCSR  //full register
#define SPM_ENABLE      SPMEN   //bit 0
#define SPM_BUSY        RWWSB   //bit 6
#define SPM_RWWENABLE   RWWSRE  //bit 4
#define SPM_INTENABLE   SPMIE   //bit 7
//execute to the page as Z-pointer specifies
#define CMD_ENABLEINTRUPT 0b10000000
#define CMD_SIGNATUREREAD 0b00100001
#define CMD_REENABLEFLASH 0b00010001 //re-enable readable after a write or erase
#define CMD_BOOTLOCKSET   0b00001001 
#define CMD_PAGEWRITE     0b00000101 
#define CMD_PAGEERASE     0b00000011 
#define CMD_ENABLEBIT     0b00000001
address_t   Z_ADDRESS = 0; //define global address Z 32 bit
#define boot_spm_busy_wait()          do{}while(boot_is_spm_busy())
#define boot_is_spm_interrupt()       (SPM_REG & (uint8_t)_BV(SPM_INTENABLE))
#define boot_is_rww_busy()            (SPM_REG & (uint8_t)_BV(SPM_BUSY))
#define boot_is_spm_busy()            (SPM_REG & (uint8_t)_BV(SPM_ENABLE)) //autoclears when complete
//make change to SPMCSR register
#define boot_cmd_spm_interrupt_enable()   (SPM_REG |= (uint8_t)_BV(SPM_INTENABLE))
#define boot_cmd_spm_interrupt_disable()  (SPM_REG &= (uint8_t)~_BV(SPM_INTENABLE))
#define boot_cmd_spm_erase()              (SPM_REG |= CMD_PAGEERASE)      //sets 2 bits
#define boot_cmd_spm_write()              (SPM_REG |= CMD_PAGEWRITE)      //sets 2 bits
#define boot_cmd_spm_reenableread()       (SPM_REG |= CMD_REENABLEFLASH)  //sets 2 bits
#define boot_cmd_spm_setspmen()           (SPM_REG |= CMD_ENABLEBIT)
#define boot_cmd_spm_clearspmcsr()        (SPM_REG &= 0b10000000) //clears all but keeps high bit if set

Then I have functions written as such

//Z_ADDRESS is a global defined 32bit unsinged variable
void loadZ_ADDRESS(address_t Addr)
{
  Z_ADDRESS = Addr << 1;  //shift and leave lowest bit 0 to get low first
  "LDI RAMPZ, ((Z_ADDRESS >> 16)&0xFF)\r\n"
  "LDI ZH,    ((Z_ADDRESS >> 8)&0xFF)\r\n"
  "LDI ZL,    (Z_ADDRESS & 0xFF)\r\n"
  ;
}
//--------------------------------------------------------------------------
void readFlash1Word(uint16_t* buffer, address_t Addr)
{
  loadZ_ADDRESS(Addr);  
  "ELPM %r0, Z+\r\n"; //read flash increment to high byte
  "ELPM %r1, Z \r\n";
  "MOVW HIGH(buffer):LOW(buffer), %r1:r0\r\n";
}
//--------------------------------------------------------------------------
void readFlash2Bytes(uint8_t* buffer, address_t beginAddr, int buffPos)
{
  loadZ_ADDRESS(beginAddr);
  "ELPM %r0, Z+\r\n"; //read flash increment to high byte
  "ELPM %r1, Z \r\n";
  "MOVW buffer[bufPos+1]:buffer[bufPos], %r1:r0\r\n";
}
//-----------------------------------------------------------------------------
void boot_page_erase(address_t eraseaddress)
{
  loadZ_ADDRESS(eraseaddress);  //set Z-pointer address on page
  boot_cmd_spm_erase();         //set register to erase next
  "spm\r\n";                                   
}
//-----------------------------------------------------------------------
void eraseFlash() // erase only main section (bootloader protection)
{
  address_t eraseAddress = 0;
  if (eraseAddress < APP_END )
  {
    boot_cmd_spm_clearspmcsr();     //makes sure all other bits are not flagged
    boot_page_erase(eraseAddress);  // Perform page erase
    boot_spm_busy_wait();   // Wait until the memory is erased.
    eraseAddress += SPM_PAGESIZE; // point to next page to be erase, page size in bytes
  }
  boot_spm_busy_wait();
  boot_cmd_spm_clearspmcsr(); //makes sure all others are not flagged
  boot_cmd_spm_reenableread();
  "spm";
}//end eraseflash
//----------------------------------------------------------------------------------------

Am I on the right track making these work? The compiler doesn't give errors but I haven't figured out the write instruction yet so I can't yet test the read function.

Hoping someone can tell me if I'm going the right direction with this code and if there are mistakes that need correcting. For simplicity I'm writing this on the Arduino IDE so I can try it on an Arduino mega.


Solution

  • [[Too long for a comment]]

    You cannot just load some GPR in one inline asm and then expext that the register still contains that same value in some other inline asm. This is because you cannot rule out that the compiler is using that GPR in between for something else.

    If, at all, you can read some value, say a word, and pass that around like so1:

    #include <avr/io.h>
    
    static inline __attribute__((__always_inline__))
    uint16_t load_word (const __uint24 addr)
    {
        uint16_t result, reg_z;
        __asm ("movw %[z], %[addr]"       "\n\t" // ATmega2560 has MOVW.
               "out  __RAMPZ__, %C[addr]" "\n\t" // RAMPZ is in range of OUT.
               "elpm %A[res], Z+"         "\n\t"
               "elpm %B[res], Z+"
               // avr-gcc ABI for ATmega2560 does not require to reset RAMPZ,
               // so we don't.
               : [res] "=r" (result), [z] "=&z" (reg_z)
               : [addr] "r" (addr));
        return result;
    }
    
    uint16_t call_load_word (void)
    {
        return load_word (0x12345);
    }
    

    Or, if you prefer, also write that value to some buffer in that same asm. Conveniently, AVR-LibC provides memcpy_PF which copies from far flash to RAM:

    #include <avr/pgmspace.h>
    
    void load_n_bytes (uint8_t *dst, __uint24 src, size_t n_bytes)
    {
        memcpy_PF (dst, src, n_bytes);
    }
    

    1That code assumes ATmega2560. For other devices you might need some #ifdef's.