Porting Arduboy2 library to SAMD

No problem, it’s my 7yo daughter that it needs to impress. Was a personal project name, not intended to lead discussion of future Arduino-based console names. Not sure when that other website popped up but I’ve been calling this project that name since autumn 2018. If others want to call their Arduboy-compatible designs the same or something else, it’s theirs to choose (within the constraints of the legal protections surrounding the Arduboy name).

I haven’t tried inverting the screen, as having never used it before, it looks fine to me. And the other reason not to invert is because the menu text currently appears as black on white, so inversion would look bizarre to me on those screens.

@Pharap if I have commented out the audio functions in Arduboy2 is there any reason you can think of why a game with sounds wouldn’t compile?

Just one bug encountered so far in Minesweeper - when I try and scroll down on the credits, the device freezes. I haven’t implemented all the paintScreen() versions and I haven’t looked at anything to do with scrolling. Maybe compiling a game with scrolling maps etc might highlight if this is a repeatable issue.

If that’s the aim, I think you’d be better off picking something she’s interested in and naming it after that.
I’d give an example, but I’m not exactly au fait with the interests of seven year old girls.

The best I can think of offhand is either “Arduheart” (because of the heart shape you cut out of the board),
or “Arduspark” (plucked from thin air).

It’s supposed to be white on black though.

Have a look at the original, either on an Arduboy or on the emulator,
and compare it to what’s currently running.
The contrast between the two is pretty stark.

In particular the graphics look off because they were designed to be the inverse of what they currently are.

It’s like seeing the world in negative vision…
11729_2 14172_2

Because most games that use audio use ArduboyPlaytune, ArduboyTones or ATMLib.

If you can find one that’s only using ArduboyBeep then it should work,
but ArduboyBeep isn’t as widely used because it’s a more recent addition and only produces simple sounds.
(From what I gather it’s more or less a cheaper alternative to the Arduino tone function, but I could be wrong, I don’t often dabble in sound because I have zero musical talent.)

The moment you press the down button?
Even if you press the button and hold it down without releasing it?
And only on the credits screen, not on any other screen?

The more specific you are the better chance I have of guessing the problem.

That shouldn’t matter, my code should only be using the normal display,
so only the paintScreen that draws the RAM buffer should be in use.

There aren’t any built-in scrolling functions,
this is something I’m doing manually within the credits code.

So, unicorncuteyfluff it is! You’re right though, I have a 5yo son and he’ll get one too and need to think the name isn’t awful in a year or two. Good thing the HW is on rev 0.1! I made them some little wheeled robots, which were named oomoo by my 4yo at the time.

when I attempt to scroll all the way to the bottom, it freezes on this, regardless of whether I use multiple down button presses or press and hold down. Can’t get it to freeze with D pad in Play, Themes or Stats modes. Perhaps I need to look at safe mode.

1 Like

Paint it black, adorn it with skulls and other gothic symbols and I could live with it as a symbol of irony. :P

That’s not actually that bad.
Being a nonsense word it’s certainly unique and memorable at least.
(And it’s a palindrome, so you’d probably please symmetrophiles. :P)

Hrm, interesting.
I could be wrong, but I think that’s the exact point at which the logo should start to move offscreen,
which implies it could be a problem with Sprites::drawOverwrite and negative coordinates.

There are some other possible explanations, but that seems the most obvious.

Or the fact that I commented out all the asm here:

case SPRITE_PLUS_MASK:
	{
	// TODO: Implement drawPlusMask mode functionality

?

Probably not, that should only affect Spritess::drawPlusMask,
but if you aren’t using the using Sprites = SpritesB; trick then I can think of a possible explanation.

For now, here’s a test program:

#include <Arduboy2.h>

Arduboy2 arduboy;

// White 8x8 square
const uint8_t image[] PROGMEM =
{
	8, 8,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};

int y = 8;

void printMessage()
{
	arduboy.println(F("This message"));
	arduboy.println(F("should only last"));
	arduboy.println(F("for one frame"));
}

void printSuccess()
{
	arduboy.println(F("If you're reading"));
	arduboy.println(F("this message"));
	arduboy.println(F("then negatives"));
	arduboy.println(F("aren't the issue"));
}

void setup()
{
	arduboy.begin();
}

void loop()
{
	if(!arduboy.nextFrame())
		return;
		
	if(y > -8)
		--y;
		
	arduboy.clear();
	
	if(y == 0)
		printMessage();
	
	if(y <= -8)
		printSuccess();
	
	Sprites::drawOverwrite(0, y, image, 0);
	
	arduboy.display();
}

Save this as something with an .ino extension and run it on your SAMDuboy.
If you can read the “This message should only last for one frame” message then the problem is with Sprites::drawOverwrite and negative coordinates.
If the program continues to “If you’re reading this message then negatives aren’t the issue” then we need to look elsewhere.


I have to head off for now, I’ll be back later to investigate this more,
but if it is negatives causing the problem then I think I know what the heart of the problem is.
If you have the time, have a read of this.

1 Like


Sadly not worked but I’ll have a read. Thanks


EDIT: read the link, think I edited Sprites/SpritesB suitably and tried to upload…
…seems like the SAMD gets locked before the PC can take control and upload any new code. Can’t recover it after trying multiple boards and ports. Think I’ll have to get the ICE programmer out later and try that or reflashing the bootloader. Note to self, always add a delay in before the test code, to allow normal operation where a programmer could kick in.

Aside from the slight mishap in regards to not being able to upload anything else,
this at least indicates that the suspicion about negative values is plausible.

Have you tried ‘flashlight’ (*cough* torch *cough*) mode?
(I.e. holding the up button while booting.)
Theoretically that should work for your library port.
It certainly looks like all the right bits are in there at least.

1 Like

didn’t try it with the test code but it works with Minesweeper uploaded. Well, on my display with current settings I get a “flashdark”!

I got the Atmel ICE out and it snapped the board out of the locked situation. I added delay()s in too, and then uploaded the test code with the new &0xFFFFs liberally scattered wherever I saw ofs + WIDTH in SpritesB.cpp and Sprites.cpp

After some time (because delay()s in loop()) the image disappeared off the top of the screen and I was shown the victory message.


And then loaded Minesweeper again, to find out that I can scroll all the way to the bottom of Credits and back up again:

Do you think we need to handle negative integer overflow issues for the x direction too @Pharap?


EDIT: BTW, I have committed the fix in my fork of the Github repo, so anyone else can start to test if they have a SAMD21 board wired up the same as this one and @MLXXXp’s Sparkfun-based one.

1 Like

If it gets stuck again

Nice to know that’s an option.

Now that you’ve fixed the problem, you may want to reintroduce it and test to see if ‘flashdark’ mode would have worked.
If you know that it works then you won’t have to be quite so cautious in future (or quite to quick to bring out ‘the big guns’),
and you won’t have to litter code with delays.

I’m not sure whether or not it would have been an issue for x as well,
but if it was then you should have just killed two birds with one stone.

(I’m presently wondering whether & 0xFFFF or static_cast<uint16_t> would be a better solution,
but I’m probably splitting hairs because they’ll probably result in the same machine code. If they don’t I’ll surprised and interested to know why.)


I’m actually glad this happened in a way, because I remember that adafruit’s port didn’t seem to have this issue (or at least doesn’t seem to feature a correction for this issue),
so at one point I thought maybe I’d imagined it,
but if we’re definitely getting this issue here on a SAMD then I’m beginning to wonder if adafruit’s port might still have this bug and nobody’s noticed for some reason.

I was going to bring it up back when they borrowed my port of the sprites plus mask code,
but I either forgot or something caused me to assume it wasn’t a problem.

I’d try to contact them, but I don’t actually have access to a PyGamer to test it and I’d feel a bit daft raising an issue about a potential issue that I have no way of verifying.


Edit:
I’ve just finished removing the stack size checking code from the official version of Minesweeper and altering the checksum code to use the Arduboy’s screen buffer instead.

I won’t release a new version just yet because I hope to schedule the generateRandomSeed issue sometime soon,
as well as maybe solving one or two of the other issues if I have time.

(I’d also like to check memory usage statistics when I have chance.)

1 Like

I haven’t looked at this in detail but the SAMD51 has a feature call SmartEEPROM, which may make emulating AVR style EEPROM easier than with a SAMD21. See section 25.6.8 of the SAMD51 datasheet.

Otherwise, you may have to do EEPROM the way it’s done for the ESP8266, which requires a “commit” after making changes that you would like saved to EEPROM. This would be unfortunate, though, because it would involve making changes to existing Arduboy sketches. It would probably be possible to make those changes backwards compatible with the Arduboy by defining new Arduboy2 EEPROM functions that were compatible with both AVR and SAMD. I’m not opposed to adding such EEPROM functions to the Arduboy2 library.


Another benefit of using the SAMD51 is its true random number generator (section 44 of the datasheet). It could be used by generateRandomSeed() to seed psudo-random generators or for a new function that would return a true random number.

2 Likes

Annoyingly, if Arduino had foreseen the existance of devices without EEPROM they could have preempted this by adding a commit function that was empty for devices that actually have EEPROM, but could be filled in with a meaningful flush for devices without EEPROM.

Hindsight is both a gift and a curse.

Out of interest, how do you imagine such an API might look?
An Arduboy2EEPROM class?

It may be worth noting that I have found that depending on the circumstances,
directly using avr-libc’s eeprom_ function can sometimes result in smaller code than using Arduino’s EEPROMClass class.

I think the former would certainly be a good implementation for generateRandomSeed,
but the latter seems too CPU specific to be good for general use.

That said, I can think of an argument for it as a platform specific case of a more general function.

An Arduboy2::random (or Arduboy2Base::random/Arduboy2Core::random) could serve as a wrapper for the AVR-specific random,
thus allowing other implementations that lack a parameterless random (because it’s defined in avr-libc rather than Arduino) to provide that interface in a more accessible way.

It would be backwards compatible, though it faces the obstacle of uptake (i.e. the likelihood that people would bother to go back to edit their old programs to use the new function over the AVR-specific function).

Really it would be better if Arduino could provide a similar solution at their end,
but if such a thing were proposed it could take quite some time before it were adopted,
assuming they actually accepted the proposal.

Though I admit it could be argued that for the sake of Arduboy2,
it might be easier to conditionally introduce random in the Arduboy2Core.h header.

Not looked at the SAMD51 link yet but wondered how hard it would be on SAMD21.


Does this look like it could be conditionally incorporated into Arduboy2 with some conditional #ifdef (ARDUBOY2_SAMD) and #includes ?


EDIT: so it looks like the link above was developed and eventually merged with cmagli’s FlashStorage library. Example sketch code here, for consideration about how Arduboy2 eeprom functions might need to be adapted (conditional #ifdef ARDUBOY_SAMD and the additonal commit?).

It could be incorporated, but it suffers from the same problem:
You have to give a command to ‘flush’ the data to where it’s saved.

With EEPROM, any write is immediately stored - it’s kind of like RAM that doesn’t get erased when the device is switched off.

With flash memory, as proposed in this comment, the data stored in the 1KB array of bytes (stored in a struct) must be explicitly written to flash in order to save the data.

Similarly with an SD card the data would have to be explicitly flushed.
(An SD card is basically flash memory with wear levelling and a file system.)


(Note: the typedef struct present in the example code is completely superfluous in C++.)

1 Like

I haven’t given it much thought. I just proposed it as a way of making it easier for implementing SAMD21 “non-volatile” storage in a way that would still compile and work with an Arduboy once any sketch was modified or written to use such an API.

It could be a new class or just functions added to the Arduboy2Base or Arduboy2Core class. What’s important is that it works well with AVR, SAMD, and hopefully anticipates other systems. It might just be a shell around, or workalike of, the SAMD/Zero functions that @SimonMerrett has pointed out.

It may be best to just provide equivalents to all the AVR EEPROM functions and additionally add commit and possibly begin and end functions like the ESP8266. Given the way most sketches use EEPROM, by saving and restoring a block of data, it would then just be a matter of a one to one change of the names of the existing EEPROM functions and adding a commit at the end of the “save block” code (and possibly adding a begin in setup() ).

1 Like

Given the way most sketches make use of EEPROM, it may be best to allocate a fixed size total EEPROM area (1K to match the Arduboy, or more). Rather than an ESP8266 style begin(size), we would provide begin(address, size), where address is the start of the EEPROM area (within the total) that the sketch wishes to use, and size is the number of bytes in that area.

(The term offset is more appropriate than address but Arduino uses address for its EEPROM functions.)

I’m certainly more inclined towards introducing a new class,
lest the three main classes be treated as a dumping ground for all functions.

I think a shell is mostly plausible.
init and commit can be left blank for an AVR implementation,
however the isValid function may not be replicable on AVR.

I’m not sure what these would do.
Are they the same as the begin and end on the Arduino AVR implementation of EEPROMClass,
in which they act as iterator providers?

Would this be solely for first time initialisation (i.e. when no save data already exist for the game) or on every initialisation of the Arduboy?

If it’s the latter case that’s going to kill the forwards compatibility feature of Minesweeper’s save system.
(Though I doubt it will be missed, and the backwards compatibility feature should remain intact.)

It depends on how you look at it.

An address is simply an offset from the origin (zero),
and arguably an offset from a base address could be called a ‘relative address’.
Granted people usually think of addresses as being absolute and offsets being relative,
but that’s not a hard-and-fast rule.


I think the minimal API would be:

class Arduboy2EEPROM
{
public:
	static uint8_t read(uintptr_t address);
	static void write(uintptr_t address, uint8_t value);
	static void update(uintptr_t address, uint8_t value);
	
	// One of these two overloads
	static void initialise();
	static void initialise(uintptr_t address, size_t size);
	
	// Or 'commit' if you'd rather
	static void flush();
};

Whilst an extended API would be more like:

class Arduboy2EEPROM
{
public:	
	// One of these two overloads
	static void initialise();
	static void initialise(uintptr_t address, size_t size);
	
	// Or 'commit' if you'd rather
	static void flush();
	
	// These would require both an 'EEPROMPointer'
	// and an 'EEPROMReference' class,
	// which is completely doable, but might be overkill
	static Iterator begin();
	static Iterator end();
	
	// Possibly returns 1024, possibly contexpr?
	static size_t capacity();
		
	// To make use of eeprom_*_byte
	static uint8_t read8(uintptr_t address);
	static void write8(uintptr_t address, uint8_t value);
	static void update8(uintptr_t address, uint8_t value);
	
	// To make use of eeprom_*_word
	static uint16_t read16(uintptr_t address);
	static void write16(uintptr_t address, uint16_t value);
	static void update16(uintptr_t address, uint16_t value);
	
	// To make use of eeprom_*_dword
	static uint32_t read32(uintptr_t address);
	static void write32(uintptr_t address, uint32_t value);
	static void update32(uintptr_t address, uint32_t value);
	
	// To make use of eeprom_*_float
	static float readFloat(uintptr_t address);
	static void writeFloat(uintptr_t address, float value);
	static void updateFloat(uintptr_t address, float value);
	
	// Base implementations
	template< typename Type >
	static void read(uintptr_t address, Type & object)
	{
		#if defined(__AVR__)
		eeprom_read_block(&object, reinterpret_cast<const void *>(address), sizeof(Type));
		#elif defined(__SAMD__)
		
		#endif
	}
	
	template< typename Type >
	static void write(uintptr_t address, const Type & object)
	{
		#if defined(__AVR__)
		eeprom_write_block(&object, reinterpret_cast<void *>(address), sizeof(Type));
		#elif defined(__SAMD__)
		
		#endif
	}
	
	template< typename Type >
	static void update(uintptr_t address, const Type & object)
	{
		#if defined(__AVR__)
		eeprom_update_block(&object, reinterpret_cast<void *>(address), sizeof(Type));
		#elif defined(__SAMD__)
		
		#endif
	}
	
	// For implementing array copying
	template< typename Type >
	static void read(uintptr_t address, Type * object, size_t count)
	{
		#if defined(__AVR__)
		eeprom_read_block(object, reinterpret_cast<const void *>(address), sizeof(Type) * count);
		#elif defined(__SAMD__)
		
		#endif
	}
	
	template< typename Type >
	static void write(uintptr_t address, const Type * object, size_t count)
	{
		#if defined(__AVR__)
		eeprom_write_block(object, reinterpret_cast<void *>(address), sizeof(Type) * count);
		#elif defined(__SAMD__)
		
		#endif
	}
	
	template< typename Type >
	static void update(uintptr_t address, const Type * object, size_t count)
	{
		#if defined(__AVR__)
		eeprom_update_block(object, reinterpret_cast<void *>(address), sizeof(Type) * count);
		#elif defined(__SAMD__)
		
		#endif
	}
	
	// To make array copying easier
	template< typename Type, size_t size >
	static void read(uintptr_t address, Type (&array)[size])
	{
		read(address, &array[0], size);
	}
	
	template< typename Type, size_t size >
	static void write(uintptr_t address, const Type (&array)[size])
	{
		write(address, &array[0], size);
	}
	
	template< typename Type, size_t size >
	static void update(uintptr_t address, const Type (&array)[size])
	{
		update(address, &array[0], size);
	}

	// To make array copying easier
	template< typename Type, size_t size >
	static void read(uintptr_t address, Type (&array)[size], size_t count);
	{
		// assert(size >= count);
		read(address, &array[0], count);
	}
	
	template< typename Type, size_t size >
	static void write(uintptr_t address, const Type (&array)[size], size_t count);
	{
		// assert(size >= count);
		write(address, &array[0], count);
	}
	
	template< typename Type, size_t size >
	static void update(uintptr_t address, const Type (&array)[size], size_t count);
	{
		// assert(size >= count);
		update(address, &array[0], count);
	}

	// To make array copying easier
	template< typename Type, size_t size >
	static void read(uintptr_t address, Type (&array)[size], size_t index, size_t count);
	{
		// assert(size >= count);
		// assert(index < size);
		// assert((index + count) < size);
		read(address, &array[index], count);
	}
	
	template< typename Type, size_t size >
	static void write(uintptr_t address, const Type (&array)[size], size_t index, size_t count);
	{
		// assert(size >= count);
		// assert(index < size);
		// assert((index + count) < size);
		write(address, &array[index], count);
	}
	
	template< typename Type, size_t size >
	static void update(uintptr_t address, const Type (&array)[size], size_t index, size_t count);
	{
		// assert(size >= count);
		// assert(index < size);
		// assert((index + count) < size);
		update(address, &array[index], count);
	}
};
``
1 Like

Assuming we match the AVR EEPROM functions, then maybe have a subclass, like we do with the audio subclass. If it’s instantiated as EEPROM and the functions have the same names as the AVR ones, then to modify and existing sketch you just have to add arduboy. to the beginning of each EEPROM function:

EEPROM.write() -> arduboy.EEPROM.write()

I don’t know if the Arduino EEPROM[] as an array capability fits into this scheme but I don’t think many (if any) existing sketches use this.

I assumed begin() allocates a “shadow” area in RAM and copies the current contents of EEPROM to it. All EEPROM functions manipulate this RAM area, rather than actual EEPROM. commit() updates the actual EEPROM with the RAM contents. end() frees the RAM area so it can be used for something else.

This would be for SAMD. For AVR, begin(), end() and commit() would probably be NOPs.

2 Likes

You can also use part of the actual flash as EEPROM. Or if there is guaranteed to be an SD card or other media you could source the long-term storage there - with the advantage being that each game could see it’s own 1kb of EEPROM storage without messing with any others.

1 Like

Thank you, yes I think we’re onto that trick but the others are now working out how best to present something like that to the rest of the libraries/files (I think). Go library architects, go!

1 Like