From 538e1c48bd2f2291475def2600e41b227684a0cd Mon Sep 17 00:00:00 2001 From: James Devine Date: Wed, 12 Aug 2015 11:53:41 +0100 Subject: [PATCH] microbit-dal: Initial Commit This is the first commit of the microbit-dal on GitHub. This repository contains the runtime, which is a light weight operating system developed by Lancaster University. --- inc/DynamicPwm.h | 104 ++++ inc/ErrorNo.h | 15 + inc/ExternalEvents.h | 8 + inc/MESEvents.h | 82 +++ inc/ManagedString.h | 306 +++++++++++ inc/ManagedType.h | 162 ++++++ inc/MicroBit.h | 279 ++++++++++ inc/MicroBitAccelerometer.h | 161 ++++++ inc/MicroBitButton.h | 92 ++++ inc/MicroBitCompass.h | 262 +++++++++ inc/MicroBitCompat.h | 73 +++ inc/MicroBitComponent.h | 62 +++ inc/MicroBitDFUService.h | 101 ++++ inc/MicroBitDisplay.h | 530 ++++++++++++++++++ inc/MicroBitEvent.h | 49 ++ inc/MicroBitEventService.h | 59 +++ inc/MicroBitFiber.h | 221 ++++++++ inc/MicroBitFont.h | 42 ++ inc/MicroBitI2C.h | 40 ++ inc/MicroBitIO.h | 52 ++ inc/MicroBitImage.h | 368 +++++++++++++ inc/MicroBitMalloc.h | 39 ++ inc/MicroBitMatrixMaps.h | 73 +++ inc/MicroBitMessageBus.h | 141 +++++ inc/MicroBitMultiButton.h | 71 +++ inc/MicroBitPin.h | 159 ++++++ inc/MicroBitSerial.h | 117 ++++ module.json | 17 + source/CortexContextSwitch.s | 187 +++++++ source/CortexContextSwitch.s.gcc | 187 +++++++ source/DynamicPwm.cpp | 177 +++++++ source/ManagedString.cpp | 422 +++++++++++++++ source/MicroBit.cpp | 334 ++++++++++++ source/MicroBitAccelerometer.cpp | 195 +++++++ source/MicroBitButton.cpp | 102 ++++ source/MicroBitCompass.cpp | 338 ++++++++++++ source/MicroBitCompat.cpp | 61 +++ source/MicroBitDFUService.cpp | 290 ++++++++++ source/MicroBitDisplay.cpp | 884 +++++++++++++++++++++++++++++++ source/MicroBitEvent.cpp | 46 ++ source/MicroBitEventService.cpp | 85 +++ source/MicroBitFiber.cpp | 482 +++++++++++++++++ source/MicroBitFont.cpp | 48 ++ source/MicroBitI2C.cpp | 76 +++ source/MicroBitIO.cpp | 40 ++ source/MicroBitImage.cpp | 826 +++++++++++++++++++++++++++++ source/MicroBitMessageBus.cpp | 255 +++++++++ source/MicroBitMultiButton.cpp | 156 ++++++ source/MicroBitPin.cpp | 215 ++++++++ source/MicroBitSerial.cpp | 186 +++++++ source/MicroBitSuperMain.cpp | 76 +++ 51 files changed, 9353 insertions(+) create mode 100644 inc/DynamicPwm.h create mode 100644 inc/ErrorNo.h create mode 100644 inc/ExternalEvents.h create mode 100644 inc/MESEvents.h create mode 100644 inc/ManagedString.h create mode 100644 inc/ManagedType.h create mode 100644 inc/MicroBit.h create mode 100644 inc/MicroBitAccelerometer.h create mode 100644 inc/MicroBitButton.h create mode 100644 inc/MicroBitCompass.h create mode 100644 inc/MicroBitCompat.h create mode 100644 inc/MicroBitComponent.h create mode 100644 inc/MicroBitDFUService.h create mode 100644 inc/MicroBitDisplay.h create mode 100644 inc/MicroBitEvent.h create mode 100644 inc/MicroBitEventService.h create mode 100644 inc/MicroBitFiber.h create mode 100644 inc/MicroBitFont.h create mode 100644 inc/MicroBitI2C.h create mode 100644 inc/MicroBitIO.h create mode 100644 inc/MicroBitImage.h create mode 100644 inc/MicroBitMalloc.h create mode 100644 inc/MicroBitMatrixMaps.h create mode 100644 inc/MicroBitMessageBus.h create mode 100644 inc/MicroBitMultiButton.h create mode 100644 inc/MicroBitPin.h create mode 100644 inc/MicroBitSerial.h create mode 100644 module.json create mode 100644 source/CortexContextSwitch.s create mode 100644 source/CortexContextSwitch.s.gcc create mode 100644 source/DynamicPwm.cpp create mode 100644 source/ManagedString.cpp create mode 100644 source/MicroBit.cpp create mode 100644 source/MicroBitAccelerometer.cpp create mode 100644 source/MicroBitButton.cpp create mode 100644 source/MicroBitCompass.cpp create mode 100644 source/MicroBitCompat.cpp create mode 100644 source/MicroBitDFUService.cpp create mode 100644 source/MicroBitDisplay.cpp create mode 100644 source/MicroBitEvent.cpp create mode 100644 source/MicroBitEventService.cpp create mode 100644 source/MicroBitFiber.cpp create mode 100644 source/MicroBitFont.cpp create mode 100644 source/MicroBitI2C.cpp create mode 100644 source/MicroBitIO.cpp create mode 100644 source/MicroBitImage.cpp create mode 100644 source/MicroBitMessageBus.cpp create mode 100644 source/MicroBitMultiButton.cpp create mode 100644 source/MicroBitPin.cpp create mode 100644 source/MicroBitSerial.cpp create mode 100644 source/MicroBitSuperMain.cpp diff --git a/inc/DynamicPwm.h b/inc/DynamicPwm.h new file mode 100644 index 0000000..25a5863 --- /dev/null +++ b/inc/DynamicPwm.h @@ -0,0 +1,104 @@ +#include "mbed.h" + +#ifndef MICROBIT_DYNAMIC_PWM_H +#define MICROBIT_DYNAMIC_PWM_H + +#define NO_PWMS 3 +#define MICROBIT_DISPLAY_PWM_PERIOD 1000 + +enum PwmPersistence +{ + PWM_PERSISTENCE_TRANSIENT = 1, + PWM_PERSISTENCE_PERSISTENT = 2, +}; + +/** + * Class definition for DynamicPwm. + * + * This class addresses a few issues found in the underlying libraries. + * This provides the ability for a neat, clean swap between PWM channels. + */ +class DynamicPwm : public PwmOut +{ + private: + static DynamicPwm* pwms[NO_PWMS]; + static uint8_t lastUsed; + uint8_t flags; + + + /** + * An internal constructor used when allocating a new DynamicPwm representation + * @param pin the name of the pin for the pwm to target + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + * @param period the frequency of the pwm channel in us. + */ + DynamicPwm(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT, int period = MICROBIT_DISPLAY_PWM_PERIOD); + + public: + + /** + * Redirects the pwm channel to point at a different pin. + * @param pin the new pin to direct PWM at. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->redirect(PinName n2); // pwm is now produced on n2 + * @endcode + */ + void redirect(PinName pin); + + + /** + * Retrieves a pointer to the first available free pwm channel - or the first one that can be reallocated. + * @param pin the name of the pin for the pwm to target + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + * @param period the frequency of the pwm channel in us. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * @endcode + */ + static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT, int period = MICROBIT_DISPLAY_PWM_PERIOD); + + /** + * Frees this DynamicPwm instance if the pointer is valid. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->free(); + * @endcode + */ + void free(); + + /** + * Retreives the pin name associated with this DynamicPwm instance. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPinName(); // equal to n + * @endcode + */ + PinName getPinName(); + + /** + * Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->setPeriodUs(1000); // period now is 1ms + * @endcode + * + * @note The display uses the pwm module, if you change this value the display may flicker. + */ + void setPeriodUs(int period); + +}; + +#endif diff --git a/inc/ErrorNo.h b/inc/ErrorNo.h new file mode 100644 index 0000000..845b440 --- /dev/null +++ b/inc/ErrorNo.h @@ -0,0 +1,15 @@ +#ifndef ERROR_NO_H +#define ERROR_NO_H + +/** + * Error codes using in the Micro:bit runtime. + */ +enum Error{ + MICROBIT_INVALID_VALUE = -1, // currently only used in MicroBit.cpp rand function when the max is less or equal to 0. + MICROBIT_IO_OP_NA = -2, // used in MicroBitPin.cpp for when a pin cannot perform a transition. (microbit io operation not allowed) + MICROBIT_COMPASS_IS_CALIBRATING = -3, // used in MicroBitPin.cpp for when a pin cannot perform a transition. (microbit io operation not allowed) + MICROBIT_COMPASS_CALIBRATE_REQUIRED = -4, + MICROBIT_OOM = 20, // the MicroBit Out of memory error code... + MICROBIT_I2C_LOCKUP = 10 // the MicroBit I2C bus has locked up +}; +#endif diff --git a/inc/ExternalEvents.h b/inc/ExternalEvents.h new file mode 100644 index 0000000..3606df0 --- /dev/null +++ b/inc/ExternalEvents.h @@ -0,0 +1,8 @@ +#ifndef EXTERNAL_EVENTS_H +#define EXTERNAL_EVENTS_H + +#define MICROBIT_ID_BLE 1000 + +#include "MESEvents.h" + +#endif diff --git a/inc/MESEvents.h b/inc/MESEvents.h new file mode 100644 index 0000000..676a04b --- /dev/null +++ b/inc/MESEvents.h @@ -0,0 +1,82 @@ +#ifndef MES_EVENTS_H +#define MES_EVENTS_H + +// +// MicroBit Event Service Event ID's and values +// + +// +// Events that master devices respond to: +// +#define MES_REMOTE_CONTROL_ID 1001 +#define MES_REMOTE_CONTROL_EVT_PLAY 0 +#define MES_REMOTE_CONTROL_EVT_PAUSE 1 +#define MES_REMOTE_CONTROL_EVT_STOP 2 +#define MES_REMOTE_CONTROL_EVT_NEXTTRACK 3 +#define MES_REMOTE_CONTROL_EVT_PREVTRACK 4 +#define MES_REMOTE_CONTROL_EVT_FORWARD 5 +#define MES_REMOTE_CONTROL_EVT_REWIND 6 +#define MES_REMOTE_CONTROL_EVT_VOLUMEUP 7 +#define MES_REMOTE_CONTROL_EVT_VOLUMEDOWN 8 + + +#define MES_CAMERA_ID 1002 +#define MES_CAMERA_EVT_LAUNCH_PHOTO_MODE 0 +#define MES_CAMERA_EVT_LAUNCH_VIDEO_MODE 1 +#define MES_CAMERA_EVT_TAKE_PHOTO 2 +#define MES_CAMERA_EVT_START_VIDEO_CAPTURE 3 +#define MES_CAMERA_EVT_STOP_VIDEO_CAPTURE 4 +#define MES_CAMERA_EVT_STOP_PHOTO_MODE 5 +#define MES_CAMERA_EVT_STOP_VIDEO_MODE 6 +#define MES_CAMERA_EVT_TOGGLE_FRONT_REAR 7 + + +#define MES_AUDIO_RECORDER_ID 1003 +#define MES_AUDIO_RECORDER_EVT_LAUNCH 0 +#define MES_AUDIO_RECORDER_EVT_START_CAPTURE 1 +#define MES_AUDIO_RECORDER_EVT_STOP_CAPTURE 2 +#define MES_AUDIO_RECORDER_EVT_STOP 3 + + +#define MES_ALERTS_ID 1004 +#define MES_ALERT_EVT_DISPLAY_TOAST 0 +#define MES_ALERT_EVT_VIBRATE 1 +#define MES_ALERT_EVT_PLAY_SOUND 2 +#define MES_ALERT_EVT_PLAY_RINGTONE 3 +#define MES_ALERT_EVT_FIND_MY_PHONE 4 +#define MES_ALERT_EVT_ALARM1 5 +#define MES_ALERT_EVT_ALARM2 6 +#define MES_ALERT_EVT_ALARM3 7 +#define MES_ALERT_EVT_ALARM4 8 +#define MES_ALERT_EVT_ALARM5 9 +#define MES_ALERT_EVT_ALARM6 10 + +// +// Events that master devices generate: +// +#define MES_SIGNAL_STRENGTH_ID 1101 +#define MES_SIGNAL_STRENGTH_EVT_NO_BAR 0 +#define MES_SIGNAL_STRENGTH_EVT_ONE_BAR 1 +#define MES_SIGNAL_STRENGTH_EVT_TWO_BAR 2 +#define MES_SIGNAL_STRENGTH_EVT_THREE_BAR 3 +#define MES_SIGNAL_STRENGTH_EVT_FOUR_BAR 4 + +#define MES_PLAY_CONTROLLER_ID 1102 +#define MES_BUTTON_UP 0 +#define MES_BUTTON_DOWN 1 +#define MES_BUTTON_RIGHT 2 +#define MES_BUTTON_LEFT 3 +#define MES_BUTTON_A 4 +#define MES_BUTTON_B 5 +#define MES_BUTTON_C 6 +#define MES_BUTTON_D 7 + +#define MES_DEVICE_INFO_ID 1103 +#define MES_DEVICE_ORIENTATION_LANDSCAPE 0 +#define MES_DEVICE_ORIENTATION_PORTRAIT 1 +#define MES_DEVICE_GESTURE_NONE 2 +#define MES_DEVICE_GESTURE_DEVICE_SHAKEN 3 +#define MES_DEVICE_DISPLAY_OFF 4 +#define MES_DEVICE_DISPLAY_ON 5 + +#endif diff --git a/inc/ManagedString.h b/inc/ManagedString.h new file mode 100644 index 0000000..81c06bf --- /dev/null +++ b/inc/ManagedString.h @@ -0,0 +1,306 @@ +#ifndef MANAGED_STRING_H +#define MANAGED_STRING_H + +#include "mbed.h" + +/** + * Class definition for a ManagedString. + * + * Uses basic reference counting to implement a copy-assignable, immutable string. + * This maps closely to the constructs found in many high level application languages, + * such as Touch Develop. + * + * Written from first principles here, for several reasons: + * 1) std::shared_ptr is not yet availiable on the ARMCC compiler + * 2) to reduce memory footprint - we don't need many of the other features in the std library + * 3) it makes an interestin case study for anyone interested in seeing how it works! + */ +class ManagedString +{ + // Internally we record the string as a char *, but control access to this to proide immutability + // and reference counting. + char *data; + int16_t *ref; + int16_t len; + + public: + + /** + * Constructor. + * Create a managed string from a pointer to an 8-bit character buffer. + * The buffer is copied to ensure sage memory management (the supplied + * character buffer may be decalred on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * + * Example: + * @code + * ManagedString s("abcdefg"); + * @endcode + */ + ManagedString(const char *str); + + /** + * Constructor. + * Create a managed string from a given integer. + * + * @param value The integer from which to create the ManagedString + * + * Example: + * @code + * ManagedString s(20); + * @endcode + */ + ManagedString(const int value); + + + /** + * Constructor. + * Create a managed string from a given char. + * + * @param value The char from which to create the ManagedString + * + * Example: + * @code + * ManagedString s('a'); + * @endcode + */ + ManagedString(const char value); + + /** + * Constructor. + * Create a managed string from a pointer to an 8-bit character buffer of a given length. + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * @param length The length of the character array + * + * Example: + * @code + * ManagedString s("abcdefg",7); // this is generally used for substring... why not use a normal char * constructor? + * @endcode + */ + ManagedString(const char *str, const int16_t length); + + /** + * Copy constructor. + * Makes a new ManagedString identical to the one supplied. + * Shares the character buffer and reference count with the supplied ManagedString. + * + * @param s The ManagedString to copy. + * + * Example: + * @code + * ManagedString s("abcdefg"); + * ManagedString p(s); + * @endcode + */ + ManagedString(const ManagedString &s); + + /** + * Default constructor. + * + * Create an empty ManagedString. + * + * Example: + * @code + * ManagedString s(); + * @endcode + */ + ManagedString(); + + /** + * Destructor. + * + * Free this ManagedString, and decrement the reference count to the + * internal character buffer. If we're holding the last reference, + * also free the character buffer and reference counter. + */ + ~ManagedString(); + + /** + * Copy assign operation. + * + * Called when one ManagedString is assigned the value of another. + * If the ManagedString being assigned is already refering to a character buffer, + * decrement the reference count and free up the buffer as necessary. + * Then, update our character buffer to refer to that of the supplied ManagedString, + * and increase its reference count. + * + * @param s The ManagedString to copy. + * + * Example: + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * p = s // p now points to s, s' ref is incremented + * @endcode + */ + ManagedString& operator = (const ManagedString& s); + + /** + * Equality operation. + * + * Called when one ManagedString is tested to be equal to another using the '==' operator. + * + * @param s The ManagedString to test ourselves against. + * @return true if this ManagedString is identical to the one supplied, false otherwise. + * + * Example: + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * + * if(p==s) + * print("We are the same!"); + * else + * print("We are different!"); //p is not equal to s - this will be called + * @endcode + */ + bool operator== (const ManagedString& s); + + /** + * Inequality operation. + * + * Called when one ManagedString is tested to be less than another using the '<' operator. + * + * @param s The ManagedString to test ourselves against. + * @return true if this ManagedString is alphabetically less than to the one supplied, false otherwise. + * + * Example: + * @code + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(s' operator. + * + * @param s The ManagedString to test ourselves against. + * @return true if this ManagedString is alphabetically greater than to the one supplied, false otherwise. + * + * Example: + * @code + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(p>a) + * print("b is after a!"); //b is after a + * else + * print("a is after b!"); + * @endcode + */ + bool operator> (const ManagedString& s); + + /** + * Extracts a ManagedString from this string, at the position provided. + * + * @param start The index of the first character to extract, indexed from zero. + * @param length The number of characters to extract from the start position + * @return a ManagedString representing the requested substring. + * + * Example: + * @code + * ManagedString s("abcdefg"); + * + * print(s.substring(0,2)) // prints "ab" + * @endcode + */ + ManagedString substring(int16_t start, int16_t length); + + /** + * Concatenates this string with the one provided. + * + * @param s The ManagedString to concatenate. + * @return a new ManagedString representing the joined strings. + * + * Example: + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh") + * + * print(s + p) // prints "abcdefgh" + * @endcode + */ + ManagedString operator+ (ManagedString& s); + + /** + * Provides a character value at a given position in the string, indexed from zero. + * + * @param index The position of the character to return. + * @return the character at posisiton index, zero if index is invalid. + * + * Example: + * @code + * ManagedString s("abcd"); + * + * print(s.charAt(1)) // prints "b" + * @endcode + */ + char charAt(int16_t index); + + + /** + * Provides an immutable 8 bit wide haracter buffer representing this string. + * + * @return a pointer to the character buffer. + */ + const char *toCharArray(); + + /** + * Determines the length of this ManagedString in characters. + * + * @return the length of the string in characters. + * + * Example: + * @code + * ManagedString s("abcd"); + * + * print(s.length()) // prints "4" + * @endcode + */ + int16_t length(); + + /** + * Empty String constant + */ + static ManagedString EmptyString; + + private: + + /** + * Internal constructor helper. + * Configures this ManagedString to refer to the static EmptyString + */ + void initEmpty(); + + /** + * Internal constructor helper. + * creates this ManagedString based on a given null terminated char array. + */ + void initString(const char *str); + + /** + * Private Constructor. + * Create a managed string based on a concat of two strings. + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be decalred on the stack for instance). + * + * @param str1 The first string on which to base the new ManagedString + * @param str2 The second string on which to base the new ManagedString + */ + ManagedString(const ManagedString &s1, const ManagedString &s2); + +}; + +#endif diff --git a/inc/ManagedType.h b/inc/ManagedType.h new file mode 100644 index 0000000..75d487c --- /dev/null +++ b/inc/ManagedType.h @@ -0,0 +1,162 @@ +#ifndef MICROBIT_MANAGED_TYPE +#define MICROBIT_MANAGED_TYPE + +/** + * Class definition for a Generic Managed Type. + * + * Represents a reference counted object. + * + * @info When the destructor is called delete is called on the object - implicitly calling the given objects destructor. + */ +template +class ManagedType +{ +private: + + int *ref; + +public: + + T *object; + + /** + * Constructor for the managed type, given a class space T. + * @param object the object that you would like to be ref counted - of class T + * + * Example: + * @code + * T object = new T(); + * ManagedType mt(t); + * @endcode + */ + ManagedType(T* object); + + /** + * Default constructor for the managed type, given a class space T. + */ + ManagedType(); + + /** + * Copy constructor for the managed type, given a class space T. + * @param t another managed type instance of class type T. + * + * Example: + * @code + * T* object = new T(); + * ManagedType mt(t); + * ManagedType mt1(mt); + * @endcode + */ + ManagedType(const ManagedType &t); + + /** + * Destructor for the managed type, given a class space T. + */ + ~ManagedType(); + + /** + * Copy-assign member function for the managed type, given a class space. + * + * Example: + * @code + * T* object = new T(); + * ManagedType mt(t); + * ManagedType mt1 = mt; + * @endcode + */ + ManagedType& operator = (const ManagedType&i); + + /** + * Returns the references to this ManagedType + * + * Example: + * @code + * T* object = new T(); + * ManagedType mt(t); + * ManagedType mt1(mt); + * + * mt.getReferences // this will be 2! + * @endcode + */ + int getReferences(); + +}; + +/** +* Constructor for the managed type, given a class space. +*/ +template +ManagedType::ManagedType(T* object) +{ + this->object = object; + ref = (int *)malloc(sizeof(int)); + *ref = 1; +} + +/** +* Default constructor for the managed type, given a class space. +*/ +template +ManagedType::ManagedType() +{ + this->object = NULL; + ref = (int *)malloc(sizeof(int)); + *ref = 0; +} + +/** +* Copy constructor for the managed type, given a class space. +*/ +template +ManagedType::ManagedType(const ManagedType &t) +{ + this->object = t.object; + this->ref = t.ref; + (*ref)++; +} + +/** +* Destructor for the managed type, given a class space. +*/ +template +ManagedType::~ManagedType() +{ + if (--(*ref) == 0) + { + delete object; + free(ref); + } +} + +/** +* Copy-assign member function for the managed type, given a class space. +*/ +template +ManagedType& ManagedType::operator = (const ManagedType&t) +{ + if (this == &t) + return *this; + + if (--(*ref) == 0) + { + delete object; + free(ref); + } + + object = t.object; + ref = t.ref; + + (*ref)++; + + return *this; +} + +/** +* Returns the references to this ManagedType +*/ +template +int ManagedType::getReferences() +{ + return (*ref); +} +#endif diff --git a/inc/MicroBit.h b/inc/MicroBit.h new file mode 100644 index 0000000..ee2d7bc --- /dev/null +++ b/inc/MicroBit.h @@ -0,0 +1,279 @@ +#ifndef MICROBIT_H +#define MICROBIT_H + +// DEBUG. Enable this to get debug message routed through the USB serial interface. +//#define MICROBIT_DBG + +#include "mbed.h" +#include "ble/BLE.h" +#include "ble/services/DeviceInformationService.h" + +//error number enumeration +#include "ErrorNo.h" + +/** + * Displays "=(" and an accompanying status code + * @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255. + */ +void panic(int statusCode); + +#include "MicroBitMalloc.h" +#include "MicroBitCompat.h" +#include "MicroBitFiber.h" +#include "ManagedType.h" +#include "ManagedString.h" +#include "MicroBitFont.h" +#include "MicroBitImage.h" +#include "MicroBitEvent.h" +#include "MicroBitMessageBus.h" +#include "DynamicPwm.h" +#include "MicroBitComponent.h" +#include "MicroBitI2C.h" +#include "MicroBitSerial.h" +#include "MicroBitButton.h" +#include "MicroBitMultiButton.h" +#include "MicroBitDisplay.h" +#include "MicroBitPin.h" +#include "MicroBitIO.h" +#include "MicroBitCompass.h" +#include "MicroBitAccelerometer.h" + +#include "MicroBitDFUService.h" +#include "MicroBitEventService.h" +#include "ExternalEvents.h" + +// MicroBit::flags values +#define MICROBIT_FLAG_SCHEDULER_RUNNING 0x00000001 +#define MICROBIT_FLAG_ACCELEROMETER_RUNNING 0x00000002 +#define MICROBIT_FLAG_DISPLAY_RUNNING 0x00000004 +#define MICROBIT_FLAG_COMPASS_RUNNING 0x00000008 + + +// Random number generator +#define NRF51822_RNG_ADDRESS 0x4000D000 + +#define MICROBIT_IO_PINS 20 // TODO: Need to change for live, currently 3 for test + +// Enumeration of core components. +#define MICROBIT_ID_BUTTON_A 1 +#define MICROBIT_ID_BUTTON_B 2 +#define MICROBIT_ID_BUTTON_RESET 3 +#define MICROBIT_ID_ACCELEROMETER 4 +#define MICROBIT_ID_COMPASS 5 +#define MICROBIT_ID_DISPLAY 6 + +//EDGE connector events +#define MICROBIT_ID_IO_P0 7 //P0 is the left most pad (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P1 8 //P1 is the middle pad (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P2 9 //P2 is the right most pad (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P3 10 //COL1 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P4 11 //BTN_A +#define MICROBIT_ID_IO_P5 12 //COL2 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P6 13 //ROW2 +#define MICROBIT_ID_IO_P7 14 //ROW1 +#define MICROBIT_ID_IO_P8 15 //PIN 18 +#define MICROBIT_ID_IO_P9 16 //ROW3 +#define MICROBIT_ID_IO_P10 17 //COL3 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P11 18 //BTN_B +#define MICROBIT_ID_IO_P12 19 //PIN 20 +#define MICROBIT_ID_IO_P13 20 //SCK +#define MICROBIT_ID_IO_P14 21 //MISO +#define MICROBIT_ID_IO_P15 22 //MOSI +#define MICROBIT_ID_IO_P16 23 //PIN 16 +#define MICROBIT_ID_IO_P19 24 //SCL +#define MICROBIT_ID_IO_P20 25 //SDA + +#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton + +// mBed pin assignments of core components. +//TODO: When platform is built for MB2 - pins will be defined by default, these will change... +#define MICROBIT_PIN_SDA P0_30 +#define MICROBIT_PIN_SCL P0_0 + +#define MICROBIT_SYSTEM_COMPONENTS 10 +#define MICROBIT_IDLE_COMPONENTS 6 + +#ifdef MICROBIT_DEBUG +extern Serial pc; +#endif + +/** + * Class definition for a MicroBit device. + * + * Represents the device as a whole, and includes member variables to that reflect the components of the system. + */ +class MicroBit +{ + private: + + void seedRandom(); + uint32_t randomValue; + + public: + + // Map of device state. + uint32_t flags; + + // Periodic callback + Ticker systemTicker; + + // I2C Interface + MicroBitI2C i2c; + + // Serial Interface + MicroBitSerial serial; + + // Array of components which are iterated during a system tick + MicroBitComponent* systemTickComponents[MICROBIT_SYSTEM_COMPONENTS]; + + // Array of components which are iterated during idle thread execution, isIdleCallbackNeeded is polled during a systemTick. + MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS]; + + // Device level Message Bus abstraction + MicroBitMessageBus MessageBus; + + // Member variables to represent each of the core components on the device. + MicroBitDisplay display; + MicroBitButton buttonA; + MicroBitButton buttonB; + MicroBitMultiButton buttonAB; + MicroBitAccelerometer accelerometer; + MicroBitCompass compass; + + //An object of available IO pins on the device + MicroBitIO io; + + // Bluetooth related member variables. + BLEDevice *ble; + DeviceInformationService *ble_device_information_service; + MicroBitDFUService *ble_firmware_update_service; + MicroBitEventService *ble_event_service; + + + /** + * Constructor. + * Create a representation of a MicroBit device as a global singleton. + * @param messageBus callback function to receive MicroBitMessageBus events. + * + * Exposed objects: + * @code + * uBit.systemTicker; //the Ticker callback that performs routines like updating the display. + * uBit.MessageBus; //The message bus where events are fired. + * uBit.display; //The display object for the LED matrix. + * uBit.buttonA; //The buttonA object for button a. + * uBit.buttonB; //The buttonB object for button b. + * uBit.resetButton; //The resetButton used for soft resets. + * uBit.accelerometer; //The object that represents the inbuilt accelerometer + * uBit.compass; //The object that represents the inbuilt compass(magnetometer) + * uBit.io.P*; //Where P* is P0 to P16, P19 & P20 on the edge connector + * @endcode + */ + MicroBit(); + + /** + * Post constructor initialisation method. + * After *MUCH* pain, it's noted that the BLE stack can't be brought up in a + * static context, so we bring it up here rather than in the constructor. + * n.b. This method *must* be called in main() or later, not before. + * + * Example: + * @code + * uBit.init(); + * @endcode + */ + void init(); + + /** + * Delay for the given amount of time. + * If the scheduler is running, this will deschedule the current fiber and perform + * a power efficent, concurrent sleep operation. + * If the scheduler is disabled or we're running in an interrupt context, this + * will revert to a busy wait. + * + * @note Values of 6 and below tend to lose resolution - do you really need to sleep for this short amount of time? + * + * @param milliseconds the amount of time, in ms, to wait for. This number cannot be negative. + * + * Example: + * @code + * uBit.sleep(20); //sleep for 20ms + * @endcode + */ + void sleep(int milliseconds); + + /** + * Generate a random number in the given range. + * We use the NRF51822 in built random number generator here + * TODO: Determine if we want to, given its relatively high power consumption! + * + * @param max the upper range to generate a number for. This number cannot be negative + * @return A random, natural number between 0 and the max-1. Or MICROBIT_INVALID_VALUE (defined in ErrorNo.h) if max is <= 0. + * + * Example: + * @code + * uBit.random(200); //a number between 0 and 199 + * @endcode + */ + int random(int max); + + /** + * Period callback. Used by MicroBitDisplay, FiberScheduler and I2C sensors to + * provide a power efficient sense of time. + */ + void systemTick(); + + /** + * System tasks to be executed by the idle thread when the Micro:Bit isn't busy or when data needs to be read. + */ + void systemTasks(); + + /** + * add a component to the array of system components which invocate the systemTick member function during a systemTick + */ + void addSystemComponent(MicroBitComponent *component); + + /** + * remove a component from the array of system components + */ + void removeSystemComponent(MicroBitComponent *component); + + /** + * add a component to the array of of idle thread components. + * isIdleCallbackNeeded is polled during a systemTick to determine if the idle thread should jump to the front of the queue + */ + void addIdleComponent(MicroBitComponent *component); + + /** + * remove a component from the array of idle thread components + */ + void removeIdleComponent(MicroBitComponent *component); + + /** + * Determine the time since this MicroBit was last reset. + * + * @return The time since the last reset, in milliseconds. This will result in overflow after 1.6 months. + * TODO: handle overflow case. + */ + unsigned long systemTime(); + + /** + * Triggers a microbit panic where an infinite loop will occur swapping between the panicFace and statusCode if provided. + * + * @param statusCode the status code of the associated error. Status codes must be in the range 0-255. + */ + void panic(int statusCode = 0); + +}; + +// Definition of the global instance of the MicroBit class. +// Using this as a variation on the singleton pattern, just to make +// code integration a little bit easier for 3rd parties. +extern MicroBit uBit; + +// Entry point for application programs. Called after the super-main function +// has initialized the device and runtime environment. +extern "C" void app_main(); + + +#endif + diff --git a/inc/MicroBitAccelerometer.h b/inc/MicroBitAccelerometer.h new file mode 100644 index 0000000..a4cda4a --- /dev/null +++ b/inc/MicroBitAccelerometer.h @@ -0,0 +1,161 @@ +#ifndef MICROBIT_ACCELEROMETER_H +#define MICROBIT_ACCELEROMETER_H + +#include "mbed.h" + +/** + * Relevant pin assignments + */ +#define MICROBIT_PIN_ACCEL_DATA_READY P0_28 + +/* + * I2C constants + */ +#define MMA8653_DEFAULT_ADDR 0x3A + +/* + * MMA8653 Register map (partial) + */ +#define MMA8653_STATUS 0x00 +#define MMA8653_OUT_X_MSB 0x01 +#define MMA8653_WHOAMI 0x0D +#define MMA8653_XYZ_DATA_CFG 0x0E +#define MMA8653_CTRL_REG1 0x2A +#define MMA8653_CTRL_REG2 0x2B +#define MMA8653_CTRL_REG3 0x2C +#define MMA8653_CTRL_REG4 0x2D +#define MMA8653_CTRL_REG5 0x2E + + +/** + * MMA8653 constants + */ +#define MMA8653_WHOAMI_VAL 0x5A + +struct MMA8653Sample +{ + int16_t x; + int16_t y; + int16_t z; +}; + +/** + * Class definition for MicroBit Accelerometer. + * + * Represents an implementation of the Freescale MMA8653 3 axis accelerometer + * Also includes basic data caching and on demand activation. + */ +class MicroBitAccelerometer : public MicroBitComponent +{ + /** + * Unique, enumerated ID for this component. + * Used to track asynchronous events in the event bus. + */ + + //if you are adding status here - don't it's in MicroBitComponent!!! + uint16_t address; // I2C address of this accelerometer. + MMA8653Sample sample; // The last sample read. + DigitalIn int1; // Data ready interrupt. + + public: + + /** + * Constructor. + * Create an accelerometer representation with the given ID. + * @param id the ID of the new object. + * @param address the default base address of the accelerometer. + * + * Example: + * @code + * accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR) + * @endcode + */ + MicroBitAccelerometer(uint16_t id, uint16_t address); + + /** + * Reads the acceleration data from the accelerometer, and stores it in our buffer. + * This is called by the tick() member function, if the interrupt is set! + */ + void update(); + + + /** + * Attempts to determine the 8 bit ID from the accelerometer. + * @return the 8 bit ID returned by the accelerometer + * + * Example: + * @code + * uBit.accelerometer.whoAmI(); + * @endcode + */ + int whoAmI(); + + /** + * Reads the X axis value of the latest update from the accelerometer. + * Currently limited to +/- 2g + * @return The force measured in the X axis, in milli-g. + * + * Example: + * @code + * uBit.accelerometer.getX(); + * @endcode + */ + int getX(); + + /** + * Reads the Y axis value of the latest update from the accelerometer. + * Currently limited to +/- 2g + * @return The force measured in the Y axis, in milli-g. + * + * Example: + * @code + * uBit.accelerometer.getY(); + * @endcode + */ + int getY(); + + /** + * Reads the Z axis value of the latest update from the accelerometer. + * Currently limited to +/- 2g + * @return The force measured in the Z axis, in milli-g. + * + * Example: + * @code + * uBit.accelerometer.getZ(); + * @endcode + */ + int getZ(); + + /** + * periodic callback from MicroBit idle thread. + * Check if any data is ready for reading by checking the interrupt flag on the accelerometer + */ + virtual void idleTick(); + + /** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + */ + virtual int isIdleCallbackNeeded(); + + private: + /** + * Issues a standard, 2 byte I2C command write to the accelerometer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to write to. + * @param value The value to write. + */ + void writeCommand(uint8_t reg, uint8_t value); + + /** + * Issues a read command into the specified buffer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to access. + * @param buffer Memory area to read the data into. + * @param length The number of bytes to read. + */ + void readCommand(uint8_t reg, uint8_t* buffer, int length); +}; + +#endif diff --git a/inc/MicroBitButton.h b/inc/MicroBitButton.h new file mode 100644 index 0000000..32eb20e --- /dev/null +++ b/inc/MicroBitButton.h @@ -0,0 +1,92 @@ +#ifndef MICROBIT_BUTTON_H +#define MICROBIT_BUTTON_H + +#include "mbed.h" + +//TODO: When platform is built for MB2 - pins will be defined by default, these will change... +#define MICROBIT_PIN_BUTTON_A P0_17 +#define MICROBIT_PIN_BUTTON_B P0_26 +#define MICROBIT_PIN_BUTTON_RESET P0_19 + +#define MICROBIT_BUTTON_EVT_DOWN 1 +#define MICROBIT_BUTTON_EVT_UP 2 +#define MICROBIT_BUTTON_EVT_CLICK 3 +#define MICROBIT_BUTTON_EVT_LONG_CLICK 4 +#define MICROBIT_BUTTON_EVT_HOLD 5 +#define MICROBIT_BUTTON_EVT_DOUBLE_CLICK 6 + +#define MICROBIT_BUTTON_LONG_CLICK_TIME 1000 +#define MICROBIT_BUTTON_HOLD_TIME 1500 + +#define MICROBIT_BUTTON_STATE 1 +#define MICROBIT_BUTTON_STATE_HOLD_TRIGGERED 2 +#define MICROBIT_BUTTON_STATE_CLICK 4 +#define MICROBIT_BUTTON_STATE_LONG_CLICK 8 + +#define MICROBIT_BUTTON_SIGMA_MIN 0 +#define MICROBIT_BUTTON_SIGMA_MAX 12 +#define MICROBIT_BUTTON_SIGMA_THRESH_HI 8 +#define MICROBIT_BUTTON_SIGMA_THRESH_LO 2 +#define MICROBIT_BUTTON_DOUBLE_CLICK_THRESH 50 + +/** + * Class definition for MicroBit Button. + * + * Represents a single, generic button on the device. + */ +class MicroBitButton : public MicroBitComponent +{ + PinName name; // mBed pin name of this pin. + 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 + uint8_t sigma; // integration of samples over time. + uint8_t doubleClickTimer; // double click timer (ticks). + + public: + + /** + * Constructor. + * Create a pin representation with the given ID. + * @param id the ID of the new MicroBitButton object. + * @param name the physical pin on the processor that this butotn is connected to. + * @param mode the configuration of internal pullups/pulldowns, as define in the mbed PinMode class. PullNone by default. + * + * Example: + * @code + * buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A); //a number between 0 and 200 inclusive + * @endcode + * + * Possible Events: + * @code + * MICROBIT_BUTTON_EVT_DOWN + * MICROBIT_BUTTON_EVT_UP + * MICROBIT_BUTTON_EVT_CLICK + * MICROBIT_BUTTON_EVT_LONG_CLICK + * MICROBIT_BUTTON_EVT_DOUBLE_CLICK + * MICROBIT_BUTTON_EVT_HOLD + * @endcode + */ + MicroBitButton(uint16_t id, PinName name, PinMode mode = PullNone); + + /** + * Tests if this Button is currently pressed. + * @return 1 if this button is pressed, 0 otherwise. + * + * Example: + * @code + * if(uBit.buttonA.isPressed()) + * print("Pressed!"); + * @endcode + */ + int isPressed(); + + /** + * periodic callback from MicroBit clock. + * Check for state change for this button, and fires a hold event if button is pressed. + */ + virtual void systemTick(); + +}; + +#endif diff --git a/inc/MicroBitCompass.h b/inc/MicroBitCompass.h new file mode 100644 index 0000000..6b24ad2 --- /dev/null +++ b/inc/MicroBitCompass.h @@ -0,0 +1,262 @@ +#ifndef MICROBIT_COMPASS_H +#define MICROBIT_COMPASS_H + +#include "mbed.h" + +/** + * Relevant pin assignments + */ +#define MICROBIT_PIN_COMPASS_DATA_READY P0_29 + +/* + * I2C constants + */ +#define MAG3110_DEFAULT_ADDR 0x1D + +/* + * MAG3110 Register map + */ +#define MAG_DR_STATUS 0x00 +#define MAG_OUT_X_MSB 0x01 +#define MAG_OUT_X_LSB 0x02 +#define MAG_OUT_Y_MSB 0x03 +#define MAG_OUT_Y_LSB 0x04 +#define MAG_OUT_Z_MSB 0x05 +#define MAG_OUT_Z_LSB 0x06 +#define MAG_WHOAMI 0x07 +#define MAG_SYSMOD 0x08 +#define MAG_OFF_X_MSB 0x09 +#define MAG_OFF_X_LSB 0x0A +#define MAG_OFF_Y_MSB 0x0B +#define MAG_OFF_Y_LSB 0x0C +#define MAG_OFF_Z_MSB 0x0D +#define MAG_OFF_Z_LSB 0x0E +#define MAG_DIE_TEMP 0x0F +#define MAG_CTRL_REG1 0x10 +#define MAG_CTRL_REG2 0x11 + +/* + * Compass events + */ +#define MICROBIT_COMPASS_EVT_CAL_REQUIRED 1 +#define MICROBIT_COMPASS_EVT_CAL_START 2 +#define MICROBIT_COMPASS_EVT_CAL_END 3 + +/* + * Status Bits + */ +#define MICROBIT_COMPASS_STATUS_CALIBRATED 1 +#define MICROBIT_COMPASS_STATUS_CALIBRATING 2 + + +#define MICROBIT_COMPASS_CALIBRATE_PERIOD 10000 + +/* + * MAG3110 MAGIC ID value + * Returned from the MAG_WHO_AM_I register for ID purposes. + */ +#define MAG3110_WHOAMI_VAL 0xC4 + +struct CompassSample +{ + int16_t x; + int16_t y; + int16_t z; + + CompassSample() + { + this->x = 0; + this->y = 0; + this->z = 0; + } +}; + +/** + * Class definition for MicroBit Compass. + * + * Represents an implementation of the Freescale MAG3110 I2C Magnetmometer. + * Also includes basic caching, calibration and on demand activation. + */ +class MicroBitCompass : public MicroBitComponent +{ + /** + * Unique, enumerated ID for this component. + * Used to track asynchronous events in the event bus. + */ + + uint16_t address; // I2C address of the magnetmometer. + + unsigned long eventStartTime; // used to store the current system clock when async calibration has started + + public: + + CompassSample minSample; // Calibration sample. + CompassSample maxSample; // Calibration sample. + CompassSample average; // Centre point of sample data. + CompassSample sample; // The latest sample data recorded. + DigitalIn int1; // Data ready interrupt. + + /** + * Constructor. + * Create a compass representation with the given ID. + * @param id the event ID of the compass object. + * @param address the default address for the compass register + * + * Example: + * @code + * compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR); + * @endcode + * + * Possible Events for the compass are as follows: + * @code + * MICROBIT_COMPASS_EVT_CAL_REQUIRED // triggered when no magnetometer data is available in persistent storage + * MICROBIT_COMPASS_EVT_CAL_START // triggered when calibration has begun + * MICROBIT_COMPASS_EVT_CAL_END // triggered when calibration has finished. + * @endcode + */ + MicroBitCompass(uint16_t id, uint16_t address); + + /** + * Gets the current heading of the device, relative to magnetic north. + * @return the current heading, in degrees. + * + * Example: + * @code + * uBit.compass.heading(); + * @endcode + */ + int heading(); + + /** + * Attempts to determine the 8 bit ID from the magnetometer. + * @return the id of the compass (magnetometer) + * + * Example: + * @code + * uBit.compass.whoAmI(); + * @endcode + */ + int whoAmI(); + + /** + * Reads the X axis value of the latest update from the compass. + * @return The magnetic force measured in the X axis, in no specific units. + * + * Example: + * @code + * uBit.compass.getX(); + * @endcode + */ + int getX(); + + /** + * Reads the Y axis value of the latest update from the compass. + * @return The magnetic force measured in the Y axis, in no specific units. + * + * Example: + * @code + * uBit.compass.getY(); + * @endcode + */ + int getY(); + + /** + * Reads the Z axis value of the latest update from the compass. + * @return The magnetic force measured in the Z axis, in no specific units. + * + * Example: + * @code + * uBit.compass.getZ(); + * @endcode + */ + int getZ(); + + /** + * Perform the asynchronous calibration of the compass. + * This will fire MICROBIT_COMPASS_EVT_CAL_START and MICROBIT_COMPASS_EVT_CAL_END when finished. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ + void calibrateAsync(); + + /** + * Perform a calibration of the compass. + * This will fire MICROBIT_COMPASS_EVT_CAL_START. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ + void calibrateStart(); + + /** + * Complete the calibration of the compass. + * This will fire MICROBIT_COMPASS_EVT_CAL_END. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ + void calibrateEnd(); + + /** + * Periodic callback from MicroBit idle thread. + * Check if any data is ready for reading by checking the interrupt. + */ + virtual void idleTick(); + + /** + * Returns 0 or 1. 1 indicates that the compass is calibrated, zero means the compass requires calibration. + */ + int isCalibrated(); + + /** + * Returns 0 or 1. 1 indicates that the compass is calibrating, zero means the compass is not currently calibrating. + */ + int isCalibrating(); + + /** + * Clears the calibration held in persistent storage, and sets the calibrated flag to zero. + */ + void clearCalibration(); + + /** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + */ + virtual int isIdleCallbackNeeded(); + + private: + + /** + * Issues a standard, 2 byte I2C command write to the magnetometer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to write to. + * @param value The value to write. + */ + void writeCommand(uint8_t reg, uint8_t value); + + /** + * Issues a read command into the specified buffer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to access. + * @param buffer Memory area to read the data into. + * @param length The number of bytes to read. + */ + void readCommand(uint8_t reg, uint8_t* buffer, int length); + + /** + * Issues a read of a given address, and returns the value. + * Blocks the calling thread until complete. + * + * @param reg The based address of the 16 bit register to access. + * @return The register value, interpreted as a 16 but signed value. + */ + int16_t read16(uint8_t reg); + + + /** + * Issues a read of a given address, and returns the value. + * Blocks the calling thread until complete. + * + * @param reg The based address of the 16 bit register to access. + * @return The register value, interpreted as a 8 bi signed value. + */ + int16_t read8(uint8_t reg); +}; + +#endif diff --git a/inc/MicroBitCompat.h b/inc/MicroBitCompat.h new file mode 100644 index 0000000..55e0370 --- /dev/null +++ b/inc/MicroBitCompat.h @@ -0,0 +1,73 @@ +/** + * Compatibility / portability funcitons and constants for the MicroBit DAL. + */ + +#ifndef MICROBIT_COMPAT_H +#define MICROBIT_COMPAT_H + +#define PI 3.14159265359 + + /*! \ + \brief Utility functions. + + Included here often to reduce the need to import a whole library for simple opertations. + This helps to minimize our SRAM footprint. + */ +/*! + \brief returns the smallest of the two numbers + \param a the first number + \param b the number to compare against a + \return whichever value is the smallest + */ +inline int min(int a, int b) +{ + return (a < b ? a : b); +} + +/*! + \brief returns the biggest of the two numbers + \param a the first number + \param b the number to compare against a + \return whichever value is the biggest + */ +inline int max(int a, int b) +{ + return (a > b ? a : b); +} + +/*! + \brief clears b number of bytes given the pointer a + \param a the pointer to the beginning of the memory to clear + \param b the number of bytes to clear. + */ +inline void *memclr(void *a, size_t b) +{ + return memset(a,0,b); +} + +/*! + \brief returns true if the character c lies between ascii values 48 and 57 inclusive. + \param c the character to check + \return a bool, true if it is a digit, false otherwise + */ +inline bool isdigit(char c) +{ + return (c > 47 && c < 58); +} + +/*! + \brief Performs an in buffer reverse of a given char array + \param s the char* to reverse. + \return the reversed char* + */ +void string_reverse(char *s); + +/*! + \briefConverts a given integer into a base 10 ASCII equivalent. + \param n The number to convert. + \param s Pointer to a buffer in which to store the resulting string. + */ +void itoa(int n, char *s); + + +#endif diff --git a/inc/MicroBitComponent.h b/inc/MicroBitComponent.h new file mode 100644 index 0000000..992e38c --- /dev/null +++ b/inc/MicroBitComponent.h @@ -0,0 +1,62 @@ +#ifndef MICROBIT_COMPONENT_H +#define MICROBIT_COMPONENT_H + +/** + * Class definition for MicroBitComponent + * All components should inherit from this class. + * @note if a component needs to be called regularly, then you should add the component to the systemTick and idleTick queues. + * If it's in the systemTick queue, you should override systemTick and implement the required functionality. + * Similarly if the component is in the idleTick queue, the idleTick member function should be overridden. + */ +class MicroBitComponent +{ + protected: + + uint16_t id; // Event Bus ID + uint8_t status; // keeps track of various component state, and also indicates if data is ready. + + public: + + /** + * The default constructor of a MicroBitComponent + */ + MicroBitComponent() + { + this->id = 0; + this->status = 0; + } + + /** + * Once added to the systemTickComponents array, this member function will be + * called in interrupt context on every system tick. + */ + virtual void systemTick(){ + + } + + /** + * Once added to the idleThreadComponents array, this member function will be + * called in idle thread context indiscriminately. + */ + virtual void idleTick() + { + + } + + /** + * When added to the idleThreadComponents array, this function will be called to determine + * if and when data is ready. + * @note override this if you want to request to be scheduled imminently + */ + virtual int isIdleCallbackNeeded() + { + return 0; + } + + virtual ~MicroBitComponent() + { + + } +}; + +#endif diff --git a/inc/MicroBitDFUService.h b/inc/MicroBitDFUService.h new file mode 100644 index 0000000..8ae502c --- /dev/null +++ b/inc/MicroBitDFUService.h @@ -0,0 +1,101 @@ +#ifndef MICROBIT_DFU_SERVICE_H +#define MICROBIT_DFU_SERVICE_H + +#include "MicroBit.h" + +// MicroBit ControlPoint OpCodes + +// Requests transfer to the Nordic DFU bootloader. +// This will only occur if +#define MICROBIT_DFU_OPCODE_START_DFU 1 +#define MICROBIT_DFU_OPCODE_START_PAIR 2 + +#define MICROBIT_DFU_HISTOGRAM_WIDTH 5 +#define MICROBIT_DFU_HISTOGRAM_HEIGHT 5 + + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitDFUServiceUUID[]; +extern const uint8_t MicroBitDFUServiceControlCharacteristicUUID[]; +extern const uint8_t MicroBitDFUServiceFlashCodeCharacteristicUUID[]; + +// Handle on the memory resident Nordic bootloader. +extern "C" void bootloader_start(void); + +/** + * Class definition for a MicroBit Device Firmware Update loader. + * + * This is actually just a frontend to a memory resident nordic DFU loader. + * Here we deal with the MicroBit 'pairing' functionality with BLE devices, and + * very basic authentication and authorization. + * + * This implementation is not intended to be fully secure, but rather intends to: + * + * 1. Provide a simple mechanism to identify an individual MicroBit amongst a classroom of others + * 2. Allow BLE devices to discover and cache a passcode that can be used to flash the device over BLE. + * 3. Provide an escape route for programs that 'brick' the MicroBit. + * + * Represents the device as a whole, and includes member variables to that reflect the components of the system. + */ +class MicroBitDFUService +{ + public: + + /** + * Constructor. + * Create a representation of a MicroBit device. + * @param messageBus callback function to receive MicroBitMessageBus events. + */ + MicroBitDFUService(BLEDevice &BLE); + + /** + * Returns the friendly name for this device, autogenerated from our Device ID. + * + * @param name Pointer to a string where the data will be written. + * @return The number of bytes written. + */ + int getName(char *name); + + /** + * Begin the pairing process. Typically called when device is powered up with buttons held down. + * Scroll a description on the display, then displays the device ID code as a histogram on the matrix display. + */ + void pair(); + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + virtual void onDataWritten(const GattWriteCallbackParams *params); + + private: + + // BLE pairing name of this device, encoded as an integer. + uint32_t flashCode; + + // State of paiting process. + bool authenticated; + bool flashCodeRequested; + + // Bluetooth stack we're running on. + BLEDevice &ble; + + // memory for our 8 bit control characteristics. + uint8_t controlByte; + + // Opcodes can be issued here to control the MicroBitDFU Service, as defined above. + WriteOnlyGattCharacteristic microBitDFUServiceControlCharacteristic; + + // Read/Write characteristic to enable unlocking and discovery of the MicroBit's flashcode. + GattCharacteristic microBitDFUServiceFlashCodeCharacteristic; + + // Displays the device's ID code as a histogram on the LED matrix display. + void showNameHistogram(); + + // Displays an acknowledgement on the LED matrix display. + void showTick(); + + // Update BLE characteristic to release our flash code. + void releaseFlashCode(); +}; + +#endif diff --git a/inc/MicroBitDisplay.h b/inc/MicroBitDisplay.h new file mode 100644 index 0000000..a65ba01 --- /dev/null +++ b/inc/MicroBitDisplay.h @@ -0,0 +1,530 @@ +#ifndef MICROBIT_DISPLAY_H +#define MICROBIT_DISPLAY_H + +/** + * User definable constants + */ +#define MICROBIT_DISPLAY_ROTATION_0 0 +#define MICROBIT_DISPLAY_ROTATION_90 1 +#define MICROBIT_DISPLAY_ROTATION_180 2 +#define MICROBIT_DISPLAY_ROTATION_270 3 + + +/** + * Core Configuration settings. + */ + +#define MICROBIT_SB2 +#define MICROBIT_DISPLAY_REFRESH_PERIOD 0.006 + + +/** + * Default parameters. + */ +#define MICROBIT_DEFAULT_SCROLL_SPEED 120 +#define MICROBIT_DEFAULT_PRINT_SPEED 400 +#define MICROBIT_DEFAULT_SCROLL_STRIDE -1 +#define MICROBIT_DISPLAY_MAX_BRIGHTNESS 255 +#define MICROBIT_DISPLAY_MIN_BRIGHTNESS 2 +#define MICROBIT_DEFAULT_BRIGHTNESS (MICROBIT_DISPLAY_MAX_BRIGHTNESS/2) + +/** + * MessageBus Event Codes + */ +#define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1 + +/** + * I/O configurations for common devices. + */ +#ifdef MICROBUG_REFERENCE_DEVICE +#define MICROBIT_DISPLAY_ROW_COUNT 5 +#define MICROBIT_DISPLAY_ROW_PINS P0_0, P0_1, P0_2, P0_3, P0_4 +#define MICROBIT_DISPLAY_COLUMN_COUNT 5 +#define MICROBIT_DISPLAY_COLUMN_PINS P0_24, P0_25, P0_28, P0_29, P0_30 +#endif + +#ifdef MICROBIT_3X9 +#define MICROBIT_DISPLAY_ROW_COUNT 3 +#define MICROBIT_DISPLAY_ROW_PINS P0_12, P0_13, P0_14 +#define MICROBIT_DISPLAY_COLUMN_COUNT 9 +#define MICROBIT_DISPLAY_COLUMN_PINS P0_15, P0_16, P0_17, P0_18, P0_19, P0_24, P0_25, P0_28, P0_29 +#endif + +#ifdef MICROBIT_SB1 +#define MICROBIT_DISPLAY_ROW_COUNT 9 +#define MICROBIT_DISPLAY_ROW_PINS P0_15, P0_16, P0_17, P0_18, P0_19, P0_24, P0_25, P0_28, P0_29 +#define MICROBIT_DISPLAY_COLUMN_COUNT 3 +#define MICROBIT_DISPLAY_COLUMN_PINS P0_12, P0_13, P0_14 +#endif + +#ifdef MICROBIT_SB2 +#define MICROBIT_DISPLAY_ROW_COUNT 3 +#define MICROBIT_DISPLAY_ROW_PINS P0_13, P0_14, P0_15 +#define MICROBIT_DISPLAY_COLUMN_COUNT 9 +#define MICROBIT_DISPLAY_COLUMN_PINS P0_4, P0_5, P0_6, P0_7, P0_8, P0_9, P0_10, P0_11, P0_12 +#define MICROBIT_DISPLAY_COLUMN_START P0_4 + +#define MICROBIT_DISPLAY_WIDTH 5 +#define MICROBIT_DISPLAY_HEIGHT 5 +#endif + +#define MICROBIT_DISPLAY_SPACING 1 +#define MICROBIT_DISPLAY_ERROR_CHARS 4 + +#define MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH 8 + +#define MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS -255 + +#include "mbed.h" +#include "MicroBit.h" + +enum AnimationMode { + ANIMATION_MODE_NONE, + ANIMATION_MODE_SCROLL_TEXT, + ANIMATION_MODE_PRINT_TEXT, + ANIMATION_MODE_SCROLL_IMAGE, + ANIMATION_MODE_ANIMATE_IMAGE, + ANIMATION_MODE_PRINT_CHARACTER +}; + +enum DisplayMode { + DISPLAY_MODE_BLACK_AND_WHITE, + DISPLAY_MODE_GREYSCALE +}; + +struct MatrixPoint { + uint8_t x; + uint8_t y; + + MatrixPoint(uint8_t x, uint8_t y); +}; + +/** + * Class definition for a MicroBitDisplay. + * + * A MicroBitDisplay represents the LED matrix array on the MicroBit device. + */ +class MicroBitDisplay : public MicroBitComponent +{ + uint8_t width; + uint8_t height; + uint8_t brightness; + uint8_t strobeRow; + uint8_t strobeBitMsk; + uint8_t rotation; + uint8_t mode; + uint8_t greyscaleBitMsk; + uint8_t timingCount; + Timeout renderTimer; + + MicroBitFont font; + + // + // State used by all animation routines. + // + + // The animation mode that's currently running (if any) + AnimationMode animationMode; + + // The time (in ticks) between each frame update. + uint16_t animationDelay; + + // The time (in ticks) since the frame update. + uint16_t animationTick; + + // Stop playback of any animations + void stopAnimation(int delay); + + // + // State for scrollString() method. + // This is a surprisingly intricate method. + // + // The text being displayed. + ManagedString scrollingText; + + // The index of the character currently being displayed. + uint16_t scrollingChar; + + // The number of pixels the current character has been shifted on the display. + uint8_t scrollingPosition; + + // + // State for printString() method. + // + // The text being displayed. NULL if no message is scheduled for playback. + // We *could* get some reuse in here with the scroll* variables above, + // but best to keep it clean in case kids try concurrent operation (they will!), + // given the small RAM overhead needed to maintain orthogonality. + ManagedString printingText; + + // The index of the character currently being displayed. + uint16_t printingChar; + + // + // State for scrollImage() method. + // + // The image being displayed. + MicroBitImage scrollingImage; + + // The number of pixels the image has been shifted on the display. + int16_t scrollingImagePosition; + + // The number of pixels the image is shifted on the display in each quantum. + int8_t scrollingImageStride; + + // Flag to indicate if image has been rendered to screen yet (or not) + bool scrollingImageRendered; + + static const MatrixPoint matrixMap[MICROBIT_DISPLAY_COLUMN_COUNT][MICROBIT_DISPLAY_ROW_COUNT]; + + // Internal methods to handle animation. + + /** + * Periodic callback, that we use to perform any animations we have running. + */ + void animationUpdate(); + + /** + * Called by the display in an interval determined by the brightness of the display, to give an impression + * of brightness. + */ + void renderFinish(); + + /** + * Translates a bit mask to a bit mask suitable for the nrf PORT0 and PORT1. + * Brightness has two levels on, or off. + */ + void render(); + + /** + * Translates a bit mask into a timer interrupt that gives the appearence of greyscale. + */ + void renderGreyscale(); + + /** + * Internal scrollText update method. + * Shift the screen image by one pixel to the left. If necessary, paste in the next char. + */ + void updateScrollText(); + + /** + * Internal printText update method. + * Paste in the next char in the string. + */ + void updatePrintText(); + + /** + * Internal scrollImage update method. + * Paste the stored bitmap at the appropriate point. + */ + void updateScrollImage(); + + + /** + * Internal animateImage update method. + * Paste the stored bitmap at the appropriate point and stop on the last frame. + */ + void updateAnimateImage(); + + /** + * Broadcasts an event onto the shared MessageBus + * @param eventCode The ID of the event that has occurred. + */ + void sendEvent(uint16_t eventcode); + + +public: + // The mutable bitmap buffer being rendered to the LED matrix. + MicroBitImage image; + + /** + * Constructor. + * Create a representation of a display of a given size. + * The display is initially blank. + * + * @param x the width of the display in pixels. + * @param y the height of the display in pixels. + * + * Example: + * @code + * MicroBitDisplay display(MICROBIT_ID_DISPLAY, 5, 5), + * @endcode + */ + 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. + */ + void resetAnimation(uint16_t delay); + + /** + * Frame update method, invoked periodically to strobe the display. + */ + virtual void systemTick(); + + /** + * Prints the given string to the display, one character at a time. + * Uses the given delay between characters. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.printAsync("abc123",400); + * @endcode + */ + void printAsync(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + + /** + * Prints the given character to the display. + * + * @param c The character to display. + * + * Example: + * @code + * uBit.display.print('p'); + * @endcode + */ + void print(char c, int delay = 0); + + /** + * Prints the given string to the display, one character at a time. + * Uses the given delay between characters. + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.print("abc123",400); + * @endcode + */ + void print(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + + /** + * Prints the given image to the display. + * Blocks the calling thread until all the text has been displayed. + * + * @param i The image to display. + * @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 print(MicroBitImage i, int x, int y, int alpha, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + + /** + * Scrolls the given string to the display, from right to left. + * Uses the given delay between characters. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.scrollAsync("abc123",100); + * @endcode + */ + void scrollAsync(ManagedString s, int delay = MICROBIT_DEFAULT_SCROLL_SPEED); + + /** + * Scrolls the given image across the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * @param image The image to display. + * @param delay The time to delay between each scroll update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * uBit.display.scrollAsync(i,100,1); + * @endcode + */ + void scrollAsync(MicroBitImage image, int delay = MICROBIT_DEFAULT_SCROLL_SPEED, int stride = MICROBIT_DEFAULT_SCROLL_STRIDE); + + /** + * Scrolls the given string to the display, from right to left. + * Uses the given delay between characters. + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.scroll("abc123",100); + * @endcode + */ + void scroll(ManagedString s, int delay = MICROBIT_DEFAULT_SCROLL_SPEED); + + /** + * Scrolls the given image across the display, from right to left. + * Blocks the calling thread until all the text has been displayed. + * + * @param image The image to display. + * @param delay The time to delay between each scroll update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * uBit.display.scroll(i,100,1); + * @endcode + */ + void scroll(MicroBitImage image, int delay = MICROBIT_DEFAULT_SCROLL_SPEED, int stride = MICROBIT_DEFAULT_SCROLL_STRIDE); + + /** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Returns immediately. + * + * @param image The image to display. + * @param delay The time to delay between each animation update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * uBit.display.animateAsync(i,100,5); + * @endcode + */ + void animateAsync(MicroBitImage image, int delay, int stride, int startingPosition = MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS); + + /** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Blocks the calling thread until the animation is complete. + * + * @param image The image to display. + * @param delay The time to delay between each animation update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * uBit.display.animate(i,100,5); + * @endcode + */ + void animate(MicroBitImage image, int delay, int stride, int startingPosition = MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS); + + /** + * Sets the display brightness to the specified level. + * @param b The brightness to set the brightness to, in the range 0..255. + * + * Example: + * @code + * uBit.display.setBrightness(255); //max brightness + * @endcode + */ + void setBrightness(int b); + + + /** + * Sets the mode of the display. + * @param mode The mode to swap the display into. (can be either DISPLAY_MODE_GREYSCALE, or DISPLAY_MODE_NORMAL) + * + * Example: + * @code + * uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE); //per pixel brightness + * @endcode + */ + void setDisplayMode(DisplayMode mode); + + /** + * Fetches the current brightness of this display. + * @return the brightness of this display, in the range 0..255. + * + * Example: + * @code + * uBit.display.getBrightness(); //the current brightness + * @endcode + */ + int getBrightness(); + + /** + * Rotates the display to the given position. + * Axis aligned values only. + * + * Example: + * @code + * uBit.display.rotateTo(MICROBIT_DISPLAY_ROTATION_180); //rotates 180 degrees from original orientation + * @endcode + */ + void rotateTo(uint8_t position); + + /** + * Enables the display, should only be called if the display is disabled. + * + * Example: + * @code + * uBit.display.enable(); //reenables the display mechanics + * @endcode + */ + void enable(); + + /** + * Disables the display, should only be called if the display is enabled. + * Display must be disabled to avoid MUXing of edge connector pins. + * + * Example: + * @code + * uBit.display.disable(); //disables the display + * @endcode + */ + void disable(); + + /** + * Clears the current image on the display. + * Simplifies the process, you can also use uBit.display.image.clear + * + * Example: + * @code + * uBit.display.clear(); //clears the display + * @endcode + */ + void clear(); + + /** + * Displays "=(" and an accompanying status code infinitely. + * @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255. + * + * Example: + * @code + * uBit.display.error(20); + * @endcode + */ + void error(int statusCode); + + /** + * Updates the font property of this object with the new font. + * @param font the new font that will be used to render characters.. + */ + void setFont(MicroBitFont font); + + /** + * Retreives the font object used for rendering characters on the display. + */ + MicroBitFont getFont(); + + /** + * Captures the bitmap currently being rendered on the display. + */ + MicroBitImage screenShot(); +}; + +#endif + diff --git a/inc/MicroBitEvent.h b/inc/MicroBitEvent.h new file mode 100644 index 0000000..d56c0e7 --- /dev/null +++ b/inc/MicroBitEvent.h @@ -0,0 +1,49 @@ +#ifndef MICROBIT_EVENT +#define MICROBIT_EVENT + +#include "MicroBit.h" + +/** + * Class definition for a MicrobitEvent + * It represents a common event that is generated by the various components on the MB. + */ +class MicroBitEvent +{ + public: + + //These are public at the moment for backwards compatability with old code + //will be refactored in the future! + + uint16_t source; // ID of the MicroBit Component that generated the event e.g. MICROBIT_ID_BUTTON_A. + uint16_t value; // Component specific code indicating the cause of the event. + uint32_t timestamp; // Time at which the event was generated. ms since power on. + + /** + * Constructor. + * @param src ID of the MicroBit Component that generated the event e.g. MICROBIT_ID_BUTTON_A. + * @param value Component specific code indicating the cause of the event. + * @param fire whether the event should be fire immediately upon construction + * + * Example: + * @code + * MicrobitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK,true); // auto fire + * @endcode + */ + + MicroBitEvent(uint16_t source, uint16_t value, bool fire = true); + + /** + * Default constructor - initialises all values, and sets timestamp to the current time. + */ + MicroBitEvent(); + + /** + * Fires the represented event onto the message bus. + */ + void fire(); +}; + + + +#endif + diff --git a/inc/MicroBitEventService.h b/inc/MicroBitEventService.h new file mode 100644 index 0000000..0128543 --- /dev/null +++ b/inc/MicroBitEventService.h @@ -0,0 +1,59 @@ +#ifndef MICROBIT_EVENT_SERVICE_H +#define MICROBIT_EVENT_SERVICE_H + +#include "MicroBit.h" + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitEventServiceUUID[]; +extern const uint8_t MicroBitEventServiceMicroBitEventCharacteristicUUID[]; +extern const uint8_t MicroBitEventServiceClientEventCharacteristicUUID[]; + +struct EventServiceEvent +{ + uint16_t type; + uint16_t reason; +}; + + +/** + * Class definition for a MicroBit BLE Event Service. + * Provides a _ble gateway onto the MicroBit Message Bus. + */ +class MicroBitEventService +{ + public: + + /** + * Constructor. + * Create a representation of the EventService + * @param BLE The instance of a BLE device that we're running on. + */ + MicroBitEventService(BLEDevice &_ble); + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Callback. Invoked when any events are sent on the microBit message bus. + */ + void onMicroBitEvent(MicroBitEvent evt); + + private: + + // Bluetooth stack we're running on. + BLEDevice &ble; + + // memory for our 8 bit control characteristics. + EventServiceEvent clientEventBuffer; + EventServiceEvent microBitEventBuffer; + + // BLE GATT Characteristics for sending and receiving events to an attached device + GattCharacteristic microBitEventCharacteristic; + GattCharacteristic clientEventCharacteristic; +}; + + +#endif + diff --git a/inc/MicroBitFiber.h b/inc/MicroBitFiber.h new file mode 100644 index 0000000..2070cb8 --- /dev/null +++ b/inc/MicroBitFiber.h @@ -0,0 +1,221 @@ +/** + * Definitions for the MicroBit Fiber scheduler. + * + * This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes: + * + * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). + * 2) To provide ISR decoupling for Messagebus events generted in an ISR context. + */ + +#ifndef MICROBIT_FIBER_H +#define MICROBIT_FIBER_H + +#include "mbed.h" +#include "MicroBitMessageBus.h" + +// Typical stack size of each fiber. +// A physical stack of anything less than 512 will likely hit overflow issues during ISR/mBed calls. +// However, as we're running a co=operative fiber scheduler, the size of the stack at the point of +// context switching is normally *very* small (circa 12 bytes!). Also, as we're likely to have many short lived threads +// being used, be actually perform a stack duplication on context switch, which keeps the RAM footprint of a fiber +// down to a minimum, without constraining what can be done insode a fiber context. +// +// TODO: Consider a split mode scheduler, that monitors used stack size, and maintains a dedicated, persistent +// stack for any long lived fibers with large stacks. +// +#define FIBER_STACK_SIZE 64 +#define FIBER_TICK_PERIOD_MS 6 +#define CORTEX_M0_STACK_BASE (0x20004000 - 4) + +#define MICROBIT_FLAG_DATA_READ 0x01 + +/** + * Thread Context for an ARM Cortex M0 core. + * + * This is probably overkill, but the ARMCC compiler uses a lot register optimisation + * in its calling conventions, so better safe than sorry. :-) + * + * TODO: Check with ARM guys to see is they have suggestions to optimize this context block + */ +struct Cortex_M0_TCB +{ + uint32_t R0; + uint32_t R1; + uint32_t R2; + uint32_t R3; + uint32_t R4; + uint32_t R5; + uint32_t R6; + uint32_t R7; + uint32_t R8; + uint32_t R9; + uint32_t R10; + uint32_t R11; + uint32_t R12; + uint32_t SP; + uint32_t LR; +}; + +/** + * Representation of a single Fiber + */ + +struct Fiber +{ + uint32_t stack_top, stack_bottom; // Address of this Fiber's stack. Stack is heap allocated, and full descending. + Cortex_M0_TCB tcb; // Thread context when last scheduled out. + uint32_t context; // Context specific information. + Fiber **queue; // The queue this fiber is stored on. + Fiber *next, *prev; // Position of this Fiber on the run queues. +}; + + +/** + * Initialises the Fiber scheduler. + * Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread. + * + * This function must be called once only from the main thread, and before any other Fiber operation. + */ +void scheduler_init(); + +/** + * Exit point for all fibers. + * Any fiber reaching the end of its entry function will return here for recycling. + */ +void release_fiber(void); + +/** + * Exit point for parameterised fibers. + * A wrapper around release_fiber() to enable transparent operaiton. + */ +void release_fiber(void *param); + +/** + * Launches a fiber + */ +void launch_new_fiber() +#ifdef __GCC__ + __attribute__((naked)) +#endif +; + +/** + * Creates a new Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * @param completion_fn The function called when the thread completes execution of entry_fn. + * @return The new Fiber. + */ +Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void) = release_fiber); + +/** + * Launches a paramaterised fiber + */ +void launch_new_fiber_param() +#ifdef __GCC__ + __attribute__((naked)) +#endif +; +/** + * Creates a new parameterised Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * @param param an untyped parameter passed into the entry_fn anf completion_fn. + * @param completion_fn The function called when the thread completes execution of entry_fn. + * @return The new Fiber. + */ +Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *) = release_fiber); + + + +/** + * Calls the Fiber scheduler. + * The calling Fiber will likely be blocked, and control given to another waiting fiber. + * Call this to yield control of the processor when you have nothing more to do. + */ +void schedule(); + +/** + * Blocks the calling thread for the given period of time. + * The calling thread will be immediatley descheduled, and placed onto a + * wait queue until the requested amount of time has elapsed. + * + * n.b. the fiber will not be be made runnable until after the elasped time, but there + * are no guarantees precisely when the fiber will next be scheduled. + * + * @param t The period of time to sleep, in milliseconds. + */ +void fiber_sleep(unsigned long t); + +/** + * Timer callback. Called from interrupt context, once every FIBER_TICK_PERIOD_MS milliseconds. + * Simply checks to determine if any fibers blocked on the sleep queue need to be woken up + * and made runnable. + */ +void scheduler_tick(); + +/** + * Blocks the calling thread until the specified event is raised. + * The calling thread will be immediatley descheduled, and placed onto a + * wait queue until the requested event is received. + * + * n.b. the fiber will not be be made runnable until after the event is raised, but there + * are no guarantees precisely when the fiber will next be scheduled. + * + * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) + * @param value The VALUE of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) + */ +void fiber_wait_for_event(uint16_t id, uint16_t value); + + +/** + * Event callback. Called from the message bus whenever an event is raised. + * Checks to determine if any fibers blocked on the wait queue need to be woken up + * and made runnable due to the event. + */ +void scheduler_event(MicroBitEvent evt); + + +/** + * Utility function to add the currenty running fiber to the given queue. + * Perform a simple add at the head, to avoid complexity, + * Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of + * brute force searching. + * + * @param f The fiber to add to the queue + * @param queue The run queue to add the fiber to. + */ +void queue_fiber(Fiber *f, Fiber **queue); + +/** + * Utility function to the given fiber from whichever queue it is currently stored on. + * @param f the fiber to remove. + */ +void dequeue_fiber(Fiber *f); + +/** + * IDLE task. + * Only scheduled for execution when the runqueue is empty. + * Performs a procressor sleep operation, then returns to the scheduler - most likely after a timer interrupt. + */ +void idle_task(); + +/** + * Assembler Ccontext switch routing. + * Defined in CortexContextSwitch.s + */ +extern "C" void swap_context(Cortex_M0_TCB *from, Cortex_M0_TCB *to, uint32_t from_stack, uint32_t to_stack); +extern "C" void save_context(Cortex_M0_TCB *tcb, uint32_t stack); + +/** + * Time since power on. Measured in milliseconds. + * When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-) + */ +extern unsigned long ticks; + +/** + * This variable is used to prioritise the systems' idle fibre to execute essential tasks. + */ +extern uint8_t fiber_flags; + +#endif diff --git a/inc/MicroBitFont.h b/inc/MicroBitFont.h new file mode 100644 index 0000000..4f56549 --- /dev/null +++ b/inc/MicroBitFont.h @@ -0,0 +1,42 @@ +#ifndef MICROBIT_FONT +#define MICROBIT_FONT + +#include "mbed.h" + +#define MICROBIT_FONT_WIDTH 5 +#define MICROBIT_FONT_HEIGHT 5 +#define MICROBIT_FONT_ASCII_START 32 +#define MICROBIT_FONT_ASCII_END 126 + +/** + * Class definition for a MicrobitFont + * It represents a font that can be used by the display to render text. + */ +class MicroBitFont +{ + public: + + static const unsigned char* defaultFont; + + const unsigned char* characters; + + int asciiEnd; + + /** + * Constructor. + * Sets the font represented by this font object. + * @param font A pointer to the beginning of the new font. + * @param asciiEnd the char value at which this font finishes. + * + * @note see main_font_test.cpp in the test folder for an example. + */ + MicroBitFont(const unsigned char* font, int asciiEnd = MICROBIT_FONT_ASCII_END); + + /** + * Default Constructor. + * Sets the characters to defaultFont characters and asciiEnd to MICROBIT_FONT_ASCII_END. + */ + MicroBitFont(); +}; + +#endif diff --git a/inc/MicroBitI2C.h b/inc/MicroBitI2C.h new file mode 100644 index 0000000..edcfd65 --- /dev/null +++ b/inc/MicroBitI2C.h @@ -0,0 +1,40 @@ +#include "MicroBit.h" +#ifndef MICROBIT_I2C_H +#define MICROBIT_I2C_H + +#include "MicroBitComponent.h" +#include "mbed.h" + +#define MICROBIT_I2C_MAX_RETRIES 9 + +/** + * Class definition for MicroBitI2C. + * + * Represents a wrapped mbed call to hopefully fix silicon issues once and for all. + */ +class MicroBitI2C : public I2C +{ + + uint8_t retries; + + public: + + /** + * Constructor. + * Create an instance of i2c + * @param sda the Pin to be used for SDA + * @param scl the Pin to be used for SCL + * Example: + * @code + * MicroBitI2C i2c(MICROBIT_PIN_SDA, MICROBIT_PIN_SCL); + * @endcode + * @note this should prevent i2c lockups as well. + */ + MicroBitI2C(PinName sda, PinName scl); + + int read(int address, char *data, int length, bool repeated = false); + + int write(int address, const char *data, int length, bool repeated = false); +}; + +#endif diff --git a/inc/MicroBitIO.h b/inc/MicroBitIO.h new file mode 100644 index 0000000..5080d21 --- /dev/null +++ b/inc/MicroBitIO.h @@ -0,0 +1,52 @@ +#ifndef MICROBIT_IO_H +#define MICROBIT_IO_H + +#include "mbed.h" +#include "MicroBitPin.h" + +/** + * Class definition for MicroBit IO. + * + * This is an object that contains the pins on the edge connector as properties. + */ +class MicroBitIO +{ + public: + + MicroBitPin P0; + MicroBitPin P1; + MicroBitPin P2; + MicroBitPin P3; + MicroBitPin P4; + MicroBitPin P5; + MicroBitPin P6; + MicroBitPin P7; + MicroBitPin P8; + MicroBitPin P9; + MicroBitPin P10; + MicroBitPin P11; + MicroBitPin P12; + MicroBitPin P13; + MicroBitPin P14; + MicroBitPin P15; + MicroBitPin P16; + MicroBitPin P19; + MicroBitPin P20; + + /** + * Constructor. + * Create a representation of all given I/O pins on the edge connector + */ + MicroBitIO(int MICROBIT_ID_IO_P0, int MICROBIT_ID_IO_P1, int MICROBIT_ID_IO_P2, + int MICROBIT_ID_IO_P3, int MICROBIT_ID_IO_P4, int MICROBIT_ID_IO_P5, + int MICROBIT_ID_IO_P6, int MICROBIT_ID_IO_P7, int MICROBIT_ID_IO_P8, + int MICROBIT_ID_IO_P9, int MICROBIT_ID_IO_P10,int MICROBIT_ID_IO_P11, + int MICROBIT_ID_IO_P12,int MICROBIT_ID_IO_P13,int MICROBIT_ID_IO_P14, + int MICROBIT_ID_IO_P15,int MICROBIT_ID_IO_P16,int MICROBIT_ID_IO_P19, + int MICROBIT_ID_IO_P20); + + +}; + +#endif + diff --git a/inc/MicroBitImage.h b/inc/MicroBitImage.h new file mode 100644 index 0000000..f0093f0 --- /dev/null +++ b/inc/MicroBitImage.h @@ -0,0 +1,368 @@ +#ifndef MICROBIT_IMAGE_H +#define MICROBIT_IMAGE_H + +#include "mbed.h" + +/** + * Class definition for a MicroBitImage. + * + * An MicroBitImage is a simple bitmap representation of an image. + * n.b. This is a mutable, managed type. + */ +class MicroBitImage +{ + int16_t width; // Width of the bitmap, in pixels. + int16_t height; // Height of the bitmap, in pixels. + int16_t *ref; // Reference count. + + + /** + * Internal constructor which provides sanity checking and initialises class properties. + * + * @param x the width of the image + * @param y the height of the image + * @param bitmap an array of integers that make up an image. + */ + void init(const int16_t x, const int16_t y, const uint8_t *bitmap); + + /** + * Internal constructor which defaults to the Empty Image instance variable + */ + void init_empty(); + + public: + static MicroBitImage EmptyImage; // Shared representation of a null image. + uint8_t *bitmap; // 2D array representing the bitmap image. + + /** + * Default Constructor. + * Creates a new reference to the empty MicroBitImage bitmap + * + * Example: + * @code + * MicroBitImage i(); //an empty image + * @endcode + */ + MicroBitImage(); + + + /** + * Copy Constructor. + * Add ourselves as a reference to an existing MicroBitImage. + * + * @param image The MicroBitImage to reference. + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n"); + * MicroBitImage i2(i); //points to i + * @endcode + */ + MicroBitImage(const MicroBitImage &image); + + /** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param s A text based representation of the image given whitespace delimited numeric values. + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * @endcode + */ + explicit MicroBitImage(const char *s); + + /** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param x the width of the image. + * @param y the height of the image. + * + * Bitmap buffer is linear, with 8 bits per pixel, row by row, + * top to bottom with no word alignment. Stride is therefore the image width in pixels. + * in where w and h are width and height respectively, the layout is therefore: + * + * |[0,0]...[w,o][1,0]...[w,1] ... [[w,h] + * + * A copy of the image is made in RAM, as images are mutable. + * + * Example: + * @code + * MicroBitImage i(5,5); // a blank 5x5 image + * @endcode + */ + MicroBitImage(const int16_t x, const int16_t y); + + /** + * Constructor. + * Create a bitmap representation of a given size, based on a given buffer. + * + * @param x the width of the image. + * @param y the height of the image. + * @param bitmap a 2D array representing the image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * @endcode + */ + MicroBitImage(const int16_t x, const int16_t y, const uint8_t *bitmap); + + /** + * Destructor. + * Removes buffer resources held by the instance. + */ + ~MicroBitImage(); + + /** + * Copy assign operation. + * + * Called when one MicroBitImage is assigned the value of another using the '=' operator. + * Decrement our reference count and free up the buffer as necessary. + * Then, update our buffer to refer to that of the supplied MicroBitImage, + * and increase its reference count. + * + * @param s The MicroBitImage to reference. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * MicroBitImage i1(); + * i1 = 1; // i1 now references i + * @endcode + */ + MicroBitImage& operator = (const MicroBitImage& i); + + + /** + * Equality operation. + * + * Called when one MicroBitImage is tested to be equal to another using the '==' operator. + * + * @param i The MicroBitImage to test ourselves against. + * @return true if this MicroBitImage is identical to the one supplied, false otherwise. + * + * Example: + * @code + * MicroBitImage i(); + * MicroBitImage i1(); + * + * if(i == i1) //will be true + * print("true"); + * @endcode + */ + bool operator== (const MicroBitImage& i); + + /** + * Clears all pixels in this image + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.clear(); + * @endcode + */ + void clear(); + + /** + * Sets the pixel at the given co-ordinates to a given value. + * @param x The co-ordinate of the pixel to change w.r.t. top left origin. + * @param y The co-ordinate of the pixel to change w.r.t. top left origin. + * @param value The new value of the pixel (the brightness level 0-255) + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.setPixelValue(0,0,255); + * @endcode + */ + void setPixelValue(int16_t x , int16_t y, uint8_t value); + + /** + * Determines the value of a given pixel. + * @return The value assigned to the given pixel location (the brightness level 0-255) + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.getPixelValue(0,0); //should be 0; + * @endcode + */ + int getPixelValue(int16_t x , int16_t y); + + /** + * Replaces the content of this image with that of a given + * 2D array representing the image. + * Origin is in the top left corner of the image. + * + * @param x the width of the image. + * @param y the height of the image. + * @param bitmap a 2D array representing the image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(); + * i.printImage(0,0,heart); + * @endcode + */ + void printImage(int16_t x, int16_t y, const uint8_t *bitmap); + + /** + * Pastes a given bitmap at the given co-ordinates. + * Any pixels in the relvant area of this image are replaced. + * + * @param image The MicroBitImage to paste. + * @param x The leftmost X co-ordinate in this image where the given image should be pasted. + * @param y The uppermost Y co-ordinate in this image where the given image should be pasted. + * @param alpha set to 1 if transparency clear pixels in given image should be treated as transparent. Set to 0 otherwise. + * @return The number of pixels written. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); //if you show this image - you will see a big heart + * i.paste(-5,0,i); //displays a small heart :) + * @endcode + */ + int paste(const MicroBitImage &image, int16_t x, int16_t y, uint8_t alpha); + + /** + * Prints a character to the display at the given location + * + * @param c The character to display. + * @param x The x co-ordinate of on the image to place the top left of the character + * @param y The y co-ordinate of on the image to place the top left of the character + * + * Example: + * @code + * MicroBitImage i(5,5); + * i.print('a',0,0); + * @endcode + */ + void print(char c, int16_t x, int16_t y); + + /** + * Shifts the pixels in this Image a given number of pixels to the Left. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); //if you show this image - you will see a big heart + * i.shiftLeft(5); //displays a small heart :) + * @endcode + */ + void shiftLeft(int16_t n); + + /** + * Shifts the pixels in this Image a given number of pixels to the Right. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftLeft(5); //displays a small heart :) + * i.shiftRight(5); //displays a big heart :) + * @endcode + */ + void shiftRight(int16_t n); + + /** + * Shifts the pixels in this Image a given number of pixels to Upward. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftUp(1); + * @endcode + */ + void shiftUp(int16_t n); + + /** + * Shifts the pixels in this Image a given number of pixels to Downward. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftDown(1); + * @endcode + */ + void shiftDown(int16_t n); + + /** + * Gets the width of this image. + * + * @return The width of this image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getWidth(); //equals 10... + * @endcode + */ + int getWidth(); + + /** + * Gets the height of this image. + * + * @return The height of this image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getHeight(); //equals 5... + * @endcode + */ + int getHeight(); + + /** + * Converts the bitmap to a csv string. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.printString(i.toString()); // "0,1,0,1,0,0,0,0,0,0\n..." + * @endcode + */ + ManagedString toString(); + + /** + * Crops the image to the given dimensions + * + * @param startx the location to start the crop in the x-axis + * @param starty the location to start the crop in the y-axis + * @param width the width of the desired cropped region + * @param height the height of the desired cropped region + * + * @return an instance of MicroBitImage with your cropped region + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.printImage(i.crop(0,0,2,2)); // "0,1\n1,1\n" + * @endcode + */ + MicroBitImage crop(int startx, int starty, int finx, int finy); + +}; + +#endif + diff --git a/inc/MicroBitMalloc.h b/inc/MicroBitMalloc.h new file mode 100644 index 0000000..74e48ab --- /dev/null +++ b/inc/MicroBitMalloc.h @@ -0,0 +1,39 @@ +#ifndef MICROBIT_MALLOC_H +#define MICROBIT_MALLOC_H + +#include "MicroBit.h" +#include + +/** + \brief Overrides malloc globally, and fires the panic function if we run out of memory! + */ +inline void* ubit_malloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + + if(ptr == NULL) + panic(MICROBIT_OOM); + + return ptr; +} + +/** + \brief Overrides malloc globally, and fires the panic function if we run out of memory! + */ +inline void* operator new(size_t size) throw(std::bad_alloc) +{ + void *ptr; + + ptr = malloc(size); + + if(ptr == NULL) + panic(MICROBIT_OOM); + + return ptr; +} + +#define malloc(X) ubit_malloc( X ) //macro! Override malloc! Hehehe + +#endif diff --git a/inc/MicroBitMatrixMaps.h b/inc/MicroBitMatrixMaps.h new file mode 100644 index 0000000..92cdcce --- /dev/null +++ b/inc/MicroBitMatrixMaps.h @@ -0,0 +1,73 @@ +/** + * Definition of the LED Matrix maps supported. + * Each map represents the layou of a different device. + * + * Ensure only one of these is selected. + */ + +#ifndef MICROBIT_MATRIX_MAPS_H +#define MICROBIT_MATRIX_MAPS_H + +/** + * Provides the mapping from Matrix ROW/COL to a linear X/Y buffer. + * It's arranged such that matrixMap[col, row] provides the [x,y] screen co-ord. + */ + +#define NO_CONN 0 + +#ifdef MICROBUG_REFERENCE_DEVICE + const MatrixPoint MicroBitDisplay::matrixMap[MICROBIT_DISPLAY_COLUMN_COUNT][MICROBIT_DISPLAY_ROW_COUNT] = + { {MatrixPoint(0,0),MatrixPoint(0,1),MatrixPoint(0,2), MatrixPoint(0,3), MatrixPoint(0,4)}, + {MatrixPoint(1,0),MatrixPoint(1,1),MatrixPoint(1,2), MatrixPoint(1,3), MatrixPoint(1,4)}, + {MatrixPoint(2,0),MatrixPoint(2,1),MatrixPoint(2,2), MatrixPoint(2,3), MatrixPoint(2,4)}, + {MatrixPoint(3,0),MatrixPoint(3,1),MatrixPoint(3,2), MatrixPoint(3,3), MatrixPoint(3,4)}, + {MatrixPoint(4,0),MatrixPoint(4,1),MatrixPoint(4,2), MatrixPoint(4,3), MatrixPoint(4,4)} + }; +#endif + +#ifdef MICROBIT_3X9 + const MatrixPoint MicroBitDisplay::matrixMap[MICROBIT_DISPLAY_COLUMN_COUNT][MICROBIT_DISPLAY_ROW_COUNT] = + { + {MatrixPoint(0,4),MatrixPoint(0,3),MatrixPoint(1,1)}, + {MatrixPoint(1,4),MatrixPoint(4,2),MatrixPoint(0,1)}, + {MatrixPoint(2,4),MatrixPoint(3,2),MatrixPoint(4,0)}, + {MatrixPoint(3,4),MatrixPoint(2,2),MatrixPoint(3,0)}, + {MatrixPoint(4,4),MatrixPoint(1,2),MatrixPoint(2,0)}, + {MatrixPoint(4,3),MatrixPoint(0,2),MatrixPoint(1,0)}, + {MatrixPoint(3,3),MatrixPoint(4,1),MatrixPoint(0,0)}, + {MatrixPoint(2,3),MatrixPoint(3,1),MatrixPoint(NO_CONN,NO_CONN)}, + {MatrixPoint(1,3),MatrixPoint(2,1),MatrixPoint(NO_CONN,NO_CONN)} + }; +#endif + +#ifdef MICROBIT_SB1 + const MatrixPoint MicroBitDisplay::matrixMap[MICROBIT_DISPLAY_COLUMN_COUNT][MICROBIT_DISPLAY_ROW_COUNT] = + { + {MatrixPoint(0,4), MatrixPoint(1,4), MatrixPoint(2,4), MatrixPoint(3,4), MatrixPoint(4,4), MatrixPoint(4,3), MatrixPoint(3,3), MatrixPoint(2,3), MatrixPoint(1,3)}, + {MatrixPoint(0,3), MatrixPoint(4,2), MatrixPoint(3,2), MatrixPoint(2,2), MatrixPoint(1,2), MatrixPoint(0,2), MatrixPoint(4,1), MatrixPoint(3,1), MatrixPoint(2,1)}, + {MatrixPoint(1,1), MatrixPoint(0,1), MatrixPoint(4,0), MatrixPoint(3,0), MatrixPoint(2,0), MatrixPoint(1,0), MatrixPoint(0,0), MatrixPoint(NO_CONN,NO_CONN), MatrixPoint(NO_CONN,NO_CONN)} + }; +#endif + +#ifdef MICROBIT_SB2 + const MatrixPoint MicroBitDisplay::matrixMap[MICROBIT_DISPLAY_COLUMN_COUNT][MICROBIT_DISPLAY_ROW_COUNT] = + { + {MatrixPoint(0,0),MatrixPoint(4,2),MatrixPoint(2,4)}, + {MatrixPoint(2,0),MatrixPoint(0,2),MatrixPoint(4,4)}, + {MatrixPoint(4,0),MatrixPoint(2,2),MatrixPoint(0,4)}, + {MatrixPoint(4,3),MatrixPoint(1,0),MatrixPoint(0,1)}, + {MatrixPoint(3,3),MatrixPoint(3,0),MatrixPoint(1,1)}, + {MatrixPoint(2,3),MatrixPoint(3,4),MatrixPoint(2,1)}, + {MatrixPoint(1,3),MatrixPoint(1,4),MatrixPoint(3,1)}, + {MatrixPoint(0,3),MatrixPoint(NO_CONN,NO_CONN),MatrixPoint(4,1)}, + {MatrixPoint(1,2),MatrixPoint(NO_CONN,NO_CONN),MatrixPoint(3,2)} + }; + + +#endif + +const PinName rowPins[MICROBIT_DISPLAY_ROW_COUNT] = {MICROBIT_DISPLAY_ROW_PINS}; +const uint8_t panicFace[MICROBIT_DISPLAY_COLUMN_COUNT] = {0x1B, 0x1B,0x0,0x0E,0x11}; + +#endif + diff --git a/inc/MicroBitMessageBus.h b/inc/MicroBitMessageBus.h new file mode 100644 index 0000000..84060f5 --- /dev/null +++ b/inc/MicroBitMessageBus.h @@ -0,0 +1,141 @@ +#ifndef MICROBIT_MESSAGE_BUS_H +#define MICROBIT_MESSAGE_BUS_H + +#include "mbed.h" +#include "MicroBitEvent.h" + +// Enumeration of core components. +#define MICROBIT_CONTROL_BUS_ID 0 +#define MICROBIT_ID_ANY 0 +#define MICROBIT_EVT_ANY 0 + +struct MicroBitListener +{ + uint16_t id; // The ID of the component that this listener is interested in. + uint16_t value; // Value this listener is interested in receiving. + void* cb; // Callback function associated with this listener. Either (*cb)(MicroBitEvent) or (*cb)(MicroBitEvent, void*) depending on whether cb_arg is NULL. + void* cb_arg; // Argument to be passed to the caller. This is assumed to be a pointer, so passing in NULL means that the function doesn't take an argument. + MicroBitEvent evt; + + MicroBitListener *next; + + /** + * Constructor. + * Create a new Message Bus Listener. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component + * @param handler A function pointer to call when the event is detected. + */ + MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent)); + + /** + * Alternative constructor where we register a value to be passed to the + * callback. If arg == NULL, the function takes no extra arguemnt. + * Otherwise, the function is understood to take an extra argument. + */ + MicroBitListener(uint16_t id, uint16_t value, void *handler, void* arg); +}; + +struct MicroBitMessageBusCache +{ + int seq; + MicroBitListener *ptr; +}; + + +/** + * Class definition for the MicroBitMessageBus. + * + * The MicroBitMessageBus is the common mechanism to deliver asynchronous events on the + * MicroBit platform. It serves a number of purposes: + * + * 1) It provides an eventing abstraction that is independent of the underlying substrate. + * 2) It provides a mechanism to decouple user code from trusted system code + * i.e. the basis of a message passing nano kernel. + * 3) It allows a common high level eventing abstraction across a range of hardware types.e.g. buttons, BLE... + * 4) It provides a mechanims for extensibility - new devices added via I/O pins can have OO based + drivers and communicate via the message bus with minima impact on user level languages. + * 5) It allows for the possiblility of event / data aggregation, which in turn can save energy. + * It has the following design principles: + * + * 1) Maintain a low RAM footprint where possible + * 2) Make few assumptions about the underlying platform, but allow optimizations where possible. + */ +class MicroBitMessageBus +{ + public: + + /** + * Default constructor. + * Anticipating only one MessageBus per device, as filtering is handled within the class. + */ + MicroBitMessageBus(); + + /** + * Send the given event to all regstered recipients. + * + * @param The event to send. This structure is assumed to be heap allocated, and will + * be automatically freed once all recipients have been notified. + * + * THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE... + * + * Example: + * @code + * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,false); + * evt.fire(); + * //OR YOU CAN DO THIS... + * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN); + * @endcode + */ + void send(MicroBitEvent &evt); + + /** + * Send the given event to all regstered recipients, using a cached entry to minimize lookups. + * This is particularly useful for soptimizing ensors that frequently send to the same channel. + * + * @param evt The event to send. This structure is assumed to be heap allocated, and will + * be automatically freed once all recipients have been notified. + * @param c Cache entry to reduce lookups for commonly used channels. + */ + void send(MicroBitEvent &evt, MicroBitMessageBusCache *c); + + /** + * Register a listener function. + * + * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_EVT_ANY to receive events of any value. + * + * @param hander The function to call when an event is received. + * + * Example: + * @code + * void onButtonBClick() + * { + * //do something + * } + * uBit.MessageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); // call function when ever a click event is detected. + * @endcode + */ + void listen(int id, int value, void (*handler)(MicroBitEvent)); + + /** + * Register a listener function. + * + * Same as above, except the listener function is passed an extra argument in addition to the + * MicroBitEvent, when called. + */ + void listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg); + + private: + + MicroBitListener *listeners; // Chain of active listeners. + int seq; // Sequence number. Used to invalidate cache entries. + void listen(int id, int value, void* handler, void* arg); +}; + +#endif + + diff --git a/inc/MicroBitMultiButton.h b/inc/MicroBitMultiButton.h new file mode 100644 index 0000000..1a8b34e --- /dev/null +++ b/inc/MicroBitMultiButton.h @@ -0,0 +1,71 @@ +#ifndef MICROBIT_MULTI_BUTTON_H +#define MICROBIT_MULTI_BUTTON_H + +#include "MicroBit.h" + +#define MICROBIT_MULTI_BUTTON_STATE_1 1 +#define MICROBIT_MULTI_BUTTON_STATE_2 2 +#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1 4 +#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2 8 + +/** + * Class definition for MicroBitMultiButton. + * + * Represents a virtual button, capable of reacting to simultaneous presses of multiple + * other buttons. + */ +class MicroBitMultiButton : public MicroBitComponent +{ + uint16_t button1; // ID of the first button we're monitoring + uint16_t button2; // ID of the second button we're monitoring + + uint16_t otherSubButton(uint16_t b); + int isSubButtonPressed(uint16_t button); + int isSubButtonHeld(uint16_t button); + void setButtonState(uint16_t button, int value); + void setHoldState(uint16_t button, int value); + + public: + + /** + * Constructor. + * Create a representation of a vurtual button, that generates events based upon the combination + * of two given buttons. + * @param id the ID of the new MultiButton object. + * @param button1 the ID of the first button to integrate. + * @param button2 the ID of the second button to integrate. + * @param name the physical pin on the processor that this butotn is connected to. + * + * Example: + * @code + * multiButton(MICROBIT_ID_BUTTON_AB, MICROBIT_ID_BUTTON_A, MICROBIT_ID_BUTTON_B); + * @endcode + * + * Possible Events: + * @code + * MICROBIT_BUTTON_EVT_DOWN + * MICROBIT_BUTTON_EVT_UP + * MICROBIT_BUTTON_EVT_CLICK + * MICROBIT_BUTTON_EVT_LONG_CLICK + * MICROBIT_BUTTON_EVT_DOUBLE_CLICK + * MICROBIT_BUTTON_EVT_HOLD + * @endcode + */ + MicroBitMultiButton(uint16_t id, uint16_t button1, uint16_t button2); + + /** + * Tests if this MultiButton is currently pressed. + * @return 1 if both physical buttons are pressed simultaneously. + * + * Example: + * @code + * if(uBit.buttonAB.isPressed()) + * print("Pressed!"); + * @endcode + */ + int isPressed(); + + void onEvent(MicroBitEvent evt); +}; + +#endif diff --git a/inc/MicroBitPin.h b/inc/MicroBitPin.h new file mode 100644 index 0000000..0186e93 --- /dev/null +++ b/inc/MicroBitPin.h @@ -0,0 +1,159 @@ +#ifndef MICROBIT_PIN_H +#define MICROBIT_PIN_H + +#include "MicroBitComponent.h" +#include "mbed.h" + // Status Field flags... +#define IO_STATUS_DIGITAL_IN 0x01 // Pin is configured as a digital input, with no pull up. +#define IO_STATUS_DIGITAL_OUT 0x02 // Pin is configured as a digital output +#define IO_STATUS_ANALOG_IN 0x04 // Pin is Analog in +#define IO_STATUS_ANALOG_OUT 0x08 // Pin is Analog out (not currently possible) +#define IO_STATUS_TOUCH_IN 0x10 // Pin is a makey-makey style touch sensor +#define IO_STATUS_EVENTBUS_ENABLED 0x80 // Pin is will generate events on change + +//#defines for each edge connector pin +#define MICROBIT_PIN_P0 P0_3 //P0 is the left most pad (ANALOG/DIGITAL) used to be P0_3 on green board +#define MICROBIT_PIN_P1 P0_2 //P1 is the middle pad (ANALOG/DIGITAL) +#define MICROBIT_PIN_P2 P0_1 //P2 is the right most pad (ANALOG/DIGITAL) used to be P0_1 on green board +#define MICROBIT_PIN_P3 P0_4 //COL1 (ANALOG/DIGITAL) +#define MICROBIT_PIN_P4 P0_17 //BTN_A +#define MICROBIT_PIN_P5 P0_5 //COL2 (ANALOG/DIGITAL) +#define MICROBIT_PIN_P6 P0_14 //ROW2 +#define MICROBIT_PIN_P7 P0_13 //ROW1 +#define MICROBIT_PIN_P8 P0_18 //PIN 18 +#define MICROBIT_PIN_P9 P0_15 //ROW3 +#define MICROBIT_PIN_P10 P0_6 //COL3 (ANALOG/DIGITAL) +#define MICROBIT_PIN_P11 P0_26 //BTN_B +#define MICROBIT_PIN_P12 P0_20 //PIN 20 +#define MICROBIT_PIN_P13 P0_23 //SCK +#define MICROBIT_PIN_P14 P0_22 //MISO +#define MICROBIT_PIN_P15 P0_21 //MOSI +#define MICROBIT_PIN_P16 P0_16 //PIN 16 +#define MICROBIT_PIN_P19 P0_0 //SCL +#define MICROBIT_PIN_P20 P0_30 //SDA + +#define MICROBIT_PIN_MAX_OUTPUT 1023 + + +/** + * Pin capabilities enum. + * Used to determine the capabilities of each Pin as some can only be digital, or can be both digital and analogue. + */ +enum PinCapability{ + PIN_CAPABILITY_DIGITAL = 0x01, + PIN_CAPABILITY_ANALOG = 0x02, + PIN_CAPABILITY_TOUCH = 0x04, + PIN_CAPABILITY_AD = PIN_CAPABILITY_DIGITAL | PIN_CAPABILITY_ANALOG, + PIN_CAPABILITY_ALL = PIN_CAPABILITY_DIGITAL | PIN_CAPABILITY_ANALOG | PIN_CAPABILITY_TOUCH + +}; + +/** + * Class definition for MicroBitPin. + * + * Represents a I/O on the edge connector. + */ +class MicroBitPin : public MicroBitComponent +{ + /** + * Unique, enumerated ID for this component. + * Used to track asynchronous events in the event bus. + */ + + void *pin; // The mBed object looking after this pin at any point in time (may change!). + PinName name; // mBed pin name of this pin. + PinCapability capability; + + /** + * Disconnect any attached mBed IO from this pin. + * Used only when pin changes mode (i.e. Input/Output/Analog/Digital) + */ + void disconnect(); + + public: + + /** + * Constructor. + * Create a Button representation with the given ID. + * @param id the ID of the new Pin object. + * @param name the pin name for this MicroBitPin instance to represent + * @param capability the capability of this pin, can it only be digital? can it only be analog? can it be both? + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * @endcode + */ + MicroBitPin(int id, PinName name, PinCapability capability); + + /** + * Configures this IO pin as a digital output (if necessary) and sets the pin to 'value'. + * @param value 0 (LO) or 1 (HI) + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.setDigitalValue(1); // P0 is now HI! + * @endcode + */ + void setDigitalValue(int value); + + /** + * Configures this IO pin as a digital input (if necessary) and tests its current value. + * @return 1 if this input is high, 0 otherwise. + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.getDigitalValue(); // P0 is either 0 or 1; + * @endcode + */ + int getDigitalValue(); + + /** + * Configures this IO pin as an analogue output (if necessary and possible). + * Change the DAC value to the given level. + * @param value the level to set on the output pin, in the range 0..??? + * @note NOT IN USE, but may exist in the future if we do some clever rejigging! :) + */ + void setAnalogValue(int value); + + + /** + * Configures this IO pin as an analogue input (if necessary and possible). + * @return the current analogue level on the pin, in the range 0-0xFFFF + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.getAnalogValue(); // P0 is a value in the range of 0 - 0xFFFF + * @endcode + */ + int getAnalogValue(); + + /** + * Configures this IO pin as a makey makey style touch sensor (if necessary) and tests its current debounced state. + * @return 1 if pin is touched, 0 otherwise. + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); + * if(P0.isTouched()) + * { + * uBit.display.clear(); + * } + * @endcode + */ + int isTouched(); + + /** + * Configures the PWM period of the analog output to the given value. + * If this pin is not configured as an analog output, the operation + * has no effect. + * + * @param period The new period for the analog output in milliseconds. + */ + void setAnalogPeriod(int period); +}; + +#endif diff --git a/inc/MicroBitSerial.h b/inc/MicroBitSerial.h new file mode 100644 index 0000000..2fd34de --- /dev/null +++ b/inc/MicroBitSerial.h @@ -0,0 +1,117 @@ +#include "MicroBit.h" +#ifndef MICROBIT_SERIAL_H +#define MICROBIT_SERIAL_H + +#include "MicroBitComponent.h" +#include "mbed.h" + +#define MICROBIT_SERIAL_DEFAULT_BAUD_RATE 115200 +#define MICROBIT_SERIAL_BUFFER_SIZE 20 + +#define MICROBIT_SERIAL_DEFAULT_EOF '\n' + +/** + * Class definition for MicroBitSerial. + * + * Represents an instance of Serial which accepts micro:bit specific data types. + */ +class MicroBitSerial : public Serial +{ + ssize_t readChars(void* buffer, size_t length, char eof = MICROBIT_SERIAL_DEFAULT_EOF); + + public: + + /** + * Constructor. + * Create an instance of MicroBitSerial + * @param sda the Pin to be used for SDA + * @param scl the Pin to be used for SCL + * Example: + * @code + * MicroBitSerial serial(USBTX, USBRX); + * @endcode + */ + MicroBitSerial(PinName tx, PinName rx); + + /** + * Sends a managed string over serial. + * + * @param s the ManagedString to send + * + * Example: + * @code + * uBit.serial.printString("abc123"); + * @endcode + */ + void sendString(ManagedString s); + + /** + * Reads a ManagedString from serial + * + * @param len the buffer size for the string, default is defined by MICROBIT_SERIAL_BUFFER_SIZE + * + * Example: + * @code + * uBit.serial.readString(); + * @endcode + * + * @note this member function will wait until either the buffer is full, or a \n is received + */ + ManagedString readString(int len = MICROBIT_SERIAL_BUFFER_SIZE); + + /** + * Sends a MicroBitImage over serial in csv format. + * + * @param i the instance of MicroBitImage you would like to send. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.sendImage(i); + * @endcode + */ + void sendImage(MicroBitImage i); + + /** + * Reads a MicroBitImage over serial, in csv format. + * + * + * @return a MicroBitImage with the format described over serial + * + * Example: + * @code + * MicroBitImage i = uBit.serial.readImage(2,2); + * @endcode + * + * Example Serial Format: + * @code + * 0,10x0a0,10x0a // 0x0a is a LF terminal which is used as a delimeter + * @endcode + * @note this will finish once the dimensions are met. + */ + MicroBitImage readImage(int width, int height); + + /** + * Sends the current pixel values, byte-per-pixel, over serial + * + * Example: + * @code + * uBit.serial.sendDisplayState(); + * @endcode + */ + void sendDisplayState(); + + /** + * Reads pixel values, byte-per-pixel, from serial, and sets the display. + * + * Example: + * @code + * uBit.serial.readDisplayState(); + * @endcode + */ + void readDisplayState(); + +}; + +#endif diff --git a/module.json b/module.json new file mode 100644 index 0000000..7df6c20 --- /dev/null +++ b/module.json @@ -0,0 +1,17 @@ +{ + "name": "microbit-dal", + "version": "0.0.1", + "license": "Apache2", + "description": "The runtime library for the BBC micro:bit, developed by Lancaster University", + "keywords": ["mbed-classic", "microbit", "runtime", "library", "lancaster", "University"], + "author": "James Devine ", + "homepage": "https://jamesadevine@developer.mbed.org/teams/Lancaster-University/code/microbit/", + "dependencies":{ + "mbed-classic": "~0.0.4", + "ble": "jamesadevine/BLE_API#master", + "ble-nrf51822": "jamesadevine/nrf51822#master" + }, + "extraIncludes":[ + "inc" + ] +} \ No newline at end of file diff --git a/source/CortexContextSwitch.s b/source/CortexContextSwitch.s new file mode 100644 index 0000000..090c95e --- /dev/null +++ b/source/CortexContextSwitch.s @@ -0,0 +1,187 @@ + AREA asm_func, CODE, READONLY + +; Export our context switching subroutine as a C function for use in mBed + EXPORT swap_context + EXPORT save_context + + ALIGN + +; R0 Contains a pointer to the TCB of the fibre being scheduled out. +; R1 Contains a pointer to the TCB of the fibre being scheduled in. +; R2 Contains a pointer to the base of the stack of the fibre being scheduled out. +; R3 Contains a pointer to the base of the stack of the fibre being scheduled in. + +swap_context + + ; Write our core registers into the TCB + ; First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + ; Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + ; Now the Stack and Link Register. + ; As this context is only intended for use with a fiber scheduler, + ; we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + ; Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point + ; of sceduling, but we need a lot of capacity for interrupt handling and other functions. + + MOVS R7, #0x20 ; Load R8 with top of System Stack space. + LSLS R7, #24 + MOVS R4, #0x40 + LSLS R4, #8 + ORRS R7, R4 + MOV R4, R7 + +store_stack + SUBS R4, #4 + SUBS R2, #4 + + LDR R5, [R4] + STR R5, [R2] + + CMP R4, R6 + BNE store_stack + + ; + ; Now page in the new context. + ; Update all registers except the PC. We can also safely ignore the STATUS register, as we're just a fiber scheduler. + ; + LDR R4, [R1, #56] + MOV LR, R4 + LDR R6, [R1, #52] + MOV SP, R6 + + ; Copy the stack in. + ; n.b. we do this after setting the SP to make comparisons easier. + + MOV R4, R7 ; Load R4 with top of System Stack space. + + +restore_stack + SUBS R4, #4 + SUBS R3, #4 + + LDR R5, [R3] + STR R5, [R4] + + CMP R4, R6 + BNE restore_stack + + LDR R4, [R1, #48] + MOV R12, R4 + LDR R4, [R1, #44] + MOV R11, R4 + LDR R4, [R1, #40] + MOV R10, R4 + LDR R4, [R1, #36] + MOV R9, R4 + LDR R4, [R1, #32] + MOV R8, R4 + + LDR R7, [R1, #28] + LDR R6, [R1, #24] + LDR R5, [R1, #20] + LDR R4, [R1, #16] + LDR R3, [R1, #12] + LDR R2, [R1, #8] + LDR R0, [R1, #0] + LDR R1, [R1, #4] + + ; Return to caller (scheduler). + BX LR + + + + +; R0 Contains a pointer to the TCB of the fibre to snapshot +; R1 Contains a pointer to the base of the stack of the fibre being snapshotted + +save_context + + ; Write our core registers into the TCB + ; First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + ; Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + ; Now the Stack and Link Register. + ; As this context is only intended for use with a fiber scheduler, + ; we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + ; Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point + ; of sceduling, but we need a lot of capacity for interrupt handling and other functions. + + MOVS R5, #0x20 ; Load R8 with top of System Stack space. + LSLS R5, #24 + MOVS R4, #0x40 + LSLS R4, #8 + ORRS R5, R4 + MOV R4, R5 + +store_stack1 + SUBS R4, #4 + SUBS R1, #4 + + LDR R5, [R4] + STR R5, [R1] + + CMP R4, R6 + BNE store_stack1 + + ; Restore scratch registers. + + LDR R7, [R0, #28] + LDR R6, [R0, #24] + LDR R5, [R0, #20] + LDR R4, [R0, #16] + + ; Return to caller (scheduler). + BX LR + + ALIGN + END \ No newline at end of file diff --git a/source/CortexContextSwitch.s.gcc b/source/CortexContextSwitch.s.gcc new file mode 100644 index 0000000..3e96f8c --- /dev/null +++ b/source/CortexContextSwitch.s.gcc @@ -0,0 +1,187 @@ + .syntax unified + .cpu cortex-m0 + .thumb + .text + .align 2 + +@ Export our context switching subroutine as a C function for use in mBed + .global swap_context + .global save_context + +@ R0 Contains a pointer to the TCB of the fibre being scheduled out. +@ R1 Contains a pointer to the TCB of the fibre being scheduled in. +@ R2 Contains a pointer to the base of the stack of the fibre being scheduled out. +@ R3 Contains a pointer to the base of the stack of the fibre being scheduled in. + +swap_context: + + @ Write our core registers into the TCB + @ First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + @ Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + @ Now the Stack and Link Register. + @ As this context is only intended for use with a fiber scheduler, + @ we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + @ Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point + @ of sceduling, but we need a lot of capacity for interrupt handling and other functions. + + MOVS R7, #0x20 @ Load R8 with top of System Stack space. + LSLS R7, #24 + MOVS R4, #0x40 + LSLS R4, #8 + ORRS R7, R4 + MOV R4, R7 + +store_stack: + SUBS R4, #4 + SUBS R2, #4 + + LDR R5, [R4] + STR R5, [R2] + + CMP R4, R6 + BNE store_stack + + @ + @ Now page in the new context. + @ Update all registers except the PC. We can also safely ignore the STATUS register, as we're just a fiber scheduler. + @ + LDR R4, [R1, #56] + MOV LR, R4 + LDR R6, [R1, #52] + MOV SP, R6 + + @ Copy the stack in. + @ n.b. we do this after setting the SP to make comparisons easier. + + MOV R4, R7 @ Load R4 with top of System Stack space. + + +restore_stack: + SUBS R4, #4 + SUBS R3, #4 + + LDR R5, [R3] + STR R5, [R4] + + CMP R4, R6 + BNE restore_stack + + LDR R4, [R1, #48] + MOV R12, R4 + LDR R4, [R1, #44] + MOV R11, R4 + LDR R4, [R1, #40] + MOV R10, R4 + LDR R4, [R1, #36] + MOV R9, R4 + LDR R4, [R1, #32] + MOV R8, R4 + + LDR R7, [R1, #28] + LDR R6, [R1, #24] + LDR R5, [R1, #20] + LDR R4, [R1, #16] + LDR R3, [R1, #12] + LDR R2, [R1, #8] + LDR R0, [R1, #0] + LDR R1, [R1, #4] + + @ Return to caller (scheduler). + BX LR + + + + +@ R0 Contains a pointer to the TCB of the fibre to snapshot +@ R1 Contains a pointer to the base of the stack of the fibre being snapshotted + +save_context: + + @ Write our core registers into the TCB + @ First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + @ Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + @ Now the Stack and Link Register. + @ As this context is only intended for use with a fiber scheduler, + @ we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + @ Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point + @ of sceduling, but we need a lot of capacity for interrupt handling and other functions. + + MOVS R5, #0x20 @ Load R8 with top of System Stack space. + LSLS R5, #24 + MOVS R4, #0x40 + LSLS R4, #8 + ORRS R5, R4 + MOV R4, R5 + +store_stack1: + SUBS R4, #4 + SUBS R1, #4 + + LDR R5, [R4] + STR R5, [R1] + + CMP R4, R6 + BNE store_stack1 + + @ Restore scratch registers. + + LDR R7, [R0, #28] + LDR R6, [R0, #24] + LDR R5, [R0, #20] + LDR R4, [R0, #16] + + @ Return to caller (scheduler). + BX LR + diff --git a/source/DynamicPwm.cpp b/source/DynamicPwm.cpp new file mode 100644 index 0000000..8985082 --- /dev/null +++ b/source/DynamicPwm.cpp @@ -0,0 +1,177 @@ +#include "DynamicPwm.h" + + +DynamicPwm* DynamicPwm::pwms[NO_PWMS] = { NULL }; + +uint8_t DynamicPwm::lastUsed = NO_PWMS+1; //set it to out of range i.e. 4 so we know it hasn't been used yet. + +/** + * Reassigns an already operational PWM channel to the given pin + * #HACK #BODGE # YUCK #MBED_SHOULD_DO_THIS + * + * @param pin The pin to start running PWM on + * @param oldPin The pin to stop running PWM on + * @param channel_number The GPIOTE channel being used to drive this PWM channel + */ +void gpiote_reinit(PinName pin, PinName oldPin, uint8_t channel_number) +{ + // Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output. + NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) + | (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) + | (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) + | (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) + | (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos); + + NRF_GPIO->OUTCLR = (1 << oldPin); + NRF_GPIO->OUTCLR = (1 << pin); + + /* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly. + If it does not, the channel output inheritance sets the proper level. */ + + NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) | + ((uint32_t)pin << GPIOTE_CONFIG_PSEL_Pos) | + ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) | + ((uint32_t)GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos); // ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << + // GPIOTE_CONFIG_OUTINIT_Pos);// + + /* Three NOPs are required to make sure configuration is written before setting tasks or getting events */ + __NOP(); + __NOP(); + __NOP(); + + NRF_TIMER2->CC[channel_number] = 0; +} + +/** + * An internal constructor used when allocating a new DynamicPwm representation + * @param pin the name of the pin for the pwm to target + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + * @param period the frequency of the pwm channel in us. + */ +DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence, int period) : PwmOut(pin) +{ + this->flags = persistence; + this->setPeriodUs(period); +} + +/** + * Redirects the pwm channel to point at a different pin. + * @param pin the new pin to direct PWM at. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->redirect(PinName n2); // pwm is now produced on n2 + * @endcode + */ +void DynamicPwm::redirect(PinName pin) +{ + gpiote_reinit(pin, _pwm.pin, (uint8_t)_pwm.pwm); + this->_pwm.pin = pin; +} + +/** + * Retrieves a pointer to the first available free pwm channel - or the first one that can be reallocated. + * @param pin the name of the pin for the pwm to target + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + * @param period the frequency of the pwm channel in us. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * @endcode + */ +DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence, int period) +{ + //try to find a blank spot first + for(int i = 0; i < NO_PWMS; i++) + { + if(pwms[i] == NULL) + { + lastUsed = i; + pwms[i] = new DynamicPwm(pin, persistence, period); + return pwms[i]; + } + } + + //no blank spot.. try to find a transient PWM + for(int i = 0; i < NO_PWMS; i++) + { + if(pwms[i]->flags & PWM_PERSISTENCE_TRANSIENT && i != lastUsed) + { + lastUsed = i; + pwms[i]->flags = persistence; + pwms[i]->redirect(pin); + return pwms[i]; + } + } + + //if we haven't found a free one, we must try to allocate the last used... + if(pwms[lastUsed]->flags & PWM_PERSISTENCE_TRANSIENT) + { + pwms[lastUsed]->flags = persistence; + pwms[lastUsed]->redirect(pin); + return pwms[lastUsed]; + } + + //well if we have no transient channels - we can't give any away! :( return null + return (DynamicPwm*)NULL; +} + +/** + * Frees this DynamicPwm instance if the pointer is valid. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->free(); + * @endcode + */ +void DynamicPwm::free() +{ + + //free the pwm instance. + NRF_GPIOTE->CONFIG[(uint8_t) _pwm.pwm] = 0; + pwmout_free(&_pwm); + this->flags = PWM_PERSISTENCE_TRANSIENT; + + //set the pointer to this object to null... + for(int i =0; i < NO_PWMS; i++) + if(pwms[i] == this) + { + delete pwms[i]; + pwms[i] = NULL; + } +} + +/** + * Retreives the pin name associated with this DynamicPwm instance. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPinName(); // equal to n + * @endcode + */ +PinName DynamicPwm::getPinName() +{ + return _pwm.pin; +} + +/** + * Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->setPeriodUs(1000); // period now is 1ms + * @endcode + * + * @note The display uses the pwm module, if you change this value the display may flicker. + */ +void DynamicPwm::setPeriodUs(int period) +{ + period_us(period); +} diff --git a/source/ManagedString.cpp b/source/ManagedString.cpp new file mode 100644 index 0000000..b0c541b --- /dev/null +++ b/source/ManagedString.cpp @@ -0,0 +1,422 @@ +#include +#include +#include "MicroBitCompat.h" +#include "ManagedString.h" + + +/** + * Internal constructor helper. + * Configures this ManagedString to refer to the static EmptyString + */ +void ManagedString::initEmpty() +{ + data = ManagedString::EmptyString.data; + ref = ManagedString::EmptyString.ref; + len = ManagedString::EmptyString.len; + + (*ref)++; +} + +/** + * Internal constructor helper. + * creates this ManagedString based on a given null terminated char array. + */ +void ManagedString::initString(const char *str) +{ + // Initialise this ManagedString as a new string, using the data provided. + // We assume the string is sane, and null terminated. + len = strlen(str); + data = (char *) malloc(len+1); + memcpy(data, str, len+1); + ref = (int16_t *) malloc(sizeof(int16_t)); + *ref = 1; +} + + +/** + * Constructor. + * Create a managed string from a given integer. + * + * @param value The integer from which to create the ManagedString + * + * Example: + * @code + * ManagedString s(20); + * @endcode + */ +ManagedString::ManagedString(const int value) +{ + char str[12]; + + itoa(value, str); + initString(str); +} + +/** + * Constructor. + * Create a managed string from a given char. + * + * @param value The char from which to create the ManagedString + * + * Example: + * @code + * ManagedString s('a'); + * @endcode + */ +ManagedString::ManagedString(const char value) +{ + + char str[2] = {value, 0}; + initString(str); +} + + +/** + * Constructor. + * Create a managed string from a pointer to an 8-bit character buffer. + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be decalred on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + */ +ManagedString::ManagedString(const char *str) +{ + // Sanity check. Return EmptyString for anything distasteful + if ((str == NULL || *str == 0) && this != &ManagedString::EmptyString) + { + initEmpty(); + return; + } + + initString(str); +} + +ManagedString::ManagedString(const ManagedString &s1, const ManagedString &s2) +{ + // Calculate length of new string. + len = s1.len + s2.len; + + // Create a new buffer for holding the new string data. + data = (char *) malloc(len+1); + + // Enter the data, and terminate the string. + memcpy(data, s1.data, s1.len); + memcpy(data + s1.len, s2.data, s2.len); + data[len] = 0; + + // Initialise the ref count and we're done. + ref = (int16_t *) malloc(sizeof(int16_t)); + *ref = 1; +} + + +/** + * Constructor. + * Create a managed string from a pointer to an 8-bit character buffer of a given length. + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * @param length The length of the character array + * + * Example: + * @code + * ManagedString s("abcdefg",7); // this is generally used for substring... why not use a normal char * constructor? + * @endcode + */ +ManagedString::ManagedString(const char *str, const int16_t length) +{ + // Sanity check. Return EmptyString for anything distasteful + if (str == NULL || *str == 0 || length > strlen(str)) + { + initEmpty(); + return; + } + + // Store the length of the new string + len = length; + + // Allocate a new buffer, and create a NULL terminated string. + data = (char *) malloc(len+1); + memcpy(data, str, len); + data[len] = 0; + + // Initialize a refcount and we're done. + ref = (int16_t *) malloc(sizeof(int16_t)); + *ref = 1; +} + +/** + * Copy constructor. + * Makes a new ManagedString identical to the one supplied. + * Shares the character buffer and reference count with the supplied ManagedString. + * + * @param s The ManagedString to copy. + * + * Example: + * @code + * ManagedString s("abcdefg"); + * ManagedString p(s); + * @endcode + */ +ManagedString::ManagedString(const ManagedString &s) +{ + data = s.data; + ref = s.ref; + len = s.len; + + (*ref)++; +} + + +/** + * Default constructor. + * + * Create an empty ManagedString. + * + * Example: + * @code + * ManagedString s(); + * @endcode + */ +ManagedString::ManagedString() +{ + initEmpty(); +} + +/** + * Destructor. + * + * Free this ManagedString, and decrement the reference count to the + * internal character buffer. If we're holding the last reference, + * also free the character buffer and reference counter. + */ +ManagedString::~ManagedString() +{ + if(--(*ref) == 0) + { + free(data); + free(ref); + } +} + +/** + * Copy assign operation. + * + * Called when one ManagedString is assigned the value of another. + * If the ManagedString being assigned is already refering to a character buffer, + * decrement the reference count and free up the buffer as necessary. + * Then, update our character buffer to refer to that of the supplied ManagedString, + * and increase its reference count. + * + * @param s The ManagedString to copy. + * + * Example: + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * p = s // p now points to s, s' ref is incremented + * @endcode + */ +ManagedString& ManagedString::operator = (const ManagedString& s) +{ + if(this == &s) + return *this; + + if(--(*ref) == 0) + { + free(data); + free(ref); + } + + data = s.data; + ref = s.ref; + len = s.len; + (*ref)++; + + return *this; +} + +/** + * Equality operation. + * + * Called when one ManagedString is tested to be equal to another using the '==' operator. + * + * @param s The ManagedString to test ourselves against. + * @return true if this ManagedString is identical to the one supplied, false otherwise. + * + * Example: + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * + * if(p==s) + * print("We are the same!"); + * else + * print("We are different!"); //p is not equal to s - this will be called + * @endcode + */ +bool ManagedString::operator== (const ManagedString& s) +{ + return ((len == s.len) && (memcmp(data,s.data,len)==0)); +} + +/** + * Inequality operation. + * + * Called when one ManagedString is tested to be less than another using the '<' operator. + * + * @param s The ManagedString to test ourselves against. + * @return true if this ManagedString is alphabetically less than to the one supplied, false otherwise. + * + * Example: + * @code + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(s' operator. + * + * @param s The ManagedString to test ourselves against. + * @return true if this ManagedString is alphabetically greater than to the one supplied, false otherwise. + * + * Example: + * @code + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(p>a) + * print("b is after a!"); //b is after a + * else + * print("a is after b!"); + * @endcode + */ +bool ManagedString::operator> (const ManagedString& s) +{ + return (memcmp(data, s.data,min(len,s.len))>0); +} + +/** + * Extracts a ManagedString from this string, at the position provided. + * + * @param start The index of the first character to extract, indexed from zero. + * @param length The number of characters to extract from the start position + * @return a ManagedString representing the requested substring. + * + * Example: + * @code + * ManagedString s("abcdefg"); + * + * print(s.substring(0,2)) // prints "ab" + * @endcode + */ +ManagedString ManagedString::substring(int16_t start, int16_t length) +{ + // If the parameters are illegal, just return a reference to the empty string. + if (start >= len) + return ManagedString(ManagedString::EmptyString); + + // Compute a safe copy length; + length = min(len-start, length); + + // Build a ManagedString from this. + return ManagedString(data+start, length); +} + +/** + * Concatenates this string with the one provided. + * + * @param s The ManagedString to concatenate. + * @return a new ManagedString representing the joined strings. + * + * Example: + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh") + * + * print(s + p) // prints "abcdefgh" + * @endcode + */ +ManagedString ManagedString::operator+ (ManagedString& s) +{ + // If the other string is empty, nothing to do! + if(s.len == 0) + return *this; + + if (len == 0) + return s; + + if(s == ManagedString::EmptyString) + return *this; + + if(*this == ManagedString::EmptyString) + return s; + + return ManagedString(data, s.data); +} + + +/** + * Provides a character value at a given position in the string, indexed from zero. + * + * @param index The position of the character to return. + * @return the character at posisiton index, zero if index is invalid. + * + * Example: + * @code + * ManagedString s("abcd"); + * + * print(s.charAt(1)) // prints "b" + * @endcode + */ +char ManagedString::charAt(int16_t index) +{ + return (index >=0 && index < len) ? data[index] : 0; +} + +/** + * Provides an immutable 8 bit wide haracter buffer representing this string. + * + * @return a pointer to the charcter buffer. + */ +const char *ManagedString::toCharArray() +{ + return data; +} + +/** + * Determines the length of this ManagedString in characters. + * + * @return the length of the string in characters. + * + * Example: + * @code + * ManagedString s("abcd"); + * + * print(s.length()) // prints "4" + * @endcode + */ +int16_t ManagedString::length() +{ + return len; +} + +/** + * Empty string constant literal + */ +ManagedString ManagedString::EmptyString("\0"); + + diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp new file mode 100644 index 0000000..6cb62e5 --- /dev/null +++ b/source/MicroBit.cpp @@ -0,0 +1,334 @@ +#include "MicroBit.h" + +char MICROBIT_BLE_DEVICE_NAME[] = "BBC MicroBit [xxxxx]"; +const char MICROBIT_BLE_MANUFACTURER[] = "The Cast of W1A"; +const char MICROBIT_BLE_MODEL[] = "Microbit SB2"; +const char MICROBIT_BLE_SERIAL[] = "SN1"; +const char MICROBIT_BLE_HARDWARE_VERSION[] = "0.2"; +const char MICROBIT_BLE_FIRMWARE_VERSION[] = "1.1"; +const char MICROBIT_BLE_SOFTWARE_VERSION[] = "1.0"; + +/** + * custom function for panic for malloc & new due to scoping issue. + */ +void panic(int statusCode) +{ + uBit.panic(statusCode); +} + +/** + * Callback when a BLE GATT disconnect occurs. + */ +void bleDisconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason) +{ + uBit.ble->startAdvertising(); // restart advertising! +} + + +/** + * Constructor. + * Create a representation of a MicroBit device as a global singleton. + * @param messageBus callback function to receive MicroBitMessageBus events. + * + * Exposed objects: + * @code + * uBit.systemTicker; //the Ticker callback that performs routines like updating the display. + * uBit.MessageBus; //The message bus where events are fired. + * uBit.display; //The display object for the LED matrix. + * uBit.buttonA; //The buttonA object for button a. + * uBit.buttonB; //The buttonB object for button b. + * uBit.buttonAB; //The buttonAB object for button a+b multi press. + * uBit.resetButton; //The resetButton used for soft resets. + * uBit.accelerometer; //The object that represents the inbuilt accelerometer + * uBit.compass; //The object that represents the inbuilt compass(magnetometer) + * uBit.io.P*; //Where P* is P0 to P16, P19 & P20 on the edge connector + * @endcode + */ +MicroBit::MicroBit() : + flags(0x00), + i2c(MICROBIT_PIN_SDA, MICROBIT_PIN_SCL), + serial(USBTX, USBRX), + MessageBus(), + display(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_WIDTH, MICROBIT_DISPLAY_HEIGHT), + buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A), + buttonB(MICROBIT_ID_BUTTON_B,MICROBIT_PIN_BUTTON_B), + buttonAB(MICROBIT_ID_BUTTON_AB,MICROBIT_ID_BUTTON_A,MICROBIT_ID_BUTTON_B), + accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR), + compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR), + io(MICROBIT_ID_IO_P0,MICROBIT_ID_IO_P1,MICROBIT_ID_IO_P2, + MICROBIT_ID_IO_P3,MICROBIT_ID_IO_P4,MICROBIT_ID_IO_P5, + MICROBIT_ID_IO_P6,MICROBIT_ID_IO_P7,MICROBIT_ID_IO_P8, + MICROBIT_ID_IO_P9,MICROBIT_ID_IO_P10,MICROBIT_ID_IO_P11, + MICROBIT_ID_IO_P12,MICROBIT_ID_IO_P13,MICROBIT_ID_IO_P14, + MICROBIT_ID_IO_P15,MICROBIT_ID_IO_P16,MICROBIT_ID_IO_P19, + MICROBIT_ID_IO_P20) +{ +} + +/** + * Post constructor initialisation method. + * After *MUCH* pain, it's noted that the BLE stack can't be brought up in a + * static context, so we bring it up here rather than in the constructor. + * n.b. This method *must* be called in main() or later, not before. + * + * Example: + * @code + * uBit.init(); + * @endcode + */ +void MicroBit::init() +{ + //add the display to the systemComponent array + addSystemComponent(&uBit.display); + + //add the compass and accelerometer to the idle array + addIdleComponent(&uBit.accelerometer); + addIdleComponent(&uBit.compass); + + // Seed our random number generator + seedRandom(); + + // Start the BLE stack. + ble = new BLEDevice(); + + ble->init(); + ble->onDisconnection(bleDisconnectionCallback); + + // Add our auxiliary services. + ble_firmware_update_service = new MicroBitDFUService(*ble); + ble_device_information_service = new DeviceInformationService(*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, MICROBIT_BLE_SERIAL, MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); + ble_event_service = new MicroBitEventService(*ble); + + // Compute our auto-generated MicroBit device name. + ble_firmware_update_service->getName(MICROBIT_BLE_DEVICE_NAME+14); + + // Setup advertising. + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)MICROBIT_BLE_DEVICE_NAME, sizeof(MICROBIT_BLE_DEVICE_NAME)); + ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); + ble->setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000)); + ble->startAdvertising(); + + // Start refreshing the Matrix Display + systemTicker.attach(this, &MicroBit::systemTick, MICROBIT_DISPLAY_REFRESH_PERIOD); +} + +/** + * Delay for the given amount of time. + * If the scheduler is running, this will deschedule the current fiber and perform + * a power efficent, concurrent sleep operation. + * If the scheduler is disabled or we're running in an interrupt context, this + * will revert to a busy wait. + * + * @note Values of 6 and below tend to lose resolution - do you really need to sleep for this short amount of time? + * + * @param milliseconds the amount of time, in ms, to wait for. This number cannot be negative. + * + * Example: + * @code + * uBit.sleep(20); //sleep for 20ms + * @endcode + */ +void MicroBit::sleep(int milliseconds) +{ + //sanity check, we can't time travel... (yet?) + if(milliseconds < 0) + return; + + if (flags & MICROBIT_FLAG_SCHEDULER_RUNNING) + fiber_sleep(milliseconds); + else + wait_ms(milliseconds); +} + + +/** + * Generate a random number in the given range. + * We use a simple Galois LFSR random number generator here, + * as a Galois LFSR is sufficient for our applications, and much more lightweight + * than the hardware random number generator built int the processor, which takes + * a long time and uses a lot of energy. + * + * KIDS: You shouldn't use this is the real world to generte cryptographic keys though... + * have a think why not. :-) + * + * @param max the upper range to generate a number for. This number cannot be negative + * @return A random, natural number between 0 and the max-1. Or MICROBIT_INVALID_VALUE (defined in ErrorNo.h) if max is <= 0. + * + * Example: + * @code + * uBit.random(200); //a number between 0 and 199 + * @endcode + */ +int MicroBit::random(int max) +{ + //return MICROBIT_INVALID_VALUE if max is <= 0... + if(max <= 0) + return MICROBIT_INVALID_VALUE; + + // Cycle the LFSR (Linear Feedback Shift Register). + // We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneider here (a true legend in the field!), + // For those interested, it's documented in his paper: + // "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent generator for 32-bit Microprocessors" + + randomValue = ((((randomValue >> 31) ^ (randomValue >> 6) ^ (randomValue >> 4) ^ (randomValue >> 2) ^ (randomValue >> 1) ^ randomValue) & 0x0000001) << 31 ) | (randomValue >> 1); + return randomValue % max; +} + + +/** + * Seed our a random number generator (RNG). + * We use the NRF51822 in built cryptographic random number generator to seed a Galois LFSR. + * We do this as the hardware RNG is relatively high power, and use the the BLE stack internally, + * with a less than optimal application interface. A Galois LFSR is sufficient for our + * applications, and much more lightweight. + */ +void MicroBit::seedRandom() +{ + randomValue = 0; + + // Start the Random number generator. No need to leave it running... I hope. :-) + NRF_RNG->TASKS_START = 1; + + for(int i = 0; i < 4 ;i++) + { + // Clear the VALRDY EVENT + NRF_RNG->EVENTS_VALRDY = 0; + + // Wait for a number ot be generated. + while ( NRF_RNG->EVENTS_VALRDY == 0); + + randomValue = (randomValue << 8) | ((int) NRF_RNG->VALUE); + } + + // Disable the generator to save power. + NRF_RNG->TASKS_STOP = 1; +} + + +/** + * Periodic callback. Used by MicroBitDisplay, FiberScheduler and buttons. + */ +void MicroBit::systemTick() +{ + // Scheduler callback. We do this here just as a single timer is more efficient. :-) + if (uBit.flags & MICROBIT_FLAG_SCHEDULER_RUNNING) + scheduler_tick(); + + //work out if any idle components need processing, if so prioritise the idle thread + for(int i = 0; i < MICROBIT_IDLE_COMPONENTS; i++) + if(idleThreadComponents[i] != NULL && idleThreadComponents[i]->isIdleCallbackNeeded()) + { + fiber_flags |= MICROBIT_FLAG_DATA_READ; + break; + } + + //update any components in the systemComponents array + for(int i = 0; i < MICROBIT_SYSTEM_COMPONENTS; i++) + if(systemTickComponents[i] != NULL) + systemTickComponents[i]->systemTick(); +} + +/** + * System tasks to be executed by the idle thread when the Micro:Bit isn't busy or when data needs to be read. + */ +void MicroBit::systemTasks() +{ + //call the idleTick member function indiscriminately + for(int i = 0; i < MICROBIT_IDLE_COMPONENTS; i++) + if(idleThreadComponents[i] != NULL) + idleThreadComponents[i]->idleTick(); + + fiber_flags &= ~MICROBIT_FLAG_DATA_READ; +} + +/** + * add a component to the array of components which invocate the systemTick member function during a systemTick + * @note this will be converted into a dynamic list of components + */ +void MicroBit::addSystemComponent(MicroBitComponent *component) +{ + int i = 0; + + while(systemTickComponents[i] != NULL && i < MICROBIT_SYSTEM_COMPONENTS) + i++; + + if(i == MICROBIT_SYSTEM_COMPONENTS) + return; + + systemTickComponents[i] = component; +} + +/** + * remove a component from the array of components + * @note this will be converted into a dynamic list of components + */ +void MicroBit::removeSystemComponent(MicroBitComponent *component) +{ + int i = 0; + + while(systemTickComponents[i] != component && i < MICROBIT_SYSTEM_COMPONENTS) + i++; + + if(i == MICROBIT_SYSTEM_COMPONENTS) + return; + + systemTickComponents[i] = NULL; +} + +/** + * add a component to the array of components which invocate the systemTick member function during a systemTick + * @note this will be converted into a dynamic list of components + */ +void MicroBit::addIdleComponent(MicroBitComponent *component) +{ + int i = 0; + + while(idleThreadComponents[i] != NULL && i < MICROBIT_IDLE_COMPONENTS) + i++; + + if(i == MICROBIT_IDLE_COMPONENTS) + return; + + idleThreadComponents[i] = component; +} + +/** + * remove a component from the array of components + * @note this will be converted into a dynamic list of components + */ +void MicroBit::removeIdleComponent(MicroBitComponent *component) +{ + int i = 0; + + while(idleThreadComponents[i] != component && i < MICROBIT_IDLE_COMPONENTS) + i++; + + if(i == MICROBIT_IDLE_COMPONENTS) + return; + + idleThreadComponents[i] = NULL; +} + +/** + * Determine the time since this MicroBit was last reset. + * + * @return The time since the last reset, in milliseconds. This will result in overflow after 1.6 months. + * TODO: handle overflow case. + */ +unsigned long MicroBit::systemTime() +{ + return ticks; +} + +/** + * Triggers a microbit panic where an infinite loop will occur swapping between the panicFace and statusCode if provided. + * + * @param statusCode the status code of the associated error. Status codes must be in the range 0-255. + */ +void MicroBit::panic(int statusCode) +{ + //show error and enter infinite while + uBit.display.error(statusCode); +} + diff --git a/source/MicroBitAccelerometer.cpp b/source/MicroBitAccelerometer.cpp new file mode 100644 index 0000000..9a05cd5 --- /dev/null +++ b/source/MicroBitAccelerometer.cpp @@ -0,0 +1,195 @@ +/** + * Class definition for MicroBit Accelerometer. + * + * Represents an implementation of the Freescale MMA8653 3 axis accelerometer + * Also includes basic data caching and on demand activation. + */ + +#include "MicroBit.h" + +/** + * Issues a standard, 2 byte I2C command write to the accelerometer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to write to. + * @param value The value to write. + */ +void MicroBitAccelerometer::writeCommand(uint8_t reg, uint8_t value) +{ + uint8_t command[2]; + command[0] = reg; + command[1] = value; + + uBit.i2c.write(address, (const char *)command, 2); +} + +/** + * Issues a read command into the specified buffer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to access. + * @param buffer Memory area to read the data into. + * @param length The number of bytes to read. + */ +void MicroBitAccelerometer::readCommand(uint8_t reg, uint8_t* buffer, int length) +{ + uBit.i2c.write(address, (const char *)®, 1, true); + uBit.i2c.read(address, (char *)buffer, length); +} + +/** + * Constructor. + * Create an accelerometer representation with the given ID. + * @param id the ID of the new object. + * @param address the default base address of the accelerometer. + * + * Example: + * @code + * accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR) + * @endcode + */ +MicroBitAccelerometer::MicroBitAccelerometer(uint16_t id, uint16_t address) : sample(), int1(MICROBIT_PIN_ACCEL_DATA_READY) +{ + // Store our identifiers. + this->id = id; + this->address = address; + + // Enable the accelerometer. + // First place the device into standby mode, so it can be configured. + writeCommand(MMA8653_CTRL_REG1, 0x00); + + // Enable the INT1 interrupt pin. + writeCommand(MMA8653_CTRL_REG4, 0x01); + + // Select the DATA_READY event source to be routed to INT1 + writeCommand(MMA8653_CTRL_REG5, 0x01); + + // Configure for a +/- 2g range. + writeCommand(MMA8653_XYZ_DATA_CFG, 0x00); + + // Bring the device back online, with 10bit wide samples at a 50Hz frequency. + writeCommand(MMA8653_CTRL_REG1, 0x21); + + // indicate that we're ready to receive tick callbacks. + uBit.flags |= MICROBIT_FLAG_ACCELEROMETER_RUNNING; +} + +/** + * Attempts to determine the 8 bit ID from the accelerometer. + * @return the 8 bit ID returned by the accelerometer + * + * Example: + * @code + * uBit.accelerometer.whoAmI(); + * @endcode + */ +int MicroBitAccelerometer::whoAmI() +{ + uint8_t data; + + readCommand(MMA8653_WHOAMI, &data, 1); + return (int)data; +} + +/** + * Reads the acceleration data from the accelerometer, and stores it in our buffer. + * This is called by the tick() member function, if the interrupt is set! + */ +void MicroBitAccelerometer::update() +{ + int8_t data[6]; + + readCommand(MMA8653_OUT_X_MSB, (uint8_t *)data, 6); + + // read MSB values... + sample.x = data[0]; + sample.y = data[2]; + sample.z = data[4]; + + // Scale into millig (approx!) + sample.x *= 16; + sample.y *= 16; + sample.z *= 16; + + // Invert the x and y axes, so that the reference frame aligns with micro:bit expectations + sample.x = -sample.x; + sample.y = -sample.y; + + // We ignore the LSB bits for now, as they're just noise... + // TODO: Revist this when we have working samples to see if additional resolution is needed. + +#ifdef USE_ACCEL_LSB + // Add in LSB values. + sample.x += (data[1] / 64); + sample.y += (data[3] / 64); + sample.z += (data[5] / 64); +#endif + + //TODO: Issue an event. +}; + +/** + * Reads the X axis value of the latest update from the accelerometer. + * Currently limited to +/- 2g + * @return The force measured in the X axis, in milli-g. + * + * Example: + * @code + * uBit.accelerometer.getX(); + * @endcode + */ +int MicroBitAccelerometer::getX() +{ + return sample.x; +} + +/** + * Reads the Y axis value of the latest update from the accelerometer. + * Currently limited to +/- 2g + * @return The force measured in the Y axis, in milli-g. + * + * Example: + * @code + * uBit.accelerometer.getY(); + * @endcode + */ +int MicroBitAccelerometer::getY() +{ + return sample.y; +} + +/** + * Reads the Z axis value of the latest update from the accelerometer. + * Currently limited to +/- 2g + * @return The force measured in the Z axis, in milli-g. + * + * Example: + * @code + * uBit.accelerometer.getZ(); + * @endcode + */ +int MicroBitAccelerometer::getZ() +{ + return sample.z; +} + + +/** + * periodic callback from MicroBit clock. + * Check if any data is ready for reading by checking the interrupt flag on the accelerometer + */ +void MicroBitAccelerometer::idleTick() +{ + // Poll interrupt line from accelerometer. + // n.b. Default is Active LO. Interrupt is cleared in data read. + if(!int1) + update(); +} + +/** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + */ +int MicroBitAccelerometer::isIdleCallbackNeeded() +{ + return !int1; +} diff --git a/source/MicroBitButton.cpp b/source/MicroBitButton.cpp new file mode 100644 index 0000000..1ac3ce0 --- /dev/null +++ b/source/MicroBitButton.cpp @@ -0,0 +1,102 @@ +#include "inc/MicroBit.h" +#include "inc/MicroBitButton.h" +#include "inc/MicroBitMessageBus.h" + +/** + * Constructor. + * Create a pin representation with the given ID. + * @param id the ID of the new MicroBitButton object. + * @param name the physical pin on the processor that this butotn is connected to. + * @param mode the configuration of internal pullups/pulldowns, as define in the mbed PinMode class. PullNone by default. + * + * Example: + * @code + * buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A); //a number between 0 and 200 inclusive + * @endcode + * + * Possible Events: + * @code + * MICROBIT_BUTTON_EVT_DOWN + * MICROBIT_BUTTON_EVT_UP + * MICROBIT_BUTTON_EVT_CLICK + * MICROBIT_BUTTON_EVT_LONG_CLICK + * MICROBIT_BUTTON_EVT_DOUBLE_CLICK + * MICROBIT_BUTTON_EVT_HOLD + * @endcode + */ +MicroBitButton::MicroBitButton(uint16_t id, PinName name, PinMode mode) : pin(name, mode) +{ + this->id = id; + this->name = name; + this->downStartTime = 0; + this->sigma = 0; + this->doubleClickTimer = 0; + uBit.addSystemComponent(this); +} + +/** + * periodic callback from MicroBit clock. + * Check for state change for this button, and fires a hold event if button is pressed. + */ +void MicroBitButton::systemTick() +{ + // + // If the pin is pulled low (touched), increment our culumative counter. + // otherwise, decrement it. We're essentially building a lazy follower here. + // This makes the output debounced for buttons, and desensitizes touch sensors + // (particularly in environments where there is mains noise!) + // + if(!pin) + { + if (sigma < MICROBIT_BUTTON_SIGMA_MAX) + sigma++; + } + else + { + if (sigma > MICROBIT_BUTTON_SIGMA_MIN) + sigma--; + } + + // Check to see if we have off->on state change. + if(sigma > MICROBIT_BUTTON_SIGMA_THRESH_HI && !(status & MICROBIT_BUTTON_STATE)) + { + // Record we have a state change, and raise an event. + status |= MICROBIT_BUTTON_STATE; + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN); + + //Record the time the button was pressed. + downStartTime=ticks; + } + + // Check to see if we have on->off state change. + if(sigma < MICROBIT_BUTTON_SIGMA_THRESH_LO && (status & MICROBIT_BUTTON_STATE)) + { + status = 0; + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_UP); + + //determine if this is a long click or a normal click and send event + if((ticks - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME) + 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((status & MICROBIT_BUTTON_STATE) && !(status & MICROBIT_BUTTON_STATE_HOLD_TRIGGERED) && (ticks - downStartTime) >= MICROBIT_BUTTON_HOLD_TIME) + { + //set the hold triggered event flag + status |= MICROBIT_BUTTON_STATE_HOLD_TRIGGERED; + + //fire hold event + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_HOLD); + } +} + +/** + * Tests if this Button is currently pressed. + * @return 1 if this button is pressed, 0 otherwise. + */ +int MicroBitButton::isPressed() +{ + return status & MICROBIT_BUTTON_STATE ? 1 : 0; +} diff --git a/source/MicroBitCompass.cpp b/source/MicroBitCompass.cpp new file mode 100644 index 0000000..dd8905f --- /dev/null +++ b/source/MicroBitCompass.cpp @@ -0,0 +1,338 @@ +#include "inc/MicroBit.h" + +/** + * Constructor. + * Create a compass representation with the given ID. + * @param id the event ID of the compass object. + * @param address the default address for the compass register + * + * Example: + * @code + * compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR); + * @endcode + * + * Possible Events for the compass are as follows: + * @code + * MICROBIT_COMPASS_EVT_CAL_REQUIRED // triggered when no magnetometer data is available in persistent storage + * MICROBIT_COMPASS_EVT_CAL_START // triggered when calibration has begun + * MICROBIT_COMPASS_EVT_CAL_END // triggered when calibration has finished. + * @endcode + */ +MicroBitCompass::MicroBitCompass(uint16_t id, uint16_t address) : average(), sample(), int1(MICROBIT_PIN_COMPASS_DATA_READY) +{ + this->id = id; + this->address = address; + + //we presume it's calibrated until the average values are read. + this->status = 0x01; + + //initialise eventStartTime to 0 + this->eventStartTime = 0; + + // Enable automatic reset after each sample; + writeCommand(MAG_CTRL_REG2, 0xA0); + + // Select 10Hz update rate, with oversampling. Also enables the device. + writeCommand(MAG_CTRL_REG1, 0x61); + + //fetch our previous average values + average.x = read16(MAG_OFF_X_MSB); + average.y = read16(MAG_OFF_Y_MSB); + average.z = read16(MAG_OFF_Z_MSB); + + if(average.x == 0 && average.y == 0 && average.z == 0) + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; + + // Indicate that we're up and running. + uBit.flags |= MICROBIT_FLAG_COMPASS_RUNNING; +} + +/** + * Issues a standard, 2 byte I2C command write to the magnetometer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to write to. + * @param value The value to write. + */ +void MicroBitCompass::writeCommand(uint8_t reg, uint8_t value) +{ + uint8_t command[2]; + command[0] = reg; + command[1] = value; + + uBit.i2c.write(address, (const char *)command, 2); +} + +/** + * Issues a read command into the specified buffer. + * Blocks the calling thread until complete. + * + * @param reg The address of the register to access. + * @param buffer Memory area to read the data into. + * @param length The number of bytes to read. + */ +void MicroBitCompass::readCommand(uint8_t reg, uint8_t* buffer, int length) +{ + uBit.i2c.write(address, (const char *)®, 1, true); + uBit.i2c.read(address, (char *)buffer, length); +} + + +/** + * Issues a read of a given address, and returns the value. + * Blocks the calling thread until complete. + * + * @param reg The based address of the 16 bit register to access. + * @return The register value, interpreted as a 16 but signed value. + */ +int16_t MicroBitCompass::read16(uint8_t reg) +{ + uint8_t cmd[2]; + + cmd[0] = reg; + uBit.i2c.write(address, (const char *)cmd, 1); + + cmd[0] = 0x00; + cmd[1] = 0x00; + + uBit.i2c.read(address, (char *)cmd, 2); + return (int16_t) ((cmd[1] | (cmd[0] << 8))); //concatenate the MSB and LSB +} + +/** + * Issues a read of a given address, and returns the value. + * Blocks the calling thread until complete. + * + * @param reg The based address of the 16 bit register to access. + * @return The register value, interpreted as a 8 bi signed value. + */ +int16_t MicroBitCompass::read8(uint8_t reg) +{ + int8_t data; + + data = 0; + readCommand(reg, (uint8_t*) &data, 1); + + return data; +} + + +/** + * Gets the current heading of the device, relative to magnetic north. + * @return the current heading, in degrees. Or MICROBIT_COMPASS_IS_CALIBRATING if the compass is calibrating. + * Or MICROBIT_COMPASS_CALIBRATE_REQUIRED if the compass requires calibration. + * + * Example: + * @code + * uBit.compass.heading(); + * @endcode + */ +int MicroBitCompass::heading() +{ + if(status & MICROBIT_COMPASS_STATUS_CALIBRATING) + return MICROBIT_COMPASS_IS_CALIBRATING; + else if(!(status & MICROBIT_COMPASS_STATUS_CALIBRATED)) + { + MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_REQUIRED); + return MICROBIT_COMPASS_CALIBRATE_REQUIRED; + } + + float bearing = (atan2((double)(sample.y - average.y),(double)(sample.x - average.x)))*180/PI; + if (bearing < 0) + bearing += 360.0; + + return (int) (360.0 - bearing); +} + + +/** + * Periodic callback from MicroBit clock. + * Check if any data is ready for reading by checking the interrupt. + */ +void MicroBitCompass::idleTick() +{ + // Poll interrupt line from accelerometer. + // Active HI. Interrupt is cleared in data read of MAG_OUT_X_MSB. + if(int1) + { + sample.x = read16(MAG_OUT_X_MSB); + sample.y = read16(MAG_OUT_Y_MSB); + sample.z = read16(MAG_OUT_Z_MSB); + + if (status & MICROBIT_COMPASS_STATUS_CALIBRATING) + { + minSample.x = min(sample.x, minSample.x); + minSample.y = min(sample.y, minSample.y); + minSample.z = min(sample.z, minSample.z); + + maxSample.x = max(sample.x, maxSample.x); + maxSample.y = max(sample.y, maxSample.y); + maxSample.z = max(sample.z, maxSample.z); + + if(eventStartTime && ticks > eventStartTime + MICROBIT_COMPASS_CALIBRATE_PERIOD) + { + eventStartTime = 0; + calibrateEnd(); + } + } + } +} + +/** + * Reads the X axis value of the latest update from the compass. + * @return The magnetic force measured in the X axis, in no specific units. + * + * Example: + * @code + * uBit.compass.getX(); + * @endcode + */ +int MicroBitCompass::getX() +{ + return sample.x; +} + +/** + * Reads the Y axis value of the latest update from the compass. + * @return The magnetic force measured in the Y axis, in no specific units. + * + * Example: + * @code + * uBit.compass.getY(); + * @endcode + */ +int MicroBitCompass::getY() +{ + return sample.y; +} + +/** + * Reads the Z axis value of the latest update from the compass. + * @return The magnetic force measured in the Z axis, in no specific units. + * + * Example: + * @code + * uBit.compass.getZ(); + * @endcode + */ +int MicroBitCompass::getZ() +{ + return sample.z; +} + +/** + * Attempts to determine the 8 bit ID from the magnetometer. + * @return the id of the compass (magnetometer) + * + * Example: + * @code + * uBit.compass.whoAmI(); + * @endcode + */ +int MicroBitCompass::whoAmI() +{ + uint8_t data; + + readCommand(MAG_WHOAMI, &data, 1); + return (int)data; +} + +/** + * Perform a calibration of the compass. + * This will fire MICROBIT_COMPASS_EVT_CAL_START. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ +void MicroBitCompass::calibrateStart() +{ + if(this->isCalibrating()) + return; + + status |= MICROBIT_COMPASS_STATUS_CALIBRATING; + + // Take a sane snapshot to start with. + minSample = sample; + maxSample = sample; + + MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_START); +} + + +/** + * Perform the asynchronous calibration of the compass. + * This will fire MICROBIT_COMPASS_EVT_CAL_START and MICROBIT_COMPASS_EVT_CAL_END when finished. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ +void MicroBitCompass::calibrateAsync() +{ + eventStartTime = ticks; + calibrateStart(); +} + +/** + * Complete the calibration of the compass. + * This will fire MICROBIT_COMPASS_EVT_CAL_END. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ +void MicroBitCompass::calibrateEnd() +{ + average.x = (maxSample.x + minSample.x) / 2; + average.y = (maxSample.y + minSample.y) / 2; + average.z = (maxSample.z + minSample.z) / 2; + + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATING; + status |= MICROBIT_COMPASS_STATUS_CALIBRATED; + + //Store x, y and z values in persistent storage on the MAG3110 + writeCommand(MAG_OFF_X_LSB, (uint8_t)average.x); + writeCommand(MAG_OFF_X_MSB, (uint8_t)(average.x >> 8)); + + writeCommand(MAG_OFF_Y_LSB, (uint8_t)average.y); + writeCommand(MAG_OFF_Y_MSB, (uint8_t)(average.y >> 8)); + + writeCommand(MAG_OFF_Z_LSB, (uint8_t)average.z); + writeCommand(MAG_OFF_Z_MSB, (uint8_t)(average.z >> 8)); + + MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_END); +} + +/** + * Returns 0 or 1. 1 indicates that the compass is calibrated, zero means the compass requires calibration. + */ +int MicroBitCompass::isCalibrated() +{ + return status & MICROBIT_COMPASS_STATUS_CALIBRATED; +} + +/** + * Returns 0 or 1. 1 indicates that the compass is calibrating, zero means the compass is not currently calibrating. + */ +int MicroBitCompass::isCalibrating() +{ + return status & MICROBIT_COMPASS_STATUS_CALIBRATING; +} + +/** + * Clears the calibration held in persistent storage, and sets the calibrated flag to zero. + */ +void MicroBitCompass::clearCalibration() +{ + writeCommand(MAG_OFF_X_LSB, 0); + writeCommand(MAG_OFF_X_MSB, 0); + + writeCommand(MAG_OFF_Y_LSB, 0); + writeCommand(MAG_OFF_Y_MSB, 0); + + writeCommand(MAG_OFF_Z_LSB, 0); + writeCommand(MAG_OFF_Z_MSB, 0); + + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; +} + +/** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + */ +int MicroBitCompass::isIdleCallbackNeeded() +{ + //Active HI + return int1; +} diff --git a/source/MicroBitCompat.cpp b/source/MicroBitCompat.cpp new file mode 100644 index 0000000..8bd5a7b --- /dev/null +++ b/source/MicroBitCompat.cpp @@ -0,0 +1,61 @@ +/** + * Compatibility / portability funcitons and constants for the MicroBit DAL. + */ +#include "MicroBit.h" + + +/** + * Performs an in buffer reverse of a given char array + * @param s the char* to reverse. + * @return the reversed char* + */ +void string_reverse(char *s) +{ + //sanity check... + if(s == NULL) + return; + + char *j; + int c; + + j = s + strlen(s) - 1; + + while(s < j) + { + c = *s; + *s++ = *j; + *j-- = c; + } +} + +/** + * Converts a given integer into a base 10 ASCII equivalent. + * + * @param n The number to convert. + * @param s Pointer to a buffer in which to store the resulting string. + */ +void itoa(int n, char *s) +{ + int i = 0; + int sign = (n >= 0); + + // Record the sign of the number, + // Ensure our working value is positive. + if (!sign) + n = -n; + + // Calculate each character, starting with the LSB. + do { + s[i++] = n % 10 + '0'; + } while ((n /= 10) > 0); + + // Add a negative sign as needed + if (!sign) + s[i++] = '-'; + + // Terminate the string. + s[i] = '\0'; + + // Flip the order. + string_reverse(s); +} diff --git a/source/MicroBitDFUService.cpp b/source/MicroBitDFUService.cpp new file mode 100644 index 0000000..bf07073 --- /dev/null +++ b/source/MicroBitDFUService.cpp @@ -0,0 +1,290 @@ +/** + * Class definition for a MicroBit Device Firmware Update loader. + * + * This is actually just a frontend to a memory resident nordic DFU loader. + * Here we deal with the MicroBit 'pairing' functionality with BLE devices, and + * very basic authentication and authorization. + * + * This implementation is not intended to be fully secure, but rather intends to: + * + * 1. Provide a simple mechanism to identify an individual MicroBit amongst a classroom of others + * 2. Allow BLE devices to discover and cache a passcode that can be used to flash the device over BLE. + * 3. Provide a BLE escape route for programs that 'brick' the MicroBit. + * + * Represents the device as a whole, and includes member variables to that reflect the components of the system. + */ + +#include "MicroBit.h" +#include "ble/UUID.h" + +/** + * Constructor. + * Create a representation of a MicroBit device. + * @param messageBus callback function to receive MicroBitMessageBus events. + */ +MicroBitDFUService::MicroBitDFUService(BLEDevice &_ble) : + ble(_ble), + microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte), + microBitDFUServiceFlashCodeCharacteristic(MicroBitDFUServiceFlashCodeCharacteristicUUID, (uint8_t *)&flashCode, 0, sizeof(uint32_t), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY) +{ + authenticated = false; + flashCodeRequested = false; + + controlByte = 0x00; + flashCode = 0x00; + + GattCharacteristic *characteristics[] = {µBitDFUServiceControlCharacteristic, µBitDFUServiceFlashCodeCharacteristic}; + GattService service(MicroBitDFUServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + ble.onDataWritten(this, &MicroBitDFUService::onDataWritten); +} + + +/** + * Returns the friendly name for this device, autogenerated from our Device ID. + * + * @param name Pointer to a string where the data will be written. + * @return The number of bytes written. + */ +int MicroBitDFUService::getName(char *name) +{ + const uint8_t codebook[5][5] = + { + {'z', 'v', 'g', 'p', 't'}, + {'u', 'o', 'i', 'e', 'a'}, + {'z', 'v', 'g', 'p', 't'}, + {'u', 'o', 'i', 'e', 'a'}, + {'z', 'v', 'g', 'p', 't'} + }; + + // We count right to left, so fast forward the pointer. + name += MICROBIT_DFU_HISTOGRAM_WIDTH; + + uint32_t n = NRF_FICR->DEVICEID[1]; + + int ld = 1; + int d = MICROBIT_DFU_HISTOGRAM_HEIGHT; + int h; + +#ifdef MICROBIT_DEBUG + pc.printf("MicroBitDFUService::getName: Called [%.8x]\n",n); +#endif + + for (int i=0; ihandle == microBitDFUServiceControlCharacteristic.getValueHandle()) { + +#ifdef MICROBIT_DEBUG + pc.printf("Control Point:\n "); +#endif + if (params->len < 1) + { +#ifdef MICROBIT_DEBUG + pc.printf(" invalid. Ignoring.\n"); +#endif + return; + } + +#ifdef MICROBIT_DEBUG + for (int i=0; ilen; i++) + pc.printf("%.2x ", params->data[i]); + + pc.printf("\n"); +#endif + + switch(params->data[0]) + { + case MICROBIT_DFU_OPCODE_START_DFU: + + if (authenticated) + { +#ifdef MICROBIT_DEBUG + pc.printf(" ACTIVATING BOOTLOADER.\n"); +#endif + bootloader_start(); + } + + break; + + case MICROBIT_DFU_OPCODE_START_PAIR: +#ifdef MICROBIT_DEBUG + pc.printf(" START_PAIR: "); +#endif + flashCodeRequested = true; + break; + + } + } + + if (params->handle == microBitDFUServiceFlashCodeCharacteristic.getValueHandle()) { + +#ifdef MICROBIT_DEBUG + pc.printf("FlashCode\n\n"); +#endif + + if (params->len >= 4) + { + uint32_t lockCode=0; + memcpy(&lockCode, params->data, 4); + if (lockCode == NRF_FICR->DEVICEID[0]) + { +#ifdef MICROBIT_DEBUG + pc.printf("AUTHENTICATED\n"); +#endif + authenticated = true; + }else{ +#ifdef MICROBIT_DEBUG + pc.printf("NOT AUTHENTICATED: %8x\n", lockCode); +#endif + authenticated = false; + } + } + } +} + +/** + * Displays the device's ID code as a histogram on the LED matrix display. + */ +void MicroBitDFUService::showTick() +{ + uBit.display.resetAnimation(0); + + uBit.display.image.setPixelValue(0,3, 255); + uBit.display.image.setPixelValue(1,4, 255); + uBit.display.image.setPixelValue(2,3, 255); + uBit.display.image.setPixelValue(3,2, 255); + uBit.display.image.setPixelValue(4,1, 255); +} + + +/** + * Displays the device's ID code as a histogram on the LED matrix display. + */ +void MicroBitDFUService::showNameHistogram() +{ +#ifdef MICROBIT_DEBUG + pc.printf("MicroBitDFUService::showNameHistogram: Called\n"); +#endif + + uBit.display.resetAnimation(0); + + uint32_t n = NRF_FICR->DEVICEID[1]; + int ld = 1; + int d = MICROBIT_DFU_HISTOGRAM_HEIGHT; + int h; + + uBit.display.clear(); + for (int i=0; iDEVICEID[0]; + ble.updateCharacteristicValue(microBitDFUServiceFlashCodeCharacteristic.getValueHandle(), (uint8_t *)&flashCode, sizeof(uint32_t)); +} + +/** + * UUID definitions for BLE Services and Characteristics. + */ + +const uint8_t MicroBitDFUServiceUUID[] = { + 0xd8,0xaf,0x99,0x1c,0x71,0x44,0x43,0xd7,0x95,0x4b,0x99,0x51,0x2f,0x95,0xf9,0x9c +}; + +const uint8_t MicroBitDFUServiceControlCharacteristicUUID[] = { + 0x97,0x10,0x95,0x47,0xe6,0x3a,0x44,0x2a,0xbf,0x89,0x9d,0x73,0x04,0x13,0xdc,0x2f +}; + +const uint8_t MicroBitDFUServiceFlashCodeCharacteristicUUID[] = { + 0x94,0x7b,0x69,0x34,0x64,0xd1,0x4f,0xad,0x9b,0xd0,0xcc,0x9d,0x6e,0x9f,0x3e,0xa3 +}; diff --git a/source/MicroBitDisplay.cpp b/source/MicroBitDisplay.cpp new file mode 100644 index 0000000..8f30585 --- /dev/null +++ b/source/MicroBitDisplay.cpp @@ -0,0 +1,884 @@ +/** + * Class definition for a MicroBitDisplay. + * + * A MicroBitDisplay represents the LED matrix array on the MicroBit device. + */ +#include "MicroBit.h" +#include "MicroBitMatrixMaps.h" +#include +#include "nrf_gpio.h" +#include "mbed.h" + +const float timings[MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH] = {0.000010, 0.000047, 0.000094, 0.000187, 0.000375, 0.000750, 0.001500, 0.003000}; + +/** + * Constructor. + * Create a Point representation of an LED on a matrix + * Used to handle non-linear matrix layouts. + */ +MatrixPoint::MatrixPoint(uint8_t x, uint8_t y) +{ + this->x = x; + this->y = y; +} + +/** + * Constructor. + * Create a representation of a display of a given size. + * The display is initially blank. + * + * @param x the width of the display in pixels. + * @param y the height of the display in pixels. + * + * Example: + * @code + * MicroBitDisplay display(MICROBIT_ID_DISPLAY, 5, 5), + * @endcode + */ +MicroBitDisplay::MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y) : + font(), + image(x*2,y) +{ + //set pins as output + nrf_gpio_range_cfg_output(MICROBIT_DISPLAY_COLUMN_START,MICROBIT_DISPLAY_COLUMN_START + MICROBIT_DISPLAY_COLUMN_COUNT + MICROBIT_DISPLAY_ROW_COUNT); + + this->id = id; + this->width = x; + this->height = y; + this->strobeRow = 0; + this->strobeBitMsk = 0x20; + this->rotation = MICROBIT_DISPLAY_ROTATION_0; + this->greyscaleBitMsk = 0x01; + this->timingCount = 0; + + this->setBrightness(MICROBIT_DEFAULT_BRIGHTNESS); + + this->mode = DISPLAY_MODE_BLACK_AND_WHITE; + this->animationMode = ANIMATION_MODE_NONE; + + uBit.flags |= MICROBIT_FLAG_DISPLAY_RUNNING; +} + +/** + * Internal frame update method, used to strobe the display. + * + * TODO: Write a more efficient, complementary variation of this method for the case where + * MICROBIT_DISPLAY_ROW_COUNT > MICROBIT_DISPLAY_COLUMN_COUNT. + */ +void MicroBitDisplay::systemTick() +{ + if(!(uBit.flags & MICROBIT_FLAG_DISPLAY_RUNNING)) + return; + + // Move on to the next row. + strobeBitMsk <<= 1; + strobeRow++; + + //reset the row counts and bit mask when we have hit the max. + if(strobeRow == MICROBIT_DISPLAY_ROW_COUNT){ + strobeRow = 0; + strobeBitMsk = 0x20; + } + + if(mode == DISPLAY_MODE_BLACK_AND_WHITE) + render(); + + if(mode == DISPLAY_MODE_GREYSCALE) + { + greyscaleBitMsk = 0x01; + timingCount = 0; + renderGreyscale(); + } + + // Update text and image animations if we need to. + this->animationUpdate(); +} + +void MicroBitDisplay::renderFinish() +{ + //kept inline to reduce overhead + //clear the old bit pattern for this row. + //clear port 0 4-7 and retain lower 4 bits + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT0, 0xF0 | nrf_gpio_port_read(NRF_GPIO_PORT_SELECT_PORT0) & 0x0F); + + // clear port 1 8-12 for the current row + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobeBitMsk | 0x1F); +} + +void MicroBitDisplay::render() +{ + if(brightness == 0) + return; + + int coldata = 0; + + // Calculate the bitpattern to write. + for (int i = 0; i>4 & 0x1F)); + + //timer does not have enough resolution for brightness of 1. 23.53 us + if(brightness != MICROBIT_DISPLAY_MAX_BRIGHTNESS && brightness > MICROBIT_DISPLAY_MIN_BRIGHTNESS) + renderTimer.attach(this, &MicroBitDisplay::renderFinish, (((float)brightness) / ((float)MICROBIT_DISPLAY_MAX_BRIGHTNESS)) * (float)MICROBIT_DISPLAY_REFRESH_PERIOD); + + //this will take around 23us to execute + if(brightness <= MICROBIT_DISPLAY_MIN_BRIGHTNESS) + renderFinish(); +} + +void MicroBitDisplay::renderGreyscale() +{ + int coldata = 0; + + // Calculate the bitpattern to write. + for (int i = 0; i>4 & 0x1F)); + + if(timingCount > MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH-1) + return; + + greyscaleBitMsk <<= 1; + + renderTimer.attach(this,&MicroBitDisplay::renderGreyscale, timings[timingCount++]); +} + +/** + * Periodic callback, that we use to perform any animations we have running. + */ +void +MicroBitDisplay::animationUpdate() +{ + if (animationMode == ANIMATION_MODE_NONE) + return; + + animationTick += FIBER_TICK_PERIOD_MS; + + if(animationTick >= animationDelay) + { + animationTick = 0; + + if (animationMode == ANIMATION_MODE_SCROLL_TEXT) + this->updateScrollText(); + + if (animationMode == ANIMATION_MODE_PRINT_TEXT) + this->updatePrintText(); + + if (animationMode == ANIMATION_MODE_SCROLL_IMAGE) + this->updateScrollImage(); + + if (animationMode == ANIMATION_MODE_ANIMATE_IMAGE) + this->updateAnimateImage(); + + if(animationMode == ANIMATION_MODE_PRINT_CHARACTER) + this->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + + } +} + +/** + * Broadcasts an event onto the shared MessageBus + * @param eventCode The ID of the event that has occurred. + */ +void MicroBitDisplay::sendEvent(uint16_t eventCode) +{ + MicroBitEvent evt(id,eventCode); +} + +/** + * Internal scrollText update method. + * Shift the screen image by one pixel to the left. If necessary, paste in the next char. + */ +void MicroBitDisplay::updateScrollText() +{ + image.shiftLeft(1); + scrollingPosition++; + + if (scrollingPosition == width + MICROBIT_DISPLAY_SPACING) + { + scrollingPosition = 0; + + image.print(scrollingChar < scrollingText.length() ? scrollingText.charAt(scrollingChar) : ' ',width,0); + + if (scrollingChar > scrollingText.length()) + { + animationMode = ANIMATION_MODE_NONE; + this->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + return; + } + scrollingChar++; + } +} + +/** + * Internal printText update method. + * Paste in the next char in the string. + */ +void MicroBitDisplay::updatePrintText() +{ + image.print(printingChar < printingText.length() ? printingText.charAt(printingChar) : ' ',0,0); + + if (printingChar > printingText.length()) + { + animationMode = ANIMATION_MODE_NONE; + this->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + return; + } + + printingChar++; +} + +/** + * Internal scrollImage update method. + * Paste the stored bitmap at the appropriate point. + */ +void MicroBitDisplay::updateScrollImage() +{ + image.clear(); + + if ((image.paste(scrollingImage, scrollingImagePosition, 0, 0) == 0) && scrollingImageRendered) + { + animationMode = ANIMATION_MODE_NONE; + this->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + return; + } + + scrollingImagePosition += scrollingImageStride; + scrollingImageRendered = true; +} + + +/** + * Internal animateImage update method. + * Paste the stored bitmap at the appropriate point and stop on the last frame. + */ +void MicroBitDisplay::updateAnimateImage() +{ + //wait until we have rendered the last position to give a continuous animation. + if (scrollingImagePosition <= -scrollingImage.getWidth() + (MICROBIT_DISPLAY_WIDTH + scrollingImageStride) && scrollingImageRendered) + { + animationMode = ANIMATION_MODE_NONE; + this->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + return; + } + + if(scrollingImagePosition > 0) + image.shiftLeft(-scrollingImageStride); + + image.paste(scrollingImage, scrollingImagePosition, 0, 0); + + scrollingImageRendered = true; + + scrollingImagePosition += scrollingImageStride; +} + +/** + * Resets the current given animation. + * @param delay the delay after which the animation is reset. + */ +void MicroBitDisplay::resetAnimation(uint16_t delay) +{ + //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->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + + // 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. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.printAsync("abc123",400); + * @endcode + */ +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; +} + +/** + * Prints the given character to the display. + * + * @param c The character to display. + * + * Example: + * @code + * uBit.display.print('p'); + * @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. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); +} + +/** + * Prints the given string to the display, one character at a time. + * Uses the given delay between characters. + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.print("abc123",400); + * @endcode + */ +void MicroBitDisplay::print(ManagedString s, int delay) +{ + //sanitise this value + if(delay <= 0 ) + delay = MICROBIT_DEFAULT_SCROLL_SPEED; + + // Start the effect. + this->printAsync(s, delay); + + // Wait for completion. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); +} + +/** + * Prints the given image to the display. + * Blocks the calling thread until all the text has been displayed. + * + * @param i The image to display. + * @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::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. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); +} + +/** + * Scrolls the given string to the display, from right to left. + * Uses the given delay between characters. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.scrollAsync("abc123",100); + * @endcode + */ +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; +} + +/** + * Scrolls the given image across the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * @param image The image to display. + * @param delay The time to delay between each scroll update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * uBit.display.scrollAsync(i,100,1); + * @endcode + */ +void MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) +{ + //sanitise the delay value + if(delay <= 0 ) + delay = MICROBIT_DEFAULT_SCROLL_SPEED; + + this->resetAnimation(delay); + + this->scrollingImagePosition = stride < 0 ? width : -image.getWidth(); + this->scrollingImageStride = stride; + this->scrollingImage = image; + this->scrollingImageRendered = false; + + animationMode = ANIMATION_MODE_SCROLL_IMAGE; +} + +/** + * Scrolls the given string to the display, from right to left. + * Uses the given delay between characters. + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * @param delay The time to delay between characters, in timer ticks. + * + * Example: + * @code + * uBit.display.scrollString("abc123",100); + * @endcode + */ +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. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); +} + +/** + * Scrolls the given image across the display, from right to left. + * Blocks the calling thread until all the text has been displayed. + * + * @param image The image to display. + * @param delay The time to delay between each scroll update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * uBit.display.scroll(i,100,1); + * @endcode + */ +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. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); +} + +/** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Returns immediately. + * + * @param image The image to display. + * @param delay The time to delay between each animation update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * uBit.display.animateAsync(i,100,5); + * @endcode + */ +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) + { + animationMode = ANIMATION_MODE_NONE; + this->sendEvent(MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + } + + this->animationDelay = delay; + this->animationTick = delay-1; + + //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; +} + +/** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Blocks the calling thread until the animation is complete. + * + * @param image The image to display. + * @param delay The time to delay between each animation update, in timer ticks. Has a default. + * @param stride The number of pixels to move in each quantum. Has a default. + * + * Example: + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * uBit.display.animate(i,100,5); + * @endcode + */ +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. + fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); +} + + +/** + * Sets the display brightness to the specified level. + * @param b The brightness to set the brightness to, in the range 0..255. + * + * Example: + * @code + * uBit.display.setBrightness(255); //max brightness + * @endcode + */ +void MicroBitDisplay::setBrightness(int b) +{ + //sanitise the brightness level + if(b < 0) + b = 0; + + if (b > 255) + b = 255; + + this->brightness = b; +} + + + +/** + * Sets the mode of the display. + * @param mode The mode to swap the display into. (can be either DISPLAY_MODE_GREYSCALE, or DISPLAY_MODE_NORMAL) + * + * Example: + * @code + * uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE); //per pixel brightness + * @endcode + */ +void MicroBitDisplay::setDisplayMode(DisplayMode mode) +{ + this->mode = mode; +} + +/** + * Fetches the current brightness of this display. + * @return the brightness of this display, in the range 0..255. + * + * Example: + * @code + * uBit.display.getBrightness(); //the current brightness + * @endcode + */ +int MicroBitDisplay::getBrightness() +{ + return this->brightness; +} + +/** + * Rotates the display to the given position. + * Axis aligned values only. + * + * Example: + * @code + * uBit.display.rotateTo(MICROBIT_DISPLAY_ROTATION_180); //rotates 180 degrees from original orientation + * @endcode + */ +void MicroBitDisplay::rotateTo(uint8_t position) +{ + //perform a switch on position to restrict range to distinct values + switch(position){ + case MICROBIT_DISPLAY_ROTATION_0: + this->rotation = MICROBIT_DISPLAY_ROTATION_0; + break; + case MICROBIT_DISPLAY_ROTATION_90: + this->rotation = MICROBIT_DISPLAY_ROTATION_90; + break; + case MICROBIT_DISPLAY_ROTATION_180: + this->rotation = MICROBIT_DISPLAY_ROTATION_180; + break; + case MICROBIT_DISPLAY_ROTATION_270: + this->rotation = MICROBIT_DISPLAY_ROTATION_270; + break; + } +} + +/** + * Enables the display, should only be called if the display is disabled. + * + * Example: + * @code + * uBit.display.enable(); //reenables the display mechanics + * @endcode + */ +void MicroBitDisplay::enable() +{ + if(!(uBit.flags & MICROBIT_FLAG_DISPLAY_RUNNING)) + { + setBrightness(brightness); + uBit.flags |= MICROBIT_FLAG_DISPLAY_RUNNING; //set the display running flag + } +} + +/** + * Disables the display, should only be called if the display is enabled. + * Display must be disabled to avoid MUXing of edge connector pins. + * + * Example: + * @code + * uBit.display.disable(); //disables the display + * @endcode + */ +void MicroBitDisplay::disable() +{ + if(uBit.flags & MICROBIT_FLAG_DISPLAY_RUNNING) + { + uBit.flags &= ~MICROBIT_FLAG_DISPLAY_RUNNING; //unset the display running flag + } +} + +/** + * Clears the current image on the display. + * Simplifies the process, you can also use uBit.display.image.clear + * + * Example: + * @code + * uBit.display.clear(); //clears the display + * @endcode + */ +void MicroBitDisplay::clear() +{ + image.clear(); +} + +/** + * Displays "=(" and an accompanying status code infinitely. + * @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255. + * + * Example: + * @code + * uBit.display.error(20); + * @endcode + */ +void MicroBitDisplay::error(int statusCode) +{ + __disable_irq(); //stop ALL interrupts + + if(statusCode < 0 || statusCode > 255) + statusCode = 0; + + disable(); //relinquish PWMOut's control + + uint8_t strobeRow = 0; + uint8_t strobeBitMsk = 0x20; + + //point to the font stored in Flash + const unsigned char * fontLocation = MicroBitFont::defaultFont; + + //get individual digits of status code, and place it into a single array/ + const uint8_t* chars[MICROBIT_DISPLAY_ERROR_CHARS] = { panicFace, fontLocation+((((statusCode/100 % 10)+48)-MICROBIT_FONT_ASCII_START) * 5), fontLocation+((((statusCode/10 % 10)+48)-MICROBIT_FONT_ASCII_START) * 5), fontLocation+((((statusCode % 10)+48)-MICROBIT_FONT_ASCII_START) * 5)}; + + //enter infinite loop. + while(1) + { + //iterate through our chars :) + for(int characterCount = 0; characterCount < MICROBIT_DISPLAY_ERROR_CHARS; characterCount++) + { + int outerCount = 0; + + //display the current character + while( outerCount < 100000) + { + int coldata = 0; + + int i = 0; + + //if we have hit the row limit - reset both the bit mask and the row variable + if(strobeRow == 3) + { + strobeRow = 0; + strobeBitMsk = 0x20; + } + + // Calculate the bitpattern to write. + for (i = 0; i> matrixMap[i][strobeRow].x; //chars are right aligned but read left to right + int y = matrixMap[i][strobeRow].y; + + if(chars[characterCount][y] & bitMsk) + coldata |= (1 << i); + } + + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT0, 0xF0); //clear port 0 4-7 + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobeBitMsk | 0x1F); // clear port 1 8-12 + + //write the new bit pattern + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT0, ~coldata<<4 & 0xF0); //set port 0 4-7 + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobeBitMsk | (~coldata>>4 & 0x1F)); //set port 1 8-12 + + //set i to an obscene number. + i = 100000; + + //burn cycles + while(i>0) + i--; + + //update the bit mask and row count + strobeBitMsk <<= 1; + strobeRow++; + outerCount++; + } + } + } +} + +/** + * Updates the font property of this object with the new font. + * @param font the new font that will be used to render characters.. + */ +void MicroBitDisplay::setFont(MicroBitFont font) +{ + this->font = font; +} + +/** + * Retreives the font object used for rendering characters on the display. + */ +MicroBitFont MicroBitDisplay::getFont() +{ + return this->font; +} + +/** + * Captures the bitmap currently being rendered on the display. + */ +MicroBitImage MicroBitDisplay::screenShot() +{ + return image.crop(0,0,MICROBIT_DISPLAY_WIDTH,MICROBIT_DISPLAY_HEIGHT); +} diff --git a/source/MicroBitEvent.cpp b/source/MicroBitEvent.cpp new file mode 100644 index 0000000..ea6fb14 --- /dev/null +++ b/source/MicroBitEvent.cpp @@ -0,0 +1,46 @@ +/** + * Class definition for a MicroBitEvent. + * + * The MicroBitEvent is the event object that represents an event that has occurred on the ubit. + */ + +#include "MicroBit.h" + +/** + * Constructor. + * @param src ID of the MicroBit Component that generated the event e.g. MICROBIT_ID_BUTTON_A. + * @param value Component specific code indicating the cause of the event. + * @param fire whether the event should be fire immediately upon construction + * + * Example: + * @code + * MicrobitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK,true); // auto fire + * @endcode + */ +MicroBitEvent::MicroBitEvent(uint16_t source, uint16_t value, bool fire) +{ + this->source = source; + this->value = value; + this->timestamp = ticks; + + if(fire) + this->fire(); +} + +/** + * Default constructor - initialises all values, and sets timestamp to the current time. + */ +MicroBitEvent::MicroBitEvent() +{ + this->source = 0; + this->value = 0; + this->timestamp = ticks; +} + +/** + * Fires the represented event onto the message bus. + */ +void MicroBitEvent::fire() +{ + uBit.MessageBus.send(*this); +} diff --git a/source/MicroBitEventService.cpp b/source/MicroBitEventService.cpp new file mode 100644 index 0000000..427a6c1 --- /dev/null +++ b/source/MicroBitEventService.cpp @@ -0,0 +1,85 @@ +/** + * Class definition for a MicroBit BLE Event Service. + * Provides a BLE gateway onto the MicroBit Message Bus. + */ + +#include "MicroBit.h" +#include "ble/UUID.h" + +#include "ExternalEvents.h" +#include "MicroBitEventService.h" + +/** + * Constructor. + * Create a representation of the EventService + * @param _ble The instance of a BLE device that we're running on. + */ +MicroBitEventService::MicroBitEventService(BLEDevice &_ble) : + ble(_ble), + microBitEventCharacteristic(MicroBitEventServiceMicroBitEventCharacteristicUUID, (uint8_t *)µBitEventBuffer, 0, sizeof(EventServiceEvent), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY), + clientEventCharacteristic(MicroBitEventServiceClientEventCharacteristicUUID, (uint8_t *)&clientEventBuffer, 0, sizeof(EventServiceEvent), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE) +{ + clientEventBuffer.type = 0x00; + clientEventBuffer.reason = 0x00; + + microBitEventBuffer.type = 0x00; + microBitEventBuffer.reason = 0x00; + + GattCharacteristic *characteristics[] = {µBitEventCharacteristic, &clientEventCharacteristic}; + GattService service(MicroBitEventServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + ble.onDataWritten(this, &MicroBitEventService::onDataWritten); +} + + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitEventService::onDataWritten(const GattWriteCallbackParams *params) +{ + int len = params->len; + EventServiceEvent *e = (EventServiceEvent *)params->data; + + if (params->handle == clientEventCharacteristic.getValueHandle()) { + + // Read and fire all events... + while (len >= 4) + { + MicroBitEvent evt(e->type, e->reason); + len-=4; + e++; + } + } +} + +/** + * Callback. Invoked when any events are sent on the microBit message bus. + */ +void MicroBitEventService::onMicroBitEvent(MicroBitEvent evt) +{ + EventServiceEvent *e = µBitEventBuffer; + + if (ble.getGapState().connected) { + e->type = evt.source; + e->reason = evt.value; + + ble.updateCharacteristicValue(microBitEventCharacteristic.getValueAttribute().getHandle(), (const uint8_t *)e, sizeof(EventServiceEvent)); + } +} + +const uint8_t MicroBitEventServiceUUID[] = { + 0xe9,0x5d,0x93,0xaf,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitEventServiceMicroBitEventCharacteristicUUID[] = { + 0xe9,0x5d,0x97,0x75,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitEventServiceClientEventCharacteristicUUID[] = { + 0xe9,0x5d,0x54,0x04,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + diff --git a/source/MicroBitFiber.cpp b/source/MicroBitFiber.cpp new file mode 100644 index 0000000..1d34a50 --- /dev/null +++ b/source/MicroBitFiber.cpp @@ -0,0 +1,482 @@ +/** + * The MicroBit Fiber scheduler. + * + * This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes: + * + * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). + * 2) To provide ISR decoupling for Messagebus events generted in an ISR context. + */ + +#include "MicroBit.h" + +/* + * Statically allocated values used to create and destroy Fibers. + * required to be defined here to allow persistence during context switches. + */ + +/* + * Scheduler state. + */ + +Fiber *currentFiber = NULL; // The context in which the current fiber is executing. +Fiber *runQueue = NULL; // The list of runnable fibers. +Fiber *sleepQueue = NULL; // The list of blocked fibers waiting on a fiber_sleep() operation. +Fiber *waitQueue = NULL; // The list of blocked fibers waiting on an event. +Fiber *idle = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks. +Fiber *fiberPool = NULL; // Pool of unused fibers, just waiting for a job to do. + +Cortex_M0_TCB *emptyContext = NULL; // Initialized context for fiber entry state. + +/* + * Time since power on. Measured in milliseconds. + * When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-) + */ +unsigned long ticks = 0; + +uint8_t fiber_flags = 0; + +/** + * Utility function to add the currenty running fiber to the given queue. + * Perform a simple add at the head, to avoid complexity, + * Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of + * brute force searching. + * + * @param f The fiber to add to the queue + * @param queue The run queue to add the fiber to. + */ +void queue_fiber(Fiber *f, Fiber **queue) +{ + __disable_irq(); + + f->queue = queue; + f->next = *queue; + f->prev = NULL; + + if(*queue != NULL) + (*queue)->prev = f; + + *queue = f; + + __enable_irq(); +} + +/** + * Utility function to the given fiber from whichever queue it is currently stored on. + * @param f the fiber to remove. + */ +void dequeue_fiber(Fiber *f) +{ + __disable_irq(); + + if (f->prev != NULL) + f->prev->next = f->next; + else + *(f->queue) = f->next; + + if(f->next) + f->next->prev = f->prev; + + f->next = NULL; + f->prev = NULL; + f->queue = NULL; + + __enable_irq(); + +} + + +/** + * Initialises the Fiber scheduler. + * Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread. + * + * This function must be called once only from the main thread, and before any other Fiber operation. + */ +void scheduler_init() +{ + // Create a new fiber context + currentFiber = new Fiber(); + currentFiber->stack_bottom = (uint32_t) malloc(FIBER_STACK_SIZE); + currentFiber->stack_top = ((uint32_t) currentFiber->stack_bottom) + FIBER_STACK_SIZE; + + // Add ourselves to the run queue. + queue_fiber(currentFiber, &runQueue); + + // Build a fiber context around the current thread. + swap_context(¤tFiber->tcb, ¤tFiber->tcb, currentFiber->stack_top, currentFiber->stack_top); + + // Create the IDLE task. This will actually scheduk the IDLE task, but it will immediately yeld back to us. + // Remove it from the run queue though, as IDLE is a special case. + idle = create_fiber(idle_task); + dequeue_fiber(idle); + + // Flag that we now have a scheduler running + uBit.flags |= MICROBIT_FLAG_SCHEDULER_RUNNING; +} + +/** + * Timer callback. Called from interrupt context, once every FIBER_TICK_PERIOD_MS milliseconds. + * Simply checks to determine if any fibers blocked on the sleep queue need to be woken up + * and made runnable. + */ +void scheduler_tick() +{ + Fiber *f = sleepQueue; + Fiber *t; + + // increment our real-time counter. + ticks += FIBER_TICK_PERIOD_MS; + + // Check the sleep queue, and wake up any fibers as necessary. + while (f != NULL) + { + t = f->next; + + if (ticks >= f->context) + { + // Wakey wakey! + dequeue_fiber(f); + queue_fiber(f,&runQueue); + } + + f = t; + } +} + +/** + * Event callback. Called from the message bus whenever an event is raised. + * Checks to determine if any fibers blocked on the wait queue need to be woken up + * and made runnable due to the event. + */ +void scheduler_event(MicroBitEvent evt) +{ + Fiber *f = waitQueue; + Fiber *t; + + // Check the wait queue, and wake up any fibers as necessary. + while (f != NULL) + { + t = f->next; + + // extract the event data this fiber is blocked on. + uint16_t id = f->context & 0xFF; + uint16_t value = (f->context & 0xFF00) >> 16; + + if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value)) + { + // Wakey wakey! + dequeue_fiber(f); + queue_fiber(f,&runQueue); + } + + f = t; + } +} + + +/** + * Blocks the calling thread for the given period of time. + * The calling thread will be immediatley descheduled, and placed onto a + * wait queue until the requested amount of time has elapsed. + * + * n.b. the fiber will not be be made runnable until after the elasped time, but there + * are no guarantees precisely when the fiber will next be scheduled. + * + * @param t The period of time to sleep, in milliseconds. + */ +void fiber_sleep(unsigned long t) +{ + // Calculate and store the time we want to wake up. + currentFiber->context = ticks + t; + + // Remove ourselve from the run queue + dequeue_fiber(currentFiber); + + // Add ourselves to the sleep queue. We maintain strict ordering here to reduce lookup times. + queue_fiber(currentFiber, &sleepQueue); + + // Finally, enter the scheduler. + schedule(); +} + +/** + * Blocks the calling thread until the specified event is raised. + * The calling thread will be immediatley descheduled, and placed onto a + * wait queue until the requested event is received. + * + * n.b. the fiber will not be be made runnable until after the event is raised, but there + * are no guarantees precisely when the fiber will next be scheduled. + * + * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) + * @param value The VALUE of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) + */ +void fiber_wait_for_event(uint16_t id, uint16_t value) +{ + // Encode the event data in the context field. It's handy having a 32 bit core. :-) + currentFiber->context = value << 16 | id; + + // Remove ourselve from the run queue + dequeue_fiber(currentFiber); + + // Add ourselves to the sleep queue. We maintain strict ordering here to reduce lookup times. + queue_fiber(currentFiber, &waitQueue); + + // Finally, enter the scheduler. + schedule(); +} + + +Fiber *getFiberContext() +{ + Fiber *f; + + __disable_irq(); + + if (fiberPool != NULL) + { + f = fiberPool; + dequeue_fiber(f); + + // dequeue_fiber() exits with irqs enablesd, so no need to do this again! + } + else + { + __enable_irq(); + + f = new Fiber(); + + if (f == NULL) + return NULL; + + f->stack_bottom = (uint32_t) malloc(FIBER_STACK_SIZE); + f->stack_top = f->stack_bottom + FIBER_STACK_SIZE; + + if (f->stack_bottom == NULL) + { + delete f; + return NULL; + } + } + + return f; +} + +void launch_new_fiber() +{ + // Launch into the entry point, now we're in the correct context. + //uint32_t ep = currentFiber->stack_bottom; + uint32_t ep = *((uint32_t *)(currentFiber->stack_bottom + 0)); + uint32_t cp = *((uint32_t *)(currentFiber->stack_bottom + 4)); + + // Execute the thread's entrypoint + (*(void(*)())(ep))(); + + // Execute the thread's completion routine; + (*(void(*)())(cp))(); + + // If we get here, then the completion routine didn't recycle the fiber + // so do it anyway. :-) + release_fiber(); +} + +/** + * Creates a new Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * @param completion_fn The function called when the thread completes execution of entry_fn. + * @return The new Fiber. + */ +Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) +{ + // Validate our parameters. + if (entry_fn == NULL || completion_fn == NULL) + return NULL; + + // Allocate a TCB from the new fiber. This will come from the tread pool if availiable, + // else a new one will be allocated on the heap. + Fiber *newFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + if (newFiber == NULL) + return NULL; + + *((uint32_t *)newFiber->stack_bottom) = (uint32_t) entry_fn; + *((uint32_t *)(newFiber->stack_bottom+4)) = (uint32_t) completion_fn; + + // Use cache fiber state if we have it. This is faster, and safer if we're called from + // an interrupt context. + if (emptyContext != NULL) + { + memcpy(&newFiber->tcb, emptyContext, sizeof(Cortex_M0_TCB)); + } + else + { + // Otherwise, initialize the TCB from the current context. + save_context(&newFiber->tcb, newFiber->stack_top); + + // Assign the new stack pointer and entry point. + newFiber->tcb.SP = CORTEX_M0_STACK_BASE; + newFiber->tcb.LR = (uint32_t) &launch_new_fiber; + + // Store this context for later use. + emptyContext = (Cortex_M0_TCB *) malloc (sizeof(Cortex_M0_TCB)); + memcpy(emptyContext, &newFiber->tcb, sizeof(Cortex_M0_TCB)); + } + + // Add new fiber to the run queue. + queue_fiber(newFiber, &runQueue); + + return newFiber; +} + +void launch_new_fiber_param() +{ + // Launch into the entry point, now we're in the correct context. + uint32_t ep = *((uint32_t *)(currentFiber->stack_bottom + 0)); + uint32_t pm = *((uint32_t *)(currentFiber->stack_bottom + 4)); + uint32_t cp = *((uint32_t *)(currentFiber->stack_bottom + 8)); + + // Execute the thread's entry routine. + (*(void(*)(void *))(ep))((void *)pm); + + // Execute the thread's completion routine. + // Execute the thread's entry routine. + (*(void(*)(void *))(cp))((void *)pm); + + // If we get here, then recycle the fiber context. + release_fiber((void *)pm); +} + +/** + * Creates a new parameterised Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * @param param an untyped parameter passed into the entry_fn anf completion_fn. + * @param completion_fn The function called when the thread completes execution of entry_fn. + * @return The new Fiber. + */ +Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *)) +{ + // Validate our parameters. + if (entry_fn == NULL || completion_fn == NULL) + return NULL; + + // Allocate a TCB from the new fiber. This will come from the tread pool if availiable, + // else a new one will be allocated on the heap. + Fiber *newFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + if (newFiber == NULL) + return NULL; + + *((uint32_t *)newFiber->stack_bottom) = (uint32_t) entry_fn; + *((uint32_t *)(newFiber->stack_bottom+4)) = (uint32_t) param; + *((uint32_t *)(newFiber->stack_bottom+8)) = (uint32_t) completion_fn; + + // Use cache fiber state. This is safe, as the empty context is always created in the non-paramterised + // version of create_fiber before we're ever called. + memcpy(&newFiber->tcb, emptyContext, sizeof(Cortex_M0_TCB)); + + // Assign the link register to refer to the thread entry point in THIS function. + newFiber->tcb.LR = (uint32_t) &launch_new_fiber_param; + + // Add new fiber to the run queue. + queue_fiber(newFiber, &runQueue); + + return newFiber; +} + + + +/** + * Exit point for parameterised fibers. + * A wrapper around release_fiber() to enable transparent operaiton. + */ +void release_fiber(void *param) +{ + release_fiber(); +} + + +/** + * Exit point for all fibers. + * Any fiber reaching the end of its entry function will return here for recycling. + */ +void release_fiber(void) +{ + // Remove ourselves form the runqueue. + dequeue_fiber(currentFiber); + queue_fiber(currentFiber, &fiberPool); + + // Find something else to do! + schedule(); +} + +/** + * Calls the Fiber scheduler. + * The calling Fiber will likely be blocked, and control given to another waiting fiber. + * Call this to yield control of the processor when you have nothing more to do. + */ +void schedule() +{ + // Just round robin for now! + Fiber *oldFiber = currentFiber; + + // OK - if we've nothing to do, then run the IDLE task (power saving sleep) + if (runQueue == NULL || fiber_flags & MICROBIT_FLAG_DATA_READ) + currentFiber = idle; + + // If the current fiber is on the run queue, round robin. + else if (currentFiber->queue == &runQueue) + currentFiber = currentFiber->next == NULL ? runQueue : currentFiber->next; + + // Otherwise, just pick the head of the run queue. + else + currentFiber = runQueue; + + // Swap to the context of the chosen fiber, and we're done. + // Don't bother with the overhead of switching if there's only one fiber on the runqueue! + if (currentFiber != oldFiber) + { + // Ensure the stack buffer is large enough to hold the stack Reallocate if necessary. + uint32_t stackDepth; + uint32_t bufferSize; + + // Calculate the stack depth. + stackDepth = CORTEX_M0_STACK_BASE - ((uint32_t) __get_MSP()); + bufferSize = oldFiber->stack_top - oldFiber->stack_bottom; + + // If we're too small, increase our buffer exponentially. + if (bufferSize < stackDepth) + { + while (bufferSize < stackDepth) + bufferSize = bufferSize << 1; + + free((void *)oldFiber->stack_bottom); + oldFiber->stack_bottom = (uint32_t) malloc(bufferSize); + oldFiber->stack_top = oldFiber->stack_bottom + bufferSize; + } + + // Schedule in the new fiber. + swap_context(&oldFiber->tcb, ¤tFiber->tcb, oldFiber->stack_top, currentFiber->stack_top); + } +} + +/** + * IDLE task. + * Only scheduled for execution when the runqueue is empty. + * Performs a procressor sleep operation, then returns to the scheduler - most likely after a timer interrupt. + */ +void idle_task() +{ + while(1) + { + if (uBit.ble) + uBit.ble->waitForEvent(); + else + __WFI(); + + uBit.systemTasks(); + + schedule(); + } +} diff --git a/source/MicroBitFont.cpp b/source/MicroBitFont.cpp new file mode 100644 index 0000000..7575245 --- /dev/null +++ b/source/MicroBitFont.cpp @@ -0,0 +1,48 @@ +/** + * Class definition for a MicrobitFont + * It represents a font that can be used by the display to render text. + */ + +#include "MicroBit.h" + +/** + * The original MicroBit font. + * The font is 5x5. + * Each Row is represented by a byte in the array. + * + * Row Format:| N/A | N/A | N/A | Col 1 | Col 2 | Col 3 | Col 4 | Col 5 | + * | 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01 | + * + * Example: { 0x08, 0x08, 0x08, 0x0, 0x08 } + * + * The above will produce an exclaimation mark on the second column in form the left. + * + * We could compress further, but the complexity of decode would likely outweigh the gains. + */ +const unsigned char defaultFontArray[475] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xe, 0xd3, 0x75, 0xb9, 0x2e, 0x1c, 0x84, 0x84, 0x84, 0x9f, 0xe, 0xd1, 0x26, 0xd0, 0x1f, 0x1e, 0xc1, 0x2e, 0xc1, 0x3e, 0x2, 0x46, 0xca, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0xe, 0xd0, 0x1e, 0xd1, 0x2e, 0x1f, 0xe1, 0x22, 0x44, 0x88, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2f, 0xe1, 0x2e, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xe, 0xd1, 0x3f, 0xf1, 0x31, 0x1e, 0xd1, 0x3e, 0xd1, 0x3e, 0xf, 0xf0, 0x10, 0x10, 0xf, 0x1e, 0xd1, 0x31, 0x31, 0x3e, 0x1f, 0xf0, 0x1e, 0xd0, 0x1f, 0x1f, 0xf0, 0x1e, 0xd0, 0x10, 0xf, 0xf0, 0x13, 0x71, 0x2f, 0x11, 0x31, 0x3f, 0xf1, 0x31, 0x1f, 0xe4, 0x84, 0x84, 0x9f, 0x1f, 0xe1, 0x21, 0x31, 0x2e, 0x11, 0x31, 0x3e, 0xd1, 0x31, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x11, 0x3b, 0x75, 0xb5, 0xb1, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xe, 0xd1, 0x31, 0x31, 0x2e, 0x1e, 0xd1, 0x3e, 0xd0, 0x10, 0xe, 0xd1, 0x35, 0xae, 0xc4, 0x1e, 0xd1, 0x3e, 0xd2, 0x51, 0xf, 0xf0, 0xe, 0xc1, 0x3e, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x11, 0x31, 0x31, 0x31, 0x2e, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x35, 0xb5, 0xb5, 0xaa, 0x11, 0x31, 0x2e, 0xd1, 0x31, 0x11, 0x31, 0x2a, 0x44, 0x84, 0x1f, 0xe2, 0x44, 0x88, 0x1f, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0xe, 0xd1, 0x2f, 0xf1, 0x2f, 0x1e, 0xd1, 0x3e, 0xd1, 0x3e, 0xe, 0xd1, 0x30, 0x11, 0x2e, 0x1e, 0xd1, 0x31, 0x31, 0x3e, 0xe, 0xd1, 0x3f, 0xf0, 0xf, 0x1f, 0xf0, 0x1e, 0xd0, 0x10, 0xf, 0xf0, 0x13, 0x71, 0x2f, 0x11, 0x31, 0x3f, 0xf1, 0x31, 0x1c, 0x84, 0x84, 0x84, 0x9f, 0x1e, 0xc2, 0x42, 0x52, 0x4c, 0x11, 0x31, 0x3e, 0xd1, 0x31, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x15, 0xbf, 0xf5, 0xb5, 0xb5, 0x16, 0xd9, 0x31, 0x31, 0x31, 0xe, 0xd1, 0x31, 0x31, 0x2e, 0x1e, 0xd1, 0x3e, 0xd0, 0x10, 0xe, 0xd1, 0x35, 0xae, 0xc4, 0x1b, 0x6c, 0x88, 0x8, 0x1e, 0xf, 0xf0, 0xe, 0xc1, 0x3e, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x11, 0x31, 0x31, 0x33, 0x6d, 0x11, 0x31, 0x2a, 0x4a, 0x44, 0x11, 0x35, 0xb5, 0xaa, 0x4a, 0x11, 0x31, 0x2e, 0xd1, 0x31, 0x11, 0x31, 0x2a, 0x44, 0x84, 0x1f, 0xe2, 0x44, 0x88, 0x1f, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60}; + +const unsigned char* MicroBitFont::defaultFont = defaultFontArray; + +/** + * Constructor. + * Sets the font represented by this font object. + * @param characters A pointer to the beginning of the new character values for this font. + * @param asciiEnd the char value at which this font finishes. + * + * @note see main_font_test.cpp in the test folder for an example. + */ +MicroBitFont::MicroBitFont(const unsigned char* characters, int asciiEnd) +{ + this->characters = characters; + this->asciiEnd = asciiEnd; +} + +/** + * Default Constructor. + * Sets the characters to defaultFont characters and asciiEnd to MICROBIT_FONT_ASCII_END. + */ +MicroBitFont::MicroBitFont() +{ + this->characters = defaultFont; + this->asciiEnd = MICROBIT_FONT_ASCII_END; +} diff --git a/source/MicroBitI2C.cpp b/source/MicroBitI2C.cpp new file mode 100644 index 0000000..a9e025f --- /dev/null +++ b/source/MicroBitI2C.cpp @@ -0,0 +1,76 @@ +#include "MicroBit.h" +#include "mbed.h" +#include "twi_master.h" +#include "nrf_delay.h" + +/** + * Constructor. + * Create an instance of i2c + * @param sda the Pin to be used for SDA + * @param scl the Pin to be used for SCL + * Example: + * @code + * MicroBitI2C i2c(MICROBIT_PIN_SDA, MICROBIT_PIN_SCL); + * @endcode + * @note this should prevent i2c lockups as well. + */ +MicroBitI2C::MicroBitI2C(PinName sda, PinName scl) : I2C(sda,scl) +{ + this->retries = 0; +} + + +int MicroBitI2C::read(int address, char *data, int length, bool repeated) +{ + int result = I2C::read(address,data,length,repeated); + + //0 indicates a success, presume failure + while(result != 0 && retries < MICROBIT_I2C_MAX_RETRIES) + { + _i2c.i2c->EVENTS_ERROR = 0; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos; + _i2c.i2c->POWER = 0; + nrf_delay_us(5); + _i2c.i2c->POWER = 1; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos; + twi_master_init_and_clear(); + result = I2C::read(address,data,length,repeated); + retries++; + } + + if(retries == MICROBIT_I2C_MAX_RETRIES - 1) + uBit.panic(MICROBIT_I2C_LOCKUP); + + retries = 0; + + return result; +} + +int MicroBitI2C::write(int address, const char *data, int length, bool repeated) +{ + + + int result = I2C::write(address,data,length,repeated); + + //0 indicates a success, presume failure + while(result != 0 && retries < MICROBIT_I2C_MAX_RETRIES) + { + _i2c.i2c->EVENTS_ERROR = 0; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos; + _i2c.i2c->POWER = 0; + nrf_delay_us(5); + _i2c.i2c->POWER = 1; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos; + + twi_master_init_and_clear(); + result = I2C::write(address,data,length,repeated); + retries++; + } + + if(retries == MICROBIT_I2C_MAX_RETRIES - 1) + uBit.panic(MICROBIT_I2C_LOCKUP); + + retries = 0; + + return result; +} diff --git a/source/MicroBitIO.cpp b/source/MicroBitIO.cpp new file mode 100644 index 0000000..4f3ec58 --- /dev/null +++ b/source/MicroBitIO.cpp @@ -0,0 +1,40 @@ +/** + * Class definition for MicroBit IO. + * + * Represents a single IO pin on the edge connector. + */ + +#include "inc/MicroBitIO.h" + +/** + * Constructor. + * Create a representation of all given I/O pins on the edge connector + */ +MicroBitIO::MicroBitIO(int MICROBIT_ID_IO_P0, int MICROBIT_ID_IO_P1, int MICROBIT_ID_IO_P2, + int MICROBIT_ID_IO_P3, int MICROBIT_ID_IO_P4, int MICROBIT_ID_IO_P5, + int MICROBIT_ID_IO_P6, int MICROBIT_ID_IO_P7, int MICROBIT_ID_IO_P8, + int MICROBIT_ID_IO_P9, int MICROBIT_ID_IO_P10,int MICROBIT_ID_IO_P11, + int MICROBIT_ID_IO_P12,int MICROBIT_ID_IO_P13,int MICROBIT_ID_IO_P14, + int MICROBIT_ID_IO_P15,int MICROBIT_ID_IO_P16,int MICROBIT_ID_IO_P19, + int MICROBIT_ID_IO_P20) : + P0 (MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL), //P0 is the left most pad (ANALOG/DIGITAL/TOUCH) + P1 (MICROBIT_ID_IO_P1, MICROBIT_PIN_P1, PIN_CAPABILITY_ALL), //P1 is the middle pad (ANALOG/DIGITAL/TOUCH) + P2 (MICROBIT_ID_IO_P2, MICROBIT_PIN_P2, PIN_CAPABILITY_ALL), //P2 is the right most pad (ANALOG/DIGITAL/TOUCH) + P3 (MICROBIT_ID_IO_P3, MICROBIT_PIN_P3, PIN_CAPABILITY_AD), //COL1 (ANALOG/DIGITAL) + P4 (MICROBIT_ID_IO_P4, MICROBIT_PIN_P4, PIN_CAPABILITY_DIGITAL), //BTN_A + P5 (MICROBIT_ID_IO_P5, MICROBIT_PIN_P5, PIN_CAPABILITY_AD), //COL2 (ANALOG/DIGITAL) + P6 (MICROBIT_ID_IO_P6, MICROBIT_PIN_P6, PIN_CAPABILITY_DIGITAL), //ROW2 + P7 (MICROBIT_ID_IO_P7, MICROBIT_PIN_P7, PIN_CAPABILITY_DIGITAL), //ROW1 + P8 (MICROBIT_ID_IO_P8, MICROBIT_PIN_P8, PIN_CAPABILITY_DIGITAL), //PIN 18 + P9 (MICROBIT_ID_IO_P9, MICROBIT_PIN_P9, PIN_CAPABILITY_DIGITAL), //ROW3 + P10(MICROBIT_ID_IO_P10,MICROBIT_PIN_P10,PIN_CAPABILITY_AD), //COL3 (ANALOG/DIGITAL) + P11(MICROBIT_ID_IO_P11,MICROBIT_PIN_P11,PIN_CAPABILITY_DIGITAL), //BTN_B + P12(MICROBIT_ID_IO_P12,MICROBIT_PIN_P12,PIN_CAPABILITY_DIGITAL), //PIN 20 + P13(MICROBIT_ID_IO_P13,MICROBIT_PIN_P13,PIN_CAPABILITY_DIGITAL), //SCK + P14(MICROBIT_ID_IO_P14,MICROBIT_PIN_P14,PIN_CAPABILITY_DIGITAL), //MISO + P15(MICROBIT_ID_IO_P15,MICROBIT_PIN_P15,PIN_CAPABILITY_DIGITAL), //MOSI + P16(MICROBIT_ID_IO_P16,MICROBIT_PIN_P16,PIN_CAPABILITY_DIGITAL), //PIN 16 + P19(MICROBIT_ID_IO_P19,MICROBIT_PIN_P19,PIN_CAPABILITY_DIGITAL), //SCL + P20(MICROBIT_ID_IO_P20,MICROBIT_PIN_P20,PIN_CAPABILITY_DIGITAL) //SDA +{ +} diff --git a/source/MicroBitImage.cpp b/source/MicroBitImage.cpp new file mode 100644 index 0000000..13c3a90 --- /dev/null +++ b/source/MicroBitImage.cpp @@ -0,0 +1,826 @@ +/** + * Class definition for a MicroBitImage. + * + * An MicroBitImage is a simple bitmap representation of an image. + * n.b. This is a mutable, managed type. + */ + +#include "MicroBit.h" + +/* + * The null image. We actally create a small one byte buffer here, just to keep NULL pointers out of the equation. + */ +MicroBitImage MicroBitImage::EmptyImage(1,1); + +/** + * Default Constructor. + * Creates a new reference to the empty MicroBitImage bitmap + * + * Example: + * @code + * MicroBitImage i(); //an empty image + * @endcode + */ +MicroBitImage::MicroBitImage() +{ + // Create new reference to the EmptyImage and we're done. + init_empty(); +} + + +/** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param x the width of the image. + * @param y the height of the image. + * + * Bitmap buffer is linear, with 8 bits per pixel, row by row, + * top to bottom with no word alignment. Stride is therefore the image width in pixels. + * in where w and h are width and height respectively, the layout is therefore: + * + * |[0,0]...[w,o][1,0]...[w,1] ... [[w,h] + * + * A copy of the image is made in RAM, as images are mutable. + * + * TODO: Consider an immutable flavour, which might save us RAM for animation spritesheets... + * ...as these could be kept in FLASH. + */ +MicroBitImage::MicroBitImage(const int16_t x, const int16_t y) +{ + this->init(x,y,NULL); +} + +/** + * Copy Constructor. + * Add ourselves as a reference to an existing MicroBitImage. + * + * @param image The MicroBitImage to reference. + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n"); + * MicroBitImage i2(i); //points to i + * @endcode + */ +MicroBitImage::MicroBitImage(const MicroBitImage &image) +{ + bitmap = image.bitmap; + width = image.width; + height = image.height; + ref = image.ref; + + (*ref)++; +} + +/** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param s A text based representation of the image given whitespace delimited numeric values. + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * @endcode + */ +MicroBitImage::MicroBitImage(const char *s) +{ + int width = 0; + int height = 0; + int count = 0; + int digit = 0; + + char parseBuf[10]; + + const char *parseReadPtr; + char *parseWritePtr; + uint8_t *bitmapPtr; + + if (s == NULL) + { + init_empty(); + return; + } + // First pass: Parse the string to determine the geometry of the image. + // We do this from first principles to avoid unecessary load of the strtok() libs etc. + parseReadPtr = s; + + while (*parseReadPtr) + { + if (isdigit(*parseReadPtr)) + { + // Ignore numbers. + digit = 1; + } + else if (*parseReadPtr =='\n') + { + if (digit) + { + count++; + digit = 0; + } + + height++; + + width = count > width ? count : width; + count = 0; + } + else + { + if (digit) + { + count++; + digit = 0; + } + } + + parseReadPtr++; + } + + // Store the geomtery. + this->width = width; + this->height = height; + this->bitmap = (uint8_t *) malloc(width * height); + this->ref = (int16_t *) malloc(sizeof(int16_t)); + *ref = 1; + + // Second pass: collect the data. + parseReadPtr = s; + parseWritePtr = parseBuf; + bitmapPtr = this->bitmap; + + while (*parseReadPtr) + { + if (isdigit(*parseReadPtr)) + { + *parseWritePtr = *parseReadPtr; + parseWritePtr++; + } + else + { + *parseWritePtr = 0; + if (parseWritePtr > parseBuf) + { + *bitmapPtr = atoi(parseBuf); + bitmapPtr++; + parseWritePtr = parseBuf; + } + } + + parseReadPtr++; + } +} + +/** + * Constructor. + * Create a bitmap representation of a given size, based on a given buffer. + * + * @param x the width of the image. + * @param y the height of the image. + * @param bitmap a 2D array representing the image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * @endcode + */ +MicroBitImage::MicroBitImage(const int16_t x, const int16_t y, const uint8_t *bitmap) +{ + this->init(x,y,bitmap); +} + +/** + * Destructor. + * Removes buffer resources held by the instance. + */ +MicroBitImage::~MicroBitImage() +{ + if(--(*ref) == 0) + { + free(bitmap); + free(ref); + } +} + +/** + * Internal constructor which defaults to the EmptyImage instance variable + */ +void MicroBitImage::init_empty() +{ + bitmap = MicroBitImage::EmptyImage.bitmap; + width = MicroBitImage::EmptyImage.width; + height = MicroBitImage::EmptyImage.height; + ref = MicroBitImage::EmptyImage.ref; + + (*ref)++; +} + +/** + * Internal constructor which provides sanity checking and initialises class properties. + * + * @param x the width of the image + * @param y the height of the image + * @param bitmap an array of integers that make up an image. + */ +void MicroBitImage::init(const int16_t x, const int16_t y, const uint8_t *bitmap) +{ + //sanity check size of image - you cannot have a negative sizes + if(x < 0 || y < 0) + { + init_empty(); + return; + } + + // Create a copy of the array + this->width = x; + this->height = y; + + // create a linear buffer to represent the image. We could use a jagged/2D array here, but experimentation + // showed this had a negative effect on memory management (heap fragmentation etc). + + this->bitmap = (uint8_t *) malloc(width*height); + + if (bitmap) + this->printImage(x,y,bitmap); + else + this->clear(); + + ref = (int16_t *) malloc(sizeof(int16_t)); + *ref = 1; +} + +/** + * Copy assign operation. + * + * Called when one MicroBitImage is assigned the value of another using the '=' operator. + * Decrement our reference count and free up the buffer as necessary. + * Then, update our buffer to refer to that of the supplied MicroBitImage, + * and increase its reference count. + * + * @param s The MicroBitImage to reference. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * MicroBitImage i1(); + * i1 = 1; // i1 now references i + * @endcode + */ +MicroBitImage& MicroBitImage::operator = (const MicroBitImage& i) +{ + if(this == &i) + return *this; + + if(--(*ref) == 0) + { + free(bitmap); + free(ref); + } + + bitmap = i.bitmap; + width = i.width; + height = i.height; + ref = i.ref; + + (*ref)++; + + return *this; +} + +/** + * Equality operation. + * + * Called when one MicroBitImage is tested to be equal to another using the '==' operator. + * + * @param i The MicroBitImage to test ourselves against. + * @return true if this MicroBitImage is identical to the one supplied, false otherwise. + * + * Example: + * @code + * MicroBitImage i(); + * MicroBitImage i1(); + * + * if(i == i1) //will be true + * print("true"); + * @endcode + */ +bool MicroBitImage::operator== (const MicroBitImage& i) +{ + if (bitmap == i.bitmap) + return true; + else + return ((width == i.width) && (height == i.height) && (memcmp(bitmap, i.bitmap,width*height)==0)); +} + + +/** + * Clears all pixels in this image + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.clear(); + * @endcode + */ +void MicroBitImage::clear() +{ + memclr(this->bitmap, width*height); +} + +/** + * Sets the pixel at the given co-ordinates to a given value. + * @param x The co-ordinate of the pixel to change w.r.t. top left origin. + * @param y The co-ordinate of the pixel to change w.r.t. top left origin. + * @param value The new value of the pixel (the brightness level 0-255) + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.setPixelValue(0,0,255); + * @endcode + */ +void MicroBitImage::setPixelValue(int16_t x , int16_t y, uint8_t value) +{ + //sanity check + if(x >= width || y >= height || x < 0 || y < 0) + return; + + this->bitmap[y*width+x] = value; +} + +/** + * Determines the value of a given pixel. + * @return The value assigned to the given pixel location (the brightness level 0-255) + * + * Example: + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.getPixelValue(0,0); //should be 0; + * @endcode + */ +int MicroBitImage::getPixelValue(int16_t x , int16_t y) +{ + //sanity check + if(x >= width || y >= height || x < 0 || y < 0) + return MICROBIT_INVALID_VALUE; + + return this->bitmap[y*width+x]; +} + +/** + * Replaces the content of this image with that of a given + * 2D array representing the image. + * Origin is in the top left corner of the image. + * + * @param x the width of the image. + * @param y the height of the image. + * @param bitmap a 2D array representing the image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(); + * i.printImage(0,0,heart); + * @endcode + */ +void MicroBitImage::printImage(int16_t width, int16_t height, const uint8_t *bitmap) +{ + const uint8_t *pIn; + uint8_t *pOut; + int pixelsToCopyX, pixelsToCopyY; + + // Sanity check. + if (width <= 0 || width <= 0 || bitmap == NULL) + return; + + // Calcualte sane start pointer. + pixelsToCopyX = min(width,this->width); + pixelsToCopyY = min(height,this->height); + + pIn = bitmap; + pOut = this->bitmap; + + // Copy the image, stride by stride. + for (int i=0; iwidth; + } +} + +/** + * Pastes a given bitmap at the given co-ordinates. + * Any pixels in the relvant area of this image are replaced. + * + * @param image The MicroBitImage to paste. + * @param x The leftmost X co-ordinate in this image where the given image should be pasted. + * @param y The uppermost Y co-ordinate in this image where the given image should be pasted. + * @param alpha set to 1 if transparency clear pixels in given image should be treated as transparent. Set to 0 otherwise. + * @return The number of pixels written. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); //if you show this image - you will see a big heart + * i.paste(-5,0,i); //displays a small heart :) + * @endcode + */ +int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8_t alpha) +{ + uint8_t *pIn, *pOut; + int cx, cy; + int pxWritten = 0; + + // Sanity check. + // We permit writes that overlap us, but ones that are clearly out of scope we can filter early. + if (x >= width || y >= height || x+image.width <= 0 || y+image.height <= 0) + return 0; + + //Calculate the number of byte we need to copy in each dimension. + cx = x < 0 ? min(image.width + x, width) : min(image.width, width - x); + cy = y < 0 ? min(image.height + y, height) : min(image.height, height - y); + + // Calcualte sane start pointer. + pIn = image.bitmap; + pIn += (x < 0) ? -x : 0; + pIn += (y < 0) ? -image.width*y : 0; + + pOut = bitmap; + pOut += (x > 0) ? x : 0; + pOut += (y > 0) ? width*y : 0; + + // Copy the image, stride by stride + // If we want primitive transparecy, we do this byte by byte. + // If we don't, use a more efficient block memory copy instead. Every little helps! + + if (alpha) + { + for (int i=0; i= width || y >= height || c < MICROBIT_FONT_ASCII_START || c > font.asciiEnd) + return; + + // Paste. + int offset = (c-MICROBIT_FONT_ASCII_START) * 5; + + for (int row=0; rowbitmap[y1*width+x1] = (v & (0x10 >> col)) ? 255 : 0; + } + } +} + + +/** + * Shifts the pixels in this Image a given number of pixels to the Left. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); //if you show this image - you will see a big heart + * i.shiftLeft(5); //displays a small heart :) + * @endcode + */ +void MicroBitImage::shiftLeft(int16_t n) +{ + uint8_t *p = bitmap; + int pixels = width-n; + + if (n <= 0 ) + return; + + if(n >= width) + { + clear(); + return; + } + + for (int y = 0; y < height; y++) + { + // Copy, and blank fill the rightmost column. + memcpy(p, p+n, pixels); + memclr(p+pixels, n); + p += width; + } +} + +/** + * Shifts the pixels in this Image a given number of pixels to the Right. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftLeft(5); //displays a small heart :) + * i.shiftRight(5); //displays a big heart :) + * @endcode + */ +void MicroBitImage::shiftRight(int16_t n) +{ + uint8_t *p = bitmap; + int pixels = width-n; + + if (n <= 0) + return; + + if(n >= width) + { + clear(); + return; + } + + for (int y = 0; y < height; y++) + { + // Copy, and blank fill the leftmost column. + memmove(p+n, p, pixels); + memclr(p, n); + p += width; + } +} + + +/** + * Shifts the pixels in this Image a given number of pixels to Upward. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftUp(1); + * @endcode + */ +void MicroBitImage::shiftUp(int16_t n) +{ + uint8_t *pOut, *pIn; + + if (n <= 0 ) + return; + + if(n >= height) + { + clear(); + return; + } + + pOut = bitmap; + pIn = bitmap+width*n; + + for (int y = 0; y < height; y++) + { + // Copy, and blank fill the leftmost column. + if (y < height-n) + memcpy(pOut, pIn, width); + else + memclr(pOut, width); + + pIn += width; + pOut += width; + } +} + + +/** + * Shifts the pixels in this Image a given number of pixels to Downward. + * + * @param n The number of pixels to shift. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftDown(1); + * @endcode + */ +void MicroBitImage::shiftDown(int16_t n) +{ + uint8_t *pOut, *pIn; + + if (n <= 0 ) + return; + + if(n >= height) + { + clear(); + return; + } + + pOut = bitmap + width*(height-1); + pIn = pOut - width*n; + + for (int y = 0; y < height; y++) + { + // Copy, and blank fill the leftmost column. + if (y < height-n) + memcpy(pOut, pIn, width); + else + memclr(pOut, width); + + pIn -= width; + pOut -= width; + } +} + +/** + * Gets the width of this image. + * + * @return The width of this image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getWidth(); //equals 10... + * @endcode + */ +int MicroBitImage::getWidth() +{ + return width; +} + +/** + * Gets the height of this image. + * + * @return The height of this image. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getHeight(); //equals 5... + * @endcode + */ +int MicroBitImage::getHeight() +{ + return height; +} + +/** + * Converts the bitmap to a csv string. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.printString(i.toString()); // "0,1,0,1,0,0,0,0,0,0\n..." + * @endcode + */ +ManagedString MicroBitImage::toString() +{ + //width including commans and \n * height + int stringSize = ((width * 2) * height); + + //plus one for string terminator + char parseBuffer[stringSize + 1]; + + parseBuffer[stringSize] = '\0'; + + uint8_t *bitmapPtr = bitmap; + + int parseIndex = 0; + int widthCount = 0; + + while (parseIndex < stringSize) + { + if(*bitmapPtr) + parseBuffer[parseIndex] = '1'; + else + parseBuffer[parseIndex] = '0'; + + parseIndex++; + + if(widthCount == width-1) + { + parseBuffer[parseIndex] = '\n'; + widthCount = 0; + } + else + { + parseBuffer[parseIndex] = ','; + widthCount++; + } + + parseIndex++; + bitmapPtr++; + } + + return ManagedString(parseBuffer); +} + +/** + * Crops the image to the given dimensions + * + * @param startx the location to start the crop in the x-axis + * @param starty the location to start the crop in the y-axis + * @param width the width of the desired cropped region + * @param height the height of the desired cropped region + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.printString(i.crop(0,0,2,2).toString()); // "0,1\n1,1\n" + * @endcode + */ +MicroBitImage MicroBitImage::crop(int startx, int starty, int cropWidth, int cropHeight) +{ + int newWidth = startx + cropWidth; + int newHeight = starty + cropHeight; + + if (newWidth >= width || newWidth <=0) + newWidth = width; + + if (newHeight >= height || newHeight <= 0) + newHeight = height; + + //allocate our storage. + uint8_t cropped[newWidth * newHeight]; + + //calculate the pointer to where we want to begin cropping + uint8_t *copyPointer = bitmap + (width * starty) + startx; + + //get a reference to our storage + uint8_t *pastePointer = cropped; + + //go through row by row and select our image. + for (int i = starty; i < newHeight; i++) + { + memcpy(pastePointer, copyPointer, newWidth); + + copyPointer += width; + pastePointer += newHeight; + } + + return MicroBitImage(newWidth, newHeight, cropped); +} diff --git a/source/MicroBitMessageBus.cpp b/source/MicroBitMessageBus.cpp new file mode 100644 index 0000000..7ddd91f --- /dev/null +++ b/source/MicroBitMessageBus.cpp @@ -0,0 +1,255 @@ +/** + * Class definition for a MicroBitMessageBus. + * + * The MicroBitMessageBus handles all messages passed between components. + */ + +#include "MicroBit.h" + +/** + * Constructor. + * Create a new Message Bus Listener. + * @param id The ID of the component you want to listen to. + * @param value The event ID you would like to listen to from that component + * @param handler A function pointer to call when the event is detected. + */ +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent)) +{ + this->id = id; + this->value = value; + this->cb = (void*) handler; + this->cb_arg = NULL; + this->next = NULL; +} + +/** + * A low-level, internal version of the constructor, where the handler's type is determined + * by the value of arg. (See MicroBitMessageBus.h). + */ +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void* handler, void* arg) +{ + this->id = id; + this->value = value; + this->cb = handler; + this->cb_arg = arg; + this->next = NULL; +} + +/** + * Constructor. + * Create a new Message Bus. + */ +MicroBitMessageBus::MicroBitMessageBus() +{ + this->listeners = NULL; + this->seq = 0; +} + +/** + * Invokes a callback on a given MicroBitListener + * + * Internal wrapper function, used to enable + * parameterized callbacks through the fiber scheduler. + */ +void async_callback(void *param) +{ + MicroBitListener *listener = (MicroBitListener *)param; + if (listener->cb_arg != NULL) + ((void (*)(MicroBitEvent, void*))listener->cb)(listener->evt, listener->cb_arg); + else + ((void (*)(MicroBitEvent))listener->cb)(listener->evt); +} + +/** + * Send the given event to all regstered recipients. + * + * @param The event to send. This structure is assumed to be heap allocated, and will + * be automatically freed once all recipients have been notified. + */ +void MicroBitMessageBus::send(MicroBitEvent &evt) +{ + this->send(evt, NULL); +} + +/** + * Send the given event to all regstered recipients, using a cached entry to minimize lookups. + * This is particularly useful for optimizing sensors that frequently send to the same channel. + * + * @param evt The event to send. This structure is assumed to be heap allocated, and will + * be automatically freed once all recipients have been notified. + * @param c Cache entry to reduce lookups for commonly used channels. + * + * TODO: For now, this is unbuffered. We should consider scheduling events here, and operating + * a different thread that empties the queue. This would perhaps provide greater opportunities + * for aggregation. + * + * Example: + * @code + * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,NULL); + * evt.fire(); + * //OR YOU CAN DO THIS... + * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,NULL,true); + * @endcode + * + * @note THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE... + */ +void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c) +{ + MicroBitListener *l; + MicroBitListener *start; + + // Find the start of the sublist where we'll send this event. + // Ideally, we'll have a valid, cached entry. Use it if we do. + if ( c != NULL && c->seq == this->seq) + { + l = c->ptr; + } + else + { + l = listeners; + while (l != NULL && l->id != evt.source) + l = l->next; + } + + start = l; + + // Now, send the event to all listeners registered for this event. + while (l != NULL && l->id == evt.source) + { + if(l->value == MICROBIT_EVT_ANY || l->value == evt.value) + { + l->evt = evt; + create_fiber(async_callback, (void *)l); + } + + l = l->next; + } + + // Next, send to any listeners registered for ALL event sources. + l = listeners; + while (l != NULL && l->id == MICROBIT_ID_ANY) + { + l->evt = evt; + create_fiber(async_callback, (void *)l); + + l = l->next; + } + + // If we were given a cached entry that's now invalid, update it. + if ( c != NULL && c->seq != this->seq) + { + c->ptr = start; + c->seq = this->seq; + } + + // Wake up any fibers that are blocked on this event + if (uBit.flags & MICROBIT_FLAG_SCHEDULER_RUNNING) + scheduler_event(evt); + + + // Finally, see if this event needs to be propogated through our BLE interface + if (uBit.ble_event_service) + uBit.ble_event_service->onMicroBitEvent(evt); +} + +/** + * Register a listener function. + * + * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_VALUE_ANY to receive events of any value. + * + * @param hander The function to call when an event is received. + * + * TODO: We currently don't support C++ member functions as callbacks, which we should. + * + * Example: + * @code + * void onButtonBClick(MicroBitEvent evt) + * { + * //do something + * } + * uBit.MessageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); // call function when ever a click event is detected. + * @endcode + */ + +void MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg) { + this->listen(id, value, (void*) handler, arg); +} + +void MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent)) { + this->listen(id, value, (void*) handler, NULL); +} + +void MicroBitMessageBus::listen(int id, int value, void* handler, void* arg) +{ + //handler can't be NULL! + if (handler == NULL) + return; + + MicroBitListener *l, *p; + + l = listeners; + + // Firstly, we treat a listener as an idempotent operation. Ensure we don't already have this handler + // registered in a that will already capture these events. If we do, silently ignore. + while (l != NULL) + { + if (l->id == id && l->value == value && l->cb == handler) + return; + + l = l->next; + } + + MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg); + + //if listeners is null - we can automatically add this listener to the list at the beginning... + if (listeners == NULL) + { + listeners = newListener; + return; + } + + // Maintain an ordered list of listeners. + // Chain is held stictly in increasing order of ID (first level), then value code (second level). + // Find the correct point in the chain for this event. + // Adding a listener is a rare occurance, so we just walk the list. + p = listeners; + l = listeners; + + while (l != NULL && l->id < id) + { + p = l; + l = l->next; + } + + while (l != NULL && l->id == id && l->value < value) + { + p = l; + l = l->next; + } + + + + //add at front of list + if (p == listeners && (id < p->id || (p->id == id && p->value > value))) + { + newListener->next = p; + + //this new listener is now the front! + listeners = newListener; + } + //add after p + else + { + newListener->next = p->next; + p->next = newListener; + } + + // Increase our sequence number and we're done. + // This will lazily invalidate any cached entries to the listener list. + this->seq++; +} + diff --git a/source/MicroBitMultiButton.cpp b/source/MicroBitMultiButton.cpp new file mode 100644 index 0000000..7ed1a2b --- /dev/null +++ b/source/MicroBitMultiButton.cpp @@ -0,0 +1,156 @@ +#include "inc/MicroBit.h" + +void +onMultiButtonEvent(MicroBitEvent evt) +{ + uBit.buttonAB.onEvent(evt); +} + +/** + * Constructor. + * Create a representation of a vurtual button, that generates events based upon the combination + * of two given buttons. + * @param id the ID of the new MultiButton object. + * @param button1 the ID of the first button to integrate. + * @param button2 the ID of the second button to integrate. + * @param name the physical pin on the processor that this butotn is connected to. + * + * Example: + * @code + * multiButton(MICROBIT_ID_BUTTON_AB, MICROBIT_ID_BUTTON_A, MICROBIT_ID_BUTTON_B); + * @endcode + * + * Possible Events: + * @code + * MICROBIT_BUTTON_EVT_DOWN + * MICROBIT_BUTTON_EVT_UP + * MICROBIT_BUTTON_EVT_CLICK + * MICROBIT_BUTTON_EVT_LONG_CLICK + * MICROBIT_BUTTON_EVT_HOLD + * @endcode + */ +MicroBitMultiButton::MicroBitMultiButton(uint16_t id, uint16_t button1, uint16_t button2) +{ + this->id = id; + this->button1 = button1; + this->button2 = button2; + + uBit.MessageBus.listen(button1, MICROBIT_EVT_ANY, onMultiButtonEvent); + uBit.MessageBus.listen(button2, MICROBIT_EVT_ANY, onMultiButtonEvent); +} + +uint16_t MicroBitMultiButton::otherSubButton(uint16_t b) +{ + return (b == button1 ? button2 : button1); +} + +int MicroBitMultiButton::isSubButtonPressed(uint16_t button) +{ + if (button == button1) + return status & MICROBIT_MULTI_BUTTON_STATE_1; + + if (button == button2) + return status & MICROBIT_MULTI_BUTTON_STATE_2; + + return 0; +} + +int MicroBitMultiButton::isSubButtonHeld(uint16_t button) +{ + if (button == button1) + return status & MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1; + + if (button == button2) + return status & MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2; + + return 0; +} + + +void MicroBitMultiButton::setButtonState(uint16_t button, int value) +{ + if (button == button1) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_STATE_1; + else + status &= ~MICROBIT_MULTI_BUTTON_STATE_1; + } + + if (button == button2) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_STATE_2; + else + status &= ~MICROBIT_MULTI_BUTTON_STATE_2; + } +} + +void MicroBitMultiButton::setHoldState(uint16_t button, int value) +{ + if (button == button1) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1; + else + status &= ~MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1; + } + + if (button == button2) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2; + else + status &= ~MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2; + } +} + +void MicroBitMultiButton::onEvent(MicroBitEvent evt) +{ + int button = evt.source; + int otherButton = otherSubButton(button); + + switch(evt.value) + { + case MICROBIT_BUTTON_EVT_DOWN: + setButtonState(button, 1); + if(isSubButtonPressed(otherButton)) + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_DOWN); + + 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: + setHoldState(button, 1); + if(isSubButtonHeld(otherButton)) + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_HOLD); + + break; + } +} + + +/** + * Tests if this MultiButton is currently pressed. + * @return 1 if both physical buttons are pressed simultaneously. + */ +int MicroBitMultiButton::isPressed() +{ + return ((status & MICROBIT_MULTI_BUTTON_STATE_1) && (status & MICROBIT_MULTI_BUTTON_STATE_2)); +} diff --git a/source/MicroBitPin.cpp b/source/MicroBitPin.cpp new file mode 100644 index 0000000..a42d7a9 --- /dev/null +++ b/source/MicroBitPin.cpp @@ -0,0 +1,215 @@ +#include "MicroBit.h" +#include "inc/MicroBitPin.h" + +/** + * Constructor. + * Create a Button representation with the given ID. + * @param id the ID of the new Pin object. + * @param name the pin name for this MicroBitPin instance to represent + * @param capability the capability of this pin, can it only be digital? can it only be analog? can it be both? + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * @endcode + */ +MicroBitPin::MicroBitPin(int id, PinName name, PinCapability capability) +{ + //set mandatory attributes + this->id = id; + this->name = name; + this->capability = capability; + + // Power up in a disconnected, low power state. + // If we're unused, this is how it will stay... + this->status = 0x00; + this->pin = NULL; + +} + +/** + * Disconnect any attached mbed IO from this pin. + * Used only when pin changes mode (i.e. Input/Output/Analog/Digital) + */ +void MicroBitPin::disconnect() +{ + // This is a bit ugly, but rarely used code. + // It would be much better to use some polymorphism here, but the mBed I/O classes aren't arranged in an inheritance hierarchy... yet. :-) + if (status & IO_STATUS_DIGITAL_IN) + delete ((DigitalIn *)pin); + + if (status & IO_STATUS_DIGITAL_OUT) + delete ((DigitalOut *)pin); + + if (status & IO_STATUS_ANALOG_IN){ + NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Disabled; // forcibly disable the ADC - BUG in mbed.... + delete ((AnalogIn *)pin); + } + + if (status & IO_STATUS_ANALOG_OUT) + { + if(((DynamicPwm *)pin)->getPinName() == name) + ((DynamicPwm *)pin)->free(); + } + + if (status & IO_STATUS_TOUCH_IN) + delete ((MicroBitButton *)pin); + + this->pin = NULL; + this->status = status & IO_STATUS_EVENTBUS_ENABLED; //retain event bus status +} + +/** + * Configures this IO pin as a digital output (if necessary) and sets the pin to 'value'. + * @param value 0 (LO) or 1 (HI) + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.setDigitalValue(1); // P0 is now HI! + * @endcode + */ +void MicroBitPin::setDigitalValue(int value) +{ + //check if this pin has a digital mode... + if(!(PIN_CAPABILITY_DIGITAL & capability) || value < 0 || value > 1) + return; + + // Move into a Digital input state if necessary. + if (!(status & IO_STATUS_DIGITAL_OUT)){ + disconnect(); + pin = new DigitalOut(name); + status |= IO_STATUS_DIGITAL_OUT; + } + + //write the value! + ((DigitalOut *)pin)->write(value); +} + +/** + * Configures this IO pin as a digital input (if necessary) and tests its current value. + * @return 1 if this input is high, 0 otherwise. + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.getDigitalValue(); // P0 is either 0 or 1; + * @endcode + */ +int MicroBitPin::getDigitalValue() +{ + //check if this pin has a digital mode... + if(!(PIN_CAPABILITY_DIGITAL & capability)) + return MICROBIT_IO_OP_NA; + + // Move into a Digital input state if necessary. + if (!(status & IO_STATUS_DIGITAL_IN)){ + disconnect(); + pin = new DigitalIn(name,PullDown); //pull down! + status |= IO_STATUS_DIGITAL_IN; + } + + return ((DigitalIn *)pin)->read(); +} + +/** + * Configures this IO pin as an analogue output (if necessary and possible). + * Change the DAC value to the given level. + * @param value the level to set on the output pin, in the range 0..255 + * @note We have a maximum of 3 PWM channels for this device - one is reserved for the display... the other two are reconfigured dynamically when they are required. + */ +void MicroBitPin::setAnalogValue(int value) +{ + //check if this pin has an analogue mode... + if(!(PIN_CAPABILITY_ANALOG & capability)) + return; + + //sanitise the brightness level + if(value < 0 || value > MICROBIT_PIN_MAX_OUTPUT) + return; + + float level = (float)value / float(MICROBIT_PIN_MAX_OUTPUT); + + // Move into an analogue input state if necessary, if we are no longer the focus of a DynamicPWM instance, allocate ourselves again! + if (!(status & IO_STATUS_ANALOG_OUT) || !(((DynamicPwm *)pin)->getPinName() == name)){ + disconnect(); + pin = (void *)DynamicPwm::allocate(name); + status |= IO_STATUS_ANALOG_OUT; + } + + //perform a write with an extra check! :) + if(((DynamicPwm *)pin)->getPinName() == name) + ((DynamicPwm *)pin)->write(level); +} + + +/** + * Configures this IO pin as an analogue input (if necessary and possible). + * @return the current analogue level on the pin, in the range 0-0xFFFF + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.getAnalogValue(); // P0 is a value in the range of 0 - 0xFFFF + * @endcode + */ +int MicroBitPin::getAnalogValue() +{ + + //check if this pin has an analogue mode... + if(!(PIN_CAPABILITY_ANALOG & capability)) + return MICROBIT_IO_OP_NA; + + // Move into an analogue input state if necessary. + if (!(status & IO_STATUS_ANALOG_IN)){ + disconnect(); + pin = new AnalogIn(name); + status |= IO_STATUS_ANALOG_IN; + } + + //perform a read! + return ((AnalogIn *)pin)->read_u16(); +} + +/** + * Configures this IO pin as a makey makey style touch sensor (if necessary) and tests its current debounced state. + * @return 1 if pin is touched, 0 otherwise. + * + * Example: + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); + * if(P0.isTouched()) + * { + * uBit.display.clear(); + * } + * @endcode + */ +int MicroBitPin::isTouched() +{ + //check if this pin has a touch mode... + if(!(PIN_CAPABILITY_TOUCH & capability)) + return MICROBIT_IO_OP_NA; + + // Move into a touch input state if necessary. + if (!(status & IO_STATUS_TOUCH_IN)){ + disconnect(); + pin = new MicroBitButton(id, name); + status |= IO_STATUS_TOUCH_IN; + } + + return ((MicroBitButton *)pin)->isPressed(); +} + +/** + * Configures the PWM period of the analog output to the given value. + * If this pin is not configured as an analog output, the operation + * has no effect. + * + * @param period The new period for the analog output in milliseconds. + */ +void MicroBitPin::setAnalogPeriod(int period) +{ + if (status & IO_STATUS_ANALOG_OUT) + ((DynamicPwm *)pin)->setPeriodUs(period*1000); + +} diff --git a/source/MicroBitSerial.cpp b/source/MicroBitSerial.cpp new file mode 100644 index 0000000..499a515 --- /dev/null +++ b/source/MicroBitSerial.cpp @@ -0,0 +1,186 @@ +#include "MicroBit.h" +#include "mbed.h" + +/** + * Constructor. + * Create an instance of MicroBitSerial + * + * @param tx the Pin to be used for transmission + * @param rx the Pin to be used for receiving data + * + * Example: + * @code + * MicroBitSerial serial(USBTX, USBRX); + * @endcode + * @note the default baud rate is 115200 + */ +MicroBitSerial::MicroBitSerial(PinName tx, PinName rx) : Serial(tx,rx) +{ + this->baud(MICROBIT_SERIAL_DEFAULT_BAUD_RATE); +} + +/** + * Sends a managed string over serial. + * + * @param s the ManagedString to send + * + * Example: + * @code + * uBit.serial.printString("abc123"); + * @endcode + */ +void MicroBitSerial::sendString(ManagedString s) +{ + const int len = s.length(); + const char *data = s.toCharArray(); + + Serial::write(data,len); +} + +/** + * Reads a ManagedString from serial + * + * @param len the buffer size for the string, default is defined by MICROBIT_SERIAL_BUFFER_SIZE + * + * Example: + * @code + * uBit.serial.readString(); + * @endcode + * + * @note this member function will wait until either the buffer is full, or a \n is received + */ +ManagedString MicroBitSerial::readString(int len) +{ + if(len < 3) + len = 3; + + char buffer[len]; + + memset(buffer, 0, sizeof(buffer)); + + int length = readChars(buffer,len); + + if(length == 0) + return ManagedString(); + + //add in a null terminator so bad things don't happen with ManagedString + buffer[length] = '\0'; + + return ManagedString(buffer); +} + +/** + * Sends a MicroBitImage over serial in csv format. + * + * @param i the instance of MicroBitImage you would like to send. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.sendImage(i); + * @endcode + */ +void MicroBitSerial::sendImage(MicroBitImage i) +{ + sendString(i.toString()); +} + + +/** + * Reads a MicroBitImage over serial, in csv format. + * + * + * @return a MicroBitImage with the format described over serial + * + * Example: + * @code + * MicroBitImage i = uBit.serial.readImage(5,5); + * @endcode + * + * Example Serial Format: + * @code + * 0,10x0a0,10x0a // 0x0a is a LF terminal which is used as a delimeter + * @endcode + * @note this will finish once the dimensions are met. + */ +MicroBitImage MicroBitSerial::readImage(int width, int height) +{ + int rowLength = width * 2; + int len = rowLength * height; + + char buffer[len + 1]; + + memset(buffer, 0, sizeof(buffer)); + + //add in a null terminator so bad things don't happen with MicroBitImage + buffer[len] = '\0'; + + for(int i = 0; i < height; i++) + { + int offset = i * rowLength; + + readChars(&buffer[offset],len - offset); + + buffer[(offset + rowLength) - 1] = '\n'; + } + + return MicroBitImage(buffer); +} + +/** + * Sends the current pixel values, byte-per-pixel, over serial + * + * Example: + * @code + * uBit.serial.sendDisplayState(); + * @endcode + */ +void MicroBitSerial::sendDisplayState() +{ + for(int i = 0; i < MICROBIT_DISPLAY_HEIGHT; i++) + for(int j = 0; j < MICROBIT_DISPLAY_WIDTH; j++) + _putc(uBit.display.image.getPixelValue(j,i)); +} + +/** + * Reads pixel values, byte-per-pixel, from serial, and sets the display. + * + * Example: + * @code + * uBit.serial.readDisplayState(); + * @endcode + */ +void MicroBitSerial::readDisplayState() +{ + for(int i = 0; i < MICROBIT_DISPLAY_HEIGHT; i++) + for(int j = 0; j < MICROBIT_DISPLAY_WIDTH; j++) + { + int c = _getc(); + uBit.display.image.setPixelValue(j,i,c); + } +} + +ssize_t MicroBitSerial::readChars(void* buffer, size_t length, char eof) { + + char* ptr = (char*)buffer; + char* end = ptr + length; + + int eofAscii = (int)eof; + + while (ptr != end) { + + int c = _getc(); + + //check EOF + if (c == eofAscii) + break; + + //store the character + *ptr++ = c; + } + + return ptr - (const char*)buffer; +} + + diff --git a/source/MicroBitSuperMain.cpp b/source/MicroBitSuperMain.cpp new file mode 100644 index 0000000..98cf8fe --- /dev/null +++ b/source/MicroBitSuperMain.cpp @@ -0,0 +1,76 @@ +#include "MicroBit.h" + +#ifdef MICROBIT_DBG +Serial pc(USBTX, USBRX); +#endif + +MicroBit uBit; +InterruptIn resetButton(MICROBIT_PIN_BUTTON_RESET); + +void +onResetButtonPressed() +{ + NVIC_SystemReset(); +} + +int main() +{ + // Bring up soft reset button. + resetButton.mode(PullUp); + resetButton.fall(onResetButtonPressed); + +#ifdef MICROBIT_DBG + pc.baud(115200); + + // For diagnostics. Gives time to open the console window. :-) + for (int i=10; i>0; i--) + { + pc.printf("=== SUPERMAIN: Starting in %d ===\n", i); + wait(1.0); + } + + pc.printf("=== scheduler init... "); + scheduler_init(); + pc.printf("complete ===\n"); + + pc.printf("=== uBit init... "); + uBit.init(); + pc.printf("complete ===\n"); + + pc.printf("=== Launching app_main ===\n"); + app_main(); + + pc.printf("=== app_main exited! ===\n"); + + while(1) + uBit.sleep(100); + +#else + + // Bring up fiber scheduler + scheduler_init(); + + // bring up random number generator, BLE, display and system timers. + uBit.init(); + + uBit.sleep(100); + + // Test if we need to enter BLE pairing mode... + int i=0; + while (uBit.buttonA.isPressed() && uBit.buttonB.isPressed() && i<10) + { + uBit.sleep(100); + i++; + + if (i == 10 && uBit.ble_firmware_update_service != NULL) + uBit.ble_firmware_update_service->pair(); + } + + app_main(); + + while(1) + uBit.sleep(100); + +#endif + +}