XMEGA64A3U - LPM reads nonsensical calibration values under certain conditions

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

 

I have an ATXMega64A3U, and I want to read some calibration values in software (DAC and ADC calibration).

 

The code I have works fine if the appliation starts up directly (no flashloader --> fuse bits set to "application reset"). However, if I start the application through a flashloader (which is the default case, unless I debug my code), the LPM instruction reads nonsensical values from the calibration row. Those nonsensical values are always the same for each Byte I read, and they are always consistent. I also get the same rubbish if I step-by-step debug through my code - I can accept that and blame it on some timing issues. But I should get the correct values from the NVM controller, regardless if the application got started directly (reset vector = application start) or through a flashloader (which just does a JMP 0x0000) - right?

 

Is there anything that could cause the NVM to behave this way?

 

Here's the code I use to read one calibration byte (ASM, the rest of my application is C):

 

#include <avr/io.h>

.global	NVM_fn_ReadByte
.type	NVM_fn_ReadByte, @function
NVM_fn_ReadByte:
	/* With avr-gcc: u8NVM_CMD in r24, u16Address in r22:r23 (LSB:MSB). Return value (uint8_t) in r24. */
	push r0
	in r0, SREG /* Remember SREG */
	cli /* Disable interrupts */
	movw ZL, r22 /* Copy u16Address to Z-pointer */
	sts NVM_CMD, r24 /* Copy CMD byte to NVM_CMD register */
	lpm r24, Z
        sts NVM_CMD, r1 /* Set NVM_CMD to zero (important!) */
	out SREG, r0 /* Restore SREG */
	pop r0
	ret

Here's the header, which I include in any C file which uses this function (pretty trivial):

 #include <stdint.h>

 /* Read one byte from NVM. */
 uint8_t NVM_fn_ReadByte(uint8_t u8NVM_CMD, uint16_t u16Address);

And a snippet, where I call the calibration values for both DAC channels on the chip:

	/* Load DAC calibration values */
	DACB_CH0OFFSETCAL = NVM_fn_ReadByte(NVM_CMD_READ_CALIB_ROW_gc, 0x0032);
	DACB_CH0GAINCAL = NVM_fn_ReadByte(NVM_CMD_READ_CALIB_ROW_gc, 0x0033);
	DACB_CH1OFFSETCAL = NVM_fn_ReadByte(NVM_CMD_READ_CALIB_ROW_gc, 0x0036);
	DACB_CH1GAINCAL = NVM_fn_ReadByte(NVM_CMD_READ_CALIB_ROW_gc, 0x0037);

 

Any ideas?

 

Thanks a lot!

Ben

This topic has a solution.
Last Edited: Wed. Jan 19, 2022 - 03:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

By "flashloader" do you mean "bootloader"? is it leaving the chip in a "none default" state such as having timers running (even interrupting) or something like that?

 

Most bootloaders end with a "chip reset" to get all peripherals back to the default/power on state. The startup code of the bootloader then checks the CPU status and if it says "deliberate reset" then it knows to start the application. In the old days (mega) a chip would be reset by enabling the watchdog then letting it time out. But Xmega have a deliberate "reset the CPU now" register/bit. So it is a bootloader and it's not resetting the chip do you think it should?

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is my guess to what's happening. The bootloader code is beyond the 64k boundary and at some point is using the Z-pointer to reference data in that upper range so has to set the RAMPZ register non-zero to address it. Since your asm code is only setting ZH:ZL you're reading garbage from higher memory range. Set RAMPZ to zero in your asm routine and it should work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for your replies! I'll check the RAMPZ register and report back.

 

@clawson: I like the idea of resetting the chip and then checking for conditions on which to decide whether or not to start the application. You mean the Reset STATUS flag "SRF" (Software Reset Flag), right? That would be an option, I hope I can make the time to do that.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Yep, it was the RAMPZ register, blasted thing. It was set to 0x01; I set it to zero and it works. So simple, could/should have occurred to me, too...

 

Thanks, balisong42!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


merend07 wrote:
@clawson: I like the idea of resetting the chip and then checking for conditions on which to decide whether or not to start the application. You mean the Reset STATUS flag "SRF" (Software Reset Flag), right? That would be an option, I hope I can make the time to do that.
Looking at the "Xmega A manual" I find:

 

 

So to reset the Xmega you would use a CCP write to set the SWRST bit in the CTRL register then in your early morning start up code you would read that STATUS register and if SRF is set it tells you that the CPU just reset deliberately so the bootloader itself must have asked for this - in which case it should fall through the normal bootloading stuff and just go straight on to run the app now that the CPU and peripherals are (almost) all set to a normal power on state.

 

Of course you still face the RAMPZ thing if the CPU's reset has restarted into "high" bootloader code - so this would not have "solved" the specific problem you have been observing - but it's a "nice thing" for a bootloader to do as it almost guarantees the app is not aware of anything the bootloader might previously have been up to (esp if it's just been through the whole comms/code replacement sequemce).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, yes this is what I had in mind. However, I see some risk that, if the application itself issues a soft reset for any reason, the bootloader would be unable to distinguish between a soft reset issued by the app or the BL. Probably not really an issue, since, if a software update is necessary, the uC must be reset externally anyway or through a POR.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

merend07 wrote:
if the application itself issues a soft reset for any reason,
That, indeed is the loophole. If this is a possibility then maybe use some kind of flag in RAM (EEPROM even?) where the bootloader can lag to itself "it's me asking for this reset"

 

(the same thing happens if both want to use the watchdog - though it's probably unwise for a bootloader to mess with it!)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Maybe I'll use one of the battery backup registers to let the BL know what to do. I don't have an external backup battery, but as long as ext power remains uninterrupted, the registers should remain intact, right?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

merend07 wrote:
the registers should remain intact, right?
Yeah that is actually true of SRAM too (well up to the point where C startup code might start to re-initialise some locations). So you can put some stuff that runs "early" within the C start up that gets to the flag before anything might change it or, in GCC, there is the concept of a ".noinit" region in RAM which is guaranteed to hold pervious contents and won't be changed as the C code starts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

clawson wrote:
 in GCC, there is the concept of a ".noinit" region in RAM which is guaranteed to hold pervious contents and won't be changed as the C code starts.

Indeed - Documentation is here: https://www.nongnu.org/avr-libc/user-manual/mem_sections.html

 

Most embedded compilers have something similar - to serve this very purpose.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks! The bootloader has been written by someone else a long time ago, and with the IAR AVR C compiler. But I figured out how to declare a no-init variable there, it's pretty straightforward:

volatile __no_init uint32_t u32StartupState;

I made no modifications to the linker script, the linker seems to have decided itself where to put the variable. Not a problem though.

 

Now I have to figure out how to get the compiler to execute the check of the u32StartupState variable first thing after the reset vector. It does some init stuff before jumping to main(), which is what modifies RAMPZ. But I guess that's just a matter of reading documentation.