diff --git a/keywords.txt b/keywords.txt index 63d9865..bec469e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -83,6 +83,7 @@ readUnitName KEYWORD2 safeMode KEYWORD2 saveOnOff KEYWORD2 setCursor KEYWORD2 +setFrameDuration KEYWORD2 setFrameRate KEYWORD2 setRGBled KEYWORD2 setTextBackground KEYWORD2 diff --git a/src/Arduboy2.cpp b/src/Arduboy2.cpp index 0c294a6..8497f09 100644 --- a/src/Arduboy2.cpp +++ b/src/Arduboy2.cpp @@ -19,13 +19,9 @@ Arduboy2Base::Arduboy2Base() currentButtonState = 0; previousButtonState = 0; // frame management - setFrameRate(60); - frameCount = -1; - nextFrameStart = 0; + setFrameDuration(16); + frameCount = 0; justRendered = false; - // init not necessary, will be reset after first use - // lastFrameStart - // lastFrameDurationMs } // functions called here should be public so users can create their @@ -185,6 +181,11 @@ void Arduboy2Base::setFrameRate(uint8_t rate) eachFrameMillis = 1000 / rate; } +void Arduboy2Base::setFrameDuration(uint8_t duration) +{ + eachFrameMillis = duration; +} + bool Arduboy2Base::everyXFrames(uint8_t frames) { return frameCount % frames == 0; @@ -192,38 +193,27 @@ bool Arduboy2Base::everyXFrames(uint8_t frames) bool Arduboy2Base::nextFrame() { - unsigned long now = millis(); - bool tooSoonForNextFrame = now < nextFrameStart; + uint8_t now = (uint8_t) millis(); + uint8_t frameDurationMs = now - thisFrameStart; if (justRendered) { - lastFrameDurationMs = now - lastFrameStart; + lastFrameDurationMs = frameDurationMs; justRendered = false; return false; } - else if (tooSoonForNextFrame) { - // if we have MORE than 1ms to spare (hence our comparison with 2), - // lets sleep for power savings. We don't compare against 1 to avoid - // potential rounding errors - say we're actually 0.5 ms away, but a 1 - // is returned if we go to sleep we might sleep a full 1ms and then - // we'd be running the frame slighly late. So the last 1ms we stay - // awake for perfect timing. - - // This is likely trading power savings for absolute timing precision - // and the power savings might be the better goal. At 60 FPS trusting - // chance here might actually achieve a "truer" 60 FPS than the 16ms - // frame duration we get due to integer math. - - // We should be woken up by timer0 every 1ms, so it's ok to sleep. - if ((uint8_t)(nextFrameStart - now) >= 2) + else if (frameDurationMs < eachFrameMillis) { + // Only idle if at least a full millisecond remains, since idle() may + // sleep the processor until the next millisecond timer interrupt. + if (++frameDurationMs < eachFrameMillis) { idle(); + } return false; } // pre-render justRendered = true; - lastFrameStart = now; - nextFrameStart = now + eachFrameMillis; + thisFrameStart = now; frameCount++; return true; diff --git a/src/Arduboy2.h b/src/Arduboy2.h index fa9e4e3..a59c6a1 100644 --- a/src/Arduboy2.h +++ b/src/Arduboy2.h @@ -660,17 +660,47 @@ class Arduboy2Base : public Arduboy2Core * * \details * Set the frame rate, in frames per second, used by `nextFrame()` to update - * frames at a given rate. If this function isn't used, the default rate will - * be 60. + * frames at a given rate. If this function or `setFrameDuration()` + * isn't used, the default rate will be 60 (actually 62.5, see note below). * * Normally, the frame rate would be set to the desired value once, at the * start of the game, but it can be changed at any time to alter the frame * update rate. * - * \see nextFrame() + * \note + * \parblock + * The given rate is internally converted to a frame duration in milliseconds, + * rounded down to the nearest integer. Therefore, the actual rate will be + * equal to or higher than the rate given. + + * For example, 60 FPS would be 16.67ms per frame. This will be rounded down + * to 16ms, giving an actual frame rate of 62.5 FPS. + * \endparblock + * + * \see nextFrame() setFrameDuration() */ void setFrameRate(uint8_t rate); + /** \brief + * Set the frame rate, used by the frame control functions, by giving + * the duration of each frame. + * + * \param duration The desired duration of each frame in milliseconds. + * + * \details + * Set the frame rate by specifying the duration of each frame in + * milliseconds. This is used by `nextFrame()` to update frames at a + * given rate. If this function or `setFrameRate()` isn't used, + * the default will be 16ms per frame. + * + * Normally, the frame rate would be set to the desired value once, at the + * start of the game, but it can be changed at any time to alter the frame + * update rate. + * + * \see nextFrame() setFrameRate() + */ + void setFrameDuration(uint8_t duration); + /** \brief * Indicate that it's time to render the next frame. * @@ -694,7 +724,7 @@ class Arduboy2Base : public Arduboy2Core * } * \endcode * - * \see setFrameRate() nextFrameDEV() + * \see setFrameRate() setFrameDuration() nextFrameDEV() */ bool nextFrame(); @@ -1120,8 +1150,7 @@ class Arduboy2Base : public Arduboy2Core // For frame funcions uint8_t eachFrameMillis; - unsigned long lastFrameStart; - unsigned long nextFrameStart; + uint8_t thisFrameStart; bool justRendered; uint8_t lastFrameDurationMs; };