Questions about a menu in an SD project


(Jon Raymond) #1

Edit for context - This thread was split from the Arduboy Assistant post. It was an initial question about the menu code that grew from there.

First I would like to thank @crait for sharing his code for this. It’s been a great learning tool and a really helpful utility.

I do have one question that has been puzzling me. I’m using the this menu system as a base for item selection. How or what is the best way to pass the menu items from a String array into the main menu items?

arduboy.print(F(“Button Test\n Character Test\n EEPROM Manager\n LED Test\n Screen Test\n Sound Test\n USB Transfer Test\n About”));

Is it best to read each string in the array and combine them by using the strcat() function with null characters in-between and then arduboy.print them?


Arduboy Assistant - Handy Arduboy Management Tool
(Pharap) #2

What do you mean by this?

Definitely not. Avoid strcat whenever you can, it uses a lot of progmem and resources.

If the strings are all predetermined then you should keep them in an array and use the __FlashStringHelper trick.

I know you’ve already seen my EquipmentMenuSystem repo, but if you haven’t seen the thread where I originally posted it, you may want to read that:

I discussed the __FlashStringHelper trick in this comment from that thread:

Here’s a simple really quickly written example of displaying a menu:

#pragma once

#include <Arduboy2.h>
#include <stddef.h>
#include <stdint.h>
#include <avr/pgmspace.h>

const char menuString0[] PROGMEM = "Button Test";
const char menuString1[] PROGMEM = "Character Test";
// etc

// There's a better way than this,
// but it would take some explaining
constexpr size_t menuStringsCount = 2;

// An array of 'const char *'
const char * const menuStrings[menuStringsCount] PROGMEM =
{
	menuString0,
	menuString1,
	// etc
};

using FlashString = const __FlashStringHelper *;

// Don't worry about how or why this works for now
constexpr FlashString asFlashString(const void * flashString)
{
	return reinterpret_cast<FlashString>(flashString);
}

inline void displayMenu(Arduboy2 & arduboy)
{
	for(uint8_t index = 0; index < menuStringsCount; ++index)
	{
		// Read the pointer
		const void * pointer = pgm_read_ptr(&menuStrings[index]);
		
		// Convert the pointer to a FlashString
		const FlashString string = asFlashString(pointer);
		
		// Print the string
		arduboy.println(string);
	}
}

(It doesn’t have option selecting though.)


You might also want to take a look at the menu drawing code in my Minesweeper game:


(Jon Raymond) #3

Thank you for your very detailed response. Super helpful!

The end goal is to read and sort file names from a SD card (via this method), present the user with a selection menu of the files names and then use the selected file name in a function. Very similar to the Gamebuino Loader program.

The links to the detailed explanation threads are great. I see you explain how pointers work. I’ve been trying to wrap my head around this concept. I’m going to dig into the info you graciously supplied and educate myself.

Thank you again for sharing your knowledge.


(Pharap) #4

No problem.

On an Arduboy? Are you piping the names in over serial?
Or are you using a different Arduino board?
Or maybe a homemade Arduboy with exposed pins?
(If so, you should make a post about it in the homemade category.)

On an Arduboy you’ll be struggling, there’s only 2.5KB of RAM and half of that is taken up by the screen buffer.
(In fact most AVR chips don’t have much RAM to work with.)

The method I demonstrated only works for strings in progmem.
Strings in RAM is another matter.
In particular the __FlashStringHelper trick only works for strings in progmem.

Yeah, pointers are pretty important for this sort of task.

The easiest way to understand pointers is probably this…
Imagine that RAM is just a really large array of bytes.
A pointer is actually a numeric index (specifically a ‘memory address’) into that array (i.e. RAM).
(If you convert a pointer to uintptr_t and then print it, you’ll find out exactly what that numeric index is.)

Although it’s simple to think of pointers as being like numeric indices,
it’s also important to treat them differently to integers.
You should think of them as opaque references to objects in memory or to blocks of memory.
That’s why they’re given special syntax - to symbolise that although they are implemented as plain old integers, they should be treated specially because they do a very different job.

No problem. Any other questions, feel free to ask.

(Nice profile icon by the way. :skull: )


(Holmes) #5

For anyone who isn’t using this kind of code for file storage navigation, I think the biggest take-away is that you almost never want to pass strings along from function to function.

If you have the following menu options in your game:

  1. Use Potion
  2. Use Sword
  3. Use Shield

… you wouldn’t want to pass “Use Sword” to a function. Instead, you’ll want to pass a more simple and less-memory heavy piece of data, which ought to be the index of the menu item. In this case, it would be “2”. The byte used to hold this index is going to be as big as a single letter used in a string and since all strings have a string-terminating character at the end, strings will always be at least 1 byte.

Also, you’re already likely storing that index somewhere! Whenever I make menus, I use an some kind of indicator, whether it’s an arrow or box, to show to the user which option is highlighted. Then, whenever the user selections the option, I have that index available. :slight_smile:


(Jon Raymond) #6

Not currently. I do have a blue Arduboy Kickstarter edition. The bootloader that can flash HEX files from a SD is only for a 328P to my knowledge. The Cathy3k bootloader is available for the 32u4 but IIRC it only loads from a SPI flash chip.

I enjoy design/building hardware and have a fondness AVR’s. I have lot of little gadgets I’ve built but coding has never been my strong point. I’m picking it up slowly.

Correct, the 328P only has 2k but I know what I want to do is possible (Video time stamp). I just have to figure out the software logistics.

That was the best explanation I’ve read yet. Thank you.
The reason I was trying to understand pointers is because I am trying to understand how to construct the below string_list from files names from an SD. This is from the U8g2 Selection List example which does almost exactly what I want to do menu wise.

const char *string_list =
“Altocumulus\n”
“Altostratus\n”
“Cirrocumulus\n”
“Cirrostratus\n”
“Cirrus\n”
“Cumulonimbus\n”
“Cumulus\n”
“Nimbostratus\n”
“Stratocumulus\n”
“Stratus”;

That makes complete sense. Thanks for chiming in. Also, sorry for cluttering up your thread. Maybe a Mod could move this to it’s own thread? Not sure if that is common place here.


(Pharap) #7

If you can print the option to the screen you’re going to have the whole string in memory somwhere anyway, so this is a bit of a non-issue.

The problem with using the index is that then the reaction is bound to the index rather than the actual menu item, so if you reshuffle the menu items then you have to remember to change the code as well.

This is why the menu system in my repo uses function pointers.
It results in more progmem usage than using an index,
but it’s the most flexible option.

In my Minesweeper game I do something between the two,
I have a scoped enumeration that identifies the action to perform,
that way the action is cheap but it also overcomes the issue of having the functionality bound to the index rather than the actual menu item.


So this is for an unrelated project using a different setup?

If so you should have mentioned that sooner, this is an Arduboy site so we always assume people are talking about the Arduboy.
It’s hard to make recommendations without knowing the setup.
For one thing the available pool of APIs will have a big impact on what the code looks like.

(Also technically not being Arduboy related makes this somewhat off-topic.)

Without knowing the specs of the device it’s hard to know how it’s working.
It’s possible that they have a dedicated SD reaching chip or something.

No problem, I’ve had time to refine it.

If you’re more of a hardware person then it might be worth mentioning that one of the potential hardware methods for selecting a byte for read or write is through an N-bit multiplexer (where N is the bit width of the processor’s address size).

So if you think for a 4-to-1 multiplexer you have 2 select lines,
the thing that selects a byte of RAM is essentially the same,
except it has N select lines (in the case of most AVR chips, N is 16).

I don’t know if that’s actually how it works with real hardware,
but that’s the way I discovered when trying to build virtual CPUs in video games that have logic gates.

(Also if none of that makes any sense, don’t worry.)

This code has the advantage of knowing all the text beforehand.
Constructing text ‘on the fly’ is more complicated.

The approach you can take will depend on several factors:

  • What rendering API will you have available?
  • What’s the spec of the system?
    • In particular the amount of RAM, the screen resolution and the CPU speed are going to be important
  • Will the file names all be in 8.3 format?
  • Is there an upper limit on the size of the file names?
  • How much RAM is actually free for use?
    • I.e. not in use by a screen buffer.

(Side note: I think it’s a bit strange that this library is using newlines instead of having multiple text entries.)

I think it’s going to look a bit odd because the first comment will be missing some context,
but it’s probably best to put this in the ‘off topic’ area, so I’ll move it anyway.


(Holmes) #8

There are definitely some benifits to your version of a menu, but if your menu is a little mote primative and you just want to have a 1:1 point to an item like in array it can be a little easier to explain to some folk. :kissing_heart:


(Pharap) #9

Perhaps, but I don’t think the scoped enum version is that much more complicated.

You’re essentially just changing the thing you’re testing from a specific index to a named constant identifying the purpose of the menu item.

I.e. instead of (an approximation):

struct MenuEntry
{
	const char * label;
};

const char label0[] = "Attack";
const char label1[] = "Defend";

MenuEntry menu[] =
{
	{ label0 },
	{ label1 },
};

void drawMenu(uint8_t selected)
{
	for(uint8_t index = 0; index < 2; ++index)
	{
		font.print((index == selected) ? '>' : ' ');
		font.print(menu[index].label);
	}
}

void selectMenuEntry(uint8_t index)
{
	switch(index)
	{
		// Handle "Attack"
		case 0:
			break;
		// Handle "Defend"
		case 1:
			break;
		default:
			break;
	}
}

You end up with (again, an approximation):

enum class MenuAction : uint8_t
{
	Attack,
	Defend,
};

struct MenuEntry
{
	const char * label;
	MenuAction action;
};

const char label0[] = "Attack";
const char label1[] = "Defend";

MenuEntry menu[] =
{
	{ label0, MenuAction::Attack },
	{ label1, MenuAction::Defend },
};

void drawMenu(uint8_t selected)
{
	for(uint8_t index = 0; index < 2; ++index)
	{
		font.print((index == selected) ? '>' : ' ');
		font.print(menu[index].label);
	}
}

void selectMenuEntry(MenuAction menuAction)
{
	switch(menuAction)
	{
		// Handle "Attack"
		case MenuAction::Attack:
			break;
		// Handle "Defend"
		case MenuAction::Defend:
			break;
		default:
			break;
	}
}

It uses a tiny bit more memory (1 byte per entry and a bit of overhead for reading the action),
but it’s a lot clearer and a lot more flexible.
You can reorder both the menu entries and the MenuAction enumerators to your heart’s content and never have to worry about reorganising the code.

The function pointer version is definitely the more complicated approach, but that’s only necessary in certain cases.


(Jon Raymond) #10

Currently it’s for a ATmega328 as it’s the only bootloader that supports loading from a SD that I know of. The end game would be to use it with a modified Cathy3k bootloader on my Arduboy with added SD card slot. This would allow the used of either a standard SD card or a PCB shaped SD card with SPI flash chip.

There are further videos on that channel detailing the hardware as well as the pcb. It use a single 328 running at 16mhz, level shifted sd card, I2C display, boost/charge circuit for the lipo and a rotary encoder. As mentioned hardware is my thing. :slight_smile:

The text wouldn’t change after the initial read from the sd card.
Follow my logic,
In setup

  • Read file name from SD (Files only use 8 upper case characters max)
  • Check to make sure it’s a hex file
  • Remove “.HEX” extension
  • Add file name to a buffer
  • Add new line
  • Repeat until no files remaining or max file limit reached (50ish)
  • Pass buffer to selection menu setup

Does that make sense?


(Pharap) #11

That might be difficult.
I’m sure I remember reading that people tried to add SD card processing code to the bootloader before but couldn’t get it to fit.

I could be wrong.
@Mr.Blinky might know more.
He is probably our best bootloader expert.

What resolution? (It affects the framebuffer size.)

Presumably that produces deltas (small positive or negative changes) rather than solid values (as a potentiometer would)?

How? SPI? Serial?

I still think using newlines is an odd way to manage this.

Will you specifically be using @olikraus’s U8g2 library for displaying the menu?

Overall yes, but there’s still a few important details I need to know.

Is this supposed to be a feature for the bootloader or a regular program?

If it’s supposed to be for the bootloader then that’s going to complicate matters because the bootloader has limited access to resources and you’ll probably have to use new/malloc to avoid eating RAM, which will increase the bootloader size quite a bit.

I can’t really provide any demo code without knowing what libraries you’ll be using, but the basic process would be:

  • Allocate a buffer big enough for 50 8-character strings (remembering an extra byte for the null character or the new line)
  • Iterate through the file names on the SD card
    • If it’s not a hex file, discard it
    • If it’s a hex file, discard the file extension and copy the name into the buffer
  • Sort the entries in the buffer
  • Pass the buffer to the menu handler
  • When the menu handler is finished, remember to deallocate the buffer or you’ll have a massive memory leak (unless the device is about to restart, in which case it’s probably not a problem, but it’s probably best to do it anyway to be safe)

Sorting the entries would be easier if you have a second buffer that stores pointers to the character data, but then the strings would have to be null-terminated which is at odds with the newline approach.


#12

You could also offload the sorting by using an external sorting tool like this

Rather then using variable name lengths seperated by new lines. It would be easier to handle 50 fixed length names of 8 chars stored in a static array. you could use strncmp to compare the name in the SD buffer to the ones already in the array, if it’s smaller then the name at the current position you move all names from current position in array up (you can use memcpy) and when it’s larger the next name is checked. If it’s larger then last entry, the name is stored after the last entry and total used names count is increased.

When a selection has been made to load/burn a game then you can search the name in the directory again to obtain the files start cluster for loading/burning. This saves the trouble of keeping track of the start cluster during sorting

I would recommend using Cathy2K for that which has 2K of freed space. I think it possible to write a FAT32 loader/burner in there in assembly. But if you can also cram in a text menu there with font too I’m not sure.


(Pharap) #13

This is along the lines of what I was thinking (although it will be 8 chars plus the null character of course).
But wouldn’t using static memory allocation in the bootloader eat into what’s available to the actual program?
(I haven’t actually checked, I’m making an assumption.)

If so that’s 450 bytes, which is nearly 1/4 of the entire RAM (ATmega328 has 2KB).

If static memory used by the bootloader doesn’t eat into program RAM then that makes matters a lot easier and my suggestions about new/malloc can be disregarded.

It’s worth trying a for loop too, sometimes for loops eat less memory than memcpy.

This is precisely why I mentioned you, I knew you’d have a much better idea than I would.
(I wonder if we can get you a special ‘bootloader expert’ title?)


#14

the null character isn’t necessary the length 8 is passed on to strncmp (not strcmp)

once the bootloader exits all ram is available to the program and the stack is reset.

Yes you may be right.

I think I only deserve that title when I manage to get the a basic bootloader down to 1K


(Pharap) #15

But printing functions usually expect null terminated strings.

You could copy from the main buffer to a local buffer and then print I suppose.

That makes things a lot easier then.
In that case a static buffer is certainly possible.

You’ve already done more than the rest of us, so I’d say that qualifies.


#16

Ah yes that would make it convenient for a standard printing function. When going for a minimalist printing function, the characters could just be printed with a (fixed) length.


(Pharap) #17

This is why I was hoping to find out what library is going to be used for graphics output.
Usually print functions don’t accept a length parameter,
so the library might have to be modified.

I see that Arduino’s Print class does fortunately have virtual size_t write(const uint8_t * buffer, size_t size) and size_t write(const char * buffer, size_t size),
so if the library being used implements Print then that can be used.


(Jon Raymond) #18

The bootloader is already in place for a ATmega328. Details can be found here (Source download here) The TLDR is that on boot if A3 is held low the bootloader search the SD card for a “LOADER.HEX” and loads it to flash and then runs it. This gives the Loader “app” access to all the chips resources. This program then scans the SD card and gives the user a selection menu of the file names. Once the user selects a file name the program passes the selected file name to the bootloader which then burns that hex file to flash over the loader hex file. On subsequence boots with A3 high or no SD card present the bootloader runs the current program in memory. The advantage is that the loader program can easily be compiled for different displays and button setups and loaded to the the SD card.

Oh this is really interesting. I’m currently using the U8x8 part of the U8g2 display library as it is the non buffered version and offers a “print” and “println” display function. Last night I got a rudimentary menu system cobbled together and was able to select and load programs from the SD card. It displays files in the order that they are indexed on the SD card. The “sort” feature seems to be more of an issue than maybe it’s worth?

I assume you would have to pad file names with less than 8 characters for this to work otherwise you wouldn’t know where one file name ended and another began?

FAT32 opens up options. Larger cards that 2gb would be nice as small cards are getting harder to find. Also I think the indexing and file names are different? The 8 character filename limit is only imposed when using the Gamebuino bootloader. The advantage of using the “loader” program triggered by a bootloader is that the bootloader doesn’t have to carry the menu system overhead. The disadvantage is that load times are slightly slower.

Hear, hear!


#19

Thats true but It has also a big disadvantage: flash will be worn out twice as fast.

That’s already done by the filesystem. Both the name and extension in 8.3 format are padded with spaces.

It’s possible to format a large SD card as FAT16. But you can only use 2GB ofcourse.


(Jon Raymond) #20

Valid point but is this a legitimate concern? My understanding is flash has roughly 10k cycles. Even at half that, switching a game every 10 minutes for 5 hours a day it would last for 167 days. My speculation would be that the buttons would fail before the flash would.

Great info to know. Thank you.