奇怪的UC3 用户页面 NVRAM 使用后重置行为

Weird UC3 Reset behavior after user page NVRAM usage

本文关键字:NVRAM UC3 用户      更新时间:2023-10-16

我最近需要在构建NVRAM/EEPROM的AT32UC3L0256中使用来存储一些配置数据。我终于设法使用 MCU 的用户页面 NVRAM(经过几天的反复试验和对 GCC 的诅咒,无视noinit指令并像往常一样修复和解决 ASF 中的错误)是这样的:

typedef struct
{
int writes;                 // write cycles counter
int irc_pot;                // IRC_POT_IN position state
} _cfg;
volatile static int *nvram_adr=(int*)(void*)0x80800000;     // user page NVRAM
volatile static _cfg    ram_cfg;                            // RAM copy of cfg
void cfg_load() // nvram_cfg -> ram_cfg
{
ram_cfg.writes =nvram_adr[8];
ram_cfg.irc_pot=nvram_adr[9];
}
void cfg_save() // nvram_cfg <- ram_cfg
{
int i;
U32 buf[128];
// blank
for (i=0;i<128;i++) buf[i]=0xFFFFFFFF;
// cfg
buf[8]=ram_cfg.writes;
buf[9]=ram_cfg.irc_pot;
// Bootloader default cfg
buf[126]=0x929E0B79;
buf[127]=0xE11EFFD7;
flashcdw_memcpy(nvram_adr   ,buf   ,256,true);  // write data -> nvram_cfg with erase
flashcdw_memcpy(nvram_adr+64,buf+64,256,false); // write data -> nvram_cfg without erase (fucking ASF cant write more than 256Bytes at once but erases whole page !!!)
}

我不得不从ASF 3.48.0.98更新flashcdw.c,flashcdw.h以便能够编写完整的 512 字节,因为旧的 ASF 确实编程了高达 256 字节,但擦除了整个页面,弄得一团糟。我还必须存储整个页面(而不是由于擦除而仅存储 8 个字节),并且像往常一样,由于 ASF 错误,我需要按原样执行此操作,而不仅仅是只调用flashcdw_memcpy一次......

它现在可以工作了,但我发现某些地址导致了奇怪的行为。当0xFF不在某个地址上时,设备将不再正常重置(但仍直到重置运行正常)。在非引导加载程序重置上,它会启动固件代码,但[ms]几次后它会再次重置,这种情况会永远持续下去。需要明确的是,重置发生在这部分代码中(在我的情况下):

for (U8 i=0;i<4;i++)
{
gpio_tgl_gpio_pin(_LED);
wait_ms(200);
}

配置系统后,其指示灯的简单闪烁(PLL CPU时钟,配置的定时器和ISR,但中断仍被禁用)。LED 闪烁几次(PLL 以正确的速度工作),但在环路完成之前发生复位。等待很简单:

//------------------------------------------------------------------------------------------------
#define clk_cpu 50000000
#define RDTSC_mask 0x0FFFFFFF
void wait_ms(U32 dt)
{
U32 t0,t1;
t0=Get_system_register(AVR32_COUNT);
static const U32 ms=(clk_cpu+999)/1000;
t0&=RDTSC_mask;
for (;dt>0;)
{
t1=Get_system_register(AVR32_COUNT);
t1&=RDTSC_mask;
if (t0>t1)  t1+=RDTSC_mask+1;
if ((t1-t0)>=ms)
{
dt--;
t0+=ms;
t0&=RDTSC_mask;
continue;
}
}
}
//------------------------------------------------------------------------------------------------

更奇怪的是,如果我启动到引导加载程序,然后再次正常重置设备正在正确重置并且固件再次工作(没有任何擦除/编程),但是如果我再次正常重置,重置循环再次发生......

如果我使用BatchISP(翻转)将.userpageNVRAM重新编程回原始状态,芯片将再次正常工作。

所以最后的问题:

  1. NVRAM用户页面中的哪些地址导致这种情况或应该保留/避免更改?

    我知道最后 8 个字节是引导加载程序配置。我怀疑有问题的地址是前 16 个字节。.userpage应用于用户数据,不包含保险丝。

  2. 发生了什么事情?

    是某种看门狗什么的?我以为那些是存储在其他地方的保险丝。我在数据表中没有看到任何内容。

这里是原始.userpage的十六进制:

:020000048080FA
:10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:10008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
:10009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70
:1000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
:1000B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50
:1000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
:1000D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30
:1000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
:1000F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10
:10010000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
:10011000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF
:10012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF
:10013000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF
:10014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF
:10015000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF
:10016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F
:10017000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F
:10018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
:10019000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F
:1001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F
:1001B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F
:1001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F
:1001D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F
:1001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F
:1001F000FFFFFFFFFFFFFFFF929E0B79E11EFFD77E
:00000001FF

我使用这些(翻转命令)来获取和恢复它:

Batchisp -device AT32UC3L0256 -hardware RS232 -port COM1 -baudrate 115200 -operation memory user read savebuffer cfg_userpage.hex hex386 start reset 0
Batchisp -device AT32UC3L0256 -hardware RS232 -port COM1 -baudrate 115200 -operation onfail abort memory user loadbuffer cfg_userpage.hex program start reset 0

有问题的引导加载程序是USART版本:1.0.2 具有此行为的固件使用PLL,TC,GPIO,PWMA,ADC 模块,但在任何 ISR 和/或 ADC、PWMA、TC 使用之前进行复位。

[编辑1] 看门狗

据此,NVRAM.userpage的第一个词是看门狗的保险丝,它可以解释在将数据修复为原始值并禁用重置停止WDTms重置。但是现在不是引导程序,而是启动引导加载程序,所以仍然有一些可疑的东西。引导加载程序引脚选择在最后 8 个字节中

我还查看了 USART 引导加载程序版本:1.0.2 源代码,发现他们使用的是FLASHC而不是FLASHCDW并强制使用看门狗引导(这可能会重置其状态并使我的程序以某种方式再次运行)。

[编辑2] 错误隔离

我终于发现问题是由写入 32 字节的最后一个 512 位字引起的.userpage

U32 btldr[2]={0x929E0B79,0xE11EFFD7};
flashcdw_memcpy(&nvram_adr[127],(U32*)&btldr[1]       ,4,false);

这是一个巨大的问题,因为为了正确存储数据,我必须使用擦除,无论如何都会擦除整个页面,并且为了仍然能够正确启动到引导加载程序或我的固件,我必须恢复引导加载程序配置数据:

U32 btldr[2]={0x929E0B79,0xE11EFFD7};
flashcdw_memcpy(&nvram_adr[126],(U32*)&btldr[0]       ,4,false);
flashcdw_memcpy(&nvram_adr[127],(U32*)&btldr[1]       ,4,false);

我需要找到一种解决方法,如何将芯片恢复到功能状态。也许从引导加载程序复制看门狗重置(但这在我的应用程序中会非常有问题甚至有风险),因为它即使没有任何闪烁也能恢复芯片......

所以现在地图:

:020000048080FA
:10000000---WDT--FFFFFFFFFFFFFFFFFFFFFFFF00
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:10008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
:10009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70
:1000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
:1000B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50
:1000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
:1000D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30
:1000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
:1000F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10
:10010000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
:10011000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF
:10012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF
:10013000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF
:10014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF
:10015000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF
:10016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F
:10017000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F
:10018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
:10019000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F
:1001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F
:1001B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F
:1001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F
:1001D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F
:1001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F
:1001F000FFFFFFFFFFFFFFFF------BTLDR-----7E
:00000001FF

[编辑3] 解决方法

我设法成功写入/读取程序内存闪存(与BatchISP和MCU本身一起检查),它也可以正常启动。

在这里,我用以下代码对其进行了测试:

// **** test ****
volatile static U32 *flash_adr=(U32*)(void*)0x00000000;
const U32 buf[4]=
{
0xDEADBEEF,0x00112233,
0xDEADBEEF,0x44556677,
};
volatile static U32 *adr=(U32*)(void*)0x80030000;
flashcdw_memcpy(&adr[0],(U32*)buf,4*4,true ); // erase
flash_adr=(U32*)(void*)0x80000000;
for (U32 i=0;i<0x08000000;i++,flash_adr++)
{
if (flash_adr!=buf)
if (flash_adr[0]==buf[0])
if (flash_adr[1]==buf[1])
if (flash_adr[2]==buf[2])
if (flash_adr[3]==buf[3])
{ break; }
if ((i&0xFFF)==0) gpio_tgl_gpio_pin(_LED);
}

其中flash_adr是找到的内容与buf[]签名匹配的地址...(我在LCD上打印它,所以我看看它是否符合我的预期),它终于:)了。所以我会用这个代替.userpage.

但是,.userpage启动问题修复仍然是悬而未决的问题

在主函数的早期禁用WDT

wdt_disable();

另外,我认为您不需要每次都写整页。 flashc_memcpy 写入需要字节长度,同时保留其他数据不变。

unsigned char buf[AVR32_FLASHCDW_PAGE_SIZE];
void* flash_addr = AVR32_FLASHCDW_USER_PAGE;
memcpy(buf, flash_addr, 512);
// modify data in buf
flashcdw_memcpy(flash_addr, buf,AVR32_FLASHCDW_PAGE_SIZE,TRUE);

或者只是使用

int mydata = 123;
int *nvmydata=AVR32_FLASHCDW_USER_PAGE + 16 // offset
flashcdw_memcyp(nvmydata,&mydata,sizeof(mydata),TRUE);

flashcdw_memcpy

volatile void* flashcdw_memcpy(volatile void* dst, const void* src, size_t nbytes, Bool erase)
{
// Use aggregated pointers to have several alignments available for a same address.
UnionCVPtr flash_array_end;
UnionVPtr dest;
UnionCPtr source;
StructCVPtr dest_end;
UnionCVPtr flash_page_source_end;
Bool incomplete_flash_page_end;
Union64 flash_dword;
Bool flash_dword_pending = FALSE;
UnionVPtr tmp;
unsigned int error_status = 0;
unsigned int i, j;
// Reformat arguments.
flash_array_end.u8ptr = AVR32_FLASH + flashcdw_get_flash_size();
dest.u8ptr = dst;
source.u8ptr = src;
dest_end.u8ptr = dest.u8ptr + nbytes;
// If destination is outside flash, go to next flash page if any.
if (dest.u8ptr < AVR32_FLASH)
{
source.u8ptr += AVR32_FLASH - dest.u8ptr;
dest.u8ptr = AVR32_FLASH;
}
else if (flash_array_end.u8ptr <= dest.u8ptr && dest.u8ptr < AVR32_FLASHCDW_USER_PAGE)
{
source.u8ptr += AVR32_FLASHCDW_USER_PAGE - dest.u8ptr;
dest.u8ptr = AVR32_FLASHCDW_USER_PAGE;
}
// If end of destination is outside flash, move it to the end of the previous flash page if any.
if (dest_end.u8ptr > AVR32_FLASHCDW_USER_PAGE + AVR32_FLASHCDW_USER_PAGE_SIZE)
{
dest_end.u8ptr = AVR32_FLASHCDW_USER_PAGE + AVR32_FLASHCDW_USER_PAGE_SIZE;
}
else if (AVR32_FLASHCDW_USER_PAGE >= dest_end.u8ptr && dest_end.u8ptr > flash_array_end.u8ptr)
{
dest_end.u8ptr = flash_array_end.u8ptr;
}
// Align each end of destination pointer with its natural boundary.
dest_end.u16ptr = (U16*)Align_down((U32)dest_end.u8ptr, sizeof(U16));
dest_end.u32ptr = (U32*)Align_down((U32)dest_end.u16ptr, sizeof(U32));
dest_end.u64ptr = (U64*)Align_down((U32)dest_end.u32ptr, sizeof(U64));
// While end of destination is not reached...
while (dest.u8ptr < dest_end.u8ptr)
{
// Clear the page buffer in order to prepare data for a flash page write.
flashcdw_clear_page_buffer();
error_status |= flashcdw_error_status;
// Determine where the source data will end in the current flash page.
flash_page_source_end.u64ptr =
(U64*)min((U32)dest_end.u64ptr,
Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE) + AVR32_FLASHCDW_PAGE_SIZE);
// Determine if the current destination page has an incomplete end.
incomplete_flash_page_end = (Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE) >=
Align_down((U32)dest_end.u8ptr, AVR32_FLASHCDW_PAGE_SIZE));
// If destination does not point to the beginning of the current flash page...
if (!Test_align((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE))
{
// Fill the beginning of the page buffer with the current flash page data.
// This is required by the hardware, even if page erase is not requested,
// in order to be able to write successfully to erased parts of flash
// pages that have already been written to.
for (tmp.u8ptr = (U8*)Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE);
tmp.u64ptr < (U64*)Align_down((U32)dest.u8ptr, sizeof(U64));
tmp.u64ptr++)
{
* tmp.u32ptr = *tmp.u32ptr;
* (tmp.u32ptr + 1) = *(tmp.u32ptr + 1);
}
// If destination is not 64-bit aligned...
if (!Test_align((U32)dest.u8ptr, sizeof(U64)))
{
// Fill the beginning of the flash double-word buffer with the current
// flash page data.
// This is required by the hardware, even if page erase is not
// requested, in order to be able to write successfully to erased parts
// of flash pages that have already been written to.
for (i = 0; i < Get_align((U32)dest.u8ptr, sizeof(U64)); i++)
flash_dword.u8[i] = *tmp.u8ptr++;
// Fill the end of the flash double-word buffer with the source data.
for (; i < sizeof(U64); i++)
flash_dword.u8[i] = *source.u8ptr++;
// Align the destination pointer with its 64-bit boundary.
dest.u64ptr = (U64*)Align_down((U32)dest.u8ptr, sizeof(U64));
// If the current destination double-word is not the last one...
if (dest.u64ptr < dest_end.u64ptr)
{
// Write the flash double-word buffer to the page buffer.
*dest.u32ptr++ = flash_dword.u32[0];
*dest.u32ptr++ = flash_dword.u32[1];
}
// If the current destination double-word is the last one, the flash
// double-word buffer must be kept for later.
else flash_dword_pending = TRUE;
}
}
// Read the source data with the maximal possible alignment and write it to
// the page buffer with 64-bit alignment.
switch (Get_align((U32)source.u8ptr, sizeof(U32)))
{
case 0:
for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
{
*dest.u32ptr++ = *source.u32ptr++;
*dest.u32ptr++ = *source.u32ptr++;
}
break;
case sizeof(U16) :
for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
{
for (j = 0; j < sizeof(U64) / sizeof(U16); j++) flash_dword.u16[j] = *source.u16ptr++;
* dest.u32ptr++ = flash_dword.u32[0];
* dest.u32ptr++ = flash_dword.u32[1];
}
break;
default:
for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
{
for (j = 0; j < sizeof(U64); j++) flash_dword.u8[j] = *source.u8ptr++;
dest.u32ptr++ = flash_dword.u32[0];
dest.u32ptr++ = flash_dword.u32[1];
}
}
// If the current destination page has an incomplete end...
if (incomplete_flash_page_end)
{
// If the flash double-word buffer is in use, do not initialize it.
if (flash_dword_pending) i = Get_align((U32)dest_end.u8ptr, sizeof(U64));
// If the flash double-word buffer is free...
else
{
// Fill the beginning of the flash double-word buffer with the source data.
for (i = 0; i < Get_align((U32)dest_end.u8ptr, sizeof(U64)); i++)
flash_dword.u8[i] = *source.u8ptr++;
}
// This is required by the hardware, even if page erase is not requested,
// in order to be able to write successfully to erased parts of flash
// pages that have already been written to.
{
tmp.u8ptr = (volatile U8*)dest_end.u8ptr;
// If end of destination is not 64-bit aligned...
if (!Test_align((U32)dest_end.u8ptr, sizeof(U64)))
{
// Fill the end of the flash double-word buffer with the current flash page data.
for (; i < sizeof(U64); i++)
flash_dword.u8[i] = *tmp.u8ptr++;
// Write the flash double-word buffer to the page buffer.
* dest.u32ptr++ = flash_dword.u32[0];
* dest.u32ptr++ = flash_dword.u32[1];
}
// Fill the end of the page buffer with the current flash page data.
for (; !Test_align((U32)tmp.u64ptr, AVR32_FLASHCDW_PAGE_SIZE); tmp.u64ptr++)
{
tmp.u32ptr = *tmp.u32ptr;
* (tmp.u32ptr + 1) = *(tmp.u32ptr + 1);
}
}
}
// If the current flash page is in the flash array...
if (dest.u8ptr <= AVR32_FLASHCDW_USER_PAGE)
{
// Erase the current page if requested and write it from the page buffer.
if (erase)
{
flashcdw_erase_page(-1, FALSE);
error_status |= flashcdw_error_status;
}
flashcdw_write_page(-1);
error_status |= flashcdw_error_status;
// If the end of the flash array is reached, go to the User page.
if (dest.u8ptr >= flash_array_end.u8ptr)
{
source.u8ptr += AVR32_FLASHCDW_USER_PAGE - dest.u8ptr;
dest.u8ptr = AVR32_FLASHCDW_USER_PAGE;
}
}
// If the current flash page is the User page...
else
{
// Erase the User page if requested and write it from the page buffer.
if (erase)
{
flashcdw_erase_user_page(FALSE);
error_status |= flashcdw_error_status;
}
flashcdw_write_user_page();
error_status |= flashcdw_error_status;
}
}
// Update the FLASHC error status.
flashcdw_error_status = error_status;
// Return the initial destination pointer as the standard memcpy function does.
return dst;
}

好的,这是 Atmel/Microchip 的官方分辨率:

建议的解决方案:
看起来正在恢复的DFU单词没有 有引导标志 + CRC 更改。对于客户的应用 用户页面数据不是恒定的,最好是闪烁而不是闪烁 非易失性存储。

所以我的理解是这是一个硬件错误(在像 AT32UC3L0256 这样的新芯片上),不能变通......除了使用不同的非易失性存储器,如Flash作为程序(就像我最初所做的那样)。

[edit1] Atmel/Microchip刚刚确认了它绝对的硬件错误