AVR ATmega2560 Flash访问代码正确性及语法、约束值问询
AVR ATmega2560 Flash访问代码问题解答
问题描述
我正在学习用C和汇编实现AVR ATmega2560的Flash访问,参考optiboot等Bootloader代码编写了可从主应用调用的Flash加载程序。目前代码无编译错误,但尚未实现写入指令,无法测试读取功能,现需确认:
- 代码方向是否正确、存在哪些错误
- 更新后的函数输入输出约束值是否正确
- 汇编语句中
%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都应改为OUTloadTempPage中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
相关产品推荐
相关产品推荐

