You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVR ATmega2560 Flash访问代码正确性及语法、约束值问询

AVR ATmega2560 Flash访问代码问题解答

问题描述

我正在学习用C和汇编实现AVR ATmega2560的Flash访问,参考optiboot等Bootloader代码编写了可从主应用调用的Flash加载程序。目前代码无编译错误,但尚未实现写入指令,无法测试读取功能,现需确认:

  1. 代码方向是否正确、存在哪些错误
  2. 更新后的函数输入输出约束值是否正确
  3. 汇编语句中%i的含义

原始代码

#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

更新后的代码

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
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;
}

问题解答

1. 代码方向与错误分析

代码核心围绕AVR的SPM/ELPM指令,操作Z指针与SPMCSR寄存器,整体方向符合ATmega2560 Flash操作的规范,但存在多处细节错误:

原始代码错误

  • loadZ_ADDRESS、readFlash1Word等函数中的汇编代码仅为字符串,未用asm/asm volatile包裹,完全不会被执行,Z指针设置、Flash读取等逻辑无效
  • readFlash1Word直接操作r0/r1会破坏编译器临时寄存器,且直接访问buffer的汇编写法不符合ATmega2560的寻址规则
  • boot_page_erase中boot_cmd_spm_erase()修改SPMCSR后,后续的"spm\r\n"仅为字符串,未执行SPM指令;且SPM操作前未等待就绪,也未处理RWW区保护
  • eraseFlash使用if判断而非while循环,仅会擦除第一个页面,无法遍历所有需要擦除的区域
  • boot_is_spm_busy()判断逻辑错误:ATmega2560的SPM忙标志是SPMCSR的RWWSB位(你定义的SPM_BUSY),但当前代码判断的是SPMEN位,会导致忙等待失效

更新后代码错误

  • boot_page_erase与loadTempPage中使用IN指令方向错误:IN是从I/O寄存器读入通用寄存器,而设置SPMCSR需要用OUT指令将命令写入寄存器,两处的IN都应改为OUT
  • loadTempPage中word1 = buffer[n] | (buffer[n+1] << 8));多了一个右括号,会导致编译错误
  • readFlash1Word使用__asm而非__asm volatile,编译器可能会优化掉这段汇编代码,应改为__asm volatile防止优化
  • readFlash1Word的注释错误:第二行ELPM的%B[res]对应结果的高字节,注释误写为低字节
  • 所有SPM操作前需等待SPM就绪(boot_spm_busy_wait()),boot_page_erase中执行SPM前未添加等待,可能导致操作失败
  • loadTempPage未校验noBytes是否为偶数,会存在越界访问buffer的风险

2. 更新后函数输入输出约束值检查

整体约束值方向正确,部分细节需调整:

  • [z] "=&z" (reg_z):应改为"=&Z"(大写Z,AVR GCC中Z代表Z寄存器对),&表示早期clobber,避免编译器分配被输入使用的寄存器,逻辑正确
  • [addr] "r" (Addr):用通用寄存器传递地址,符合要求
  • [rampz] "n" (RAMPZ)、[spmcsr] "n" (SPMCSR):n约束表示立即数,RAMPZ/SPMCSR是I/O寄存器地址常量,使用正确
  • [eras] "M" (cmderase)、[cmd] "M" (CMD_ENABLEBIT):M约束表示0-255的立即数,命令值均在范围内,使用正确
  • [data] "r" (word1):用通用寄存器传递16位数据,符合要求
  • [res] "=r" (result):用通用寄存器存储返回结果,符合要求

3. 汇编语句中%i的含义

在AVR GCC内联汇编中,%i是I/O寄存器地址修饰符,作用是将后续的寄存器名替换为对应的I/O空间地址(而非内存地址)。例如%i[rampz]会被替换为ATmega2560中RAMPZ的I/O地址0x3B,使得OUT %i[rampz], %C[addr]等价于OUT 0x3B, ...,符合AVR OUT指令操作I/O寄存器的语法要求。


内容的提问来源于stack exchange,提问作者josie hinton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:29:52