Pomodoro Timer

minipomodoro.hex (51.9 KB)

Hello. I’d like to present my first Arduboy/Arduino project. It’s a productivity timer for the Pomodoro Technique.

This project helped familiarize me with the Arduboy library and build process. I am new to programming embedded systems. Constructive criticism is welcome on the code :smile:

Features

  • Image of my hand-drawn tomato :tomato:
  • Total time worked since boot (resets with device)
  • 5 LED patterns, and 5 audio tones, to play when the timer lapses
  • Configurable pomodoro and break duration
  • Ticking clock sound

Care has been taken to limit CPU time. The display redraws only when dirtied, and it turns off after a user-configured period of idleness (no key presses). The frame rate is also limited to 15. I used Ardens to profile the CPU, which idles around 4.5% - 6.5%. I would be curious to know if this can be lowered further (I suspect implementing dynamic frame rate, slowing to a crawl when appropriate, may help).

Keys

  • (A) starts/stops the clock on the timer (tomato) screen.
  • (B) silences any notification LED/sounds immediately.
  • Left/Right navigates between the application pages: timer, stats, config, about, help.
  • Up/Down to scroll between options, and to confirm certain actions (cancel timer, skip break).

Anti-features

The app does not store anything in EEPROM. I do not consider program config worth the writes, for something the user can reconfigure in under 60 seconds. Similarly, total time worked can be noted down by the user on their physical task list, if they are so inclined.

Source: minipomodoro.ino

8 Likes

Beautifully made. I love that you have made the effort to make the graphics slide in / out.

If you are running out of memory, you might want to look at @pharap’s ’ FlashStringHelper code here.

2 Likes

I think @acedent has been remembering things that haven’t happened yet…


Someday I really ought to add some text to the readme file explaining how the trick works…

Fortunately the comments in the .ino are just about enough to work out how to use it properly (for basic stuff anyway).

@wesbat would also need to replace some of the strlens with strlen_Ps since the strings would then be kept in progmem.

(Also there’s quite a few bare strings that could be wrapped in F to save a bit of RAM.)


To elaborate a bit, basically you can turn stuff like this:

const char* get_timer_text()
{
  if (is_timing)
  {
    if (on_break)
    {
      if (completed_breaks == 0)
        return " 1st break ";
      else if (completed_breaks == 1)
        return " 2nd break ";
      else if (completed_breaks == 2)
        return " 3rd break ";
      else if (completed_breaks == 3)
        return " 4th break ";
    }
    else
    {
      if (completed_poms == 0)
        return " 1st pomodoro ";
      else if (completed_poms == 1)
        return " 2nd pomodoro ";
      else if (completed_poms == 2)
        return " 3rd pomodoro ";
      else if (completed_poms == 3)
        return " 4th pomodoro ";
    }
  }
  return PRODUCT_NAME;
}

Into something like:

FlashStringHelper get_timer_text()
{
  if (is_timing)
  {
    if (on_break)
    {
      if (completed_breaks == 0)
        return F(" 1st break ");
      else if (completed_breaks == 1)
        return F(" 2nd break ");
      else if (completed_breaks == 2)
        return F(" 3rd break ");
      else if (completed_breaks == 3)
        return F(" 4th break ");
    }
    else
    {
      if (completed_poms == 0)
        return F(" 1st pomodoro ");
      else if (completed_poms == 1)
        return F(" 2nd pomodoro ");
      else if (completed_poms == 2)
        return F(" 3rd pomodoro ");
      else if (completed_poms == 3)
        return F(" 4th pomodoro ");
    }
  }
  return F(PRODUCT_NAME);
}

Which would then allow:

arduboy.print(get_timer_text());

Though actually it would actually be more efficient to do:

const char breakString0[] PROGMEM = " 1st break ";
const char breakString1[] PROGMEM = " 2nd break ";
const char breakString2[] PROGMEM = " 3rd break ";
const char breakString3[] PROGMEM = " 4th break ";

const char * const breakStrings[] PROGMEM
{
	breakString0,
	breakString1,
	breakString2,
	breakString3,
};

const char pomodoroString0[] PROGMEM = " 1st pomodoro ";
const char pomodoroString1[] PROGMEM = " 2nd pomodoro ";
const char pomodoroString2[] PROGMEM = " 3rd pomodoro ";
const char pomodoroString3[] PROGMEM = " 4th pomodoro ";

const char * const pomodoroStrings[] PROGMEM
{
	pomodoroString0,
	pomodoroString1,
	pomodoroString2,
	pomodoroString3,
};

const char * productName[] PROGMEM = PRODUCT_NAME;

const char * errorText[] PROGMEM = "Error";

FlashStringHelper get_timer_text()
{
  if (!is_timing)
    return asFlashStringHelper(productName);
	
  if(completed_breaks > 3)
    return asFlashStringHelper(errorText);

  const auto source = (on_break) ?
    &breakStrings[completed_breaks] :
    &pomodoroStrings[completed_breaks];

  return readFlashStringPointer(source);
}

Because the resulting code would end up using some simple pointer arithmetic instead of a load of branching.

Either way the point is that the strings end up being only in progmem.
They never enter RAM, thus they never consume RAM, and RAM is a very scarce resource.

1 Like

Thank you for the kind words.

Serendipitous indeed!

Thank you, those tips are great, and just the kind of learning experience I am looking for. I will check out FlashStringHelper too.

Let me know if you have any questions about how RAM and progmem work, or how any of the flashstring trickery works, or if you’re struggling to get any of it to work properly.

Nice! Hope to use this soon! :tomato: :smiley:

2 Likes

Thanks @Pharap. I have read over the Arduino memory guide, and I also played around with FlashStringHelper to see how it works. I will certainly make use of it!

My compiler reports that the pomodoro timer only uses 80% of dynamic memory, which leaves plenty free for future changes, I have no immediate need to implement FlashStringHelper for this app, however I will have to use it for a future project I have in mind, which will be extremely text-heavy, I get the feeling I will get the most use out of your helper then. Of course I will write a couple proof of concepts before I dive into that project. Thank you @Pharap, this has been most helpful.

Actually, 80% does not leave as much as you think. This percentage is for all of the statically allocated stuff in your program but that isn’t everything. The Arduino uses dynamic memory for necessary evils, such as a call stack. Depending on how ‘complex’ your code is, this requirement can be quite significant.

You will notice the warning for high memory usage kicks in at 80% when you are compiling. This is often a little low but you would not want to go to far above this.

If you are looking at a lot of text, you may want to consider using the FX chip to store it (assuming you have an Arduboy FX or FX-enabled clone).

2 Likes

As @filmote says, that’s not as much as you might think.

That’s actually 80% of 2560 bytes, so you’ve probably only got just under 512 bytes free, some of which is needed for the call stack.

The Arduboy (which uses an ATmega32u4 processor) has:

  • 32768 bytes of flash memory
    • Of which 4096 is occupied by the bootloader, leaving 28672 for user code, which includes whatever library functions you use and the implicit Arduino stuff like the boilerplate that makes setup and loop work.
  • 2560 bytes of RAM (‘dynamic memory’)
    • Of which some must always be reserved for the call stack, otherwise you can’t call functions. The call stack is needed to store return addresses, register values, and sometimes local variables.
  • 1024 bytes of EEPROM
    • Which must be shared by all games.
    • The first 16 bytes are reserved by Arduboy2 for user settings like the mute bit.

A slight correction to one of their claims: AVR is actually modified Harvard architecture because its code space also stores data, as opposed to a true Harvard architecture where the code space stores only code.

(The section about ‘hybrid’ models is also pretty confusing considering they don’t go into what set of characteristics they are classifying as ‘hybrid’, and don’t actually use ‘hybrid’ in their table of boards at all.)

Thanks for the detailed breakdown @Pharap, in fact I was searching for those exact numbers, how apt!

Hi everyone, I’m having problems with the code. When I copy and paste the code into Arduino IDE I get the error 'class Arduboy2' has no member named 'getCharacterSpacing'. Can anyone help?
I’ve already installed the libraries if that helps.

Thanks, from an utterly confused person. :grimacing:

Edit (using the arduboy FX)

I believe that is because it is a static function according to the class reference: Arduboy2 Library: Arduboy2 Class Reference

The code could be trying to access it through an object rather than the scope resolution operator.

What version of the Arduboy2 library are you using? getCharacterSpacing() was added in the latest V6.0.0 release.

If you’re using a “non-official” ported version of the library, perhaps it hasn’t been updated to include this function.

1 Like

Static functions of a class can always be accessed from instantiated objects of that class.

1 Like

Oh, huh. I always thought they could only be accessed with Class::function.

I updated the arduboy 2 library and it works now. I thought the library would work because I downloaded it 18 days ago.
Thanks again!

2 Likes

In some languages (e.g. C#, I think maybe Java) that’s true (though in such languages it would probably be Class.function), but C++ has an extra rule that says static member functions can be accessed the same way as non-static member functions.

So theoretically any time you write Arduboy2::something() you can instead get away with arduboy.something(). However, using the Arduboy2:: approach means you don’t have to worry about your code having access to an arduboy object in the first place, which can be quite useful in certain contexts.


You probably grabbed the wrong version by mistake.
Version 6.0.0 has been the current version since the tail end of 2020.

3 Likes