Merge branch 'simplified-eventing'

This commit is contained in:
Joe Finney 2015-11-01 18:22:27 +00:00
commit 194f19a428
16 changed files with 606 additions and 241 deletions

View File

@ -23,6 +23,12 @@ enum ErrorCode{
// The requested operation could not be performed as the device has run out of some essential resource (e.g. allocated memory) // The requested operation could not be performed as the device has run out of some essential resource (e.g. allocated memory)
MICROBIT_NO_RESOURCES = -1005, MICROBIT_NO_RESOURCES = -1005,
// The requested operation could not be performed as some essential resource is busy (e.g. the display)
MICROBIT_BUSY = -1006,
// The requested operation was cancelled before it completed.
MICROBIT_CANCELLED = -1007,
// I2C Communication error occured (typically I2C module on processor has locked up.) // I2C Communication error occured (typically I2C module on processor has locked up.)
MICROBIT_I2C_ERROR = -1010 MICROBIT_I2C_ERROR = -1010
}; };

View File

@ -31,6 +31,13 @@
#define MICROBIT_BUTTON_SIGMA_THRESH_LO 2 #define MICROBIT_BUTTON_SIGMA_THRESH_LO 2
#define MICROBIT_BUTTON_DOUBLE_CLICK_THRESH 50 #define MICROBIT_BUTTON_DOUBLE_CLICK_THRESH 50
enum MicroBitButtonEventConfiguration
{
MICROBIT_BUTTON_SIMPLE_EVENTS,
MICROBIT_BUTTON_ALL_EVENTS
};
/** /**
* Class definition for MicroBit Button. * Class definition for MicroBit Button.
* *
@ -38,12 +45,12 @@
*/ */
class MicroBitButton : public MicroBitComponent class MicroBitButton : public MicroBitComponent
{ {
PinName name; // mBed pin name of this pin. PinName name; // mbed pin name of this pin.
DigitalIn pin; // The mBed object looking after this pin at any point in time (may change!). DigitalIn pin; // The mbed object looking after this pin at any point in time (may change!).
unsigned long downStartTime; // used to store the current system clock when a button down event occurs unsigned long downStartTime; // used to store the current system clock when a button down event occurs
uint8_t sigma; // integration of samples over time. uint8_t sigma; // integration of samples over time. We use this for debouncing, and noise tolerance for touch sensing
uint8_t doubleClickTimer; // double click timer (ticks). MicroBitButtonEventConfiguration eventConfiguration; // Do we want to generate high level event (clicks), or defer this to another service.
public: public:
@ -69,7 +76,7 @@ class MicroBitButton : public MicroBitComponent
* MICROBIT_BUTTON_EVT_HOLD * MICROBIT_BUTTON_EVT_HOLD
* @endcode * @endcode
*/ */
MicroBitButton(uint16_t id, PinName name, PinMode mode = PullNone); MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventConfiguration eventConfiguration = MICROBIT_BUTTON_ALL_EVENTS, PinMode mode = PullNone);
/** /**
* Tests if this Button is currently pressed. * Tests if this Button is currently pressed.

View File

@ -42,7 +42,9 @@
#define MICROBIT_ID_IO_P20 25 //SDA #define MICROBIT_ID_IO_P20 25 //SDA
#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton #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 class MicroBitComponent
{ {

View File

@ -88,9 +88,16 @@
// MESSAGE_BUS_LISTENER_NONBLOCKING // MESSAGE_BUS_LISTENER_NONBLOCKING
#ifndef MESSAGE_BUS_LISTENER_DEFAULT_FLAGS #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 #endif
//
// Maximum event queue depth. If a queue exceeds this depth, further events will be dropped.
// Used to prevent message queues growing uncontrollably due to badly behaved user code and causing panic conditions.
//
#ifndef MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH
#define MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH 20
#endif
// //
// Core micro:bit services // Core micro:bit services
// //

View File

@ -11,6 +11,7 @@
* MessageBus Event Codes * MessageBus Event Codes
*/ */
#define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1 #define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1
#define MICROBIT_DISPLAY_EVT_FREE 2
/** /**
* I/O configurations for common devices. * I/O configurations for common devices.
@ -62,6 +63,7 @@
enum AnimationMode { enum AnimationMode {
ANIMATION_MODE_NONE, ANIMATION_MODE_NONE,
ANIMATION_MODE_STOPPED,
ANIMATION_MODE_SCROLL_TEXT, ANIMATION_MODE_SCROLL_TEXT,
ANIMATION_MODE_PRINT_TEXT, ANIMATION_MODE_PRINT_TEXT,
ANIMATION_MODE_SCROLL_IMAGE, ANIMATION_MODE_SCROLL_IMAGE,
@ -102,7 +104,6 @@ class MicroBitDisplay : public MicroBitComponent
uint8_t mode; uint8_t mode;
uint8_t greyscaleBitMsk; uint8_t greyscaleBitMsk;
uint8_t timingCount; uint8_t timingCount;
uint16_t nonce;
Timeout renderTimer; Timeout renderTimer;
MicroBitFont font; MicroBitFont font;
@ -220,6 +221,11 @@ class MicroBitDisplay : public MicroBitComponent
*/ */
void sendAnimationCompleteEvent(); 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: public:
// The mutable bitmap buffer being rendered to the LED matrix. // The mutable bitmap buffer being rendered to the LED matrix.
@ -241,17 +247,30 @@ public:
MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y); MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y);
/** /**
* Resets the current given animation. * Stops any currently running animation, and any that are waiting to be displayed.
* @param delay the delay after which the animation is reset. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER.
*/ */
int resetAnimation(uint16_t delay); void stopAnimation();
/** /**
* Frame update method, invoked periodically to strobe the display. * Frame update method, invoked periodically to strobe the display.
*/ */
virtual void systemTick(); virtual void systemTick();
/**
* Prints the given character to the display, if it is not in use.
*
* @param c The character to display.
* @param delay Optional parameter - the time for which to show the character. Zero displays the character forever.
* @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER.
*
* Example:
* @code
* uBit.display.printAsync('p');
* uBit.display.printAsync('p',100);
* @endcode
*/
int printAsync(char c, int delay = 0);
/** /**
* Prints the given string to the display, one character at a time. * Prints the given string to the display, one character at a time.
* Uses the given delay between characters. * Uses the given delay between characters.
@ -268,12 +287,30 @@ public:
*/ */
int printAsync(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); int 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 milliseconds. set to 0 to display forever. (default 0).
*
* Example:
* @code
* MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n");
* uBit.display.print(i,400);
* @endcode
*/
int printAsync(MicroBitImage i, int x, int y, int alpha, int delay = 0);
/** /**
* Prints the given character to the display. * Prints the given character to the display.
* *
* @param c The character to display. * @param c The character to display.
* @param delay The time to delay between characters, in milliseconds. Must be > 0. * @param delay The time to delay between characters, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -289,7 +326,7 @@ public:
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between characters, in milliseconds. Must be > 0. * @param delay The time to delay between characters, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -304,7 +341,7 @@ public:
* *
* @param i The image to display. * @param i The image to display.
* @param delay The time to display the image for, or zero to show the image forever. Must be >= 0. * @param delay The time to display the image for, or zero to show the image forever. Must be >= 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -312,7 +349,7 @@ public:
* uBit.display.print(i,400); * uBit.display.print(i,400);
* @endcode * @endcode
*/ */
int print(MicroBitImage i, int x, int y, int alpha, int delay = MICROBIT_DEFAULT_PRINT_SPEED); int print(MicroBitImage i, int x, int y, int alpha, int delay = 0);
/** /**
* Scrolls the given string to the display, from right to left. * Scrolls the given string to the display, from right to left.
@ -321,7 +358,7 @@ public:
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -337,7 +374,7 @@ public:
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -354,7 +391,7 @@ public:
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -370,7 +407,7 @@ public:
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -387,7 +424,7 @@ public:
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -408,7 +445,7 @@ public:
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code

View File

@ -14,6 +14,8 @@
#define MESSAGE_BUS_LISTENER_DROP_IF_BUSY 0x0020 #define MESSAGE_BUS_LISTENER_DROP_IF_BUSY 0x0020
#define MESSAGE_BUS_LISTENER_NONBLOCKING 0x0040 #define MESSAGE_BUS_LISTENER_NONBLOCKING 0x0040
#define MESSAGE_BUS_LISTENER_URGENT 0x0080 #define MESSAGE_BUS_LISTENER_URGENT 0x0080
#define MESSAGE_BUS_LISTENER_DELETING 0x8000
#define MESSAGE_BUS_LISTENER_IMMEDIATE (MESSAGE_BUS_LISTENER_NONBLOCKING | MESSAGE_BUS_LISTENER_URGENT) #define MESSAGE_BUS_LISTENER_IMMEDIATE (MESSAGE_BUS_LISTENER_NONBLOCKING | MESSAGE_BUS_LISTENER_URGENT)

View File

@ -64,10 +64,11 @@ class MicroBitMessageBus : public MicroBitComponent
* or the constructors provided by MicroBitEvent. * or the constructors provided by MicroBitEvent.
* *
* @param evt The event to send. * @param evt The event to send.
* @param mask The type of listeners to process (optional). Matches MicroBitListener flags. If not defined, all standard listeners will be processed. * @param urgent The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed
* @return The 1 if all matching listeners were processed, 0 if further processing is required. * otherwise, all other (standard) listeners will be processed.
* @return 1 if all matching listeners were processed, 0 if further processing is required.
*/ */
int process(MicroBitEvent &evt, uint32_t mask = MESSAGE_BUS_LISTENER_REENTRANT | MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY | MESSAGE_BUS_LISTENER_DROP_IF_BUSY | MESSAGE_BUS_LISTENER_NONBLOCKING); int process(MicroBitEvent &evt, bool urgent = false);
/** /**
* Register a listener function. * Register a listener function.
@ -222,11 +223,6 @@ class MicroBitMessageBus : public MicroBitComponent
*/ */
MicroBitListener *elementAt(int n); MicroBitListener *elementAt(int n);
/**
* Returns a 'nonce' for use with the NONCE_ID channel of the message bus.
*/
uint16_t nonce();
private: private:
/** /**
@ -243,10 +239,17 @@ class MicroBitMessageBus : public MicroBitComponent
*/ */
int remove(MicroBitListener *newListener); int remove(MicroBitListener *newListener);
/**
* Cleanup any MicroBitListeners marked for deletion from the list.
* @return The number of listeners removed from the list.
*/
int deleteMarkedListeners();
MicroBitListener *listeners; // Chain of active listeners. MicroBitListener *listeners; // Chain of active listeners.
MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed. MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed.
MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed. MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed.
uint16_t nonce_val; // The last nonce issued. uint16_t nonce_val; // The last nonce issued.
uint16_t queueLength; // The number of events currently waiting to be processed.
void queueEvent(MicroBitEvent &evt); void queueEvent(MicroBitEvent &evt);
MicroBitEventQueueItem* dequeueEvent(); MicroBitEventQueueItem* dequeueEvent();

View File

@ -3,10 +3,12 @@
#include "MicroBit.h" #include "MicroBit.h"
#define MICROBIT_MULTI_BUTTON_STATE_1 1 #define MICROBIT_MULTI_BUTTON_STATE_1 0x01
#define MICROBIT_MULTI_BUTTON_STATE_2 2 #define MICROBIT_MULTI_BUTTON_STATE_2 0x02
#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1 4 #define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1 0x04
#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2 8 #define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2 0x08
#define MICROBIT_MULTI_BUTTON_SUPRESSED_1 0X10
#define MICROBIT_MULTI_BUTTON_SUPRESSED_2 0x20
/** /**
* Class definition for MicroBitMultiButton. * Class definition for MicroBitMultiButton.
@ -22,8 +24,10 @@ class MicroBitMultiButton : public MicroBitComponent
uint16_t otherSubButton(uint16_t b); uint16_t otherSubButton(uint16_t b);
int isSubButtonPressed(uint16_t button); int isSubButtonPressed(uint16_t button);
int isSubButtonHeld(uint16_t button); int isSubButtonHeld(uint16_t button);
int isSubButtonSupressed(uint16_t button);
void setButtonState(uint16_t button, int value); void setButtonState(uint16_t button, int value);
void setHoldState(uint16_t button, int value); void setHoldState(uint16_t button, int value);
void setSupressedState(uint16_t button, int value);
public: public:

View File

@ -63,8 +63,8 @@ MicroBit::MicroBit() :
#endif #endif
MessageBus(), MessageBus(),
display(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_WIDTH, MICROBIT_DISPLAY_HEIGHT), display(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_WIDTH, MICROBIT_DISPLAY_HEIGHT),
buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A), buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A, MICROBIT_BUTTON_SIMPLE_EVENTS),
buttonB(MICROBIT_ID_BUTTON_B,MICROBIT_PIN_BUTTON_B), buttonB(MICROBIT_ID_BUTTON_B,MICROBIT_PIN_BUTTON_B, MICROBIT_BUTTON_SIMPLE_EVENTS),
buttonAB(MICROBIT_ID_BUTTON_AB,MICROBIT_ID_BUTTON_A,MICROBIT_ID_BUTTON_B), buttonAB(MICROBIT_ID_BUTTON_AB,MICROBIT_ID_BUTTON_A,MICROBIT_ID_BUTTON_B),
accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR), accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR),
compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR), compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR),

View File

@ -18,17 +18,16 @@
* MICROBIT_BUTTON_EVT_UP * MICROBIT_BUTTON_EVT_UP
* MICROBIT_BUTTON_EVT_CLICK * MICROBIT_BUTTON_EVT_CLICK
* MICROBIT_BUTTON_EVT_LONG_CLICK * MICROBIT_BUTTON_EVT_LONG_CLICK
* MICROBIT_BUTTON_EVT_DOUBLE_CLICK
* MICROBIT_BUTTON_EVT_HOLD * MICROBIT_BUTTON_EVT_HOLD
* @endcode * @endcode
*/ */
MicroBitButton::MicroBitButton(uint16_t id, PinName name, PinMode mode) : pin(name, mode) MicroBitButton::MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventConfiguration eventConfiguration, PinMode mode) : pin(name, mode)
{ {
this->id = id; this->id = id;
this->name = name; this->name = name;
this->eventConfiguration = eventConfiguration;
this->downStartTime = 0; this->downStartTime = 0;
this->sigma = 0; this->sigma = 0;
this->doubleClickTimer = 0;
uBit.addSystemComponent(this); uBit.addSystemComponent(this);
} }
@ -71,12 +70,15 @@ void MicroBitButton::systemTick()
{ {
status = 0; status = 0;
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_UP); MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_UP);
//determine if this is a long click or a normal click and send event if (eventConfiguration == MICROBIT_BUTTON_ALL_EVENTS)
if((ticks - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME) {
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK); //determine if this is a long click or a normal click and send event
else if((ticks - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME)
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK);
else
MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK);
}
} }
//if button is pressed and the hold triggered event state is not triggered AND we are greater than the button debounce value //if button is pressed and the hold triggered event state is not triggered AND we are greater than the button debounce value

View File

@ -236,10 +236,10 @@ MicroBitDisplay::animationUpdate()
void MicroBitDisplay::sendAnimationCompleteEvent() void MicroBitDisplay::sendAnimationCompleteEvent()
{ {
// Signal that we've completed an animation. // 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). // Wake up a fiber that was blocked on the animation (if any).
MicroBitEvent evt2(MICROBIT_ID_ALERT, nonce); MicroBitEvent(MICROBIT_ID_NOTIFY_ONE, MICROBIT_DISPLAY_EVT_FREE);
} }
/** /**
@ -306,7 +306,6 @@ void MicroBitDisplay::updateScrollImage()
scrollingImageRendered = true; scrollingImageRendered = true;
} }
/** /**
* Internal animateImage update method. * Internal animateImage update method.
* Paste the stored bitmap at the appropriate point and stop on the last frame. * Paste the stored bitmap at the appropriate point and stop on the last frame.
@ -317,6 +316,7 @@ void MicroBitDisplay::updateAnimateImage()
if (scrollingImagePosition <= -scrollingImage.getWidth() + (MICROBIT_DISPLAY_WIDTH + scrollingImageStride) && scrollingImageRendered) if (scrollingImagePosition <= -scrollingImage.getWidth() + (MICROBIT_DISPLAY_WIDTH + scrollingImageStride) && scrollingImageRendered)
{ {
animationMode = ANIMATION_MODE_NONE; animationMode = ANIMATION_MODE_NONE;
this->clear();
this->sendAnimationCompleteEvent(); this->sendAnimationCompleteEvent();
return; return;
} }
@ -333,38 +333,84 @@ void MicroBitDisplay::updateAnimateImage()
/** /**
* Resets the current given animation. * Resets the current given animation.
* @param delay the delay after which the animation is reset. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER.
*/ */
int MicroBitDisplay::resetAnimation(uint16_t delay) void MicroBitDisplay::stopAnimation()
{ {
//sanitise this value
if(delay <= 0 )
return MICROBIT_INVALID_PARAMETER;
// Reset any ongoing animation. // Reset any ongoing animation.
if (animationMode != ANIMATION_MODE_NONE) if (animationMode != ANIMATION_MODE_NONE)
{ {
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. // Clear the display and setup the animation timers.
this->image.clear(); this->image.clear();
this->animationDelay = delay; }
this->animationTick = delay-1;
/**
* 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 delay Optional parameter - the time for which to show the character. Zero displays the character forever.
* @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER.
*
* Example:
* @code
* uBit.display.printAsync('p');
* uBit.display.printAsync('p',100);
* @endcode
*/
int MicroBitDisplay::printAsync(char c, int delay)
{
//sanitise this value
if(delay < 0)
return MICROBIT_INVALID_PARAMETER;
// 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)
{
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
}
}
else
{
return MICROBIT_BUSY;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
/** /**
* Prints the given string to the display, one character at a time. * Prints the given string to the display, one character at a time, if the display is not in use.
* Uses the given delay between characters.
* Returns immediately, and executes the animation asynchronously. * Returns immediately, and executes the animation asynchronously.
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between characters, in milliseconds. Must be > 0. * @param delay The time to delay between characters, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -377,25 +423,75 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay)
if(delay <= 0 ) if(delay <= 0 )
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
this->resetAnimation(delay); if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
this->printingChar = 0; printingChar = 0;
this->printingText = s; printingText = s;
animationDelay = delay;
animationMode = ANIMATION_MODE_PRINT_TEXT; animationTick = 0;
animationMode = ANIMATION_MODE_PRINT_TEXT;
}
else
{
return MICROBIT_BUSY;
}
return MICROBIT_OK;
}
/**
* 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 milliseconds. set to 0 to display forever. (default 0).
*
* Example:
* @code
* MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n");
* uBit.display.print(i,400);
* @endcode
*/
int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int delay)
{
if(delay < 0)
return MICROBIT_INVALID_PARAMETER;
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
{
image.paste(i, x, y, alpha);
if(delay > 0)
{
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_PRINT_CHARACTER;
}
}
else
{
return MICROBIT_BUSY;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
/** /**
* Prints the given character to the display. * Prints the given character to the display, and wait for it to complete.
* *
* @param c The character to display. * @param c The character to display.
* @param delay The time to delay between characters, in milliseconds. Must be > 0. * @param delay The time to delay between characters, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
* uBit.display.print('p'); * uBit.display.print('p');
* uBit.display.print('p',100);
* @endcode * @endcode
*/ */
int MicroBitDisplay::print(char c, int delay) int MicroBitDisplay::print(char c, int delay)
@ -403,17 +499,21 @@ int MicroBitDisplay::print(char c, int delay)
if (delay < 0) if (delay < 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
image.print(c, 0, 0); // If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
if (delay == 0)
return MICROBIT_OK; // If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
this->animationDelay = delay; if (animationMode == ANIMATION_MODE_NONE)
animationMode = ANIMATION_MODE_PRINT_CHARACTER; {
this->printAsync(c, delay);
// Wait for completion. if (delay > 0)
nonce = uBit.MessageBus.nonce(); fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); }
else
{
return MICROBIT_CANCELLED;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -425,7 +525,7 @@ int MicroBitDisplay::print(char c, int delay)
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between characters, in milliseconds. Must be > 0. * @param delay The time to delay between characters, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -438,12 +538,20 @@ int MicroBitDisplay::print(ManagedString s, int delay)
if(delay <= 0 ) if(delay <= 0 )
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
// Start the effect. // If there's an ongoing animation, wait for our turn to display.
this->printAsync(s, delay); this->waitForFreeDisplay();
// Wait for completion. // If the display is free, it's our turn to display.
nonce = uBit.MessageBus.nonce(); // If someone called stopAnimation(), then we simply skip...
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); if (animationMode == ANIMATION_MODE_NONE)
{
this->printAsync(s, delay);
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
else
{
return MICROBIT_CANCELLED;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -454,7 +562,7 @@ int MicroBitDisplay::print(ManagedString s, int delay)
* *
* @param i The image to display. * @param i The image to display.
* @param delay The time to display the image for, or zero to show the image forever. Must be >= 0. * @param delay The time to display the image for, or zero to show the image forever. Must be >= 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -467,17 +575,21 @@ int MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay)
if(delay < 0) if(delay < 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
image.paste(i, x, y, alpha); // If there's an ongoing animation, wait for our turn to display.
this->waitForFreeDisplay();
if(delay == 0)
return MICROBIT_OK; // If the display is free, it's our turn to display.
// If someone called stopAnimation(), then we simply skip...
this->animationDelay = delay; if (animationMode == ANIMATION_MODE_NONE)
animationMode = ANIMATION_MODE_PRINT_CHARACTER; {
this->printAsync(i, x, y, alpha, delay);
// Wait for completion. if (delay > 0)
nonce = uBit.MessageBus.nonce(); fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); }
else
{
return MICROBIT_CANCELLED;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -489,7 +601,7 @@ int MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay)
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -502,13 +614,21 @@ int MicroBitDisplay::scrollAsync(ManagedString s, int delay)
if(delay <= 0) if(delay <= 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
this->resetAnimation(delay); // If the display is free, it's our turn to display.
if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
this->scrollingPosition = width-1; {
this->scrollingChar = 0; scrollingPosition = width-1;
this->scrollingText = s; scrollingChar = 0;
scrollingText = s;
animationMode = ANIMATION_MODE_SCROLL_TEXT;
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_SCROLL_TEXT;
}
else
{
return MICROBIT_BUSY;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -520,7 +640,7 @@ int MicroBitDisplay::scrollAsync(ManagedString s, int delay)
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -533,15 +653,23 @@ int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride)
//sanitise the delay value //sanitise the delay value
if(delay <= 0) if(delay <= 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
this->resetAnimation(delay);
this->scrollingImagePosition = stride < 0 ? width : -image.getWidth(); // If the display is free, it's our turn to display.
this->scrollingImageStride = stride; if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
this->scrollingImage = image; {
this->scrollingImageRendered = false; scrollingImagePosition = stride < 0 ? width : -image.getWidth();
scrollingImageStride = stride;
animationMode = ANIMATION_MODE_SCROLL_IMAGE; scrollingImage = image;
scrollingImageRendered = false;
animationDelay = delay;
animationTick = 0;
animationMode = ANIMATION_MODE_SCROLL_IMAGE;
}
else
{
return MICROBIT_BUSY;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -553,7 +681,7 @@ int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride)
* *
* @param s The string to display. * @param s The string to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -565,14 +693,25 @@ int MicroBitDisplay::scroll(ManagedString s, int delay)
//sanitise this value //sanitise this value
if(delay <= 0) if(delay <= 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
// 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);
}
else
{
return MICROBIT_CANCELLED;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -583,7 +722,7 @@ int MicroBitDisplay::scroll(ManagedString s, int delay)
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -597,12 +736,23 @@ int MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride)
if(delay <= 0) if(delay <= 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
// Start the effect. // If there's an ongoing animation, wait for our turn to display.
this->scrollAsync(image, delay, stride); this->waitForFreeDisplay();
// Wait for completion. // If the display is free, it's our turn to display.
nonce = uBit.MessageBus.nonce(); // If someone called stopAnimation(), then we simply skip...
fiber_wait_for_event(MICROBIT_ID_ALERT, nonce); 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);
}
else
{
return MICROBIT_CANCELLED;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -614,7 +764,7 @@ int MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride)
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -632,26 +782,26 @@ int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, in
if(delay <= 0) if(delay <= 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
// Assume right to left functionality, to align with scrollString() // If the display is free, we can display.
stride = -stride; if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED)
// Reset any ongoing animation.
if (animationMode != ANIMATION_MODE_NONE)
{ {
animationMode = ANIMATION_MODE_NONE; // Assume right to left functionality, to align with scrollString()
this->sendAnimationCompleteEvent(); stride = -stride;
}
this->animationDelay = delay;
this->animationTick = delay-1;
//calculate starting position which is offset by the stride //calculate starting position which is offset by the stride
this->scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS)?MICROBIT_DISPLAY_WIDTH + stride:startingPosition; scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition;
this->scrollingImageStride = stride; scrollingImageStride = stride;
this->scrollingImage = image; scrollingImage = image;
this->scrollingImageRendered = false; scrollingImageRendered = false;
animationMode = ANIMATION_MODE_ANIMATE_IMAGE; animationDelay = delay;
animationTick = delay-1;
animationMode = ANIMATION_MODE_ANIMATE_IMAGE;
}
else
{
return MICROBIT_BUSY;
}
return MICROBIT_OK; return MICROBIT_OK;
} }
@ -663,7 +813,7 @@ int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, in
* @param image The image to display. * @param image The image to display.
* @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0.
* @param stride The number of pixels to move in each update. Default value is the screen width. * @param stride The number of pixels to move in each update. Default value is the screen width.
* @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER.
* *
* Example: * Example:
* @code * @code
@ -680,14 +830,26 @@ int MicroBitDisplay::animate(MicroBitImage image, int delay, int stride, int sta
//sanitise the delay value //sanitise the delay value
if(delay <= 0) if(delay <= 0)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
// 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.
//TODO: Put this in when we merge tight-validation
//if (delay > 0)
fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE);
}
else
{
return MICROBIT_CANCELLED;
}
return MICROBIT_OK; return MICROBIT_OK;
} }

View File

@ -15,7 +15,7 @@
*/ */
Fiber *currentFiber = NULL; // The context in which the current fiber is executing. 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 *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. * Scheduler state.
@ -162,6 +162,10 @@ void scheduler_init()
idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
idleFiber->tcb.LR = (uint32_t) &idle_task; idleFiber->tcb.LR = (uint32_t) &idle_task;
// Register to receive events in the NOTIFY channel - this is used to implement wait-notify semantics
uBit.MessageBus.listen(MICROBIT_ID_NOTIFY, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE);
uBit.MessageBus.listen(MICROBIT_ID_NOTIFY_ONE, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE);
// Flag that we now have a scheduler running // Flag that we now have a scheduler running
uBit.flags |= MICROBIT_FLAG_SCHEDULER_RUNNING; uBit.flags |= MICROBIT_FLAG_SCHEDULER_RUNNING;
} }
@ -204,6 +208,7 @@ void scheduler_event(MicroBitEvent evt)
{ {
Fiber *f = waitQueue; Fiber *f = waitQueue;
Fiber *t; Fiber *t;
int notifyOneComplete = 0;
// Check the wait queue, and wake up any fibers as necessary. // Check the wait queue, and wake up any fibers as necessary.
while (f != NULL) while (f != NULL)
@ -213,8 +218,21 @@ void scheduler_event(MicroBitEvent evt)
// extract the event data this fiber is blocked on. // extract the event data this fiber is blocked on.
uint16_t id = f->context & 0xFFFF; uint16_t id = f->context & 0xFFFF;
uint16_t value = (f->context & 0xFFFF0000) >> 16; uint16_t value = (f->context & 0xFFFF0000) >> 16;
if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value)) // Special case for the NOTIFY_ONE channel...
if ((evt.source == MICROBIT_ID_NOTIFY_ONE && id == MICROBIT_ID_NOTIFY) && (value == MICROBIT_EVT_ANY || value == evt.value))
{
if (!notifyOneComplete)
{
// Wakey wakey!
dequeue_fiber(f);
queue_fiber(f,&runQueue);
notifyOneComplete = 1;
}
}
// Normal case.
else if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value))
{ {
// Wakey wakey! // Wakey wakey!
dequeue_fiber(f); dequeue_fiber(f);
@ -225,7 +243,8 @@ void scheduler_event(MicroBitEvent evt)
} }
// Unregister this event, as we've woken up all the fibers with this match. // Unregister this event, as we've woken up all the fibers with this match.
uBit.MessageBus.ignore(evt.source, evt.value, scheduler_event); if (evt.source != MICROBIT_ID_NOTIFY && evt.source != MICROBIT_ID_NOTIFY_ONE)
uBit.MessageBus.ignore(evt.source, evt.value, scheduler_event);
} }
@ -309,7 +328,9 @@ void fiber_wait_for_event(uint16_t id, uint16_t value)
queue_fiber(f, &waitQueue); queue_fiber(f, &waitQueue);
// Register to receive this event, so we can wake up the fiber when it happens. // Register to receive this event, so we can wake up the fiber when it happens.
uBit.MessageBus.listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); // Special case for teh notify channel, as we always stay registered for that.
if (id != MICROBIT_ID_NOTIFY && id != MICROBIT_ID_NOTIFY_ONE)
uBit.MessageBus.listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE);
// Finally, enter the scheduler. // Finally, enter the scheduler.
schedule(); schedule();

View File

@ -23,6 +23,7 @@ MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(
this->cb_arg = NULL; this->cb_arg = NULL;
this->flags = flags; this->flags = flags;
this->next = NULL; this->next = NULL;
this->evt_queue = NULL;
} }
/** /**
@ -41,6 +42,7 @@ MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(
this->cb_arg = arg; this->cb_arg = arg;
this->flags = flags | MESSAGE_BUS_LISTENER_PARAMETERISED; this->flags = flags | MESSAGE_BUS_LISTENER_PARAMETERISED;
this->next = NULL; this->next = NULL;
this->evt_queue = NULL;
} }
/** /**
@ -58,16 +60,23 @@ MicroBitListener::~MicroBitListener()
*/ */
void MicroBitListener::queue(MicroBitEvent e) void MicroBitListener::queue(MicroBitEvent e)
{ {
MicroBitEventQueueItem *q = new MicroBitEventQueueItem(e); int queueDepth;
MicroBitEventQueueItem *p = evt_queue; MicroBitEventQueueItem *p = evt_queue;
if (evt_queue == NULL) if (evt_queue == NULL)
evt_queue = q; evt_queue = new MicroBitEventQueueItem(e);
else else
{ {
while (p->next != NULL) queueDepth = 1;
p = p->next;
p->next = q; while (p->next != NULL)
{
p = p->next;
queueDepth++;
}
if (queueDepth < MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH)
p->next = new MicroBitEventQueueItem(e);
} }
} }

View File

@ -15,18 +15,7 @@ MicroBitMessageBus::MicroBitMessageBus()
this->listeners = NULL; this->listeners = NULL;
this->evt_queue_head = NULL; this->evt_queue_head = NULL;
this->evt_queue_tail = NULL; this->evt_queue_tail = NULL;
this->nonce_val = 0; this->queueLength = 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++;
} }
/** /**
@ -78,7 +67,7 @@ void async_callback(void *param)
else else
listener->cb(listener->evt); listener->cb(listener->evt);
// If there are more events to process, dequeue te next one and process it. // If there are more events to process, dequeue the next one and process it.
if ((listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) && listener->evt_queue) if ((listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) && listener->evt_queue)
{ {
MicroBitEventQueueItem *item = listener->evt_queue; MicroBitEventQueueItem *item = listener->evt_queue;
@ -86,6 +75,9 @@ void async_callback(void *param)
listener->evt = item->evt; listener->evt = item->evt;
listener->evt_queue = listener->evt_queue->next; listener->evt_queue = listener->evt_queue->next;
delete item; delete item;
// We spin the scheduler here, to preven any particular event handler from continuously holding onto resources.
schedule();
} }
else else
break; break;
@ -95,7 +87,6 @@ void async_callback(void *param)
listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY; listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY;
} }
/** /**
* Queue the given event for processing at a later time. * Queue the given event for processing at a later time.
* Add the given event at the tail of our queue. * Add the given event at the tail of our queue.
@ -106,23 +97,47 @@ void MicroBitMessageBus::queueEvent(MicroBitEvent &evt)
{ {
int processingComplete; int processingComplete;
// Firstly, process all handler regsitered as URGENT. These pre-empt the queue, and are useful for fast, high priority services. MicroBitEventQueueItem *prev = evt_queue_tail;
processingComplete = this->process(evt, MESSAGE_BUS_LISTENER_URGENT);
if (!processingComplete) // Now process all handler regsitered as URGENT.
// These pre-empt the queue, and are useful for fast, high priority services.
processingComplete = this->process(evt, true);
// If we've already processed all event handlers, we're all done.
// No need to queue the event.
if (processingComplete)
return;
// If we need to queue, but there is no space, then there's nothg we can do.
if (queueLength >= MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH)
return;
// Otherwise, we need to queue this event for later processing...
// We queue this event at the tail of the queue at the point where we entered queueEvent()
// This is important as the processing above *may* have generated further events, and
// we want to maintain ordering of events.
MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt);
// The queue was empty when we entered this function, so queue our event at the start of the queue.
__disable_irq();
if (prev == NULL)
{ {
// We need to queue this event for later processing... item->next = evt_queue_head;
MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt); evt_queue_head = item;
}
__disable_irq(); else
{
if (evt_queue_tail == NULL) item->next = prev->next;
evt_queue_head = evt_queue_tail = item; prev->next = item;
else
evt_queue_tail->next = item;
__enable_irq();
} }
if (item->next == NULL)
evt_queue_tail = item;
queueLength++;
__enable_irq();
} }
/** /**
@ -144,13 +159,55 @@ MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent()
if (evt_queue_head == NULL) if (evt_queue_head == NULL)
evt_queue_tail = NULL; evt_queue_tail = NULL;
queueLength--;
} }
__enable_irq(); __enable_irq();
return item; return item;
} }
/**
* Cleanup any MicroBitListeners marked for deletion from the list.
* @return The number of listeners removed from the list.
*/
int MicroBitMessageBus::deleteMarkedListeners()
{
MicroBitListener *l, *p;
int removed = 0;
l = listeners;
p = NULL;
// Walk this list of event handlers. Delete any that match the given listener.
while (l != NULL)
{
if (l->flags & MESSAGE_BUS_LISTENER_DELETING && !l->flags & MESSAGE_BUS_LISTENER_BUSY)
{
if (p == NULL)
listeners = l->next;
else
p->next = l->next;
// delete the listener.
MicroBitListener *t = l;
l = l->next;
delete t;
removed++;
continue;
}
p = l;
l = l->next;
}
return removed;
}
/** /**
* Periodic callback from MicroBit. * Periodic callback from MicroBit.
* Process at least one event from the event queue, if it is not empty. * Process at least one event from the event queue, if it is not empty.
@ -158,6 +215,9 @@ MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent()
*/ */
void MicroBitMessageBus::idleTick() void MicroBitMessageBus::idleTick()
{ {
// Clear out any listeners marked for deletion
this->deleteMarkedListeners();
MicroBitEventQueueItem *item = this->dequeueEvent(); MicroBitEventQueueItem *item = this->dequeueEvent();
// Whilst there are events to process and we have no useful other work to do, pull them off the queue and process them. // Whilst there are events to process and we have no useful other work to do, pull them off the queue and process them.
@ -219,20 +279,23 @@ void MicroBitMessageBus::send(MicroBitEvent evt)
* This will attempt to call the event handler directly, but spawn a fiber should that * This will attempt to call the event handler directly, but spawn a fiber should that
* event handler attempt a blocking operation. * event handler attempt a blocking operation.
* @param evt The event to be delivered. * @param evt The event to be delivered.
* @param mask The type of listeners to process (optional). Matches MicroBitListener flags. If not defined, all standard listeners will be processed. * @param urgent The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed
* @return The 1 if all matching listeners were processed, 0 if further processing is required. * otherwise, all other (standard) listeners will be processed.
* @return 1 if all matching listeners were processed, 0 if further processing is required.
*/ */
int MicroBitMessageBus::process(MicroBitEvent &evt, uint32_t mask) int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent)
{ {
MicroBitListener *l; MicroBitListener *l;
int complete = 1; int complete = 1;
bool listenerUrgent;
l = listeners; l = listeners;
while (l != NULL) while (l != NULL)
{ {
if((l->id == evt.source || l->id == MICROBIT_ID_ANY) && (l->value == evt.value || l->value == MICROBIT_EVT_ANY)) if((l->id == evt.source || l->id == MICROBIT_ID_ANY) && (l->value == evt.value || l->value == MICROBIT_EVT_ANY))
{ {
if(l->flags & mask) listenerUrgent = (l->flags & MESSAGE_BUS_LISTENER_IMMEDIATE) == MESSAGE_BUS_LISTENER_IMMEDIATE;
if(listenerUrgent == urgent && !(l->flags & MESSAGE_BUS_LISTENER_DELETING))
{ {
l->evt = evt; l->evt = evt;
@ -292,6 +355,7 @@ int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent)
return MICROBIT_OK; return MICROBIT_OK;
delete newListener; delete newListener;
return MICROBIT_NO_RESOURCES; return MICROBIT_NO_RESOURCES;
} }
@ -304,9 +368,10 @@ int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent,
MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg, flags); MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg, flags);
if(add(newListener) == MICROBIT_OK) if(add(newListener) == MICROBIT_OK)
return MICROBIT_OK; return MICROBIT_OK;
delete newListener; delete newListener;
return MICROBIT_NO_RESOURCES; return MICROBIT_NO_RESOURCES;
} }
@ -398,7 +463,15 @@ int MicroBitMessageBus::add(MicroBitListener *newListener)
methodCallback = (newListener->flags & MESSAGE_BUS_LISTENER_METHOD) && (l->flags & MESSAGE_BUS_LISTENER_METHOD); methodCallback = (newListener->flags & MESSAGE_BUS_LISTENER_METHOD) && (l->flags & MESSAGE_BUS_LISTENER_METHOD);
if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb)) if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb))
{
// We have a perfect match for this event listener already registered.
// If it's marked for deletion, we simply resurrect the listener, and we're done.
// Either way, we return an error code, as the *new* listener should be released...
if(l->flags & MESSAGE_BUS_LISTENER_DELETING)
l->flags &= ~MESSAGE_BUS_LISTENER_DELETING;
return MICROBIT_NOT_SUPPORTED; return MICROBIT_NOT_SUPPORTED;
}
l = l->next; l = l->next;
} }
@ -457,7 +530,7 @@ int MicroBitMessageBus::add(MicroBitListener *newListener)
*/ */
int MicroBitMessageBus::remove(MicroBitListener *listener) int MicroBitMessageBus::remove(MicroBitListener *listener)
{ {
MicroBitListener *l, *p; MicroBitListener *l;
int removed = 0; int removed = 0;
//handler can't be NULL! //handler can't be NULL!
@ -465,7 +538,6 @@ int MicroBitMessageBus::remove(MicroBitListener *listener)
return MICROBIT_INVALID_PARAMETER; return MICROBIT_INVALID_PARAMETER;
l = listeners; l = listeners;
p = NULL;
// Walk this list of event handlers. Delete any that match the given listener. // Walk this list of event handlers. Delete any that match the given listener.
while (l != NULL) while (l != NULL)
@ -473,33 +545,24 @@ int MicroBitMessageBus::remove(MicroBitListener *listener)
if ((listener->flags & MESSAGE_BUS_LISTENER_METHOD) == (l->flags & MESSAGE_BUS_LISTENER_METHOD)) if ((listener->flags & MESSAGE_BUS_LISTENER_METHOD) == (l->flags & MESSAGE_BUS_LISTENER_METHOD))
{ {
if(((listener->flags & MESSAGE_BUS_LISTENER_METHOD) && (*l->cb_method == *listener->cb_method)) || if(((listener->flags & MESSAGE_BUS_LISTENER_METHOD) && (*l->cb_method == *listener->cb_method)) ||
((!(listener->flags & MESSAGE_BUS_LISTENER_METHOD) && l->cb == listener->cb))) ((!(listener->flags & MESSAGE_BUS_LISTENER_METHOD) && l->cb == listener->cb)))
{ {
if ((listener->id == MICROBIT_ID_ANY || listener->id == l->id) && (listener->value == MICROBIT_EVT_ANY || listener->value == l->value)) if ((listener->id == MICROBIT_ID_ANY || listener->id == l->id) && (listener->value == MICROBIT_EVT_ANY || listener->value == l->value))
{ {
// Found a match. Remove from the list. // Found a match. mark this to be removed from the list.
if (p == NULL) l->flags |= MESSAGE_BUS_LISTENER_DELETING;
listeners = l->next;
else
p->next = l->next;
// delete the listener.
MicroBitListener *t = l;
l = l->next;
delete t;
removed++; removed++;
continue;
} }
} }
} }
p = l;
l = l->next; l = l->next;
} }
return MICROBIT_OK; if (removed > 0)
return MICROBIT_OK;
else
return MICROBIT_INVALID_PARAMETER;
} }
/** /**

View File

@ -66,6 +66,16 @@ int MicroBitMultiButton::isSubButtonHeld(uint16_t button)
return 0; return 0;
} }
int MicroBitMultiButton::isSubButtonSupressed(uint16_t button)
{
if (button == button1)
return status & MICROBIT_MULTI_BUTTON_SUPRESSED_1;
if (button == button2)
return status & MICROBIT_MULTI_BUTTON_SUPRESSED_2;
return 0;
}
void MicroBitMultiButton::setButtonState(uint16_t button, int value) void MicroBitMultiButton::setButtonState(uint16_t button, int value)
{ {
@ -105,6 +115,26 @@ void MicroBitMultiButton::setHoldState(uint16_t button, int value)
} }
} }
void MicroBitMultiButton::setSupressedState(uint16_t button, int value)
{
if (button == button1)
{
if (value)
status |= MICROBIT_MULTI_BUTTON_SUPRESSED_1;
else
status &= ~MICROBIT_MULTI_BUTTON_SUPRESSED_1;
}
if (button == button2)
{
if (value)
status |= MICROBIT_MULTI_BUTTON_SUPRESSED_2;
else
status &= ~MICROBIT_MULTI_BUTTON_SUPRESSED_2;
}
}
void MicroBitMultiButton::onEvent(MicroBitEvent evt) void MicroBitMultiButton::onEvent(MicroBitEvent evt)
{ {
int button = evt.source; int button = evt.source;
@ -118,30 +148,40 @@ void MicroBitMultiButton::onEvent(MicroBitEvent evt)
MicroBitEvent e(id, MICROBIT_BUTTON_EVT_DOWN); MicroBitEvent e(id, MICROBIT_BUTTON_EVT_DOWN);
break; break;
case MICROBIT_BUTTON_EVT_UP:
setButtonState(button, 0);
setHoldState(button, 0);
if(isSubButtonPressed(otherButton))
MicroBitEvent e(id, MICROBIT_BUTTON_EVT_UP);
break;
case MICROBIT_BUTTON_EVT_CLICK:
case MICROBIT_BUTTON_EVT_LONG_CLICK:
setButtonState(button, 0);
setHoldState(button, 0);
if(isSubButtonPressed(otherButton))
MicroBitEvent e(id, evt.value);
break;
case MICROBIT_BUTTON_EVT_HOLD: case MICROBIT_BUTTON_EVT_HOLD:
setHoldState(button, 1); setHoldState(button, 1);
if(isSubButtonHeld(otherButton)) if(isSubButtonHeld(otherButton))
MicroBitEvent e(id, MICROBIT_BUTTON_EVT_HOLD); MicroBitEvent e(id, MICROBIT_BUTTON_EVT_HOLD);
break; break;
case MICROBIT_BUTTON_EVT_UP:
if(isSubButtonPressed(otherButton))
{
MicroBitEvent e(id, MICROBIT_BUTTON_EVT_UP);
if (isSubButtonHeld(button) && isSubButtonHeld(otherButton))
MicroBitEvent e(id, MICROBIT_BUTTON_EVT_LONG_CLICK);
else
MicroBitEvent e(id, MICROBIT_BUTTON_EVT_CLICK);
setSupressedState(otherButton, 1);
}
else if (!isSubButtonSupressed(button))
{
if (isSubButtonHeld(button))
MicroBitEvent e(button, MICROBIT_BUTTON_EVT_LONG_CLICK);
else
MicroBitEvent e(button, MICROBIT_BUTTON_EVT_CLICK);
}
setButtonState(button, 0);
setHoldState(button, 0);
setSupressedState(button, 0);
break;
} }
} }

View File

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