microbit: Updates to enable queing of display animation calls

Updates to change the behaviour of the scroll/print/animate faily of function away
from being pre-emtive and instead prroviding queing behaviour.

Minor updates to provide complete sets of async equivalent operations

Updates to the scheduler to provide wait/notify/waitone semantics.
This commit is contained in:
Joe Finney 2015-10-17 20:35:16 +01:00
parent 85b2b1e09e
commit aca544677e
8 changed files with 281 additions and 155 deletions

View file

@ -41,7 +41,9 @@
#define MICROBIT_ID_IO_P20 25 //SDA
#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton
#define MICROBIT_ID_ALERT 27 // Alert channel, used for general purpose condition synchronisation and alerting.
#define MICROBIT_ID_NOTIFY 1023 // Notfication channel, for general purpose synchronisation
#define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation
class MicroBitComponent
{

View file

@ -88,7 +88,7 @@
// MESSAGE_BUS_LISTENER_NONBLOCKING
#ifndef MESSAGE_BUS_LISTENER_DEFAULT_FLAGS
#define MESSAGE_BUS_LISTENER_DEFAULT_FLAGS MESSAGE_BUS_LISTENER_REENTRANT
#define MESSAGE_BUS_LISTENER_DEFAULT_FLAGS MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY
#endif
//

View file

@ -19,6 +19,7 @@
* MessageBus Event Codes
*/
#define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1
#define MICROBIT_DISPLAY_EVT_FREE 2
/**
* I/O configurations for common devices.
@ -70,6 +71,7 @@
enum AnimationMode {
ANIMATION_MODE_NONE,
ANIMATION_MODE_STOPPED,
ANIMATION_MODE_SCROLL_TEXT,
ANIMATION_MODE_PRINT_TEXT,
ANIMATION_MODE_SCROLL_IMAGE,
@ -103,7 +105,6 @@ class MicroBitDisplay : public MicroBitComponent
uint8_t mode;
uint8_t greyscaleBitMsk;
uint8_t timingCount;
uint16_t nonce;
Timeout renderTimer;
MicroBitFont font;
@ -221,6 +222,11 @@ class MicroBitDisplay : public MicroBitComponent
*/
void sendAnimationCompleteEvent();
/**
* Blocks the current fiber until the display is available (i.e. not effect is being displayed).
* Animations are queued until their time to display.
*/
void waitForFreeDisplay();
public:
// The mutable bitmap buffer being rendered to the LED matrix.
@ -242,16 +248,29 @@ public:
MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y);
/**
* Resets the current given animation.
* @param delay the delay after which the animation is reset.
* Stops any currently running animation, and any that are waiting to be displayed.
*/
void resetAnimation(uint16_t delay);
void stopAnimation();
/**
* Frame update method, invoked periodically to strobe the display.
*/
virtual void systemTick();
/**
* Prints the given character to the display, if it is not in use.
*
* @param c The character to display.
* @param d Optional parameter - the time for which to show the character. Zero displays the character forever.
*
* Example:
* @code
* uBit.display.printAsync('p');
* uBit.display.printAsync('p',100);
* @endcode
*/
void printAsync(char c, int delay = 0);
/**
* Prints the given string to the display, one character at a time.
* Uses the given delay between characters.
@ -267,6 +286,24 @@ public:
*/
void printAsync(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED);
/**
* Prints the given image to the display, if the display is not in use.
* Returns immediately, and executes the animation asynchronously.
*
* @param i The image to display.
* @param x The horizontal position on the screen to display the image (default 0)
* @param y The vertical position on the screen to display the image (default 0)
* @param alpha Treats the brightness level '0' as transparent (default 0)
* @param delay The time to delay between characters, in timer ticks.
*
* Example:
* @code
* MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n");
* uBit.display.print(i,400);
* @endcode
*/
void printAsync(MicroBitImage i, int x, int y, int alpha, int delay = 0);
/**
* Prints the given character to the display.
*
@ -307,7 +344,7 @@ public:
* uBit.display.print(i,400);
* @endcode
*/
void print(MicroBitImage i, int x, int y, int alpha, int delay = MICROBIT_DEFAULT_PRINT_SPEED);
void print(MicroBitImage i, int x, int y, int alpha, int delay = 0);
/**
* Scrolls the given string to the display, from right to left.

View file

@ -203,11 +203,6 @@ class MicroBitMessageBus : public MicroBitComponent
template <typename T>
void ignore(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent));
/**
* Returns a 'nonce' for use with the NONCE_ID channel of the message bus.
*/
uint16_t nonce();
private:
/**

View file

@ -191,7 +191,7 @@ void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params)
*/
void MicroBitDFUService::showTick()
{
uBit.display.resetAnimation(0);
uBit.display.stopAnimation();
uBit.display.image.setPixelValue(0,3, 255);
uBit.display.image.setPixelValue(1,4, 255);
@ -206,7 +206,7 @@ void MicroBitDFUService::showTick()
*/
void MicroBitDFUService::showNameHistogram()
{
uBit.display.resetAnimation(0);
uBit.display.stopAnimation();
uint32_t n = NRF_FICR->DEVICEID[1];
int ld = 1;

View file

@ -234,10 +234,10 @@ MicroBitDisplay::animationUpdate()
void MicroBitDisplay::sendAnimationCompleteEvent()
{
// Signal that we've completed an animation.
MicroBitEvent evt1(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
// Wake up any fibers that were blocked on the animation (if any).
MicroBitEvent evt2(MICROBIT_ID_ALERT, nonce);
// Wake up a fiber that was blocked on the animation (if any).
MicroBitEvent(MICROBIT_ID_NOTIFY_ONE, MICROBIT_DISPLAY_EVT_FREE);
}
/**
@ -304,7 +304,6 @@ void MicroBitDisplay::updateScrollImage()
scrollingImageRendered = true;
}
/**
* Internal animateImage update method.
* Paste the stored bitmap at the appropriate point and stop on the last frame.
@ -333,28 +332,70 @@ void MicroBitDisplay::updateAnimateImage()
* Resets the current given animation.
* @param delay the delay after which the animation is reset.
*/
void MicroBitDisplay::resetAnimation(uint16_t delay)
void MicroBitDisplay::stopAnimation()
{
//sanitise this value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
// Reset any ongoing animation.
if (animationMode != ANIMATION_MODE_NONE)
{
animationMode = ANIMATION_MODE_NONE;
this->sendAnimationCompleteEvent();
// Indicate that we've completed an animation.
MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
// Wake up aall fibers that may blocked on the animation (if any).
MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE);
}
// Clear the display and setup the animation timers.
this->image.clear();
this->animationDelay = delay;
this->animationTick = delay-1;
}
/**
* Prints the given string to the display, one character at a time.
* Uses the given delay between characters.
* Blocks the current fiber until the display is available (i.e. not effect is being displayed).
* Animations are queued until their time to display.
*
*/
void MicroBitDisplay::waitForFreeDisplay()
{
// If there's an ongoing animation, wait for our turn to display.
if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED)
fiber_wait_for_event(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE);
}
/**
* Prints the given character to the display, if it is not in use.
*
* @param c The character to display.
* @param d Optional parameter - the time for which to show the character. Zero displays the character forever.
*
* Example:
* @code
* uBit.display.printAsync('p');
* uBit.display.printAsync('p',100);
* @endcode
*/
void MicroBitDisplay::printAsync(char c, int delay)
{
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
image.print(c, 0, 0);
if(delay <= 0)
return;
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
}
}
/**
* Prints the given string to the display, one character at a time, if the display is not in use.
* Returns immediately, and executes the animation asynchronously.
*
* @param s The string to display.
@ -367,41 +408,77 @@ void MicroBitDisplay::resetAnimation(uint16_t delay)
*/
void MicroBitDisplay::printAsync(ManagedString s, int delay)
{
//sanitise this value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
this->resetAnimation(delay);
this->printingChar = 0;
this->printingText = s;
animationMode = ANIMATION_MODE_PRINT_TEXT;
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
//sanitise this value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
printingChar = 0;
printingText = s;
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_PRINT_TEXT;
}
}
/**
* Prints the given character to the display.
* Prints the given image to the display, if the display is not in use.
* Returns immediately, and executes the animation asynchronously.
*
* @param i The image to display.
* @param x The horizontal position on the screen to display the image (default 0)
* @param y The vertical position on the screen to display the image (default 0)
* @param alpha Treats the brightness level '0' as transparent (default 0)
* @param delay The time to delay between characters, in timer ticks.
*
* Example:
* @code
* MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n");
* uBit.display.print(i,400);
* @endcode
*/
void MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int delay)
{
// If the display is free, it's our turn to display.
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
image.paste(i, x, y, alpha);
if(delay <= 0)
return;
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
}
}
/**
* Prints the given character to the display, and wait for it to complete.
*
* @param c The character to display.
*
* Example:
* @code
* uBit.display.print('p');
* uBit.display.print('p',100);
* @endcode
*/
void MicroBitDisplay::print(char c, int delay)
{
image.print(c, 0, 0);
if(delay <= 0)
return;
this->animationDelay = delay;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
// Wait for completion.
nonce = uBit.MessageBus.nonce();
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce);
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
if (animationMode == ANIMATION_MODE_NONE)
{
this->printAsync(c, delay);
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
}
/**
@ -423,12 +500,16 @@ void MicroBitDisplay::print(ManagedString s, int delay)
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
// Start the effect.
this->printAsync(s, delay);
// Wait for completion.
nonce = uBit.MessageBus.nonce();
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce);
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
if (animationMode == ANIMATION_MODE_NONE)
{
this->printAsync(s, delay);
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
}
/**
@ -446,17 +527,17 @@ void MicroBitDisplay::print(ManagedString s, int delay)
*/
void MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay)
{
image.paste(i, x, y, alpha);
if(delay <= 0)
return;
this->animationDelay = delay;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
// Wait for completion.
nonce = uBit.MessageBus.nonce();
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce);
// If there's an ongoing animation, wait for our turn to display.
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
if (animationMode == ANIMATION_MODE_NONE)
{
this->printAsync(i, x, y, alpha, delay);
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
}
/**
@ -474,17 +555,19 @@ void MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay)
*/
void MicroBitDisplay::scrollAsync(ManagedString s, int delay)
{
//sanitise this value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
this->resetAnimation(delay);
this->scrollingPosition = width-1;
this->scrollingChar = 0;
this->scrollingText = s;
animationMode = ANIMATION_MODE_SCROLL_TEXT;
// If the display is free, it's our turn to display.
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
//sanitise the delay parameter
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
scrollingPosition = width-1;
scrollingChar = 0;
scrollingText = s;
animationMode = ANIMATION_MODE_SCROLL_TEXT;
}
}
/**
@ -502,18 +585,20 @@ void MicroBitDisplay::scrollAsync(ManagedString s, int delay)
*/
void MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride)
{
//sanitise the delay value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
this->resetAnimation(delay);
// If the display is free, it's our turn to display.
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
//sanitise the delay value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
this->scrollingImagePosition = stride < 0 ? width : -image.getWidth();
this->scrollingImageStride = stride;
this->scrollingImage = image;
this->scrollingImageRendered = false;
animationMode = ANIMATION_MODE_SCROLL_IMAGE;
this->scrollingImagePosition = stride < 0 ? width : -image.getWidth();
this->scrollingImageStride = stride;
this->scrollingImage = image;
this->scrollingImageRendered = false;
animationMode = ANIMATION_MODE_SCROLL_IMAGE;
}
}
/**
@ -531,16 +616,19 @@ void MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride)
*/
void MicroBitDisplay::scroll(ManagedString s, int delay)
{
//sanitise this value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
// Start the effect.
this->scrollAsync(s, delay);
// Wait for completion.
nonce = uBit.MessageBus.nonce();
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce);
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
if (animationMode == ANIMATION_MODE_NONE)
{
// Start the effect.
this->scrollAsync(s, delay);
// Wait for completion.
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
}
/**
@ -559,16 +647,19 @@ void MicroBitDisplay::scroll(ManagedString s, int delay)
*/
void MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride)
{
//sanitise the delay value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
// Start the effect.
this->scrollAsync(image, delay, stride);
// Wait for completion.
nonce = uBit.MessageBus.nonce();
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce);
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
if (animationMode == ANIMATION_MODE_NONE)
{
// Start the effect.
this->scrollAsync(image, delay, stride);
// Wait for completion.
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
}
/**
@ -591,30 +682,27 @@ void MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride)
*/
void MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, int startingPosition)
{
// Assume right to left functionality, to align with scrollString()
stride = -stride;
//sanitise the delay value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
// Reset any ongoing animation.
if (animationMode != ANIMATION_MODE_NONE)
// If the display is free, it's our turn to display.
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
animationMode = ANIMATION_MODE_NONE;
this->sendAnimationCompleteEvent();
}
this->animationDelay = delay;
this->animationTick = delay-1;
// Assume right to left functionality, to align with scrollString()
stride = -stride;
//calculate starting position which is offset by the stride
this->scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS)?MICROBIT_DISPLAY_WIDTH + stride:startingPosition;
this->scrollingImageStride = stride;
this->scrollingImage = image;
this->scrollingImageRendered = false;
animationMode = ANIMATION_MODE_ANIMATE_IMAGE;
//sanitise the delay value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
animationDelay = delay;
animationTick = delay-1;
//calculate starting position which is offset by the stride
scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition;
scrollingImageStride = stride;
scrollingImage = image;
scrollingImageRendered = false;
animationMode = ANIMATION_MODE_ANIMATE_IMAGE;
}
}
/**
@ -637,16 +725,19 @@ void MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, i
*/
void MicroBitDisplay::animate(MicroBitImage image, int delay, int stride, int startingPosition)
{
//sanitise the delay value
if(delay <= 0 )
delay = MICROBIT_DEFAULT_SCROLL_SPEED;
// Start the effect.
this->animateAsync(image, delay, stride, startingPosition);
// Wait for completion.
nonce = uBit.MessageBus.nonce();
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce);
// If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
// If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
if (animationMode == ANIMATION_MODE_NONE)
{
// Start the effect.
this->animateAsync(image, delay, stride, startingPosition);
// Wait for completion.
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
}

View file

@ -15,7 +15,7 @@
*/
Fiber *currentFiber = NULL; // The context in which the current fiber is executing.
Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing.
Fiber *idleFiber = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks.
Fiber *idleFiber = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks.
/*
* Scheduler state.
@ -205,6 +205,7 @@ void scheduler_event(MicroBitEvent evt)
{
Fiber *f = waitQueue;
Fiber *t;
int notifyOneComplete = 0;
// Check the wait queue, and wake up any fibers as necessary.
while (f != NULL)
@ -214,7 +215,16 @@ void scheduler_event(MicroBitEvent evt)
// extract the event data this fiber is blocked on.
uint16_t id = f->context & 0xFFFF;
uint16_t value = (f->context & 0xFFFF0000) >> 16;
// Special case for the NOTIFY_ONE channel...
if ((notifyOneComplete == 0) && (id == MICROBIT_ID_NOTIFY && evt.source == MICROBIT_ID_NOTIFY_ONE) && (value == MICROBIT_EVT_ANY || value == evt.value))
{
// Wakey wakey!
dequeue_fiber(f);
queue_fiber(f,&runQueue);
notifyOneComplete = 1;
}
if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value))
{
// Wakey wakey!

View file

@ -15,18 +15,6 @@ MicroBitMessageBus::MicroBitMessageBus()
this->listeners = NULL;
this->evt_queue_head = NULL;
this->evt_queue_tail = NULL;
this->nonce_val = 0;
}
/**
* Returns a 'nonce' for use with the NONCE_ID channel of the message bus.
*/
uint16_t MicroBitMessageBus::nonce()
{
// In the global scheme of things, a terrible nonce generator.
// However, for our purposes, this is simple and adequate for local use.
// This would be a bad idea if our events were networked though - can you think why?
return nonce_val++;
}
/**
@ -86,6 +74,9 @@ void async_callback(void *param)
listener->evt = item->evt;
listener->evt_queue = listener->evt_queue->next;
delete item;
// We spin the scheduler here, to preven any particular event handler from continuously holding onto resources.
schedule();
}
else
break;