我正在尝试使用 C 和汇编命令来理解闪存访问。我一直在阅读几个引导加载程序(如 optiboot)的代码,以了解其工作原理。我正在尝试编写一个闪存加载程序,该程序将放置在引导段中,但不会被硬件熔丝指向。当收到信号时可以从主应用程序调用的东西。
#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
然后我就这样编写了函数
//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
//----------------------------------------------------------------------------------------
我的这些工作是否走在正确的轨道上?编译器没有给出错误,但我还没有弄清楚写入指令,所以我还不能测试读取函数。
希望有人能告诉我这段代码的方向是否正确,以及是否有错误需要纠正。为简单起见,我在 Arduino IDE 上编写此代码,以便我可以在 Arduino mega 上尝试。
[[评论太长]]
您不能只在一个内联汇编中加载一些 GPR,然后说明该寄存器在其他一些内联汇编中仍包含相同的值。这是因为您不能排除编译器将 GPR 用于其他用途。
如果你可以读取一些值,说一个词,然后像这样传递它:
#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 %i[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), [rampz] "n" (& RAMPZ));
return result;
}
uint16_t call_load_word (void)
{
return load_word (0x12345);
}
或者,如果您愿意,也可以将该值写入同一缓冲区中。
请注意,上面的代码假定为 ATmega2560。对于其他设备,您可能需要一些#ifdef
。