diff --git a/docs/microbit pairing and over the air firmware updates.txt b/docs/microbit pairing and over the air firmware updates.txt index 3254ec3..675d973 100644 --- a/docs/microbit pairing and over the air firmware updates.txt +++ b/docs/microbit pairing and over the air firmware updates.txt @@ -56,9 +56,12 @@ Either (1) the client does not possess the flash code and therefore must somehow Stage 1 - Client Does Not Possess flash code ------------------------------------------ -MicroBit has a special mode of operation or state known currently as "Blue Zone". The device enters this state when rebooted by pressing the reset button whilst at the same time holding down both buttons A and B. The device indicates itself to be in the Blue Zone mode by scrolling "BLUEZONE" across the display. +MicroBit has a special mode of operation or state known currently as "Pairing +Mode". The device enters this state when rebooted by pressing the reset button +whilst at the same time holding down both buttons A and B. The device +indicates itself to be in the pairing mode by scrolling "PAIRING MODE!" across the display. -Once in the Blue Zone state, the MicroBit DFU Service also enables another command via the ControlPoint characteristic known as 'REQUEST_FLASHCODE' which is represented by a value of 0x02. +Once in the pairin mode state, the MicroBit DFU Service also enables another command via the ControlPoint characteristic known as 'REQUEST_FLASHCODE' which is represented by a value of 0x02. To obtain the flash code the client should enable GATT notifications on the FlashCode characteristic and then write 0x02 to the Control characteristic. The micro:bit will respond by displaying "PAIR?" on the LED display. @@ -66,7 +69,12 @@ The user must now press Button A on the micro:bit. This is an "authorization to Stage 2 - Client in Possession of flash code ------------------------------------------- -If a device already knows the flashcode, it can connect any time and initiate rebooting into "Nordic DFU mode" by writing the FlashCode characteristic with the previously cached value and then writing the 'ENTER NORDIC BOOTLOADER' command (0x01) to the Control characteristic. The device will reboot into the stock nordic bootloader and then the attached device can interact with that to reflash the device. The device does NOT need to be in Blue Zone mode. +If a device already knows the flashcode, it can connect any time and initiate +rebooting into "Nordic DFU mode" by writing the FlashCode characteristic with +the previously cached value and then writing the 'ENTER NORDIC BOOTLOADER' +command (0x01) to the Control characteristic. The device will reboot into the +stock nordic bootloader and then the attached device can interact with that to +reflash the device. The device does NOT need to be in pairing mode. Issues for Client Application Developers ---------------------------------------- @@ -80,14 +88,16 @@ micro:bit human identifiers --------------------------- In addition to a secret key (flash code) used in the FOTA process as described, micro:bits have a human readable identifier (public name) which is included in BLE advertising packets and can be discovered and used by human users during any process, including FOTA, where there needs to be a confirmation that the device which is to be interacted with is the one the human intends to interact with. In the context of this document, "interact with" means update using the FOTA procedure. -The public name is generated by the run time from the other half of the Nordic serial number. Humans can discover the public name of a device by switching into BLUEZONE mode. The public name is displayed in a coded but simple graphical form on the LED display and can then be entered into (say) a mobile application screen to verify the device to be updated is the one we mean to update. +The public name is generated by the run time from the other half of the Nordic +serial number. Humans can discover the public name of a device by switching +into pairing mode mode. The public name is displayed in a coded but simple graphical form on the LED display and can then be entered into (say) a mobile application screen to verify the device to be updated is the one we mean to update. Summary of the FOTA Process --------------------------- Case 1 - Client does not know the flash code -------------------------------------------- -a) User switches micro:bit into BLUEZONE mode - must do this first since it involves a reboot and will therefore disconnect the client +a) User switches micro:bit into pairing mode - must do this first since it involves a reboot and will therefore disconnect the client b) Client connects to micro:bit c) Client discovers MicroBit DFU service d) Client enables notifications on the MicroBit DFU Service::FlashCode characteristic diff --git a/inc/DynamicPwm.h b/inc/DynamicPwm.h index dbd66ea..55cfafe 100644 --- a/inc/DynamicPwm.h +++ b/inc/DynamicPwm.h @@ -4,7 +4,7 @@ #define MICROBIT_DYNAMIC_PWM_H #define NO_PWMS 3 -#define MICROBIT_DISPLAY_PWM_PERIOD 1000 +#define MICROBIT_DEFAULT_PWM_PERIOD 20000 enum PwmPersistence { @@ -15,28 +15,30 @@ enum PwmPersistence /** * Class definition for DynamicPwm. * - * This class addresses a few issues found in the underlying libraries. + * 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: + private: static DynamicPwm* pwms[NO_PWMS]; static uint8_t lastUsed; + static uint16_t sharedPeriod; uint8_t flags; - - + float lastValue; + + + /** * 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.) + * @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: - + DynamicPwm(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT); + + public: + /** * Redirects the pwm channel to point at a different pin. * @param pin the new pin to direct PWM at. @@ -48,12 +50,12 @@ class DynamicPwm : public PwmOut * @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.) + * @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. * @@ -62,8 +64,8 @@ class DynamicPwm : public PwmOut * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); * @endcode */ - static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT, int period = MICROBIT_DISPLAY_PWM_PERIOD); - + static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT); + /** * Frees this DynamicPwm instance if the pointer is valid. * @@ -74,31 +76,95 @@ class DynamicPwm : public PwmOut * @endcode */ void release(); - + + /** + * A lightweight wrapper around the super class' write in order to capture the value + * + * @param value the duty cycle percentage in floating point format. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->write(0.5); + * @endcode + */ + int write(float value); + /** * Retreives the pin name associated with this DynamicPwm instance. * * Example: * @code * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); - * pwm->getPinName(); // equal to n + * pwm->getPinName(); * @endcode */ PinName getPinName(); - + + /** + * Retreives the last value that has been written to this pwm channel. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPeriod(); + * @endcode + */ + int getValue(); + + /** + * Retreives the current period in use by the entire PWM module in microseconds. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPeriod(); + * @endcode + */ + int getPeriodUs(); + + /** + * Retreives the current period in use by the entire PWM module in milliseconds. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPeriod(); + * @endcode + */ + int getPeriod(); + /** * Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS. * + * @param period the desired period in microseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range + * * 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); - + int setPeriodUs(int period); + + /** + * Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS. + * + * @param period the desired period in milliseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->setPeriod(1); // period now is 1ms + * @endcode + */ + int setPeriod(int period); }; #endif diff --git a/inc/MESEvents.h b/inc/MESEvents.h index 676a04b..641429e 100644 --- a/inc/MESEvents.h +++ b/inc/MESEvents.h @@ -9,74 +9,63 @@ // 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_REMOTE_CONTROL_EVT_PLAY 1 +#define MES_REMOTE_CONTROL_EVT_PAUSE 2 +#define MES_REMOTE_CONTROL_EVT_STOP 3 +#define MES_REMOTE_CONTROL_EVT_NEXTTRACK 4 +#define MES_REMOTE_CONTROL_EVT_PREVTRACK 5 +#define MES_REMOTE_CONTROL_EVT_FORWARD 6 +#define MES_REMOTE_CONTROL_EVT_REWIND 7 +#define MES_REMOTE_CONTROL_EVT_VOLUMEUP 8 +#define MES_REMOTE_CONTROL_EVT_VOLUMEDOWN 9 #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_CAMERA_EVT_LAUNCH_PHOTO_MODE 1 +#define MES_CAMERA_EVT_LAUNCH_VIDEO_MODE 2 +#define MES_CAMERA_EVT_TAKE_PHOTO 3 +#define MES_CAMERA_EVT_START_VIDEO_CAPTURE 4 +#define MES_CAMERA_EVT_STOP_VIDEO_CAPTURE 5 +#define MES_CAMERA_EVT_STOP_PHOTO_MODE 6 +#define MES_CAMERA_EVT_STOP_VIDEO_MODE 7 +#define MES_CAMERA_EVT_TOGGLE_FRONT_REAR 8 #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 +// TODO Multiple DISPLAY_TOAST event values +#define MES_ALERT_EVT_DISPLAY_TOAST 1 +#define MES_ALERT_EVT_VIBRATE 2 +// TODO PLAY_SOUND and ALARMN to be combined +#define MES_ALERT_EVT_PLAY_SOUND 3 +#define MES_ALERT_EVT_PLAY_RINGTONE 4 +#define MES_ALERT_EVT_FIND_MY_PHONE 5 +#define MES_ALERT_EVT_ALARM1 6 +#define MES_ALERT_EVT_ALARM2 7 +#define MES_ALERT_EVT_ALARM3 8 +#define MES_ALERT_EVT_ALARM4 9 +#define MES_ALERT_EVT_ALARM5 10 +#define MES_ALERT_EVT_ALARM6 11 // // 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_SIGNAL_STRENGTH_EVT_NO_BAR 1 +#define MES_SIGNAL_STRENGTH_EVT_ONE_BAR 2 +#define MES_SIGNAL_STRENGTH_EVT_TWO_BAR 3 +#define MES_SIGNAL_STRENGTH_EVT_THREE_BAR 4 +#define MES_SIGNAL_STRENGTH_EVT_FOUR_BAR 5 -#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 +#define MES_DEVICE_ORIENTATION_LANDSCAPE 1 +#define MES_DEVICE_ORIENTATION_PORTRAIT 2 +#define MES_DEVICE_GESTURE_NONE 3 +#define MES_DEVICE_GESTURE_DEVICE_SHAKEN 4 +#define MES_DEVICE_DISPLAY_OFF 5 +#define MES_DEVICE_DISPLAY_ON 6 +#define MES_DEVICE_INCOMING_CALL 7 +#define MES_DEVICE_INCOMING_MESSAGE 8 + #endif diff --git a/inc/Matrix4.h b/inc/Matrix4.h new file mode 100644 index 0000000..ca4c07e --- /dev/null +++ b/inc/Matrix4.h @@ -0,0 +1,143 @@ +#ifndef MICROBIT_MATRIX4_H +#define MICROBIT_MATRIX4_H + +/** +* Class definition for a simple matrix, that is optimised for nx4 or 4xn matrices. +* +* This class is heavily optimised for these commonly used matrices as used in 3D geometry. +* Whilst this class does support basic operations on matrices of any dimension, it is not intended as a +* general purpose matrix class as inversion operations are only provided for 4x4 matrices. +* For programmers needing more flexible Matrix support, the Matrix and MatrixMath classes from +* Ernsesto Palacios provide a good basis: +* +* https://developer.mbed.org/cookbook/MatrixClass +* https://developer.mbed.org/users/Yo_Robot/code/MatrixMath/ +*/ +class Matrix4 +{ + double *data; // Linear buffer representing the matrix. + int rows; // The number of rows in the matrix. + int cols; // The number of columns in the matrix. + +public: + + /** + * Constructor. + * Create a matrix of the given size. + * @param rows the number of rows in the matrix to be created. + * @param cols the number of columns in the matrix to be created. + * + * Example: + * @code + * Matrix4(10, 4); // Creates a Matrix with 10 rows and 4 columns. + * @endcode + */ + Matrix4(int rows, int cols); + + /** + * Constructor. + * Create a matrix that is an identical copy of the given matrix. + * @param matrix The matrix to copy. + * + * Example: + * @code + * + * Matrix newMatrix(matrix); . + * @endcode + */ + Matrix4(const Matrix4 &matrix); + + /** + * Determines the number of columns in this matrix. + * + * @return The number of columns in the matrix. + * + * Example: + * @code + * int c = matrix.width(); + * @endcode + */ + int width(); + + /** + * Determines the number of rows in this matrix. + * + * @return The number of rows in the matrix. + * + * Example: + * @code + * int r = matrix.height(); + * @endcode + */ + int height(); + + /** + * Reads the matrix element at the given position. + * + * @param row The row of the element to read + * @param col The column of the element to read + * @return The value of the matrix element at the given position. NAN is returned if the given index is out of range. + * + * Example: + * @code + * double v = matrix.get(1,2); + * @endcode + */ + double get(int row, int col); + + /** + * Writes the matrix element at the given position. + * + * @param row The row of the element to write + * @param col The column of the element to write + * @param v The new value of the element + * + * Example: + * @code + * matrix.set(1,2,42.0); + * @endcode + */ + void set(int row, int col, double v); + + /** + * Transposes this matrix. + * @return the resultant matrix. + * + * Example: + * @code + * matrix.transpose(); + * @endcode + */ + Matrix4 transpose(); + + /** + * Multiplies this matrix with the given matrix (if possible). + * @return the resultant matrix. An empty matrix is returned if the operation canot be completed. + * + * Example: + * @code + * Matrix result = matrixA.multiply(matrixB); + * @endcode + */ + Matrix4 multiply(Matrix4 &matrix); + + /** + * Performs an optimisaed inversion of a 4x4 matrix. + * Only 4x4 matrics are supported by this operation. + * + * @return the resultant matrix. An empty matrix is returned if the operation canot be completed. + * + * Example: + * @code + * Matrix result = matrixA.invert(); + * @endcode + */ + Matrix4 invert(); + + /** + * Destructor. + */ + ~Matrix4(); +}; + +#endif diff --git a/inc/MicroBit.h b/inc/MicroBit.h index 417b300..184a3a0 100644 --- a/inc/MicroBit.h +++ b/inc/MicroBit.h @@ -3,15 +3,16 @@ #include "mbed.h" -#include "MicroBitConfig.h" +#include "MicroBitConfig.h" #include "MicroBitHeapAllocator.h" #include "MicroBitPanic.h" #include "ErrorNo.h" +#include "Matrix4.h" #include "MicroBitCompat.h" #include "MicroBitComponent.h" #include "ManagedType.h" #include "ManagedString.h" -#include "MicroBitImage.h" +#include "MicroBitImage.h" #include "MicroBitFont.h" #include "MicroBitEvent.h" #include "DynamicPwm.h" @@ -32,35 +33,7 @@ #include "MicroBitFiber.h" #include "MicroBitMessageBus.h" -/* - * The underlying Nordic libraries that support BLE do not compile cleanly with the stringent GCC settings we employ - * If we're compiling under GCC, then we suppress any warnings generated from this code (but not the rest of the DAL) - * The ARM cc compiler is more tolerant. We don't test __GNUC__ here to detect GCC as ARMCC also typically sets this - * as a compatability option, but does not support the options used... - */ -#if !defined (__arm) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#endif -#include "ble/BLE.h" - -/* - * Return to our predefined compiler settings. - */ -#if !defined (__arm) -#pragma GCC diagnostic pop -#endif - -#include "ble/services/DeviceInformationService.h" -#include "MicroBitDFUService.h" -#include "MicroBitEventService.h" -#include "MicroBitLEDService.h" -#include "MicroBitAccelerometerService.h" -#include "MicroBitMagnetometerService.h" -#include "MicroBitButtonService.h" -#include "MicroBitIOPinService.h" -#include "MicroBitTemperatureService.h" -#include "ExternalEvents.h" +#include "MicroBitBLEManager.h" // MicroBit::flags values #define MICROBIT_FLAG_SCHEDULER_RUNNING 0x00000001 @@ -86,14 +59,16 @@ * Represents the device as a whole, and includes member variables to that reflect the components of the system. */ class MicroBit -{ +{ private: - + void seedRandom(); + void compassCalibrator(MicroBitEvent e); uint32_t randomValue; - + + public: - + // Map of device state. uint32_t flags; @@ -101,43 +76,43 @@ class MicroBit Ticker systemTicker; // I2C Interface - MicroBitI2C i2c; - + MicroBitI2C i2c; + // Serial Interface - MicroBitSerial serial; + 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; - + MicroBitMessageBus MessageBus; + // Member variables to represent each of the core components on the device. MicroBitDisplay display; MicroBitButton buttonA; MicroBitButton buttonB; - MicroBitMultiButton buttonAB; + MicroBitMultiButton buttonAB; MicroBitAccelerometer accelerometer; MicroBitCompass compass; MicroBitThermometer thermometer; //An object of available IO pins on the device MicroBitIO io; - + // Bluetooth related member variables. - BLEDevice *ble; - MicroBitDFUService *ble_firmware_update_service; - + MicroBitBLEManager bleManager; + BLEDevice *ble; + /** - * Constructor. + * Constructor. * Create a representation of a MicroBit device as a global singleton. * @param messageBus callback function to receive MicroBitMessageBus events. * * Exposed objects: - * @code + * @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. @@ -149,26 +124,21 @@ class MicroBit * uBit.io.P*; //Where P* is P0 to P16, P19 & P20 on the edge connector * @endcode */ - MicroBit(); + MicroBit(); /** * Post constructor initialisation method. - * After *MUCH* pain, it's noted that the BLE stack can't be brought up in a + * 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 + * @code * uBit.init(); * @endcode */ void init(); - /** - * Derives the friendly name for this device, autogenerated from our hardware Device ID. - */ - void deriveName(); - /** * Return the friendly name for this device. * @@ -187,7 +157,7 @@ class MicroBit * Will reset the micro:bit when called. * * Example: - * @code + * @code * uBit.reset(); * @endcode */ @@ -198,15 +168,15 @@ class MicroBit * 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. - * + * 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. - * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER milliseconds is less than zero. + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER milliseconds is less than zero. * * Example: - * @code + * @code * uBit.sleep(20); //sleep for 20ms * @endcode */ @@ -221,7 +191,7 @@ class MicroBit * @return A random, natural number between 0 and the max-1. Or MICROBIT_INVALID_PARAMETER if max is <= 0. * * Example: - * @code + * @code * uBit.random(200); //a number between 0 and 199 * @endcode */ @@ -232,27 +202,27 @@ class MicroBit * 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 + * add a component to the array of system components which invocate the systemTick member function during a systemTick * * @param component The component to add. * @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported. */ int addSystemComponent(MicroBitComponent *component); - + /** * remove a component from the array of system components * @param component The component to remove. * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previous added. */ int 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 @@ -260,7 +230,7 @@ class MicroBit * @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported. */ int addIdleComponent(MicroBitComponent *component); - + /** * remove a component from the array of idle thread components * @param component The component to remove. @@ -275,7 +245,7 @@ class MicroBit * TODO: handle overflow case. */ unsigned long systemTime(); - + /** * Determine the version of the micro:bit runtime currently in use. * @@ -286,7 +256,7 @@ class MicroBit /** * 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); @@ -298,16 +268,9 @@ class MicroBit // code integration a little bit easier for third parties. extern MicroBit uBit; -// -// BLE callback when an active GATT session with another device is terminated. -// Used to reset state and restart advertising ourselves. -// -void bleDisconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason); - // 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 index a9bc583..6d7b00b 100644 --- a/inc/MicroBitAccelerometer.h +++ b/inc/MicroBitAccelerometer.h @@ -3,12 +3,18 @@ #include "mbed.h" #include "MicroBitComponent.h" +#include "MicroBitCoordinateSystem.h" /** - * Relevant pin assignments - */ + * Relevant pin assignments + */ #define MICROBIT_PIN_ACCEL_DATA_READY P0_28 +/** + * Status flags + */ +#define MICROBIT_ACCEL_PITCH_ROLL_VALID 0x01 + /* * I2C constants */ @@ -29,8 +35,8 @@ /** - * MMA8653 constants - */ + * MMA8653 constants + */ #define MMA8653_WHOAMI_VAL 0x5A #define MMA8653_SAMPLE_RANGES 3 @@ -39,7 +45,42 @@ /* * Accelerometer events */ -#define MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE 1 +#define MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE 1 + +/* + * Gesture events + */ +#define MICROBIT_ACCELEROMETER_EVT_TILT_UP 1 +#define MICROBIT_ACCELEROMETER_EVT_TILT_DOWN 2 +#define MICROBIT_ACCELEROMETER_EVT_TILT_LEFT 3 +#define MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT 4 +#define MICROBIT_ACCELEROMETER_EVT_FACE_UP 5 +#define MICROBIT_ACCELEROMETER_EVT_FACE_DOWN 6 +#define MICROBIT_ACCELEROMETER_EVT_FREEFALL 7 +#define MICROBIT_ACCELEROMETER_EVT_3G 8 +#define MICROBIT_ACCELEROMETER_EVT_6G 9 +#define MICROBIT_ACCELEROMETER_EVT_8G 10 +#define MICROBIT_ACCELEROMETER_EVT_SHAKE 11 + +/* + * Gesture recogniser constants + */ +#define MICROBIT_ACCELEROMETER_REST_TOLERANCE 200 +#define MICROBIT_ACCELEROMETER_TILT_TOLERANCE 200 +#define MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE 400 +#define MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE 1000 +#define MICROBIT_ACCELEROMETER_3G_TOLERANCE 3072 +#define MICROBIT_ACCELEROMETER_6G_TOLERANCE 6144 +#define MICROBIT_ACCELEROMETER_8G_TOLERANCE 8192 +#define MICROBIT_ACCELEROMETER_GESTURE_DAMPING 10 +#define MICROBIT_ACCELEROMETER_SHAKE_DAMPING 10 + +#define MICROBIT_ACCELEROMETER_REST_THRESHOLD (MICROBIT_ACCELEROMETER_REST_TOLERANCE * MICROBIT_ACCELEROMETER_REST_TOLERANCE) +#define MICROBIT_ACCELEROMETER_FREEFALL_THRESHOLD (MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE * MICROBIT_ACCELEROMETER_FREEFALL_TOLERANCE) +#define MICROBIT_ACCELEROMETER_3G_THRESHOLD (MICROBIT_ACCELEROMETER_3G_TOLERANCE * MICROBIT_ACCELEROMETER_3G_TOLERANCE) +#define MICROBIT_ACCELEROMETER_6G_THRESHOLD (MICROBIT_ACCELEROMETER_6G_TOLERANCE * MICROBIT_ACCELEROMETER_6G_TOLERANCE) +#define MICROBIT_ACCELEROMETER_8G_THRESHOLD (MICROBIT_ACCELEROMETER_8G_TOLERANCE * MICROBIT_ACCELEROMETER_8G_TOLERANCE) +#define MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD 4 struct MMA8653Sample { @@ -60,43 +101,76 @@ struct MMA8653SampleRangeConfig uint8_t xyz_data_cfg; }; + extern const MMA8653SampleRangeConfig MMA8653SampleRange[]; extern const MMA8653SampleRateConfig MMA8653SampleRate[]; +enum BasicGesture +{ + GESTURE_NONE, + GESTURE_UP, + GESTURE_DOWN, + GESTURE_LEFT, + GESTURE_RIGHT, + GESTURE_FACE_UP, + GESTURE_FACE_DOWN, + GESTURE_FREEFALL, + GESTURE_3G, + GESTURE_6G, + GESTURE_8G, + GESTURE_SHAKE +}; + +struct ShakeHistory +{ + uint16_t shaken:1, + x:1, + y:1, + z:1, + count:4, + timer:8; +}; + /** - * 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 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. + * Unique, enumerated ID for this component. * Used to track asynchronous events in the event bus. */ - - uint16_t address; // I2C address of this accelerometer. - uint16_t samplePeriod; // The time between samples, in milliseconds. - uint8_t sampleRange; // The sample range of the accelerometer in g. - MMA8653Sample sample; // The last sample read. - DigitalIn int1; // Data ready interrupt. - + + uint16_t address; // I2C address of this accelerometer. + uint16_t samplePeriod; // The time between samples, in milliseconds. + uint8_t sampleRange; // The sample range of the accelerometer in g. + MMA8653Sample sample; // The last sample read. + DigitalIn int1; // Data ready interrupt. + float pitch; // Pitch of the device, in radians. + float roll; // Roll of the device, in radians. + uint8_t sigma; // the number of ticks that the instantaneous gesture has been stable. + BasicGesture lastGesture; // the last, stable gesture recorded. + BasicGesture currentGesture; // the instantaneous, unfiltered gesture detected. + ShakeHistory shake; // State information needed to detect shake events. + 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 - */ + * 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); - + /** * Configures the accelerometer for G range and sample rate defined * in this object. The nearest values are chosen to those defined @@ -108,26 +182,26 @@ class MicroBitAccelerometer : public MicroBitComponent int configure(); /** - * 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. - * - * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the read request fails. - */ + * 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. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the read request fails. + */ int update(); - + /** - * Attempts to set the sample rate of the accelerometer to the specified value (in ms). - * n.b. the requested rate may not be possible on the hardware. In this case, the - * nearest lower rate is chosen. - * @param period the requested time between samples, in milliseconds. - * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the request fails. - */ + * Attempts to set the sample rate of the accelerometer to the specified value (in ms). + * n.b. the requested rate may not be possible on the hardware. In this case, the + * nearest lower rate is chosen. + * @param period the requested time between samples, in milliseconds. + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the request fails. + */ int setPeriod(int period); /** - * Reads the currently configured sample rate of the accelerometer. - * @return The time between samples, in milliseconds. - */ + * Reads the currently configured sample rate of the accelerometer. + * @return The time between samples, in milliseconds. + */ int getPeriod(); /** @@ -140,90 +214,157 @@ class MicroBitAccelerometer : public MicroBitComponent int setRange(int range); /** - * Reads the currently configured sample range of the accelerometer. + * Reads the currently configured sample range of the accelerometer. * @return The sample range, in g. */ int getRange(); /** - * Attempts to determine the 8 bit ID from the accelerometer. - * @return the 8 bit ID returned by the accelerometer, or MICROBIT_I2C_ERROR if the request fails. - * - * Example: - * @code - * uBit.accelerometer.whoAmI(); - * @endcode - */ + * Attempts to determine the 8 bit ID from the accelerometer. + * @return the 8 bit ID returned by the accelerometer, or MICROBIT_I2C_ERROR if the request fails. + * + * Example: + * @code + * uBit.accelerometer.whoAmI(); + * @endcode + */ int whoAmI(); /** * Reads the X axis value of the latest update from the accelerometer. - * Currently limited to +/- 2g + * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the X axis, in milli-g. * * Example: - * @code + * @code * uBit.accelerometer.getX(); * @endcode */ - int getX(); - + int getX(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + /** * 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 + * @code * uBit.accelerometer.getY(); * @endcode - */ - int getY(); - + */ + int getY(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + /** * 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 + * @code * uBit.accelerometer.getZ(); * @endcode - */ - int getZ(); + */ + int getZ(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); /** - * 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. + * Provides a rotation compensated pitch of the device, based on the latest update from the accelerometer. + * @return The pitch of the device, in degrees. + * + * Example: + * @code + * uBit.accelerometer.getPitch(); + * @endcode */ + int getPitch(); + float getPitchRadians(); + + /** + * Provides a rotation compensated roll of the device, based on the latest update from the accelerometer. + * @return The roll of the device, in degrees. + * + * Example: + * @code + * uBit.accelerometer.getRoll(); + * @endcode + */ + int getRoll(); + float getRollRadians(); + + /** + * Reads the last recorded gesture detected. + * @return The last gesture detected. + * + * Example: + * @code + * if (uBit.accelerometer.getGesture() == SHAKE) + * @endcode + */ + BasicGesture getGesture(); + + /** + * 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(); - + + /** + * Destructor for MicroBitButton, so that we deregister ourselves as an idleComponent + */ + ~MicroBitAccelerometer(); + 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. - * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the the write request failed. - */ + * 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. + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the the write request failed. + */ int 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. - * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER or MICROBIT_I2C_ERROR if the the read request failed. - */ + * 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. + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER or MICROBIT_I2C_ERROR if the the read request failed. + */ int readCommand(uint8_t reg, uint8_t* buffer, int length); + + /** + * Recalculate roll and pitch values for the current sample. + * We only do this at most once per sample, as the necessary trigonemteric functions are rather + * heavyweight for a CPU without a floating point unit... + */ + void recalculatePitchRoll(); + + /** + * + * Updates the basic gesture recognizer. This performs instantaneous pose recognition, and also some low pass filtering to promote + * stability. + */ + void updateGesture(); + + /** + * Service function. Calculates the current scalar acceleration of the device (x^2 + y^2 + z^2). + * It does not, however, square root the result, as this is a relatively high cost operation. + * This is left to application code should it be needed. + * @return the sum of the square of the acceleration of the device across all axes. + */ + int instantaneousAccelerationSquared(); + + /** + * Service function. Determines the best guess posture of the device based on instantaneous data. + * This makes no use of historic data, and forms this input to th filter implemented in updateGesture(). + * @return A best guess of the curret posture of the device, based on instanataneous data. + */ + BasicGesture instantaneousPosture(); }; #endif diff --git a/inc/MicroBitBLEManager.h b/inc/MicroBitBLEManager.h new file mode 100644 index 0000000..0c53e22 --- /dev/null +++ b/inc/MicroBitBLEManager.h @@ -0,0 +1,116 @@ +#ifndef MICROBIT_BLE_MANAGER_H +#define MICROBIT_BLE_MANAGER_H + +#include "mbed.h" + +/* + * The underlying Nordic libraries that support BLE do not compile cleanly with the stringent GCC settings we employ + * If we're compiling under GCC, then we suppress any warnings generated from this code (but not the rest of the DAL) + * The ARM cc compiler is more tolerant. We don't test __GNUC__ here to detect GCC as ARMCC also typically sets this + * as a compatability option, but does not support the options used... + */ +#if !defined (__arm) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif +#include "ble/BLE.h" + +/* + * Return to our predefined compiler settings. + */ +#if !defined (__arm) +#pragma GCC diagnostic pop +#endif + +#include "ble/services/DeviceInformationService.h" +#include "MicroBitDFUService.h" +#include "MicroBitEventService.h" +#include "MicroBitLEDService.h" +#include "MicroBitAccelerometerService.h" +#include "MicroBitMagnetometerService.h" +#include "MicroBitButtonService.h" +#include "MicroBitIOPinService.h" +#include "MicroBitTemperatureService.h" +#include "ExternalEvents.h" + +#define MICROBIT_BLE_PAIR_REQUEST 0x01 +#define MICROBIT_BLE_PAIR_COMPLETE 0x02 +#define MICROBIT_BLE_PAIR_PASSCODE 0x04 +#define MICROBIT_BLE_PAIR_SUCCESSFUL 0x08 + +/** + * Class definition for the MicroBitBLEManager. + * + */ +class MicroBitBLEManager +{ + public: + + // The mbed abstraction of the BlueTooth Low Energy (BLE) hardware + BLEDevice *ble; + + /** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * Note that the BLE stack *cannot* be brought up in a static context. + * (the software simply hangs or corrupts itself). + * Hence, we bring it up in an explicit init() method, rather than in the constructor. + */ + MicroBitBLEManager(); + + /** + * 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(ManagedString deviceName, ManagedString serialNumber); + + /** + * Enter pairing mode. This is mode is called to initiate pairing, and to enable FOTA programming + * of the micro:bit in cases where BLE is disabled during normal operation. + * + * @param display a MicroBitDisplay to use when displaying pairing information. + */ + void pairingMode(MicroBitDisplay &display); + + /** + * Method that is called whenever a BLE device disconnects from us. + * The nordic stack stops dvertising whenever a device connects, so we use + * this callback to restart advertising. + */ + void onDisconnectionCallback(); + + /** + * Displays the device's ID code as a histogram on the LED matrix display. + */ + void showNameHistogram(MicroBitDisplay &display); + + /** + * A request to pair has been received from a BLE device. + * If we're in pairing mode, display the passkey to the user. + */ + void pairingRequested(ManagedString passKey); + + /** + * A pairing request has been sucesfully completed. + * If we're in pairing mode, display feedback to the user. + */ + void pairingComplete(bool success); + + private: + int pairingStatus; + ManagedString passKey; + ManagedString deviceName; + +}; + + +#endif + diff --git a/inc/MicroBitButton.h b/inc/MicroBitButton.h index 59370d4..abc50df 100644 --- a/inc/MicroBitButton.h +++ b/inc/MicroBitButton.h @@ -17,8 +17,8 @@ #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_LONG_CLICK_TIME 1000 +#define MICROBIT_BUTTON_HOLD_TIME 1500 #define MICROBIT_BUTTON_STATE 1 #define MICROBIT_BUTTON_STATE_HOLD_TRIGGERED 2 @@ -47,27 +47,27 @@ 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. We use this for debouncing, and noise tolerance for touch sensing MicroBitButtonEventConfiguration eventConfiguration; // Do we want to generate high level event (clicks), or defer this to another service. - + public: /** - * Constructor. + * 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 + * @code * buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A); //a number between 0 and 200 inclusive * @endcode * * Possible Events: - * @code + * @code * MICROBIT_BUTTON_EVT_DOWN * MICROBIT_BUTTON_EVT_UP * MICROBIT_BUTTON_EVT_CLICK @@ -77,25 +77,29 @@ class MicroBitButton : public MicroBitComponent * @endcode */ MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventConfiguration eventConfiguration = MICROBIT_BUTTON_ALL_EVENTS, PinMode mode = PullNone); - + /** * Tests if this Button is currently pressed. * @return 1 if this button is pressed, 0 otherwise. * * Example: - * @code + * @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(); - + + /** + * Destructor for MicroBitButton, so that we deregister ourselves as a systemComponent + */ + ~MicroBitButton(); }; #endif diff --git a/inc/MicroBitCompass.h b/inc/MicroBitCompass.h index c12daee..37b96ce 100644 --- a/inc/MicroBitCompass.h +++ b/inc/MicroBitCompass.h @@ -3,6 +3,7 @@ #include "mbed.h" #include "MicroBitComponent.h" +#include "MicroBitCoordinateSystem.h" /** * Relevant pin assignments @@ -47,25 +48,29 @@ struct MAG3110SampleRateConfig extern const MAG3110SampleRateConfig MAG3110SampleRate[]; -#define MAG3110_SAMPLE_RATES 11 +#define MAG3110_SAMPLE_RATES 11 /* * Compass events */ -#define MICROBIT_COMPASS_EVT_CAL_REQUIRED 1 -#define MICROBIT_COMPASS_EVT_CAL_START 2 -#define MICROBIT_COMPASS_EVT_CAL_END 3 +#define MICROBIT_COMPASS_EVT_CAL_REQUIRED 1 // DEPRECATED +#define MICROBIT_COMPASS_EVT_CAL_START 2 // DEPRECATED +#define MICROBIT_COMPASS_EVT_CAL_END 3 // DEPRECATED + #define MICROBIT_COMPASS_EVT_DATA_UPDATE 4 #define MICROBIT_COMPASS_EVT_CONFIG_NEEDED 5 +#define MICROBIT_COMPASS_EVT_CALIBRATE 6 /* * Status Bits */ #define MICROBIT_COMPASS_STATUS_CALIBRATED 1 #define MICROBIT_COMPASS_STATUS_CALIBRATING 2 - - -#define MICROBIT_COMPASS_CALIBRATE_PERIOD 10000 + +/* + * Term to convert sample data into SI units + */ +#define MAG3110_NORMALIZE_SAMPLE(x) (100*x) /* * MAG3110 MAGIC ID value @@ -75,15 +80,22 @@ extern const MAG3110SampleRateConfig MAG3110SampleRate[]; struct CompassSample { - int16_t x; - int16_t y; - int16_t z; - + int x; + int y; + int z; + CompassSample() { this->x = 0; this->y = 0; - this->z = 0; + this->z = 0; + } + + CompassSample(int x, int y, int z) + { + this->x = x; + this->y = y; + this->z = z; } }; @@ -96,24 +108,21 @@ struct CompassSample class MicroBitCompass : public MicroBitComponent { /** - * Unique, enumerated ID for this component. + * Unique, enumerated ID for this component. * Used to track asynchronous events in the event bus. */ - - uint16_t address; // I2C address of the magnetmometer. - uint16_t samplePeriod; // The time between samples, in millseconds. - unsigned long eventStartTime; // used to store the current system clock when async calibration has started - 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. + uint16_t address; // I2C address of the magnetmometer. + uint16_t samplePeriod; // The time between samples, in millseconds. + + CompassSample average; // Centre point of sample data. + CompassSample sample; // The latest sample data recorded. + DigitalIn int1; // Data ready interrupt. public: - + /** - * Constructor. + * 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 @@ -131,7 +140,7 @@ class MicroBitCompass : public MicroBitComponent * @endcode */ MicroBitCompass(uint16_t id, uint16_t address); - + /** * Configures the compass for the sample rate defined * in this object. The nearest values are chosen to those defined @@ -151,25 +160,29 @@ class MicroBitCompass : public MicroBitComponent int setPeriod(int period); /** - * Reads the currently configured sample rate of the compass. + * Reads the currently configured sample rate of the compass. * @return The time between samples, in milliseconds. */ int getPeriod(); /** - * 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 - */ + * Gets the current heading of the device, relative to magnetic north. + * If the compass is not calibrated, it will raise the MICROBIT_COMPASS_EVT_CALIBRATE event. + * Users wishing to implement their own calibration algorithms should listen for this event, + * using MESSAGE_BUS_LISTENER_IMMEDIATE model. This ensures that calibration is complete before + * the user program continues. + * + * @return the current heading, in degrees. Or MICROBIT_CALIBRATION_IN_PROGRESS if the compass is calibrating. + * + * Example: + * @code + * uBit.compass.heading(); + * @endcode + */ int heading(); - + /** - * Attempts to determine the 8 bit ID from the magnetometer. + * Attempts to determine the 8 bit ID from the magnetometer. * @return the id of the compass (magnetometer), or MICROBIT_I2C_ERROR if the magnetometer could not be updated. * * Example: @@ -180,94 +193,148 @@ class MicroBitCompass : public MicroBitComponent 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(); + * Reads the X axis value of the latest update from the compass. + * @return The magnetic force measured in the X axis, in nano teslas. + * + * Example: + * @code + * uBit.compass.getX(); + * @endcode + */ + int getX(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); /** - * Reads the currently die temperature of the compass. + * Reads the Y axis value of the latest update from the compass. + * @return The magnetic force measured in the Y axis, in nano teslas. + * + * Example: + * @code + * uBit.compass.getY(); + * @endcode + */ + int getY(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Reads the Z axis value of the latest update from the compass. + * @return The magnetic force measured in the Z axis, in nano teslas. + * + * Example: + * @code + * uBit.compass.getZ(); + * @endcode + */ + int getZ(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Determines the overall magnetic field strength based on the latest update from the compass. + * @return The magnetic force measured across all axes, in nano teslas. + * + * Example: + * @code + * uBit.compass.getFieldStrength(); + * @endcode + */ + int getFieldStrength(); + + /** + * Reads the current die temperature of the compass. * @return the temperature in degrees celsius, or MICROBIT_I2C_ERROR if the magnetometer could not be updated. */ int readTemperature(); + /** + * Perform a calibration of the compass. + * + * This method will be called automatically if a user attempts to read a compass value when + * the compass is uncalibrated. It can also be called at any time by the user. + * + * Any old calibration data is deleted. + * The method will only return once the compass has been calibrated. + * + * @return MICROBIT_OK, MICROBIT_I2C_ERROR if the magnetometer could not be accessed, + * or MICROBIT_CALIBRATION_REQUIRED if the calibration algorithm failed to complete succesfully. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ + int calibrate(); + /** * Perform the asynchronous calibration of the compass. * This will fire MICROBIT_COMPASS_EVT_CAL_START and MICROBIT_COMPASS_EVT_CAL_END when finished. * @return MICROBIT_OK, or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. - * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + * + * @note *** THIS FUNCITON IS NOW DEPRECATED AND WILL BE REMOVED IN THE NEXT MAJOR RELEASE *** + * @note *** PLEASE USE THE calibrate() FUNCTION INSTEAD *** */ - void calibrateAsync(); + 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 + * + * @note *** THIS FUNCITON IS NOW DEPRECATED AND WILL BE REMOVED IN THE NEXT MAJOR RELEASE *** + * @note *** PLEASE USE THE calibrate() FUNCTION INSTEAD *** */ - int calibrateStart(); + int 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(); + * + * @note *** THIS FUNCITON IS NOW DEPRECATED AND WILL BE REMOVED IN THE NEXT MAJOR RELEASE *** + */ + void calibrateEnd(); + + /** + * Configure the compass to use the given calibration data. + * Calibration data is comprised of the perceived zero offset of each axis of the compass. + * After calibration this should now take into account trimming errors in the magnetometer, + * and any "hard iron" offsets on the device. + * + * @param The x, y and z zero offsets to use as calibration data. + */ + void setCalibration(CompassSample calibration); + + /** + * Provides the calibration data currently in use by the compass. + * More specifically, the x, y and z zero offsets of the compass. + * + * @return The x, y and z xero offsets of the compass. + */ + CompassSample getCalibration(); /** * 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(); - + + /** + * Destructor for MicroBitCompass, so that we deregister ourselves as an idleComponent + */ + ~MicroBitCompass(); + private: - + /** * Issues a standard, 2 byte I2C command write to the magnetometer. * Blocks the calling thread until complete. @@ -277,7 +344,7 @@ class MicroBitCompass : public MicroBitComponent * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the magnetometer could not be accessed. */ int writeCommand(uint8_t reg, uint8_t value); - + /** * Issues a read command into the specified buffer. * Blocks the calling thread until complete. @@ -288,7 +355,7 @@ class MicroBitCompass : public MicroBitComponent * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. */ int 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. @@ -297,8 +364,8 @@ class MicroBitCompass : public MicroBitComponent * @return The register value, interpreted as a 16 but signed value, or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. */ int read16(uint8_t reg); - - + + /** * Issues a read of a given address, and returns the value. * Blocks the calling thread until complete. diff --git a/inc/MicroBitComponent.h b/inc/MicroBitComponent.h index 3278498..bbac2bd 100644 --- a/inc/MicroBitComponent.h +++ b/inc/MicroBitComponent.h @@ -16,22 +16,21 @@ #define MICROBIT_ID_ACCELEROMETER 4 #define MICROBIT_ID_COMPASS 5 #define MICROBIT_ID_DISPLAY 6 -#define MICROBIT_ID_THERMOMETER 7 //EDGE connector events -#define MICROBIT_IO_PINS 20 +#define MICROBIT_IO_PINS 20 -#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_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_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_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 @@ -42,19 +41,22 @@ #define MICROBIT_ID_IO_P20 25 //SDA #define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton +#define MICROBIT_ID_GESTURE 27 // Gesture events -#define MICROBIT_ID_NOTIFY 1023 // Notfication channel, for general purpose synchronisation -#define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation +#define MICROBIT_ID_THERMOMETER 28 + +#define MICROBIT_ID_NOTIFY 1023 // Notfication channel, for general purpose synchronisation +#define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation class MicroBitComponent { 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 */ @@ -63,13 +65,13 @@ class 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. + * called in interrupt context on every system tick. */ virtual void systemTick(){ - + } /** @@ -78,9 +80,9 @@ class MicroBitComponent */ virtual void idleTick() { - + } - + /** * When added to the idleThreadComponents array, this function will be called to determine * if and when data is ready. @@ -88,12 +90,12 @@ class MicroBitComponent */ virtual int isIdleCallbackNeeded() { - return 0; + return 0; } - + virtual ~MicroBitComponent() { - + } }; diff --git a/inc/MicroBitConfig.h b/inc/MicroBitConfig.h index aace015..68567dc 100644 --- a/inc/MicroBitConfig.h +++ b/inc/MicroBitConfig.h @@ -39,7 +39,7 @@ // The proportion of SRAM available on the mbed heap to reserve for the micro:bit heap. #ifndef MICROBIT_HEAP_SIZE -#define MICROBIT_HEAP_SIZE 0.95 +#define MICROBIT_HEAP_SIZE 0.9 #endif // if defined, reuse the 8K of SRAM reserved for SoftDevice (Nordic's memory resident BLE stack) as heap memory. @@ -123,7 +123,7 @@ // by enabling/disabling the options below. // // n.b. The minimum set of services to enable over the air programming of the device will -// still be brought up in 'BLUEZONE' mode regardless of the settings below. +// still be brought up in pairing mode regardless of the settings below. // // Enable/Disable BLE during normal operation. @@ -132,10 +132,16 @@ #define MICROBIT_BLE_ENABLED 1 #endif -// Enable/Disable BLUEZONE mode at power up. +// Enable/Disable BLE pairing mode mode at power up. // Set '1' to enable. -#ifndef MICROBIT_BLE_BLUEZONE -#define MICROBIT_BLE_BLUEZONE 1 +#ifndef MICROBIT_BLE_PAIRING_MODE +#define MICROBIT_BLE_PAIRING_MODE 1 +#endif + +// Enable/Disable the use of private resolvable addresses. +// Set '1' to enable. +#ifndef MICROBIT_BLE_PRIVATE_ADDRESSES +#define MICROBIT_BLE_PRIVATE_ADDRESSES 0 #endif // Enable/Disable BLE Service: MicroBitDFU @@ -247,7 +253,7 @@ // Selects the default brightness for the display // in the region of zero (off) to 255 (full brightness) #ifndef MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS -#define MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS ((MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS - MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) / 2) +#define MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS #endif // Selects the default scroll speed for the display. diff --git a/inc/MicroBitCoordinateSystem.h b/inc/MicroBitCoordinateSystem.h new file mode 100644 index 0000000..f615812 --- /dev/null +++ b/inc/MicroBitCoordinateSystem.h @@ -0,0 +1,41 @@ +#ifndef MICROBIT_COORDINATE_SYSTEM_H +#define MICROBIT_COORDINATE_SYSTEM_H + +/** + * Co-ordinate systems that can be used. + * RAW: Unaltered data. Data will be returned directly from the accelerometer. + * + * SIMPLE_CARTESIAN: Data will be returned based on an easy to understand alignment, consistent with the cartesian system taught in schools. + * When held upright, facing the user: + * + * / + * +--------------------+ z + * | | + * | ..... | + * | * ..... * | + * ^ | ..... | + * | | | + * y +--------------------+ x--> + * + * + * NORTH_EAST_DOWN: Data will be returned based on the industry convention of the North East Down (NED) system. + * When held upright, facing the user: + * + * z + * +--------------------+ / + * | | + * | ..... | + * | * ..... * | + * ^ | ..... | + * | | | + * x +--------------------+ y--> + * + */ +enum MicroBitCoordinateSystem +{ + RAW, + SIMPLE_CARTESIAN, + NORTH_EAST_DOWN +}; + +#endif diff --git a/inc/MicroBitDisplay.h b/inc/MicroBitDisplay.h index 86e6dd3..539569b 100644 --- a/inc/MicroBitDisplay.h +++ b/inc/MicroBitDisplay.h @@ -73,7 +73,7 @@ enum AnimationMode { enum DisplayMode { DISPLAY_MODE_BLACK_AND_WHITE, - DISPLAY_MODE_GREYSCALE + DISPLAY_MODE_GREYSCALE }; enum DisplayRotation { @@ -172,24 +172,24 @@ class MicroBitDisplay : public MicroBitComponent * 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. @@ -224,7 +224,7 @@ class MicroBitDisplay : public MicroBitComponent /** * Blocks the current fiber until the display is available (i.e. not effect is being displayed). * Animations are queued until their time to display. - */ + */ void waitForFreeDisplay(); public: @@ -255,16 +255,16 @@ public: * Frame update method, invoked periodically to strobe the display. */ virtual void systemTick(); - + /** * Prints the given character to the display, if it is not in use. * * @param c The character to display. * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever. * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. - * + * * Example: - * @code + * @code * uBit.display.printAsync('p'); * uBit.display.printAsync('p',100); * @endcode @@ -294,7 +294,7 @@ public: * @param i The image to display. * @param x The horizontal position on the screen to display the image (default 0) * @param y The vertical position on the screen to display the image (default 0) - * @param alpha Treats the brightness level '0' as transparent (default 0) + * @param alpha Treats the brightness level '0' as transparent (default 0) * @param delay The time to delay between characters, in milliseconds. set to 0 to display forever. (default 0). * * Example: @@ -334,7 +334,7 @@ public: * @endcode */ int 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. @@ -350,7 +350,7 @@ public: * @endcode */ int print(MicroBitImage i, int x, int y, int alpha, int delay = 0); - + /** * Scrolls the given string to the display, from right to left. * Uses the given delay between characters. @@ -474,14 +474,14 @@ public: /** * 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 + * @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. @@ -557,12 +557,16 @@ public: * Retreives the font object used for rendering characters on the display. */ MicroBitFont getFont(); - + /** * Captures the bitmap currently being rendered on the display. */ MicroBitImage screenShot(); + + /** + * Destructor for MicroBitDisplay, so that we deregister ourselves as a systemComponent + */ + ~MicroBitDisplay(); }; #endif - diff --git a/inc/MicroBitHeapAllocator.h b/inc/MicroBitHeapAllocator.h index fef6ffb..c8934c5 100644 --- a/inc/MicroBitHeapAllocator.h +++ b/inc/MicroBitHeapAllocator.h @@ -91,6 +91,14 @@ inline void* operator new(size_t size) throw(std::bad_alloc) return microbit_malloc(size); } +/** + * Overrides the 'new' operator globally, and redirects calls to the micro:bit theap allocator. + */ +inline void* operator new[](size_t size) throw(std::bad_alloc) +{ + return microbit_malloc(size); +} + /** * Overrides the 'delete' operator globally, and redirects calls to the micro:bit theap allocator. */ diff --git a/inc/MicroBitMessageBus.h b/inc/MicroBitMessageBus.h index abd38f7..8cf63c3 100644 --- a/inc/MicroBitMessageBus.h +++ b/inc/MicroBitMessageBus.h @@ -18,7 +18,7 @@ * 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 + * 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 @@ -34,23 +34,23 @@ class MicroBitMessageBus : public MicroBitComponent public: /** - * Default constructor. + * Default constructor. * Anticipating only one MessageBus per device, as filtering is handled within the class. */ - MicroBitMessageBus(); + MicroBitMessageBus(); /** * Queues the given event to be sent to all registered recipients. * - * @param The event to send. + * @param The event to send. * * n.b. THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE... * * Example: - * @code + * @code * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,false); * evt.fire(); - * //OR YOU CAN DO THIS... + * //OR YOU CAN DO THIS... * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN); * @endcode */ @@ -63,7 +63,7 @@ class MicroBitMessageBus : public MicroBitComponent * IT IS RECOMMENDED THAT ALL EXTERNAL CODE USE THE send() FUNCTIONS INSTEAD OF THIS FUNCTION, * or the constructors provided by MicroBitEvent. * - * @param evt The event to send. + * @param evt The event to send. * @param urgent The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed * otherwise, all other (standard) listeners will be processed. * @return 1 if all matching listeners were processed, 0 if further processing is required. @@ -72,19 +72,19 @@ class MicroBitMessageBus : public MicroBitComponent /** * Register a listener function. - * - * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * + * @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. + * @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 handler The function to call when an event is received. * * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER - * + * * Example: - * @code + * @code * void onButtonBClick() * { * //do something @@ -93,22 +93,22 @@ class MicroBitMessageBus : public MicroBitComponent * @endcode */ int listen(int id, int value, void (*handler)(MicroBitEvent), uint16_t flags = MESSAGE_BUS_LISTENER_DEFAULT_FLAGS); - + /** * Register a listener function. - * - * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * + * @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. + * @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. * * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER - * + * * Example: - * @code + * @code * void onButtonBClick(void *arg) * { * //do something @@ -120,26 +120,26 @@ class MicroBitMessageBus : public MicroBitComponent /** * Register a listener function. - * - * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * + * @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. + * @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. * * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER - * + * * Example: - * @code + * @code * void SomeClass::onButtonBClick() * { * //do something * } * * SomeClass s = new SomeClass(); - * uBit.MessageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * uBit.MessageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); * @endcode */ template @@ -149,7 +149,7 @@ class MicroBitMessageBus : public MicroBitComponent /** * Unregister a listener function. * Listners are identified by the Event ID, Event VALUE and handler registered using listen(). - * + * * @param id The Event ID used to register the listener. * @param value The Event VALUE used to register the listener. * @param handler The function used to register the listener. @@ -158,21 +158,21 @@ class MicroBitMessageBus : public MicroBitComponent * * * Example: - * @code + * @code * void onButtonBClick() * { * //do something * } * - * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); * @endcode */ int ignore(int id, int value, void (*handler)(MicroBitEvent)); - + /** * Unregister a listener function. * Listners are identified by the Event ID, Event VALUE and handler registered using listen(). - * + * * @param id The Event ID used to register the listener. * @param value The Event VALUE used to register the listener. * @param handler The function used to register the listener. @@ -180,13 +180,13 @@ class MicroBitMessageBus : public MicroBitComponent * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER * * Example: - * @code + * @code * void onButtonBClick(void *arg) * { * //do something * } * - * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); * @endcode */ int ignore(int id, int value, void (*handler)(MicroBitEvent, void*)); @@ -194,7 +194,7 @@ class MicroBitMessageBus : public MicroBitComponent /** * Unregister a listener function. * Listners are identified by the Event ID, Event VALUE and handler registered using listen(). - * + * * @param id The Event ID used to register the listener. * @param value The Event VALUE used to register the listener. * @param handler The function used to register the listener. @@ -202,15 +202,15 @@ class MicroBitMessageBus : public MicroBitComponent * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER * * Example: - * @code - * + * @code + * * void SomeClass::onButtonBClick() * { * //do something * } * * SomeClass s = new SomeClass(); - * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); * @endcode */ template @@ -223,6 +223,11 @@ class MicroBitMessageBus : public MicroBitComponent */ MicroBitListener *elementAt(int n); + /** + * Destructor for MicroBitMessageBus, so that we deregister ourselves as an idleComponent + */ + ~MicroBitMessageBus(); + private: /** @@ -250,7 +255,7 @@ class MicroBitMessageBus : public MicroBitComponent MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed. uint16_t nonce_val; // The last nonce issued. uint16_t queueLength; // The number of events currently waiting to be processed. - + void queueEvent(MicroBitEvent &evt); MicroBitEventQueueItem* dequeueEvent(); @@ -262,10 +267,10 @@ class MicroBitMessageBus : public MicroBitComponent * A registration function to allow C++ member funcitons (methods) to be registered as an event * listener. * - * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * @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. + * @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 object The object on which the method should be invoked. @@ -291,7 +296,7 @@ int MicroBitMessageBus::listen(uint16_t id, uint16_t value, T* object, void (T:: /** * Unregister a listener function. * Listners are identified by the Event ID, Event VALUE and handler registered using listen(). - * + * * @param id The Event ID used to register the listener. * @param value The Event VALUE used to register the listener. * @param handler The function used to register the listener. @@ -299,13 +304,13 @@ int MicroBitMessageBus::listen(uint16_t id, uint16_t value, T* object, void (T:: * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER * * Example: - * @code + * @code * void onButtonBClick(void *arg) * { * //do something * } * - * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); * @endcode */ template @@ -322,5 +327,3 @@ int MicroBitMessageBus::ignore(uint16_t id, uint16_t value, T* object, void (T:: #endif - - diff --git a/inc/MicroBitPin.h b/inc/MicroBitPin.h index 5fa0439..133e6b1 100644 --- a/inc/MicroBitPin.h +++ b/inc/MicroBitPin.h @@ -3,40 +3,44 @@ #include "mbed.h" #include "MicroBitComponent.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 + // 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 +#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_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_12 //COL9 +#define MICROBIT_PIN_P7 P0_11 //COL8 +#define MICROBIT_PIN_P8 P0_18 //PIN 18 +#define MICROBIT_PIN_P9 P0_10 //COL7 +#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 +#define MICROBIT_PIN_MAX_OUTPUT 1023 + +#define MICROBIT_PIN_MAX_SERVO_RANGE 180 +#define MICROBIT_PIN_DEFAULT_SERVO_RANGE 1000 +#define MICROBIT_PIN_DEFAULT_SERVO_CENTER MICROBIT_PIN_DEFAULT_SERVO_RANGE + MICROBIT_PIN_DEFAULT_SERVO_RANGE/2 /** - * Pin capabilities enum. + * 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{ @@ -45,7 +49,7 @@ enum PinCapability{ 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 - + }; /** @@ -56,56 +60,62 @@ enum PinCapability{ class MicroBitPin : public MicroBitComponent { /** - * Unique, enumerated ID for this component. + * 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!). PinCapability capability; - + /** * Disconnect any attached mBed IO from this pin. * Used only when pin changes mode (i.e. Input/Output/Analog/Digital) */ void disconnect(); - + + /** + * Performs a check to ensure that the current Pin is in control of a + * DynamicPwm instance, and if it's not, allocates a new DynamicPwm instance. + */ + int obtainAnalogChannel(); + public: PinName name; // mBed pin name of this pin. - + /** - * Constructor. + * 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. - * + * * Example: - * @code + * @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) * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED * if the given pin does not have digital capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); * P0.setDigitalValue(1); // P0 is now HI * @endcode */ int 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 if input is LO, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); * P0.getDigitalValue(); // P0 is either 0 or 1; * @endcode @@ -120,13 +130,28 @@ class MicroBitPin : public MicroBitComponent */ int setAnalogValue(int value); + /** + * Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms, + * and sets the duty cycle between 0.05 and 0.1 (i.e. 5% or 10%) based on the value given to this method. + * + * A value of 180 sets the duty cycle to be 10%, and a value of 0 sets the duty cycle to be 5% by default. + * + * This range can be modified to fine tune, and also tolerate different servos. + * + * @param value the level to set on the output pin, in the range 0 - 180 + * @param range which gives the span of possible values the i.e. lower and upper bounds center ± range/2 (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_RANGE) + * @param center the center point from which to calculate the lower and upper bounds (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_CENTER) + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED + * if the given pin does not have analog capability. + */ + int setServoValue(int value, int range = MICROBIT_PIN_DEFAULT_SERVO_RANGE, int center = MICROBIT_PIN_DEFAULT_SERVO_CENTER); /** * Configures this IO pin as an analogue input (if necessary and possible). * @return the current analogue level on the pin, in the range 0 - 1024, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); * P0.getAnalogValue(); // P0 is a value in the range of 0 - 1024 * @endcode @@ -160,35 +185,61 @@ class MicroBitPin : public MicroBitComponent /** * 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 if not, or MICROBIT_NOT_SUPPORTED if this pin does not support touch capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); * if(P0.isTouched()) * { * uBit.display.clear(); - * } + * } * @endcode */ int isTouched(); + /** + * Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms, + * and sets the pulse width, based on the value it is given + * + * @param pulseWidth the desired pulse width in microseconds. + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED + * if the given pin does not have analog capability. + */ + int setServoPulseUs(int pulseWidth); + /** * Configures the PWM period of the analog output to the given value. * * @param period The new period for the analog output in milliseconds. - * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the * given pin is not configured as an analog output. - */ + */ int setAnalogPeriod(int period); /** * Configures the PWM period of the analog output to the given value. * * @param period The new period for the analog output in microseconds. - * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the * given pin is not configured as an analog output. - */ + */ int setAnalogPeriodUs(int period); + + /** + * Retrieves the PWM period of the analog output. + * + * @return the period on success, or MICROBIT_NOT_SUPPORTED if the + * given pin is not configured as an analog output. + */ + int getAnalogPeriodUs(); + + /** + * Retrieves the PWM period of the analog output. + * + * @return the period on success, or MICROBIT_NOT_SUPPORTED if the + * given pin is not configured as an analog output. + */ + int getAnalogPeriod(); }; #endif diff --git a/module.json b/module.json index 790b9aa..f2c5d88 100644 --- a/module.json +++ b/module.json @@ -1,6 +1,6 @@ { "name": "microbit-dal", - "version": "1.3.8", + "version": "1.4.5", "license": "Apache2", "description": "The runtime library for the BBC micro:bit, developed by Lancaster University", "keywords": [ @@ -15,10 +15,10 @@ "homepage": "https://developer.mbed.org/teams/Lancaster-University/code/microbit/", "dependencies": { "mbed-classic": "~0.0.4", - "ble": "lancaster-university/BLE_API#master", - "ble-nrf51822": "lancaster-university/nrf51822#master" + "ble": "lancaster-university/BLE_API#v2.1.11", + "ble-nrf51822": "lancaster-university/nrf51822#v2.2.5" }, "extraIncludes": [ "inc" ] -} +} \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d77a320..615e6fd 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -17,6 +17,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "MicroBitEvent.cpp" "MicroBitFiber.cpp" "ManagedString.cpp" + "Matrix4.cpp" "MicroBitAccelerometer.cpp" "MicroBitThermometer.cpp" "MicroBitIO.cpp" @@ -30,6 +31,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "MicroBitListener.cpp" "RefCounted.cpp" "MemberFunctionCallback.cpp" + "ble-services/MicroBitBLEManager.cpp" "ble-services/MicroBitDFUService.cpp" "ble-services/MicroBitEventService.cpp" "ble-services/MicroBitLEDService.cpp" @@ -83,8 +85,9 @@ target_link_libraries(microbit-dal ) if(CMAKE_COMPILER_IS_GNUCC) - message("suppressing warnings from mbed-classic, ble & ble-nrf51822") + message("suppressing ALL warnings from mbed-classic, ble, ble-nrf51822 & nrf51-sdk") target_compile_options(mbed-classic PRIVATE "-w") target_compile_options(ble PRIVATE "-w") target_compile_options(ble-nrf51822 PRIVATE "-w") + target_compile_options(nrf51-sdk PRIVATE "-w") endif() diff --git a/source/DynamicPwm.cpp b/source/DynamicPwm.cpp index 8f82258..f57c21a 100644 --- a/source/DynamicPwm.cpp +++ b/source/DynamicPwm.cpp @@ -4,6 +4,8 @@ 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. +uint16_t DynamicPwm::sharedPeriod = 0; //set the shared period to an unknown state + /** * Reassigns an already operational PWM channel to the given pin * #HACK #BODGE # YUCK #MBED_SHOULD_DO_THIS @@ -11,9 +13,9 @@ uint8_t DynamicPwm::lastUsed = NO_PWMS+1; //set it to out of range i.e. 4 so we * @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) @@ -37,21 +39,20 @@ void gpiote_reinit(PinName pin, PinName oldPin, uint8_t channel_number) __NOP(); __NOP(); __NOP(); - - NRF_TIMER2->CC[channel_number] = 0; + + 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.) + * @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) +DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence) : PwmOut(pin) { this->flags = persistence; - this->setPeriodUs(period); } /** @@ -65,24 +66,23 @@ DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence, int period) : Pw * @endcode */ void DynamicPwm::redirect(PinName pin) -{ - gpiote_reinit(pin, _pwm.pin, (uint8_t)_pwm.pwm); +{ + 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.) + * @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) +DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence) { //try to find a blank spot first for(int i = 0; i < NO_PWMS; i++) @@ -90,31 +90,35 @@ DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence, int pe if(pwms[i] == NULL) { lastUsed = i; - pwms[i] = new DynamicPwm(pin, persistence, period); + pwms[i] = new DynamicPwm(pin, persistence); return pwms[i]; - } + } } - + //no blank spot.. try to find a transient PWM - for(int i = 0; i < NO_PWMS; i++) + int channelIterator = (lastUsed + 1 > NO_PWMS - 1) ? 0 : lastUsed + 1; + + while(channelIterator != lastUsed) { - if(pwms[i]->flags & PWM_PERSISTENCE_TRANSIENT && i != lastUsed) + if(pwms[channelIterator]->flags & PWM_PERSISTENCE_TRANSIENT) { - lastUsed = i; - pwms[i]->flags = persistence; - pwms[i]->redirect(pin); - return pwms[i]; - } + lastUsed = channelIterator; + pwms[channelIterator]->flags = persistence; + pwms[channelIterator]->redirect(pin); + return pwms[channelIterator]; + } + + channelIterator = (channelIterator + 1 > NO_PWMS - 1) ? 0 : channelIterator + 1; } - + //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; } @@ -132,9 +136,9 @@ void DynamicPwm::release() { //free the pwm instance. NRF_GPIOTE->CONFIG[(uint8_t) _pwm.pwm] = 0; - pwmout_free(&_pwm); + 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) @@ -144,6 +148,30 @@ void DynamicPwm::release() } } +/** + * A lightweight wrapper around the super class' write in order to capture the value + * + * @param value the duty cycle percentage in floating point format. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->write(0.5); + * @endcode + */ +int DynamicPwm::write(float value){ + + if(value < 0) + return MICROBIT_INVALID_PARAMETER; + + PwmOut::write(value); + lastValue = value; + + return MICROBIT_OK; +} + /** * Retreives the pin name associated with this DynamicPwm instance. * @@ -155,21 +183,92 @@ void DynamicPwm::release() */ PinName DynamicPwm::getPinName() { - return _pwm.pin; + return _pwm.pin; +} + +/** + * Retreives the last value that has been written to this DynamicPwm instance. + * The value is in the range 0-1024 + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getValue(); + * @endcode + */ +int DynamicPwm::getValue() +{ + return (float)lastValue * float(MICROBIT_PIN_MAX_OUTPUT); +} + +/** + * Retreives the current period in use by the entire PWM module + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPeriod(); + * @endcode + */ +int DynamicPwm::getPeriodUs() +{ + return sharedPeriod; +} + +/** + * Retreives the current period in use by the entire PWM module + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->getPeriod(); + * @endcode + */ +int DynamicPwm::getPeriod() +{ + return getPeriodUs() / 1000; } /** * Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS. * + * @param period the desired period in microseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range + * * 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) +int DynamicPwm::setPeriodUs(int period) { + if(period < 0) + return MICROBIT_INVALID_PARAMETER; + + //#HACK this forces mbed to update the pulse width calculation. period_us(period); + write(lastValue); + sharedPeriod = period; + + return MICROBIT_OK; +} + +/** + * Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS. + * + * @param period the desired period in microseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->setPeriod(1); // period now is 1ms + * @endcode + */ +int DynamicPwm::setPeriod(int period) +{ + return setPeriodUs(period * 1000); } diff --git a/source/Matrix4.cpp b/source/Matrix4.cpp new file mode 100644 index 0000000..c1ef515 --- /dev/null +++ b/source/Matrix4.cpp @@ -0,0 +1,251 @@ +#include "MicroBit.h" + +/** +* Class definition for a simple matrix, optimised for n x 4 or 4 x n matrices. +* +* This class is heavily optimised for these commonly used matrices as used in 3D geometry, +* and is not intended as a general purpose matrix class. For programmers needing more flexible +* Matrix support, the mbed Matrix and MatrixMath classes from Ernsesto Palacios provide a good basis: +* +* https://developer.mbed.org/cookbook/MatrixClass +* https://developer.mbed.org/users/Yo_Robot/code/MatrixMath/ +*/ + +/** + * Constructor. + * Create a matrix of the given size. + * @param rows the number of rows in the matrix to be created. + * @param cols the number of columns in the matrix to be created. + * + * Example: + * @code + * Matrix4(10, 4); // Creates a Matrix with 10 rows and 4 columns. + * @endcode + */ +Matrix4::Matrix4(int rows, int cols) +{ + this->rows = rows; + this->cols = cols; + + int size = rows * cols; + + if (size > 0) + data = new double[size]; + else + data = NULL; +} + +/** +* Constructor. +* Create a matrix that is an identicval copy of the given matrix. +* @param matrix The matrix to copy. +* +* Example: +* @code +* +* Matrix newMatrix(matrix); . +* @endcode +*/ +Matrix4::Matrix4(const Matrix4 &matrix) +{ + this->rows = matrix.rows; + this->cols = matrix.cols; + + int size = rows * cols; + + if (size > 0) + { + data = new double[size]; + for (int i = 0; i < size; i++) + data[i] = matrix.data[i]; + } + else + { + data = NULL; + } + +} + +/** +* Determines the number of columns in this matrix. +* +* @return The number of columns in the matrix. +* +* Example: +* @code +* int c = matrix.width(); +* @endcode +*/ +int Matrix4::width() +{ + return cols; +} + +/** +* Determines the number of rows in this matrix. +* +* @return The number of rows in the matrix. +* +* Example: +* @code +* int r = matrix.height(); +* @endcode +*/ +int Matrix4::height() +{ + return rows; +} + +/** +* Reads the matrix element at the given position. +* +* @param row The row of the element to read +* @param col The column of the element to read +* @return The value of the matrix element at the given position. NAN is returned if the given index is out of range. +* +* Example: +* @code +* double v = matrix.get(1,2); +* @endcode +*/ +double Matrix4::get(int row, int col) +{ + if (row < 0 || col < 0 || row >= rows || col >= cols) + return 0; + + return data[width() * row + col]; +} + +/** +* Writes the matrix element at the given position. +* +* @param row The row of the element to write +* @param col The column of the element to write +* @param v The new value of the element +* +* Example: +* @code +* matrix.set(1,2,42.0); +* @endcode +*/ +void Matrix4::set(int row, int col, double v) +{ + if (row < 0 || col < 0 || row >= rows || col >= cols) + return; + + data[width() * row + col] = v; +} + +/** +* Transposes this matrix. +* @return the resultant matrix. +* +* Example: +* @code +* matrix.transpose(); +* @endcode +*/ +Matrix4 Matrix4::transpose() +{ + Matrix4 result = Matrix4(cols, rows); + + for (int i = 0; i < width(); i++) + for (int j = 0; j < height(); j++) + result.set(i, j, get(j, i)); + + return result; +} + +/** +* Multiplies this matrix with the given matrix (if possible). +* @return the resultant matrix. An empty matrix is returned if the operation canot be completed. +* +* Example: +* @code +* Matrix result = matrixA.multiply(matrixB); +* @endcode +*/ +Matrix4 Matrix4::multiply(Matrix4 &matrix) +{ + if (width() != matrix.height()) + return Matrix4(0, 0); + + Matrix4 result(height(), matrix.width()); + + for (int r = 0; r < result.height(); r++) + { + for (int c = 0; c < result.width(); c++) + { + double v = 0.0; + + for (int i = 0; i < width(); i++) + v += get(r, i) * matrix.get(i, c); + + result.set(r, c, v); + } + } + + return result; +} + +/** +* Performs an optimised inversion of a 4x4 matrix. +* Only 4x4 matrices are supported by this operation. +* +* @return the resultant matrix. An empty matrix is returned if the operation canot be completed. +* +* Example: +* @code +* Matrix result = matrixA.invert(); +* @endcode +*/ +Matrix4 Matrix4::invert() +{ + // We only support square matrices of size 4... + if (width() != height() || width() != 4) + return Matrix4(0, 0); + + Matrix4 result(width(), height()); + + result.data[0] = data[5] * data[10] * data[15] - data[5] * data[11] * data[14] - data[9] * data[6] * data[15] + data[9] * data[7] * data[14] + data[13] * data[6] * data[11] - data[13] * data[7] * data[10]; + result.data[1] = -data[1] * data[10] * data[15] + data[1] * data[11] * data[14] + data[9] * data[2] * data[15] - data[9] * data[3] * data[14] - data[13] * data[2] * data[11] + data[13] * data[3] * data[10]; + result.data[2] = data[1] * data[6] * data[15] - data[1] * data[7] * data[14] - data[5] * data[2] * data[15] + data[5] * data[3] * data[14] + data[13] * data[2] * data[7] - data[13] * data[3] * data[6]; + result.data[3] = -data[1] * data[6] * data[11] + data[1] * data[7] * data[10] + data[5] * data[2] * data[11] - data[5] * data[3] * data[10] - data[9] * data[2] * data[7] + data[9] * data[3] * data[6]; + result.data[4] = -data[4] * data[10] * data[15] + data[4] * data[11] * data[14] + data[8] * data[6] * data[15] - data[8] * data[7] * data[14] - data[12] * data[6] * data[11] + data[12] * data[7] * data[10]; + result.data[5] = data[0] * data[10] * data[15] - data[0] * data[11] * data[14] - data[8] * data[2] * data[15] + data[8] * data[3] * data[14] + data[12] * data[2] * data[11] - data[12] * data[3] * data[10]; + result.data[6] = -data[0] * data[6] * data[15] + data[0] * data[7] * data[14] + data[4] * data[2] * data[15] - data[4] * data[3] * data[14] - data[12] * data[2] * data[7] + data[12] * data[3] * data[6]; + result.data[7] = data[0] * data[6] * data[11] - data[0] * data[7] * data[10] - data[4] * data[2] * data[11] + data[4] * data[3] * data[10] + data[8] * data[2] * data[7] - data[8] * data[3] * data[6]; + result.data[8] = data[4] * data[9] * data[15] - data[4] * data[11] * data[13] - data[8] * data[5] * data[15] + data[8] * data[7] * data[13] + data[12] * data[5] * data[11] - data[12] * data[7] * data[9]; + result.data[9] = -data[0] * data[9] * data[15] + data[0] * data[11] * data[13] + data[8] * data[1] * data[15] - data[8] * data[3] * data[13] - data[12] * data[1] * data[11] + data[12] * data[3] * data[9]; + result.data[10] = data[0] * data[5] * data[15] - data[0] * data[7] * data[13] - data[4] * data[1] * data[15] + data[4] * data[3] * data[13] + data[12] * data[1] * data[7] - data[12] * data[3] * data[5]; + result.data[11] = -data[0] * data[5] * data[11] + data[0] * data[7] * data[9] + data[4] * data[1] * data[11] - data[4] * data[3] * data[9] - data[8] * data[1] * data[7] + data[8] * data[3] * data[5]; + result.data[12] = -data[4] * data[9] * data[14] + data[4] * data[10] * data[13] + data[8] * data[5] * data[14] - data[8] * data[6] * data[13] - data[12] * data[5] * data[10] + data[12] * data[6] * data[9]; + result.data[13] = data[0] * data[9] * data[14] - data[0] * data[10] * data[13] - data[8] * data[1] * data[14] + data[8] * data[2] * data[13] + data[12] * data[1] * data[10] - data[12] * data[2] * data[9]; + result.data[14] = -data[0] * data[5] * data[14] + data[0] * data[6] * data[13] + data[4] * data[1] * data[14] - data[4] * data[2] * data[13] - data[12] * data[1] * data[6] + data[12] * data[2] * data[5]; + result.data[15] = data[0] * data[5] * data[10] - data[0] * data[6] * data[9] - data[4] * data[1] * data[10] + data[4] * data[2] * data[9] + data[8] * data[1] * data[6] - data[8] * data[2] * data[5]; + + double det = data[0] * result.data[0] + data[1] * result.data[4] + data[2] * result.data[8] + data[3] * result.data[12]; + + if (det == 0) + return Matrix4(0, 0); + + det = 1.0f / det; + + for (int i = 0; i < 16; i++) + result.data[i] *= det; + + return result; +} + +/** +* Destructor. +*/ +Matrix4::~Matrix4() +{ + if (data != NULL) + { + delete data; + data = NULL; + } +} + diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp index 1d82965..a5682c1 100644 --- a/source/MicroBit.cpp +++ b/source/MicroBit.cpp @@ -1,61 +1,70 @@ #include "MicroBit.h" -char MICROBIT_BLE_DEVICE_NAME[] = "BBC micro:bit [xxxxx]"; - -#if CONFIG_ENABLED(MICROBIT_BLE_ENABLED) && CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE) -const char* MICROBIT_BLE_MANUFACTURER = "The Cast of W1A"; -const char* MICROBIT_BLE_MODEL = "BBC micro:bit"; -const char* MICROBIT_BLE_HARDWARE_VERSION = "1.0"; -const char* MICROBIT_BLE_FIRMWARE_VERSION = MICROBIT_DAL_VERSION; -const char* MICROBIT_BLE_SOFTWARE_VERSION = NULL; -#endif - /** * custom function for panic for malloc & new due to scoping issue. */ void panic(int statusCode) { - uBit.panic(statusCode); + uBit.panic(statusCode); } /** - * Perform a hard reset of the micro:bit. + * Callback that performs a hard reset when a BLE GAP disconnect occurs. + * Only used when an explicit reset is invoked locally whilst a BLE connection is in progress. + * This allows for a clean diconnect of the BLE connection before resetting. */ -void -microbit_reset() +void bleDisconnectionResetCallback(const Gap::DisconnectionCallbackParams_t *) { NVIC_SystemReset(); } - /** - * Callback when a BLE GATT disconnect occurs. + * Perform a hard reset of the micro:bit. + * If BLE connected, then try to signal a disconnect first */ -void bleDisconnectionCallback(Gap::Handle_t, Gap::DisconnectionReason_t) +void +microbit_reset() { - uBit.ble->startAdvertising(); + if(uBit.ble && uBit.ble->getGapState().connected) { + uBit.ble->onDisconnection(bleDisconnectionResetCallback); + + uBit.ble->gap().disconnect(Gap::REMOTE_USER_TERMINATED_CONNECTION); + // We should be reset by the disconnection callback, so we wait to + // allow that to happen. If it doesn't happen, then we fall through to the + // hard rest here. (For example there is a race condition where + // the remote device disconnects between us testing the connection + // state and re-setting the disconnection callback). + uBit.sleep(1000); + } + NVIC_SystemReset(); } +void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *) +{ + uBit.ble->startAdvertising(); +} + + /** - * Constructor. + * Constructor. * Create a representation of a MicroBit device as a global singleton. * @param messageBus callback function to receive MicroBitMessageBus events. * * Exposed objects: - * @code + * @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.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() : +MicroBit::MicroBit() : flags(0x00), i2c(MICROBIT_PIN_SDA, MICROBIT_PIN_SCL), serial(USBTX, USBRX), @@ -63,7 +72,7 @@ MicroBit::MicroBit() : display(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_WIDTH, MICROBIT_DISPLAY_HEIGHT), buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A, MICROBIT_BUTTON_SIMPLE_EVENTS), buttonB(MICROBIT_ID_BUTTON_B,MICROBIT_PIN_BUTTON_B, MICROBIT_BUTTON_SIMPLE_EVENTS), - buttonAB(MICROBIT_ID_BUTTON_AB,MICROBIT_ID_BUTTON_A,MICROBIT_ID_BUTTON_B), + buttonAB(MICROBIT_ID_BUTTON_AB,MICROBIT_ID_BUTTON_A,MICROBIT_ID_BUTTON_B), accelerometer(MICROBIT_ID_ACCELEROMETER, MMA8653_DEFAULT_ADDR), compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR), thermometer(MICROBIT_ID_THERMOMETER), @@ -73,29 +82,27 @@ MicroBit::MicroBit() : 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) -{ + MICROBIT_ID_IO_P20), + bleManager() +{ } /** * Post constructor initialisation method. - * After *MUCH* pain, it's noted that the BLE stack can't be brought up in a + * 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 + * @code * uBit.init(); * @endcode */ void MicroBit::init() -{ - // Set the default baud rate for the serial port.` - uBit.serial.baud(115200); - +{ //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); @@ -104,95 +111,175 @@ void MicroBit::init() // Seed our random number generator seedRandom(); - // Generate the name for our device. - this->deriveName(); - #if CONFIG_ENABLED(MICROBIT_BLE_ENABLED) - // Start the BLE stack. - ble = new BLEDevice(); - ble->init(); - ble->onDisconnection(bleDisconnectionCallback); + // Start the BLE stack. + bleManager.init(this->getName(), this->getSerial()); - // Bring up any configured auxiliary services. -#if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE) - ble_firmware_update_service = new MicroBitDFUService(*ble); + ble = bleManager.ble; #endif -#if CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE) - // Create a temporary, so that compiler doesn't delete the pointer before DeviceInformationService copies it - ManagedString tmp = getSerial(); - DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, tmp.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE) - new MicroBitEventService(*ble); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_LED_SERVICE) - new MicroBitLEDService(*ble); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_ACCELEROMETER_SERVICE) - new MicroBitAccelerometerService(*ble); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_MAGNETOMETER_SERVICE) - new MicroBitMagnetometerService(*ble); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_BUTTON_SERVICE) - new MicroBitButtonService(*ble); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_IO_PIN_SERVICE) - new MicroBitIOPinService(*ble); -#endif - -#if CONFIG_ENABLED(MICROBIT_BLE_TEMPERATURE_SERVICE) - new MicroBitTemperatureService(*ble); -#endif - - // Configure for high speed mode where possible. - Gap::ConnectionParams_t fast; - ble->getPreferredConnectionParams(&fast); - fast.minConnectionInterval = 8; // 10 ms - fast.maxConnectionInterval = 16; // 20 ms - fast.slaveLatency = 0; - ble->setPreferredConnectionParams(&fast); - - // 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(200)); - ble->startAdvertising(); -#endif - // Start refreshing the Matrix Display - systemTicker.attach(this, &MicroBit::systemTick, MICROBIT_DISPLAY_REFRESH_PERIOD); + systemTicker.attach(this, &MicroBit::systemTick, MICROBIT_DISPLAY_REFRESH_PERIOD); + + // Register our compass calibration algorithm. + MessageBus.listen(MICROBIT_ID_COMPASS, MICROBIT_COMPASS_EVT_CALIBRATE, this, &MicroBit::compassCalibrator, MESSAGE_BUS_LISTENER_IMMEDIATE); } /** - * Derives the friendly name for this device, autogenerated from our hardware Device ID. + * Performs a simple game that in parallel, calibrates the compass. + * This function is executed automatically when the user requests a compass bearing, and compass calibration is required. + * This function is, by design, synchronous and only returns once calibration is complete. */ -void MicroBit::deriveName() +void MicroBit::compassCalibrator(MicroBitEvent) { - const uint8_t codebook[MICROBIT_NAME_LENGTH][MICROBIT_NAME_CODE_LETTERS] = + struct Point { - {'z', 'v', 'g', 'p', 't'}, + uint8_t x; + uint8_t y; + uint8_t on; + }; + + const int PERIMETER_POINTS = 12; + const int PIXEL1_THRESHOLD = 200; + const int PIXEL2_THRESHOLD = 800; + + Matrix4 X(PERIMETER_POINTS, 4); + Point perimeter[PERIMETER_POINTS] = {{1,0,0}, {2,0,0}, {3,0,0}, {4,1,0}, {4,2,0}, {4,3,0}, {3,4,0}, {2,4,0}, {1,4,0}, {0,3,0}, {0,2,0}, {0,1,0}}; + Point cursor = {2,2,0}; + + MicroBitImage img(5,5); + MicroBitImage smiley("0,255,0,255,0\n0,255,0,255,0\n0,0,0,0,0\n255,0,0,0,255\n0,255,255,255,0\n"); + int samples = 0; + + // Firstly, we need to take over the display. Ensure all active animations are paused. + display.stopAnimation(); + display.scrollAsync("DRAW A CIRCLE"); + for (int i=0; i<110; i++) + { + if (buttonA.isPressed() || buttonB.isPressed()) + { + break; + } + sleep(100); + } + + display.stopAnimation(); + display.clear(); + + while(samples < PERIMETER_POINTS) + { + // update our model of the flash status of the user controlled pixel. + cursor.on = (cursor.on + 1) % 4; + + // take a snapshot of the current accelerometer data. + int x = uBit.accelerometer.getX(); + int y = uBit.accelerometer.getY(); + + // Deterine the position of the user controlled pixel on the screen. + if (x < -PIXEL2_THRESHOLD) + cursor.x = 0; + else if (x < -PIXEL1_THRESHOLD) + cursor.x = 1; + else if (x > PIXEL2_THRESHOLD) + cursor.x = 4; + else if (x > PIXEL1_THRESHOLD) + cursor.x = 3; + else + cursor.x = 2; + + if (y < -PIXEL2_THRESHOLD) + cursor.y = 0; + else if (y < -PIXEL1_THRESHOLD) + cursor.y = 1; + else if (y > PIXEL2_THRESHOLD) + cursor.y = 4; + else if (y > PIXEL1_THRESHOLD) + cursor.y = 3; + else + cursor.y = 2; + + img.clear(); + + // Turn on any pixels that have been visited. + for (int i=0; iDEVICEID[1]; - int ld = 1; int d = MICROBIT_NAME_CODE_LETTERS; int h; @@ -205,16 +292,8 @@ void MicroBit::deriveName() ld *= MICROBIT_NAME_CODE_LETTERS; *--name = codebook[i][h]; } -} -/** - * Return the friendly name for this device. - * - * @return A string representing the friendly name of this device. - */ -ManagedString MicroBit::getName() -{ - return ManagedString(MICROBIT_BLE_DEVICE_NAME+15, MICROBIT_NAME_LENGTH); + return ManagedString(nameBuffer, MICROBIT_NAME_LENGTH); } /** @@ -228,7 +307,7 @@ ManagedString MicroBit::getSerial() int n1 = NRF_FICR->DEVICEID[1] & 0xffff; int n2 = (NRF_FICR->DEVICEID[1] >> 16) & 0xffff; - // Simply concat the two numbers. + // Simply concat the two numbers. ManagedString s1 = ManagedString(n1); ManagedString s2 = ManagedString(n2); @@ -239,7 +318,7 @@ ManagedString MicroBit::getSerial() * Will reset the micro:bit when called. * * Example: - * @code + * @code * uBit.reset(); * @endcode */ @@ -253,15 +332,15 @@ void MicroBit::reset() * 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. + * will revert to a busy wait. * * @note Values of below below the scheduling period (typical 6ms) tend to lose resolution. - * + * * @param milliseconds the amount of time, in ms, to wait for. This number cannot be negative. - * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER milliseconds is less than zero. + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER milliseconds is less than zero. * * Example: - * @code + * @code * uBit.sleep(20); //sleep for 20ms * @endcode */ @@ -270,7 +349,7 @@ int MicroBit::sleep(int milliseconds) //sanity check, we can't time travel... (yet?) if(milliseconds < 0) return MICROBIT_INVALID_PARAMETER; - + if (flags & MICROBIT_FLAG_SCHEDULER_RUNNING) fiber_sleep(milliseconds); else @@ -287,30 +366,57 @@ int MicroBit::sleep(int milliseconds) * 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... + * 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 + * @code * uBit.random(200); //a number between 0 and 199 * @endcode */ int MicroBit::random(int max) { + uint32_t m, result; + //return MICROBIT_INVALID_VALUE if max is <= 0... if(max <= 0) return MICROBIT_INVALID_PARAMETER; - - // 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; + + // Our maximum return value is actually one less than passed + max--; + + do { + m = (uint32_t)max; + result = 0; + do { + // Cycle the LFSR (Linear Feedback Shift Register). + // We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneier 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" + // https://www.schneier.com/paper-pseudorandom-sequence.html + uint32_t rnd = randomValue; + + rnd = ((((rnd >> 31) + ^ (rnd >> 6) + ^ (rnd >> 4) + ^ (rnd >> 2) + ^ (rnd >> 1) + ^ rnd) + & 0x0000001) + << 31 ) + | (rnd >> 1); + + randomValue = rnd; + + result = ((result << 1) | (rnd & 0x00000001)); + } while(m >>= 1); + } while (result > (uint32_t)max); + + + return result; } @@ -324,21 +430,21 @@ int MicroBit::random(int max) 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; } @@ -348,11 +454,11 @@ void MicroBit::seedRandom() * 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(); - + 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()) @@ -360,7 +466,7 @@ void MicroBit::systemTick() fiber_flags |= MICROBIT_FLAG_DATA_READY; break; } - + //update any components in the systemComponents array for(int i = 0; i < MICROBIT_SYSTEM_COMPONENTS; i++) if(systemTickComponents[i] != NULL) @@ -371,17 +477,17 @@ void MicroBit::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 +{ + //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_READY; } /** - * add a component to the array of components which invocate the systemTick member function during a systemTick + * add a component to the array of components which invocate the systemTick member function during a systemTick * @param component The component to add. * @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported. * @note this will be converted into a dynamic list of components @@ -389,14 +495,14 @@ void MicroBit::systemTasks() int MicroBit::addSystemComponent(MicroBitComponent *component) { int i = 0; - - while(systemTickComponents[i] != NULL && i < MICROBIT_SYSTEM_COMPONENTS) + + while(systemTickComponents[i] != NULL && i < MICROBIT_SYSTEM_COMPONENTS) i++; - + if(i == MICROBIT_SYSTEM_COMPONENTS) return MICROBIT_NO_RESOURCES; - - systemTickComponents[i] = component; + + systemTickComponents[i] = component; return MICROBIT_OK; } @@ -409,10 +515,10 @@ int MicroBit::addSystemComponent(MicroBitComponent *component) int MicroBit::removeSystemComponent(MicroBitComponent *component) { int i = 0; - - while(systemTickComponents[i] != component && i < MICROBIT_SYSTEM_COMPONENTS) + + while(systemTickComponents[i] != component && i < MICROBIT_SYSTEM_COMPONENTS) i++; - + if(i == MICROBIT_SYSTEM_COMPONENTS) return MICROBIT_INVALID_PARAMETER; @@ -422,7 +528,7 @@ int MicroBit::removeSystemComponent(MicroBitComponent *component) } /** - * add a component to the array of components which invocate the systemTick member function during a systemTick + * add a component to the array of components which invocate the systemTick member function during a systemTick * @param component The component to add. * @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if further components cannot be supported. * @note this will be converted into a dynamic list of components @@ -430,14 +536,14 @@ int MicroBit::removeSystemComponent(MicroBitComponent *component) int MicroBit::addIdleComponent(MicroBitComponent *component) { int i = 0; - - while(idleThreadComponents[i] != NULL && i < MICROBIT_IDLE_COMPONENTS) + + while(idleThreadComponents[i] != NULL && i < MICROBIT_IDLE_COMPONENTS) i++; - + if(i == MICROBIT_IDLE_COMPONENTS) return MICROBIT_NO_RESOURCES; - - idleThreadComponents[i] = component; + + idleThreadComponents[i] = component; return MICROBIT_OK; } @@ -451,10 +557,10 @@ int MicroBit::addIdleComponent(MicroBitComponent *component) int MicroBit::removeIdleComponent(MicroBitComponent *component) { int i = 0; - - while(idleThreadComponents[i] != component && i < MICROBIT_IDLE_COMPONENTS) + + while(idleThreadComponents[i] != component && i < MICROBIT_IDLE_COMPONENTS) i++; - + if(i == MICROBIT_IDLE_COMPONENTS) return MICROBIT_INVALID_PARAMETER; @@ -488,7 +594,7 @@ const char *MicroBit::systemVersion() /** * 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) @@ -496,4 +602,3 @@ 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 index d4ba4db..f06fae2 100644 --- a/source/MicroBitAccelerometer.cpp +++ b/source/MicroBitAccelerometer.cpp @@ -1,20 +1,20 @@ /** - * 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 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" /** - * Configures the accelerometer for G range and sample rate defined - * in this object. The nearest values are chosen to those defined - * that are supported by the hardware. The instance variables are then - * updated to reflect reality. - * - * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the accelerometer could not be configured. - */ + * Configures the accelerometer for G range and sample rate defined + * in this object. The nearest values are chosen to those defined + * that are supported by the hardware. The instance variables are then + * updated to reflect reality. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the accelerometer could not be configured. + */ int MicroBitAccelerometer::configure() { const MMA8653SampleRangeConfig *actualSampleRange; @@ -48,63 +48,63 @@ int MicroBitAccelerometer::configure() // Now configure the accelerometer accordingly. // First place the device into standby mode, so it can be configured. result = writeCommand(MMA8653_CTRL_REG1, 0x00); - if (result != 0) + if (result != 0) return MICROBIT_I2C_ERROR; - + // Enable high precisiosn mode. This consumes a bit more power, but still only 184 uA! result = writeCommand(MMA8653_CTRL_REG2, 0x10); - if (result != 0) + if (result != 0) return MICROBIT_I2C_ERROR; // Enable the INT1 interrupt pin. result = writeCommand(MMA8653_CTRL_REG4, 0x01); - if (result != 0) + if (result != 0) return MICROBIT_I2C_ERROR; // Select the DATA_READY event source to be routed to INT1 result = writeCommand(MMA8653_CTRL_REG5, 0x01); - if (result != 0) + if (result != 0) return MICROBIT_I2C_ERROR; // Configure for the selected g range. result = writeCommand(MMA8653_XYZ_DATA_CFG, actualSampleRange->xyz_data_cfg); - if (result != 0) + if (result != 0) return MICROBIT_I2C_ERROR; - + // Bring the device back online, with 10bit wide samples at the requested frequency. result = writeCommand(MMA8653_CTRL_REG1, actualSampleRate->ctrl_reg1 | 0x01); - if (result != 0) + if (result != 0) return MICROBIT_I2C_ERROR; return MICROBIT_OK; } /** - * 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. - * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the the write request failed. - */ + * 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. + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the the write request failed. + */ int MicroBitAccelerometer::writeCommand(uint8_t reg, uint8_t value) { uint8_t command[2]; command[0] = reg; command[1] = value; - + return 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. - * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER or MICROBIT_I2C_ERROR if the the read request failed. - */ + * 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. + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER or MICROBIT_I2C_ERROR if the the read request failed. + */ int MicroBitAccelerometer::readCommand(uint8_t reg, uint8_t* buffer, int length) { int result; @@ -124,46 +124,57 @@ int MicroBitAccelerometer::readCommand(uint8_t reg, uint8_t* buffer, int 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 - */ + * 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->status = 0; this->address = address; // Update our internal state for 50Hz at +/- 2g (50Hz has a period af 20ms). this->samplePeriod = 20; this->sampleRange = 2; + // Initialise gesture history + this->sigma = 0; + this->lastGesture = GESTURE_NONE; + this->currentGesture = GESTURE_NONE; + this->shake.x = 0; + this->shake.y = 0; + this->shake.z = 0; + this->shake.count = 0; + this->shake.timer = 0; + // Configure and enable the accelerometer. if (this->configure() == MICROBIT_OK) 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, or MICROBIT_I2C_ERROR if the request fails. - * - * Example: - * @code - * uBit.accelerometer.whoAmI(); - * @endcode - */ + * Attempts to determine the 8 bit ID from the accelerometer. + * @return the 8 bit ID returned by the accelerometer, or MICROBIT_I2C_ERROR if the request fails. + * + * Example: + * @code + * uBit.accelerometer.whoAmI(); + * @endcode + */ int MicroBitAccelerometer::whoAmI() { uint8_t data; int result; - result = readCommand(MMA8653_WHOAMI, &data, 1); + result = readCommand(MMA8653_WHOAMI, &data, 1); if (result !=0) return MICROBIT_I2C_ERROR; @@ -171,11 +182,11 @@ int MicroBitAccelerometer::whoAmI() } /** - * 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! - * - * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the read request fails. - */ + * 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! + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the read request fails. + */ int MicroBitAccelerometer::update() { int8_t data[6]; @@ -186,19 +197,15 @@ int MicroBitAccelerometer::update() return MICROBIT_I2C_ERROR; // read MSB values... - sample.x = data[0]; + sample.x = data[0]; sample.y = data[2]; sample.z = data[4]; - + // Normalize the data in the 0..1024 range. sample.x *= 8; sample.y *= 8; sample.z *= 8; - // Invert the x and y axes, so that the reference frame aligns with micro:bit expectations - sample.x = -sample.x; - sample.y = -sample.y; - #if CONFIG_ENABLED(USE_ACCEL_LSB) // Add in LSB values. sample.x += (data[1] / 64); @@ -211,12 +218,141 @@ int MicroBitAccelerometer::update() sample.y *= this->sampleRange; sample.z *= this->sampleRange; + // Indicate that pitch and roll data is now stale, and needs to be recalculated if needed. + status &= ~MICROBIT_ACCEL_PITCH_ROLL_VALID; + + // Update gesture tracking + updateGesture(); + // Indicate that a new sample is available MicroBitEvent e(id, MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE); return MICROBIT_OK; }; +/** + * Service function. Calculates the current scalar acceleration of the device (x^2 + y^2 + z^2). + * It does not, however, square root the result, as this is a relatively high cost operation. + * This is left to application code should it be needed. + * + * @return the sum of the square of the acceleration of the device across all axes. + */ +int MicroBitAccelerometer::instantaneousAccelerationSquared() +{ + // Use pythagoras theorem to determine the combined force acting on the device. + return (int)sample.x*(int)sample.x + (int)sample.y*(int)sample.y + (int)sample.z*(int)sample.z; +} + +/** + * Service function. Determines the best guess posture of the device based on instantaneous data. + * This makes no use of historic data (except for shake), and forms this input to the filter implemented in updateGesture(). + * + * @return A best guess of the current posture of the device, based on instantaneous data. + */ +BasicGesture MicroBitAccelerometer::instantaneousPosture() +{ + int force = instantaneousAccelerationSquared(); + bool shakeDetected = false; + + // Test for shake events. + // We detect a shake by measuring zero crossings in each axis. In other words, if we see a strong acceleration to the left followed by + // a string acceleration to the right, then we can infer a shake. Similarly, we can do this for each acxis (left/right, up/down, in/out). + // + // If we see enough zero crossings in succession (MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD), then we decide that the device + // has been shaken. + if ((getX() < -MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && shake.x) || (getX() > MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !shake.x)) + { + shakeDetected = true; + shake.x = !shake.x; + } + + if ((getY() < -MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && shake.y) || (getY() > MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !shake.y)) + { + shakeDetected = true; + shake.y = !shake.y; + } + + if ((getZ() < -MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && shake.z) || (getZ() > MICROBIT_ACCELEROMETER_SHAKE_TOLERANCE && !shake.z)) + { + shakeDetected = true; + shake.z = !shake.z; + } + + if (shakeDetected && shake.count < MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD && ++shake.count == MICROBIT_ACCELEROMETER_SHAKE_COUNT_THRESHOLD) + shake.shaken = 1; + + if (++shake.timer >= MICROBIT_ACCELEROMETER_SHAKE_DAMPING) + { + shake.timer = 0; + if (shake.count > 0) + { + if(--shake.count == 0) + shake.shaken = 0; + } + } + + if (shake.shaken) + return GESTURE_SHAKE; + + if (force < MICROBIT_ACCELEROMETER_FREEFALL_THRESHOLD) + return GESTURE_FREEFALL; + + if (force > MICROBIT_ACCELEROMETER_3G_THRESHOLD) + return GESTURE_3G; + + if (force > MICROBIT_ACCELEROMETER_6G_THRESHOLD) + return GESTURE_6G; + + if (force > MICROBIT_ACCELEROMETER_8G_THRESHOLD) + return GESTURE_8G; + + // Determine our posture. + if (getX() < (-1000 + MICROBIT_ACCELEROMETER_TILT_TOLERANCE)) + return GESTURE_LEFT; + + if (getX() > (1000 - MICROBIT_ACCELEROMETER_TILT_TOLERANCE)) + return GESTURE_RIGHT; + + if (getY() < (-1000 + MICROBIT_ACCELEROMETER_TILT_TOLERANCE)) + return GESTURE_DOWN; + + if (getY() > (1000 - MICROBIT_ACCELEROMETER_TILT_TOLERANCE)) + return GESTURE_UP; + + if (getZ() < (-1000 + MICROBIT_ACCELEROMETER_TILT_TOLERANCE)) + return GESTURE_FACE_UP; + + if (getZ() > (1000 - MICROBIT_ACCELEROMETER_TILT_TOLERANCE)) + return GESTURE_FACE_DOWN; + + return GESTURE_NONE; +} + +void MicroBitAccelerometer::updateGesture() +{ + // Determine what it looks like we're doing based on the latest sample... + BasicGesture g = instantaneousPosture(); + + // Perform some low pass filtering to reduce jitter from any detected effects + if (g == currentGesture) + { + if (sigma < MICROBIT_ACCELEROMETER_GESTURE_DAMPING) + sigma++; + } + else + { + currentGesture = g; + sigma = 0; + } + + // If we've reached threshold, update our record and raise the relevant event... + if (currentGesture != lastGesture && sigma >= MICROBIT_ACCELEROMETER_GESTURE_DAMPING) + { + lastGesture = currentGesture; + MicroBitEvent e(MICROBIT_ID_GESTURE, lastGesture); + } +} + /** * Attempts to set the sample rate of the accelerometer to the specified value (in ms). * n.b. the requested rate may not be possible on the hardware. In this case, the @@ -231,7 +367,7 @@ int MicroBitAccelerometer::setPeriod(int period) } /** - * Reads the currently configured sample rate of the accelerometer. + * Reads the currently configured sample rate of the accelerometer. * @return The time between samples, in milliseconds. */ int MicroBitAccelerometer::getPeriod() @@ -253,7 +389,7 @@ int MicroBitAccelerometer::setRange(int range) } /** - * Reads the currently configured sample range of the accelerometer. + * Reads the currently configured sample range of the accelerometer. * @return The sample range, in g. */ int MicroBitAccelerometer::getRange() @@ -263,54 +399,161 @@ int MicroBitAccelerometer::getRange() /** * Reads the X axis value of the latest update from the accelerometer. - * Currently limited to +/- 2g + * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the X axis, in milli-g. * * Example: - * @code + * @code * uBit.accelerometer.getX(); + * uBit.accelerometer.getX(RAW); * @endcode */ -int MicroBitAccelerometer::getX() +int MicroBitAccelerometer::getX(MicroBitCoordinateSystem system) { - return sample.x; + switch (system) + { + case SIMPLE_CARTESIAN: + return -sample.x; + + case NORTH_EAST_DOWN: + return sample.y; + + case RAW: + default: + return sample.x; + } } /** * Reads the Y axis value of the latest update from the accelerometer. - * Currently limited to +/- 2g + * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the Y axis, in milli-g. * * Example: - * @code + * @code * uBit.accelerometer.getY(); + * uBit.accelerometer.getY(RAW); * @endcode - */ -int MicroBitAccelerometer::getY() + */ +int MicroBitAccelerometer::getY(MicroBitCoordinateSystem system) { - return sample.y; + switch (system) + { + case SIMPLE_CARTESIAN: + return -sample.y; + + case NORTH_EAST_DOWN: + return -sample.x; + + case RAW: + default: + return sample.y; + } } /** * Reads the Z axis value of the latest update from the accelerometer. - * Currently limited to +/- 2g + * @param system The coordinate system to use. By default, a simple cartesian system is provided. * @return The force measured in the Z axis, in milli-g. * * Example: - * @code + * @code * uBit.accelerometer.getZ(); + * uBit.accelerometer.getZ(RAW); * @endcode - */ -int MicroBitAccelerometer::getZ() + */ +int MicroBitAccelerometer::getZ(MicroBitCoordinateSystem system) { - return sample.z; + switch (system) + { + case NORTH_EAST_DOWN: + return -sample.z; + + case SIMPLE_CARTESIAN: + case RAW: + default: + return sample.z; + } } - /** - * periodic callback from MicroBit clock. - * Check if any data is ready for reading by checking the interrupt flag on the accelerometer - */ + * Provides a rotation compensated pitch of the device, based on the latest update from the accelerometer. + * @return The pitch of the device, in degrees. + * + * Example: + * @code + * uBit.accelerometer.getPitch(); + * @endcode + */ +int MicroBitAccelerometer::getPitch() +{ + return (int) ((360*getPitchRadians()) / (2*PI)); +} + +float MicroBitAccelerometer::getPitchRadians() +{ + if (!(status & MICROBIT_ACCEL_PITCH_ROLL_VALID)) + recalculatePitchRoll(); + + return pitch; +} + +/** + * Provides a rotation compensated roll of the device, based on the latest update from the accelerometer. + * @return The roll of the device, in degrees. + * + * Example: + * @code + * uBit.accelerometer.getRoll(); + * @endcode + */ +int MicroBitAccelerometer::getRoll() +{ + return (int) ((360*getRollRadians()) / (2*PI)); +} + +float MicroBitAccelerometer::getRollRadians() +{ + if (!(status & MICROBIT_ACCEL_PITCH_ROLL_VALID)) + recalculatePitchRoll(); + + return roll; +} + +/** + * Recalculate roll and pitch values for the current sample. + * We only do this at most once per sample, as the necessary trigonemteric functions are rather + * heavyweight for a CPU without a floating point unit... + */ +void MicroBitAccelerometer::recalculatePitchRoll() +{ + float x = (float) getX(NORTH_EAST_DOWN); + float y = (float) getY(NORTH_EAST_DOWN); + float z = (float) getZ(NORTH_EAST_DOWN); + + roll = atan2(getY(NORTH_EAST_DOWN), getZ(NORTH_EAST_DOWN)); + pitch = atan(-x / (y*sin(roll) + z*cos(roll))); + status |= MICROBIT_ACCEL_PITCH_ROLL_VALID; +} + +/** + * Reads the last recorded gesture detected. + * @return The last gesture detected. + * + * Example: + * @code + * if (uBit.accelerometer.getGesture() == SHAKE) + * @endcode + */ +BasicGesture MicroBitAccelerometer::getGesture() +{ + return lastGesture; +} + +/** + * 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. @@ -321,26 +564,34 @@ void MicroBitAccelerometer::idleTick() } /** - * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. - */ + * 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; } +/** + * Destructor for MicroBitAccelerometer, so that we deregister ourselves as an idleComponent + */ +MicroBitAccelerometer::~MicroBitAccelerometer() +{ + uBit.removeIdleComponent(this); +} + const MMA8653SampleRangeConfig MMA8653SampleRange[MMA8653_SAMPLE_RANGES] = { - {2, 0}, - {4, 1}, + {2, 0}, + {4, 1}, {8, 2} }; const MMA8653SampleRateConfig MMA8653SampleRate[MMA8653_SAMPLE_RATES] = { - {1250, 0x00}, - {2500, 0x08}, - {5000, 0x10}, - {10000, 0x18}, - {20000, 0x20}, - {80000, 0x28}, + {1250, 0x00}, + {2500, 0x08}, + {5000, 0x10}, + {10000, 0x18}, + {20000, 0x20}, + {80000, 0x28}, {160000, 0x30}, {640000, 0x38} }; diff --git a/source/MicroBitButton.cpp b/source/MicroBitButton.cpp index 1311447..64c211e 100644 --- a/source/MicroBitButton.cpp +++ b/source/MicroBitButton.cpp @@ -1,19 +1,19 @@ #include "MicroBit.h" /** - * Constructor. + * 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 + * @code * buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A); //a number between 0 and 200 inclusive * @endcode * * Possible Events: - * @code + * @code * MICROBIT_BUTTON_EVT_DOWN * MICROBIT_BUTTON_EVT_UP * MICROBIT_BUTTON_EVT_CLICK @@ -34,9 +34,9 @@ MicroBitButton::MicroBitButton(uint16_t id, PinName name, MicroBitButtonEventCon /** * 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. @@ -45,37 +45,37 @@ void MicroBitButton::systemTick() // if(!pin) { - if (sigma < MICROBIT_BUTTON_SIGMA_MAX) - sigma++; + if (sigma < MICROBIT_BUTTON_SIGMA_MAX) + sigma++; } else { - if (sigma > MICROBIT_BUTTON_SIGMA_MIN) + 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; + downStartTime=ticks; } - - // Check to see if we have on->off state change. + + // 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); - + if (eventConfiguration == MICROBIT_BUTTON_ALL_EVENTS) - { + { //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); + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK); else MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); } @@ -86,7 +86,7 @@ void MicroBitButton::systemTick() { //set the hold triggered event flag status |= MICROBIT_BUTTON_STATE_HOLD_TRIGGERED; - + //fire hold event MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_HOLD); } @@ -100,3 +100,11 @@ int MicroBitButton::isPressed() { return status & MICROBIT_BUTTON_STATE ? 1 : 0; } + +/** + * Destructor for MicroBitButton, so that we deregister ourselves as a systemComponent + */ +MicroBitButton::~MicroBitButton() +{ + uBit.removeSystemComponent(this); +} diff --git a/source/MicroBitCompass.cpp b/source/MicroBitCompass.cpp index 385784f..d89b6a5 100644 --- a/source/MicroBitCompass.cpp +++ b/source/MicroBitCompass.cpp @@ -1,7 +1,7 @@ #include "MicroBit.h" /** - * Constructor. + * 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 @@ -22,19 +22,17 @@ MicroBitCompass::MicroBitCompass(uint16_t id, uint16_t address) : average(), sam { this->id = id; this->address = address; - - //we presume the device calibrated until the average values are read. + + // We presume the device calibrated until the average values are read. this->status = 0x01; - - //initialise eventStartTime to 0 - this->eventStartTime = 0; - + // Select 10Hz update rate, with oversampling, and enable the device. this->samplePeriod = 100; this->configure(); - + + // Assume that we have no calibraiton information. status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; - + // Indicate that we're up and running. uBit.flags |= MICROBIT_FLAG_COMPASS_RUNNING; } @@ -52,7 +50,7 @@ int MicroBitCompass::writeCommand(uint8_t reg, uint8_t value) uint8_t command[2]; command[0] = reg; command[1] = value; - + return uBit.i2c.write(address, (const char *)command, 2); } @@ -102,7 +100,7 @@ int MicroBitCompass::read16(uint8_t reg) cmd[0] = 0x00; cmd[1] = 0x00; - + result = uBit.i2c.read(address, (char *)cmd, 2); if (result !=0) return MICROBIT_I2C_ERROR; @@ -130,35 +128,50 @@ int MicroBitCompass::read8(uint8_t reg) 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 - */ + * Gets the current heading of the device, relative to magnetic north. + * If the compass is not calibrated, it will raise the MICROBIT_COMPASS_EVT_CALIBRATE event. + * Users wishing to implement their own calibration algorithms should listen for this event, + * using MESSAGE_BUS_LISTENER_IMMEDIATE model. This ensures that calibration is complete before + * the user program continues. + * + * @return the current heading, in degrees. Or MICROBIT_CALIBRATION_IN_PROGRESS if the compass is calibrating. + * + * Example: + * @code + * uBit.compass.heading(); + * @endcode + */ int MicroBitCompass::heading() { - if(status & MICROBIT_COMPASS_STATUS_CALIBRATING) - return MICROBIT_CALIBRATION_IN_PROGRESS; + float bearing; + + if(status & MICROBIT_COMPASS_STATUS_CALIBRATING) + return MICROBIT_CALIBRATION_IN_PROGRESS; + + if(!(status & MICROBIT_COMPASS_STATUS_CALIBRATED)) + calibrate(); + + // Precompute the tilt compensation parameters to improve readability. + float phi = uBit.accelerometer.getRollRadians(); + float theta = uBit.accelerometer.getPitchRadians(); + float x = (float) getX(NORTH_EAST_DOWN); + float y = (float) getY(NORTH_EAST_DOWN); + float z = (float) getZ(NORTH_EAST_DOWN); + + // Precompute cos and sin of pitch and roll angles to make the calculation a little more efficient. + float sinPhi = sin(phi); + float cosPhi = cos(phi); + float sinTheta = sin(theta); + float cosTheta = cos(theta); + + bearing = (360*atan2(z*sinPhi - y*cosPhi, x*cosTheta + y*sinTheta*sinPhi + z*sinTheta*cosPhi)) / (2*PI); - else if(!(status & MICROBIT_COMPASS_STATUS_CALIBRATED)) - { - MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_REQUIRED); - return MICROBIT_CALIBRATION_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); -} + return (int) bearing; +} /** * Periodic callback from MicroBit clock. @@ -166,79 +179,108 @@ int MicroBitCompass::heading() */ void MicroBitCompass::idleTick() { - // Poll interrupt line from accelerometer. - // Active HI. Interrupt is cleared in data read of MAG_OUT_X_MSB. + // Poll interrupt line from accelerometer (Active HI). + // Interrupt is cleared on data read of MAG_OUT_X_MSB. if(int1) { - sample.x = (int16_t) read16(MAG_OUT_X_MSB); - sample.y = (int16_t) read16(MAG_OUT_Y_MSB); - sample.z = (int16_t) read16(MAG_OUT_Z_MSB); + sample.x = MAG3110_NORMALIZE_SAMPLE((int) read16(MAG_OUT_X_MSB)); + sample.y = MAG3110_NORMALIZE_SAMPLE((int) read16(MAG_OUT_Y_MSB)); + sample.z = MAG3110_NORMALIZE_SAMPLE((int) 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(); - } - } - else - { - // Indicate that a new sample is available - MicroBitEvent e(id, MICROBIT_COMPASS_EVT_DATA_UPDATE); - } + // Indicate that a new sample is available + MicroBitEvent e(id, MICROBIT_COMPASS_EVT_DATA_UPDATE); } - } /** * 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. + * @return The magnetic force measured in the X axis, in nano teslas. * * Example: * @code * uBit.compass.getX(); * @endcode */ -int MicroBitCompass::getX() +int MicroBitCompass::getX(MicroBitCoordinateSystem system) { - return sample.x; + switch (system) + { + case SIMPLE_CARTESIAN: + return sample.x - average.x; + + case NORTH_EAST_DOWN: + return -(sample.y - average.y); + + case RAW: + default: + 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. + * @return The magnetic force measured in the Y axis, in nano teslas. * * Example: * @code * uBit.compass.getY(); * @endcode - */ -int MicroBitCompass::getY() + */ +int MicroBitCompass::getY(MicroBitCoordinateSystem system) { - return sample.y; + switch (system) + { + case SIMPLE_CARTESIAN: + return -(sample.y - average.y); + + case NORTH_EAST_DOWN: + return (sample.x - average.x); + + case RAW: + default: + 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. + * @return The magnetic force measured in the Z axis, in nano teslas. * * Example: * @code * uBit.compass.getZ(); * @endcode - */ -int MicroBitCompass::getZ() + */ +int MicroBitCompass::getZ(MicroBitCoordinateSystem system) { - return sample.z; + switch (system) + { + case SIMPLE_CARTESIAN: + case NORTH_EAST_DOWN: + return -(sample.z - average.z); + + case RAW: + default: + return sample.z; + } +} + +/** + * Determines the overall magnetic field strength based on the latest update from the compass. + * @return The magnetic force measured across all axes, in nano teslas. + * + * Example: + * @code + * uBit.compass.getFieldStrength(); + * @endcode + */ +int MicroBitCompass::getFieldStrength() +{ + double x = getX(); + double y = getY(); + double z = getZ(); + + return (int) sqrt(x*x + y*y + z*z); } /** @@ -258,7 +300,7 @@ int MicroBitCompass::configure() if (result != MICROBIT_OK) return MICROBIT_I2C_ERROR; - // Wait for the part to enter standby mode... + // Wait for the part to enter standby mode... while(1) { // Read the status of the part... @@ -293,7 +335,7 @@ int MicroBitCompass::configure() if (result != MICROBIT_OK) return MICROBIT_I2C_ERROR; - + // Bring the device online, with the requested sample frequency. result = writeCommand(MAG_CTRL_REG1, actualSampleRate->ctrl_reg1 | 0x01); if (result != MICROBIT_OK) @@ -316,7 +358,7 @@ int MicroBitCompass::setPeriod(int period) } /** - * Reads the currently configured sample rate of the compass. + * Reads the currently configured sample rate of the compass. * @return The time between samples, in milliseconds. */ int MicroBitCompass::getPeriod() @@ -326,7 +368,7 @@ int MicroBitCompass::getPeriod() /** - * Attempts to determine the 8 bit ID from the magnetometer. + * Attempts to determine the 8 bit ID from the magnetometer. * @return the id of the compass (magnetometer), or MICROBIT_I2C_ERROR if the magnetometer could not be updated. * * Example: @@ -339,7 +381,7 @@ int MicroBitCompass::whoAmI() uint8_t data; int result; - result = readCommand(MAG_WHOAMI, &data, 1); + result = readCommand(MAG_WHOAMI, &data, 1); if (result != MICROBIT_OK) return MICROBIT_I2C_ERROR; @@ -347,7 +389,7 @@ int MicroBitCompass::whoAmI() } /** - * Reads the currently die temperature of the compass. + * Reads the current die temperature of the compass. * @return the temperature in degrees celsius, or MICROBIT_I2C_ERROR if the magnetometer could not be updated. */ int MicroBitCompass::readTemperature() @@ -362,63 +404,111 @@ int MicroBitCompass::readTemperature() return temperature; } +/** + * Perform a calibration of the compass. + * + * This method will be called automatically if a user attempts to read a compass value when + * the compass is uncalibrated. It can also be called at any time by the user. + * + * Any old calibration data is deleted. + * The method will only return once the compass has been calibrated. + * + * @return MICROBIT_OK, MICROBIT_I2C_ERROR if the magnetometer could not be accessed, + * or MICROBIT_CALIBRATION_REQUIRED if the calibration algorithm failed to complete succesfully. + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ +int MicroBitCompass::calibrate() +{ + // Only perform one calibration process at a time. + if(isCalibrating()) + return MICROBIT_CALIBRATION_IN_PROGRESS; + + // Delete old calibration data + clearCalibration(); + + // Record that we've started calibrating. + status |= MICROBIT_COMPASS_STATUS_CALIBRATING; + + // Launch any registred calibration alogrithm visialisation + MicroBitEvent(id, MICROBIT_COMPASS_EVT_CALIBRATE); + + // Record that we've finished calibrating. + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATING; + + // If there are no changes to our sample data, we either have no calibration algorithm, or it couldn't complete succesfully. + if(!(status & MICROBIT_COMPASS_STATUS_CALIBRATED)) + return MICROBIT_CALIBRATION_REQUIRED; + + return MICROBIT_OK; +} + /** * Perform a calibration of the compass. * This will fire MICROBIT_COMPASS_EVT_CAL_START. * @return MICROBIT_OK, or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. - * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + * + * @note *** THIS FUNCTION IS NOW DEPRECATED AND WILL BE REMOVED IN THE NEXT MAJOR RELEASE *** + * @note *** PLEASE USE THE calibrate() FUNCTION INSTEAD *** */ int MicroBitCompass::calibrateStart() { - if(this->isCalibrating()) - return MICROBIT_CALIBRATION_IN_PROGRESS; + return calibrate(); +} - status |= MICROBIT_COMPASS_STATUS_CALIBRATING; - - // Take a sane snapshot to start with. - minSample = sample; - maxSample = sample; - - MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_START); - - return MICROBIT_OK; -} - - /** - * 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 - */ + * 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 FUNCITON IS NOW DEPRECATED AND WILL BE REMOVED IN THE NEXT MAJOR RELEASE *** + * @note *** PLEASE USE THE calibrate() FUNCTION INSTEAD *** + */ void MicroBitCompass::calibrateAsync() -{ - eventStartTime = ticks; - calibrateStart(); +{ + calibrate(); } /** * 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 - */ + * + * @note *** THIS FUNCTION IS NOW DEPRECATED AND WILL BE REMOVED IN THE NEXT MAJOR RELEASE *** + */ 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; +} + + +/** + * Configure the compass to use the given calibration data. + * Calibration data is comprised of the perceived zero offset of each axis of the compass. + * After calibration this should now take into account trimming errors in the magnetometer, + * and any "hard iron" offsets on the device. + * + * @param The x, y and z zero offsets to use as calibration data. + */ +void MicroBitCompass::setCalibration(CompassSample calibration) +{ + average = calibration; status |= MICROBIT_COMPASS_STATUS_CALIBRATED; - - MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_END); -} +} + +/** + * Provides the calibration data currently in use by the compass. + * More specifically, the x, y and z zero offsets of the compass. + * + * @return The x, y and z xero offsets of the compass. + */ +CompassSample MicroBitCompass::getCalibration() +{ + return average; +} /** * 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; + return status & MICROBIT_COMPASS_STATUS_CALIBRATED; } /** @@ -426,7 +516,7 @@ int MicroBitCompass::isCalibrated() */ int MicroBitCompass::isCalibrating() { - return status & MICROBIT_COMPASS_STATUS_CALIBRATING; + return status & MICROBIT_COMPASS_STATUS_CALIBRATING; } /** @@ -434,7 +524,7 @@ int MicroBitCompass::isCalibrating() */ void MicroBitCompass::clearCalibration() { - status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; } /** @@ -447,16 +537,24 @@ int MicroBitCompass::isIdleCallbackNeeded() return int1; } +/** + * Destructor for MicroBitMessageBus, so that we deregister ourselves as an idleComponent + */ +MicroBitCompass::~MicroBitCompass() +{ + uBit.removeIdleComponent(this); +} + const MAG3110SampleRateConfig MAG3110SampleRate[MAG3110_SAMPLE_RATES] = { {12500, 0x00}, // 80 Hz {25000, 0x20}, // 40 Hz - {50000, 0x40}, // 20 Hz - {100000, 0x60}, // 10 hz - {200000, 0x80}, // 5 hz - {400000, 0x88}, // 2.5 hz - {800000, 0x90}, // 1.25 hz - {1600000, 0xb0}, // 0.63 hz - {3200000, 0xd0}, // 0.31 hz - {6400000, 0xf0}, // 0.16 hz - {12800000, 0xf8} // 0.08 hz + {50000, 0x40}, // 20 Hz + {100000, 0x60}, // 10 hz + {200000, 0x80}, // 5 hz + {400000, 0x88}, // 2.5 hz + {800000, 0x90}, // 1.25 hz + {1600000, 0xb0}, // 0.63 hz + {3200000, 0xd0}, // 0.31 hz + {6400000, 0xf0}, // 0.16 hz + {12800000, 0xf8} // 0.08 hz }; diff --git a/source/MicroBitDisplay.cpp b/source/MicroBitDisplay.cpp index 1513c10..f953acd 100644 --- a/source/MicroBitDisplay.cpp +++ b/source/MicroBitDisplay.cpp @@ -17,19 +17,19 @@ const float timings[MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH] = {0.000010, 0.000047, * * @param x the width of the display in pixels. * @param y the height of the display in pixels. - * + * * Example: - * @code + * @code * MicroBitDisplay display(MICROBIT_ID_DISPLAY, 5, 5), * @endcode */ -MicroBitDisplay::MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y) : +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; @@ -38,46 +38,46 @@ MicroBitDisplay::MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y) : this->rotation = MICROBIT_DISPLAY_ROTATION_0; this->greyscaleBitMsk = 0x01; this->timingCount = 0; - + this->setBrightness(MICROBIT_DISPLAY_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 + * 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. + + // 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; + 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(); } @@ -87,60 +87,60 @@ 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)); - + 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); + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobeBitMsk | 0x1F); } void MicroBitDisplay::render() -{ +{ // Simple optimisation. If display is at zero brightness, there's nothign to do. if(brightness == 0) return; int coldata = 0; - + // Calculate the bitpattern to write. for (int i = 0; i>4 & 0x1F)); + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobeBitMsk | (~coldata>>4 & 0x1F)); //timer does not have enough resolution for brightness of 1. 23.53 us if(brightness != MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS && brightness > MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) renderTimer.attach(this, &MicroBitDisplay::renderFinish, (((float)brightness) / ((float)MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * (float)MICROBIT_DISPLAY_REFRESH_PERIOD); - + //this will take around 23us to execute if(brightness <= MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) renderFinish(); @@ -149,47 +149,47 @@ void MicroBitDisplay::render() void MicroBitDisplay::renderGreyscale() { int coldata = 0; - + // Calculate the bitpattern to write. for (int i = 0; i>4 & 0x1F)); + nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobeBitMsk | (~coldata>>4 & 0x1F)); if(timingCount > MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH-1) return; greyscaleBitMsk <<= 1; - + renderTimer.attach(this,&MicroBitDisplay::renderGreyscale, timings[timingCount++]); } @@ -198,29 +198,29 @@ void MicroBitDisplay::renderGreyscale() */ void MicroBitDisplay::animationUpdate() -{ +{ // If there's no ongoing animation, then nothing to do. if (animationMode == ANIMATION_MODE_NONE) return; - - animationTick += FIBER_TICK_PERIOD_MS; - + + 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) { animationMode = ANIMATION_MODE_NONE; @@ -243,18 +243,18 @@ void MicroBitDisplay::sendAnimationCompleteEvent() } /** - * Internal scrollText update method. + * 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()) @@ -268,36 +268,36 @@ void MicroBitDisplay::updateScrollText() } /** - * Internal printText update method. + * 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; + animationMode = ANIMATION_MODE_NONE; this->sendAnimationCompleteEvent(); return; } - + printingChar++; } /** - * Internal scrollImage update method. + * Internal scrollImage update method. * Paste the stored bitmap at the appropriate point. - */ + */ void MicroBitDisplay::updateScrollImage() -{ - image.clear(); +{ + image.clear(); if ((image.paste(scrollingImage, scrollingImagePosition, 0, 0) == 0) && scrollingImageRendered) { - animationMode = ANIMATION_MODE_NONE; - this->sendAnimationCompleteEvent(); + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); return; } @@ -307,27 +307,27 @@ void MicroBitDisplay::updateScrollImage() } /** - * Internal animateImage update method. + * 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; + animationMode = ANIMATION_MODE_NONE; this->clear(); - this->sendAnimationCompleteEvent(); + this->sendAnimationCompleteEvent(); return; } - + if(scrollingImagePosition > 0) image.shiftLeft(-scrollingImageStride); - + image.paste(scrollingImage, scrollingImagePosition, 0, 0); - + scrollingImageRendered = true; - + scrollingImagePosition += scrollingImageStride; } @@ -347,7 +347,7 @@ void MicroBitDisplay::stopAnimation() // Wake up aall fibers that may blocked on the animation (if any). MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); } - + // Clear the display and setup the animation timers. this->image.clear(); } @@ -356,7 +356,7 @@ void MicroBitDisplay::stopAnimation() * Blocks the current fiber until the display is available (i.e. not effect is being displayed). * Animations are queued until their time to display. * - */ + */ void MicroBitDisplay::waitForFreeDisplay() { // If there's an ongoing animation, wait for our turn to display. @@ -371,9 +371,9 @@ void MicroBitDisplay::waitForFreeDisplay() * @param c The character to display. * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever. * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. - * + * * Example: - * @code + * @code * uBit.display.printAsync('p'); * uBit.display.printAsync('p',100); * @endcode @@ -422,7 +422,7 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) //sanitise this value if(delay <= 0 ) return MICROBIT_INVALID_PARAMETER; - + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) { printingChar = 0; @@ -431,7 +431,7 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) animationTick = 0; animationMode = ANIMATION_MODE_PRINT_TEXT; - } + } else { return MICROBIT_BUSY; @@ -448,7 +448,7 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) * @param i The image to display. * @param x The horizontal position on the screen to display the image (default 0) * @param y The vertical position on the screen to display the image (default 0) - * @param alpha Treats the brightness level '0' as transparent (default 0) + * @param alpha Treats the brightness level '0' as transparent (default 0) * @param delay The time to delay between characters, in milliseconds. set to 0 to display forever. (default 0). * * Example: @@ -487,9 +487,9 @@ int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int de * @param c The character to display. * @param delay The time to delay between characters, in milliseconds. Must be > 0. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. - * + * * Example: - * @code + * @code * uBit.display.print('p'); * uBit.display.print('p',100); * @endcode @@ -537,7 +537,7 @@ int MicroBitDisplay::print(ManagedString s, int delay) //sanitise this value if(delay <= 0 ) return MICROBIT_INVALID_PARAMETER; - + // If there's an ongoing animation, wait for our turn to display. this->waitForFreeDisplay(); @@ -613,7 +613,7 @@ int MicroBitDisplay::scrollAsync(ManagedString s, int delay) //sanitise this value if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - + // If the display is free, it's our turn to display. if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) { @@ -649,7 +649,7 @@ int MicroBitDisplay::scrollAsync(ManagedString s, int delay) * @endcode */ int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) -{ +{ //sanitise the delay value if(delay <= 0) return MICROBIT_INVALID_PARAMETER; @@ -682,9 +682,9 @@ int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) * @param s The string to display. * @param delay The time to delay between each update to the display, in milliseconds. Must be > 0. * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. - * + * * Example: - * @code + * @code * uBit.display.scrollString("abc123",100); * @endcode */ @@ -706,12 +706,12 @@ int MicroBitDisplay::scroll(ManagedString s, int delay) // Wait for completion. fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); - } + } else { return MICROBIT_CANCELLED; } - + return MICROBIT_OK; } @@ -731,11 +731,11 @@ int MicroBitDisplay::scroll(ManagedString s, int delay) * @endcode */ int MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride) -{ +{ //sanitise the delay value if(delay <= 0) return MICROBIT_INVALID_PARAMETER; - + // If there's an ongoing animation, wait for our turn to display. this->waitForFreeDisplay(); @@ -789,7 +789,7 @@ int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, in stride = -stride; //calculate starting position which is offset by the stride - scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition; + scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition; scrollingImageStride = stride; scrollingImage = image; scrollingImageRendered = false; @@ -858,14 +858,14 @@ int MicroBitDisplay::animate(MicroBitImage image, int delay, int stride, int sta * Sets the display brightness to the specified level. * @param b The brightness to set the brightness to, in the range 0..255. * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER - * + * * Example: - * @code + * @code * uBit.display.setBrightness(255); //max brightness * @endcode - */ + */ int MicroBitDisplay::setBrightness(int b) -{ +{ //sanitise the brightness level if(b < 0 || b > 255) return MICROBIT_INVALID_PARAMETER; @@ -878,26 +878,26 @@ int MicroBitDisplay::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 + * @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 + * @code * uBit.display.getBrightness(); //the current brightness * @endcode - */ + */ int MicroBitDisplay::getBrightness() { return this->brightness; @@ -908,10 +908,10 @@ int MicroBitDisplay::getBrightness() * Axis aligned values only. * * Example: - * @code + * @code * uBit.display.rotateTo(MICROBIT_DISPLAY_ROTATION_180); //rotates 180 degrees from original orientation * @endcode - */ + */ void MicroBitDisplay::rotateTo(DisplayRotation rotation) { this->rotation = rotation; @@ -921,7 +921,7 @@ void MicroBitDisplay::rotateTo(DisplayRotation rotation) * Enables the display, should only be called if the display is disabled. * * Example: - * @code + * @code * uBit.display.enable(); //reenables the display mechanics * @endcode */ @@ -933,13 +933,13 @@ void MicroBitDisplay::enable() 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 + * @code * uBit.display.disable(); //disables the display * @endcode */ @@ -948,7 +948,7 @@ void MicroBitDisplay::disable() if(uBit.flags & MICROBIT_FLAG_DISPLAY_RUNNING) { uBit.flags &= ~MICROBIT_FLAG_DISPLAY_RUNNING; //unset the display running flag - } + } } /** @@ -956,13 +956,13 @@ void MicroBitDisplay::disable() * Simplifies the process, you can also use uBit.display.image.clear * * Example: - * @code + * @code * uBit.display.clear(); //clears the display * @endcode - */ + */ void MicroBitDisplay::clear() { - image.clear(); + image.clear(); } /** @@ -970,12 +970,12 @@ void MicroBitDisplay::clear() * @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255. * * Example: - * @code + * @code * uBit.display.error(20); * @endcode */ void MicroBitDisplay::error(int statusCode) -{ +{ extern InterruptIn resetButton; __disable_irq(); //stop ALL interrupts @@ -984,16 +984,16 @@ void MicroBitDisplay::error(int statusCode) 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) { @@ -1001,42 +1001,42 @@ void MicroBitDisplay::error(int statusCode) for(int characterCount = 0; characterCount < MICROBIT_DISPLAY_ERROR_CHARS; characterCount++) { int outerCount = 0; - + //display the current character while(outerCount < 500) { 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; + 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 = 1000; - + //burn cycles while(i>0) { @@ -1046,9 +1046,9 @@ void MicroBitDisplay::error(int statusCode) i--; } - + //update the bit mask and row count - strobeBitMsk <<= 1; + strobeBitMsk <<= 1; strobeRow++; outerCount++; } @@ -1080,3 +1080,11 @@ MicroBitImage MicroBitDisplay::screenShot() { return image.crop(0,0,MICROBIT_DISPLAY_WIDTH,MICROBIT_DISPLAY_HEIGHT); } + +/** + * Destructor for MicroBitDisplay, so that we deregister ourselves as a systemComponent + */ +MicroBitDisplay::~MicroBitDisplay() +{ + uBit.removeSystemComponent(this); +} diff --git a/source/MicroBitHeapAllocator.cpp b/source/MicroBitHeapAllocator.cpp index 965e893..34f5d6e 100644 --- a/source/MicroBitHeapAllocator.cpp +++ b/source/MicroBitHeapAllocator.cpp @@ -127,20 +127,23 @@ microbit_create_sd_heap(HeapDefinition &heap) { #if CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD) - // OK, see how much of the RAM assigned to Soft Device we can reclaim. #if CONFIG_ENABLED(MICROBIT_BLE_ENABLED) + // Reclaim RAM from unusused areas on the BLE stack GATT table. heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_ENABLED; heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT; #else + // Reclaim all the RAM normally reserved for the Nordic SoftDevice. heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_DISABLED; heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT; #endif microbit_initialise_heap(heap); - return MICROBIT_OK; #else - return MICROBIT_NOT_SUPPORTED; + heap.heap_start = 0; + heap.heap_end = 0; #endif + + return MICROBIT_OK; } int diff --git a/source/MicroBitMessageBus.cpp b/source/MicroBitMessageBus.cpp index bf85e52..7fcd28d 100644 --- a/source/MicroBitMessageBus.cpp +++ b/source/MicroBitMessageBus.cpp @@ -7,7 +7,7 @@ #include "MicroBit.h" /** - * Constructor. + * Constructor. * Create a new Message Bus. */ MicroBitMessageBus::MicroBitMessageBus() @@ -30,7 +30,7 @@ void async_callback(void *param) // OK, now we need to decide how to behave depending on our configuration. // If this a fiber f already active within this listener then check our - // configuration to determine the correct course of action. + // configuration to determine the correct course of action. // if (listener->flags & MESSAGE_BUS_LISTENER_BUSY) @@ -47,7 +47,7 @@ void async_callback(void *param) } } - // Determine the calling convention for the callback, and invoke... + // Determine the calling convention for the callback, and invoke... // C++ is really bad at this! Especially as the ARM compiler is yet to support C++ 11 :-/ // Record that we have a fiber going into this listener... @@ -91,7 +91,7 @@ void async_callback(void *param) * Queue the given event for processing at a later time. * Add the given event at the tail of our queue. * - * @param The event to queue. + * @param The event to queue. */ void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) { @@ -99,7 +99,7 @@ void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) MicroBitEventQueueItem *prev = evt_queue_tail; - // Now process all handler regsitered as URGENT. + // Now process all handler regsitered as URGENT. // These pre-empt the queue, and are useful for fast, high priority services. processingComplete = this->process(evt, true); @@ -125,7 +125,7 @@ void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) { item->next = evt_queue_head; evt_queue_head = item; - } + } else { item->next = prev->next; @@ -142,18 +142,18 @@ void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) /** * Extract the next event from the front of the event queue (if present). - * @return + * @return * - * @param The event to queue. + * @param The event to queue. */ MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent() { MicroBitEventQueueItem *item = NULL; __disable_irq(); - + if (evt_queue_head != NULL) - { + { item = evt_queue_head; evt_queue_head = item->next; @@ -162,7 +162,7 @@ MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent() queueLength--; } - + __enable_irq(); @@ -188,7 +188,7 @@ int MicroBitMessageBus::deleteMarkedListeners() { if (p == NULL) listeners = l->next; - else + else p->next = l->next; // delete the listener. @@ -212,7 +212,7 @@ int MicroBitMessageBus::deleteMarkedListeners() * Periodic callback from MicroBit. * Process at least one event from the event queue, if it is not empty. * We then continue processing events until something appears on the runqueue. - */ + */ void MicroBitMessageBus::idleTick() { // Clear out any listeners marked for deletion @@ -231,7 +231,7 @@ void MicroBitMessageBus::idleTick() // If we have created some useful work to do, we stop processing. // This helps to minimise the number of blocked fibers we create at any point in time, therefore - // also reducing the RAM footprint. + // also reducing the RAM footprint. if(!scheduler_runqueue_empty()) break; @@ -242,26 +242,26 @@ void MicroBitMessageBus::idleTick() /** * Indicates whether or not we have any background work to do. - * @ return 1 if there are any events waitingto be processed, 0 otherwise. + * @ return 1 if there are any events waitingto be processed, 0 otherwise. */ int MicroBitMessageBus::isIdleCallbackNeeded() { - return !(evt_queue_head == NULL); + return !(evt_queue_head == NULL); } /** * Queues the given event to be sent to all registered recipients. * - * @param The event to send. + * @param The event to send. * * n.b. THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE... * * Example: - * @code + * @code * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,CREATE_ONLY); * evt.fire(); * - * //OR YOU CAN DO THIS... + * //OR YOU CAN DO THIS... * MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN); * @endcode */ @@ -299,7 +299,7 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) { l->evt = evt; - // OK, if this handler has regisitered itself as non-blocking, we just execute it directly... + // OK, if this handler has regisitered itself as non-blocking, we just execute it directly... // This is normally only done for trusted system components. // Otherwise, we invoke it in a 'fork on block' context, that will automatically create a fiber // should the event handler attempt a blocking operation, but doesn't have the overhead @@ -323,11 +323,11 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) /** * Register a listener function. - * - * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * + * @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. + * @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 handler The function to call when an event is received. @@ -335,7 +335,7 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) * @return MICROBIT_OK on success MICROBIT_INVALID_PARAMETER * * Example: - * @code + * @code * void onButtonBClick(MicroBitEvent evt) * { * //do something @@ -344,7 +344,7 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) * @endcode */ -int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent), uint16_t flags) +int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent), uint16_t flags) { if (handler == NULL) return MICROBIT_INVALID_PARAMETER; @@ -360,7 +360,7 @@ int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent) } -int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg, uint16_t flags) +int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg, uint16_t flags) { if (handler == NULL) return MICROBIT_INVALID_PARAMETER; @@ -378,20 +378,20 @@ int MicroBitMessageBus::listen(int id, int value, void (*handler)(MicroBitEvent, /** * Unregister a listener function. * Listners are identified by the Event ID, Event VALUE and handler registered using listen(). - * + * * @param id The Event ID used to register the listener. * @param value The Event VALUE used to register the listener. * @param handler The function used to register the listener. * * * Example: - * @code + * @code * void onButtonBClick() * { * //do something * } * - * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); * @endcode */ int MicroBitMessageBus::ignore(int id, int value, void (*handler)(MicroBitEvent)) @@ -408,20 +408,20 @@ int MicroBitMessageBus::ignore(int id, int value, void (*handler)(MicroBitEvent) /** * Unregister a listener function. * Listners are identified by the Event ID, Event VALUE and handler registered using listen(). - * + * * @param id The Event ID used to register the listener. * @param value The Event VALUE used to register the listener. * @param handler The function used to register the listener. * * * Example: - * @code + * @code * void onButtonBClick(void *arg) * { * //do something * } * - * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * uBit.MessageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); * @endcode */ int MicroBitMessageBus::ignore(int id, int value, void (*handler)(MicroBitEvent, void*)) @@ -464,7 +464,7 @@ int MicroBitMessageBus::add(MicroBitListener *newListener) if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb)) { - // We have a perfect match for this event listener already registered. + // We have a perfect match for this event listener already registered. // If it's marked for deletion, we simply resurrect the listener, and we're done. // Either way, we return an error code, as the *new* listener should be released... if(l->flags & MESSAGE_BUS_LISTENER_DELETING) @@ -484,7 +484,7 @@ int MicroBitMessageBus::add(MicroBitListener *newListener) return MICROBIT_OK; } - // We maintain an ordered list of listeners. + // We maintain an ordered list of listeners. // The 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... @@ -544,7 +544,7 @@ int MicroBitMessageBus::remove(MicroBitListener *listener) { if ((listener->flags & MESSAGE_BUS_LISTENER_METHOD) == (l->flags & MESSAGE_BUS_LISTENER_METHOD)) { - if(((listener->flags & MESSAGE_BUS_LISTENER_METHOD) && (*l->cb_method == *listener->cb_method)) || + if(((listener->flags & MESSAGE_BUS_LISTENER_METHOD) && (*l->cb_method == *listener->cb_method)) || ((!(listener->flags & MESSAGE_BUS_LISTENER_METHOD) && l->cb == listener->cb))) { if ((listener->id == MICROBIT_ID_ANY || listener->id == l->id) && (listener->value == MICROBIT_EVT_ANY || listener->value == l->value)) @@ -586,3 +586,10 @@ MicroBitListener* MicroBitMessageBus::elementAt(int n) return l; } +/** + * Destructor for MicroBitMessageBus, so that we deregister ourselves as an idleComponent + */ +MicroBitMessageBus::~MicroBitMessageBus() +{ + uBit.removeIdleComponent(this); +} diff --git a/source/MicroBitPin.cpp b/source/MicroBitPin.cpp index 857590e..dc8e6b7 100644 --- a/source/MicroBitPin.cpp +++ b/source/MicroBitPin.cpp @@ -2,29 +2,29 @@ #include "MicroBitPin.h" /** - * Constructor. + * 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 + * @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 +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; - + } /** @@ -45,16 +45,16 @@ void MicroBitPin::disconnect() 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)->release(); - } + ((DynamicPwm *)pin)->release(); + } if (status & IO_STATUS_TOUCH_IN) delete ((MicroBitButton *)pin); - + this->pin = NULL; this->status = status & IO_STATUS_EVENTBUS_ENABLED; //retain event bus status } @@ -62,9 +62,9 @@ void MicroBitPin::disconnect() /** * 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 + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); * P0.setDigitalValue(1); // P0 is now HI * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED @@ -80,14 +80,14 @@ int MicroBitPin::setDigitalValue(int value) // Ensure we have a valid value. if (value < 0 || value > 1) return MICROBIT_INVALID_PARAMETER; - + // Move into a Digital input state if necessary. if (!(status & IO_STATUS_DIGITAL_OUT)){ - disconnect(); + disconnect(); pin = new DigitalOut(name); status |= IO_STATUS_DIGITAL_OUT; } - + // Write the value. ((DigitalOut *)pin)->write(value); @@ -97,9 +97,9 @@ int MicroBitPin::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 if input is LO, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); * P0.getDigitalValue(); // P0 is either 0 or 1; * @endcode @@ -109,20 +109,32 @@ int MicroBitPin::getDigitalValue() //check if this pin has a digital mode... if(!(PIN_CAPABILITY_DIGITAL & capability)) return MICROBIT_NOT_SUPPORTED; - + // Move into a Digital input state if necessary. if (!(status & IO_STATUS_DIGITAL_IN)){ - disconnect(); - pin = new DigitalIn(name,PullDown); + disconnect(); + pin = new DigitalIn(name,PullDown); status |= IO_STATUS_DIGITAL_IN; } - + return ((DigitalIn *)pin)->read(); } +int MicroBitPin::obtainAnalogChannel() +{ + // 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; + } + + return MICROBIT_OK; +} + /** * Configures this IO pin as an analog/pwm output, and change the output value to the given level. - * @param value the level to set on the output pin, in the range 0 - 1024 + * @param value the level to set on the output pin, in the range 0 - 1023 * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED * if the given pin does not have analog capability. */ @@ -131,34 +143,65 @@ int MicroBitPin::setAnalogValue(int value) //check if this pin has an analogue mode... if(!(PIN_CAPABILITY_ANALOG & capability)) return MICROBIT_NOT_SUPPORTED; - - //sanitise the brightness level + + //sanitise the level value if(value < 0 || value > MICROBIT_PIN_MAX_OUTPUT) return MICROBIT_INVALID_PARAMETER; - + 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); + + //obtain use of the DynamicPwm instance, if it has changed / configure if we do not have one + if(obtainAnalogChannel() == MICROBIT_OK) + return ((DynamicPwm *)pin)->write(level); return MICROBIT_OK; } +/** + * Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms, + * and sets the duty cycle between 0.05 and 0.1 (i.e. 5% or 10%) based on the value given to this method. + * + * A value of 180 sets the duty cycle to be 10%, and a value of 0 sets the duty cycle to be 5% by default. + * + * This range can be modified to fine tune, and also tolerate different servos. + * + * @param value the level to set on the output pin, in the range 0 - 180 + * @param range which gives the span of possible values the i.e. lower and upper bounds center ± range/2 (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_RANGE) + * @param center the center point from which to calculate the lower and upper bounds (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_CENTER) + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED + * if the given pin does not have analog capability. + */ +int MicroBitPin::setServoValue(int value, int range, int center) +{ + //check if this pin has an analogue mode... + if(!(PIN_CAPABILITY_ANALOG & capability)) + return MICROBIT_NOT_SUPPORTED; + + //sanitise the servo level + if(value < 0 || range < 1 || center < 1) + return MICROBIT_INVALID_PARAMETER; + + //clip - just in case + if(value > MICROBIT_PIN_MAX_SERVO_RANGE) + value = MICROBIT_PIN_MAX_SERVO_RANGE; + + //calculate the lower bound based on the midpoint + int lower = (center - (range / 2)) * 1000; + + value = value * 1000; + + //add the percentage of the range based on the value between 0 and 180 + int scaled = lower + (range * (value / MICROBIT_PIN_MAX_SERVO_RANGE)); + + return setServoPulseUs(scaled / 1000); +} /** * Configures this IO pin as an analogue input (if necessary and possible). * @return the current analogue level on the pin, in the range 0 - 1024, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); * P0.getAnalogValue(); // P0 is a value in the range of 0 - 1024 * @endcode @@ -168,14 +211,14 @@ int MicroBitPin::getAnalogValue() //check if this pin has an analogue mode... if(!(PIN_CAPABILITY_ANALOG & capability)) return MICROBIT_NOT_SUPPORTED; - + // Move into an analogue input state if necessary. if (!(status & IO_STATUS_ANALOG_IN)){ - disconnect(); + disconnect(); pin = new AnalogIn(name); status |= IO_STATUS_ANALOG_IN; } - + //perform a read! return ((AnalogIn *)pin)->read_u16(); } @@ -219,14 +262,14 @@ int MicroBitPin::isAnalog() /** * 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 if not, or MICROBIT_NOT_SUPPORTED if this pin does not support touch capability. - * + * * Example: - * @code + * @code * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); * if(P0.isTouched()) * { * uBit.display.clear(); - * } + * } * @endcode */ int MicroBitPin::isTouched() @@ -234,22 +277,53 @@ int MicroBitPin::isTouched() //check if this pin has a touch mode... if(!(PIN_CAPABILITY_TOUCH & capability)) return MICROBIT_NOT_SUPPORTED; - + // Move into a touch input state if necessary. if (!(status & IO_STATUS_TOUCH_IN)){ - disconnect(); - pin = new MicroBitButton(id, name); + disconnect(); + pin = new MicroBitButton(id, name); status |= IO_STATUS_TOUCH_IN; } - + return ((MicroBitButton *)pin)->isPressed(); } +/** + * Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms, + * and sets the pulse width, based on the value it is given + * + * @param pulseWidth the desired pulse width in microseconds. + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED + * if the given pin does not have analog capability. + */ +int MicroBitPin::setServoPulseUs(int pulseWidth) +{ + //check if this pin has an analogue mode... + if(!(PIN_CAPABILITY_ANALOG & capability)) + return MICROBIT_NOT_SUPPORTED; + + //sanitise the pulse width + if(pulseWidth < 0) + return MICROBIT_INVALID_PARAMETER; + + //Check we still have the control over the DynamicPwm instance + if(obtainAnalogChannel() == MICROBIT_OK) + { + //check if the period is set to 20ms + if(((DynamicPwm *)pin)->getPeriodUs() != MICROBIT_DEFAULT_PWM_PERIOD) + ((DynamicPwm *)pin)->setPeriodUs(MICROBIT_DEFAULT_PWM_PERIOD); + + ((DynamicPwm *)pin)->pulsewidth_us(pulseWidth); + } + + return MICROBIT_OK; +} + /** * Configures the PWM period of the analog output to the given value. * * @param period The new period for the analog output in microseconds. - * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the * given pin is not configured as an analog output. */ int MicroBitPin::setAnalogPeriodUs(int period) @@ -257,18 +331,42 @@ int MicroBitPin::setAnalogPeriodUs(int period) if (!(status & IO_STATUS_ANALOG_OUT)) return MICROBIT_NOT_SUPPORTED; - ((DynamicPwm *)pin)->setPeriodUs(period); - return MICROBIT_OK; + return ((DynamicPwm *)pin)->setPeriodUs(period); } /** * Configures the PWM period of the analog output to the given value. * * @param period The new period for the analog output in microseconds. - * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the * given pin is not configured as an analog output. - */ + */ int MicroBitPin::setAnalogPeriod(int period) { return setAnalogPeriodUs(period*1000); } + +/** + * Retrieves the PWM period of the analog output. + * + * @return the period on success, or MICROBIT_NOT_SUPPORTED if the + * given pin is not configured as an analog output. + */ +int MicroBitPin::getAnalogPeriodUs() +{ + if (!(status & IO_STATUS_ANALOG_OUT)) + return MICROBIT_NOT_SUPPORTED; + + return ((DynamicPwm *)pin)->getPeriodUs(); +} + +/** + * Retrieves the PWM period of the analog output. + * + * @return the period on success, or MICROBIT_NOT_SUPPORTED if the + * given pin is not configured as an analog output. + */ +int MicroBitPin::getAnalogPeriod() +{ + return getAnalogPeriodUs()/1000; +} diff --git a/source/MicroBitSuperMain.cpp b/source/MicroBitSuperMain.cpp index ed1a790..0885f95 100644 --- a/source/MicroBitSuperMain.cpp +++ b/source/MicroBitSuperMain.cpp @@ -10,11 +10,10 @@ int main() // Bring up soft reset button. resetButton.mode(PullUp); resetButton.fall(microbit_reset); - + #if CONFIG_ENABLED(MICROBIT_DBG) // For diagnostics. Gives time to open the console window. :-) - uBit.serial.baud(115200); for (int i=3; i>0; i--) { uBit.serial.printf("=== SUPERMAIN: Starting in %d ===\n", i); @@ -30,56 +29,40 @@ int main() // Bring up fiber scheduler scheduler_init(); - + // Bring up random number generator, BLE, display and system timers. uBit.init(); // Provide time for all threaded initialisers to complete. uBit.sleep(100); -#if CONFIG_ENABLED(MICROBIT_BLE_BLUEZONE) +#if CONFIG_ENABLED(MICROBIT_BLE_PAIRING_MODE) // 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) { - // OK - we need to enter BLUE ZONE mode. - // Test to see if BLE and the necessary services have been brought up already. - // If not, start them. + // Bring up the BLE stack if it isn't alredy done. if (!uBit.ble) - { - uBit.ble = new BLEDevice(); - uBit.ble->init(); - uBit.ble->onDisconnection(bleDisconnectionCallback); + uBit.bleManager.init(uBit.getName(), uBit.getSerial()); - // Ensure we're advertising. - uBit.ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); - uBit.ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)MICROBIT_BLE_DEVICE_NAME, sizeof(MICROBIT_BLE_DEVICE_NAME)); - uBit.ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); - uBit.ble->setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(200)); - uBit.ble->startAdvertising(); - } - - if (!uBit.ble_firmware_update_service) - uBit.ble_firmware_update_service = new MicroBitDFUService(*uBit.ble); - - // enter BLUE ZONE mode. - uBit.ble_firmware_update_service->pair(); + // Enter pairing mode, using the LED matrix for any necessary pairing operations + uBit.bleManager.pairingMode(uBit.display); } } #endif - + app_main(); // If app_main exits, there may still be other fibers running, registered event handlers etc. // Simply release this fiber, which will mean we enter the scheduler. Worse case, we then // sit in the idle task forever, in a power efficient sleep. release_fiber(); - + // We should never get here, but just in case. while(1); } diff --git a/source/ble-services/MicroBitAccelerometerService.cpp b/source/ble-services/MicroBitAccelerometerService.cpp index d1d2631..b293ea5 100644 --- a/source/ble-services/MicroBitAccelerometerService.cpp +++ b/source/ble-services/MicroBitAccelerometerService.cpp @@ -29,7 +29,11 @@ MicroBitAccelerometerService::MicroBitAccelerometerService(BLEDevice &_ble) : accelerometerDataCharacteristicBuffer[1] = 0; accelerometerDataCharacteristicBuffer[2] = 0; accelerometerPeriodCharacteristicBuffer = uBit.accelerometer.getPeriod(); - + + // Set default security requirements + accelerometerDataCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + accelerometerPeriodCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + GattCharacteristic *characteristics[] = {&accelerometerDataCharacteristic, &accelerometerPeriodCharacteristic}; GattService service(MicroBitAccelerometerServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); @@ -69,12 +73,12 @@ void MicroBitAccelerometerService::accelerometerUpdate(MicroBitEvent) { if (ble.getGapState().connected) { - accelerometerDataCharacteristicBuffer[0] = uBit.accelerometer.getX(); - accelerometerDataCharacteristicBuffer[1] = uBit.accelerometer.getY(); - accelerometerDataCharacteristicBuffer[2] = uBit.accelerometer.getZ(); + accelerometerDataCharacteristicBuffer[0] = uBit.accelerometer.getX(); + accelerometerDataCharacteristicBuffer[1] = uBit.accelerometer.getY(); + accelerometerDataCharacteristicBuffer[2] = uBit.accelerometer.getZ(); - ble.gattServer().notify(accelerometerDataCharacteristicHandle,(uint8_t *)accelerometerDataCharacteristicBuffer, sizeof(accelerometerDataCharacteristicBuffer)); - } + ble.gattServer().notify(accelerometerDataCharacteristicHandle,(uint8_t *)accelerometerDataCharacteristicBuffer, sizeof(accelerometerDataCharacteristicBuffer)); + } } const uint8_t MicroBitAccelerometerServiceUUID[] = { diff --git a/source/ble-services/MicroBitBLEManager.cpp b/source/ble-services/MicroBitBLEManager.cpp new file mode 100644 index 0000000..5534ae5 --- /dev/null +++ b/source/ble-services/MicroBitBLEManager.cpp @@ -0,0 +1,340 @@ +#include "MicroBit.h" + + +/* The underlying Nordic libraries that support BLE do not compile cleanly with the stringent GCC settings we employ + * If we're compiling under GCC, then we suppress any warnings generated from this code (but not the rest of the DAL) + * The ARM cc compiler is more tolerant. We don't test __GNUC__ here to detect GCC as ARMCC also typically sets this + * as a compatability option, but does not support the options used... + */ +#if !defined(__arm) +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include "ble.h" + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + +#define MICROBIT_BLE_ENABLE_BONDING true +#define MICROBIT_BLE_REQUIRE_MITM true + + +#define MICROBIT_PAIRING_MODE_TIMEOUT 90 +#define MICROBIT_PAIRING_FADE_SPEED 4 + + +const char* MICROBIT_BLE_MANUFACTURER = "The Cast of W1A"; +const char* MICROBIT_BLE_MODEL = "BBC micro:bit"; +const char* MICROBIT_BLE_HARDWARE_VERSION = "1.0"; +const char* MICROBIT_BLE_FIRMWARE_VERSION = MICROBIT_DAL_VERSION; +const char* MICROBIT_BLE_SOFTWARE_VERSION = NULL; + +/* + * Many of the mbed interfaces we need to use only support callbacks to plain C functions, rather than C++ methods. + * So, we maintain a pointer to the MicroBitBLEManager that's in use. Ths way, we can still access resources on the micro:bit + * whilst keeping the code modular. + */ +static MicroBitBLEManager *manager = NULL; + +/** + * Callback when a BLE GATT disconnect occurs. + */ +static void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *reason) +{ + (void) reason; /* -Wunused-param */ + + if (manager) + manager->onDisconnectionCallback(); + +} + +static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey) +{ + (void) handle; /* -Wunused-param */ + + ManagedString passKey((const char *)passkey, SecurityManager::PASSKEY_LEN); + + if (manager) + manager->pairingRequested(passKey); +} + +static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status) +{ + (void) handle; /* -Wunused-param */ + + if (manager) + manager->pairingComplete(status == SecurityManager::SEC_STATUS_SUCCESS); +} + +/** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * Note that the BLE stack *cannot* be brought up in a static context. + * (the software simply hangs or corrupts itself). + * Hence, we bring it up in an explicit init() method, rather than in the constructor. + */ +MicroBitBLEManager::MicroBitBLEManager() +{ + manager = this; + this->ble = NULL; + this->pairingStatus = 0; +} + +/** + * Method that is called whenever a BLE device disconnects from us. + * The nordic stack stops dvertising whenever a device connects, so we use + * this callback to restart advertising. + */ +void MicroBitBLEManager::onDisconnectionCallback() +{ + if(ble) + ble->startAdvertising(); +} + +/** + * 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 MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber) +{ + ManagedString BLEName("BBC micro:bit"); + + this->deviceName = deviceName; + + // Start the BLE stack. + ble = new BLEDevice(); + ble->init(); + + // automatically restart advertising after a device disconnects. + ble->onDisconnection(bleDisconnectionCallback); + + // configure the stack to hold on to CPU during critical timing events. + // mbed-classic performs __disabe_irq calls in its timers, which can cause MIC failures + // on secure BLE channels. + ble_common_opt_radio_cpu_mutex_t opt; + opt.enable = 1; + sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt); + +#if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES) + // Configure for private addresses, so kids' behaviour can't be easily tracked. + ble->setAddress(Gap::ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, NULL); +#endif + + // Setup our security requirements. + ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback); + ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback); + ble->securityManager().init(MICROBIT_BLE_ENABLE_BONDING, MICROBIT_BLE_REQUIRE_MITM, SecurityManager::IO_CAPS_DISPLAY_ONLY); + + // Bring up any configured auxiliary services. +#if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE) + new MicroBitDFUService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE) + DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, serialNumber.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE) + new MicroBitEventService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_LED_SERVICE) + new MicroBitLEDService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_ACCELEROMETER_SERVICE) + new MicroBitAccelerometerService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_MAGNETOMETER_SERVICE) + new MicroBitMagnetometerService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_BUTTON_SERVICE) + new MicroBitButtonService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_IO_PIN_SERVICE) + new MicroBitIOPinService(*ble); +#endif + +#if CONFIG_ENABLED(MICROBIT_BLE_TEMPERATURE_SERVICE) + new MicroBitTemperatureService(*ble); +#endif + + // Configure for high speed mode where possible. + Gap::ConnectionParams_t fast; + ble->getPreferredConnectionParams(&fast); + fast.minConnectionInterval = 8; // 10 ms + fast.maxConnectionInterval = 16; // 20 ms + fast.slaveLatency = 0; + ble->setPreferredConnectionParams(&fast); + + // Setup advertising. + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length()); + ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); + ble->setAdvertisingInterval(200); + ble->startAdvertising(); +} + +/** + * A request to pair has been received from a BLE device. + * If we're in pairing mode, display the passkey to the user. + */ +void MicroBitBLEManager::pairingRequested(ManagedString passKey) +{ + this->passKey = passKey; + this->pairingStatus = MICROBIT_BLE_PAIR_REQUEST; +} + +/** + * A pairing request has been sucesfully completed. + * If we're in pairing mode, display feedback to the user. + */ +void MicroBitBLEManager::pairingComplete(bool success) +{ + this->pairingStatus = MICROBIT_BLE_PAIR_COMPLETE; + + if(success) + this->pairingStatus |= MICROBIT_BLE_PAIR_SUCCESSFUL; +} + +/** + * Enter pairing mode. This is mode is called to initiate pairing, and to enable FOTA programming + * of the micro:bit in cases where BLE is disabled during normal operation. + */ +void MicroBitBLEManager::pairingMode(MicroBitDisplay &display) +{ + ManagedString namePrefix("BBC micro:bit ["); + ManagedString namePostfix("]"); + ManagedString BLEName = namePrefix + deviceName + namePostfix; + + ManagedString msg("PAIRING MODE!"); + + int timeInPairingMode = 0; + int brightness = 255; + int fadeDirection = 0; + + // Update the advertised name of this micro:bit to include the device name + ble->clearAdvertisingPayload(); + + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); + ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length()); + ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); + ble->setAdvertisingInterval(200); + ble->startAdvertising(); + + // Stop any running animations on the display + display.stopAnimation(); + display.scroll(msg); + + // Display our name, visualised as a histogram in the display to aid identification. + showNameHistogram(display); + + while(1) + { + if (pairingStatus & MICROBIT_BLE_PAIR_REQUEST) + { + timeInPairingMode = 0; + MicroBitImage arrow("0,0,255,0,0\n0,255,0,0,0\n255,255,255,255,255\n0,255,0,0,0\n0,0,255,0,0\n"); + display.print(arrow,0,0,0); + + if (fadeDirection == 0) + brightness -= MICROBIT_PAIRING_FADE_SPEED; + else + brightness += MICROBIT_PAIRING_FADE_SPEED; + + if (brightness <= 40) + display.clear(); + + if (brightness <= 0) + fadeDirection = 1; + + if (brightness >= 255) + fadeDirection = 0; + + if (uBit.buttonA.isPressed()) + { + pairingStatus &= ~MICROBIT_BLE_PAIR_REQUEST; + pairingStatus |= MICROBIT_BLE_PAIR_PASSCODE; + } + } + + if (pairingStatus & MICROBIT_BLE_PAIR_PASSCODE) + { + timeInPairingMode = 0; + display.setBrightness(255); + for (int i=0; i= MICROBIT_PAIRING_MODE_TIMEOUT * 30) + microbit_reset(); + } +} + +/** + * Displays the device's ID code as a histogram on the LED matrix display. + */ +void MicroBitBLEManager::showNameHistogram(MicroBitDisplay &display) +{ + uint32_t n = NRF_FICR->DEVICEID[1]; + int ld = 1; + int d = MICROBIT_DFU_HISTOGRAM_HEIGHT; + int h; + + display.clear(); + for (int i=0; i microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte); - - // Read/Write characteristic to enable unlocking and discovery of the MicroBit's flashcode. - GattCharacteristic 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; + GattCharacteristic microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte, 0, sizeof(uint8_t), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); controlByte = 0x00; - flashCode = 0x00; - - GattCharacteristic *characteristics[] = {µBitDFUServiceControlCharacteristic, µBitDFUServiceFlashCodeCharacteristic}; + + // Set default security requirements + microBitDFUServiceControlCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + + GattCharacteristic *characteristics[] = {µBitDFUServiceControlCharacteristic}; GattService service(MicroBitDFUServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); ble.addService(service); microBitDFUServiceControlCharacteristicHandle = microBitDFUServiceControlCharacteristic.getValueHandle(); - microBitDFUServiceFlashCodeCharacteristicHandle = microBitDFUServiceFlashCodeCharacteristic.getValueHandle(); + ble.gattServer().write(microBitDFUServiceControlCharacteristicHandle, &controlByte, sizeof(uint8_t)); ble.gattServer().onDataWritten(this, &MicroBitDFUService::onDataWritten); } -void MicroBitDFUService::onButtonA(MicroBitEvent) -{ - if (flashCodeRequested) - { - releaseFlashCode(); - uBit.display.scroll(""); - showTick(); - flashCodeRequested = false; - authenticated = true; - } -} - -void MicroBitDFUService::onButtonB(MicroBitEvent) -{ - uBit.display.scroll("VERSION: TODO"); - showNameHistogram(); -} - /** - * 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 MicroBitDFUService::pair() -{ - uBit.MessageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, this, &MicroBitDFUService::onButtonA); - uBit.MessageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, this, &MicroBitDFUService::onButtonB); - - uBit.display.scroll("BLUE ZONE..."); - showNameHistogram(); - - while(1) - { - if (flashCodeRequested) - uBit.display.scroll("PAIR?"); - - // If our peer disconnects, drop all state. - if ((authenticated || flashCodeRequested) && !ble.getGapState().connected) - { - authenticated = false; - flashCodeRequested = false; - flashCode = 0x00; - } - - uBit.sleep(500); - } -} - - -/** - * Callback. Invoked when any of our attributes are written via BLE. - */ + * Callback. Invoked when any of our attributes are written via BLE. + */ void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params) { if (params->handle == microBitDFUServiceControlCharacteristicHandle) { - if (params->len < 1) - return; - - switch(params->data[0]) + if(params->len > 0 && params->data[0] == MICROBIT_DFU_OPCODE_START_DFU) { - case MICROBIT_DFU_OPCODE_START_DFU: - - if (authenticated) - { - uBit.display.scroll(""); - uBit.display.clear(); + uBit.display.stopAnimation(); + uBit.display.clear(); #if CONFIG_ENABLED(MICROBIT_DBG) - uBit.serial.printf(" ACTIVATING BOOTLOADER.\n"); + uBit.serial.printf(" ACTIVATING BOOTLOADER.\n"); #endif - bootloader_start(); - } - - break; - - case MICROBIT_DFU_OPCODE_START_PAIR: -#if CONFIG_ENABLED(MICROBIT_DBG) - uBit.serial.printf(" PAIRING REQUESTED.\n"); -#endif - flashCodeRequested = true; - break; - + + // Perform an explicit disconnection to assist our peer to reconnect to the DFU service + ble.disconnect(Gap::LOCAL_HOST_TERMINATED_CONNECTION); + + // Call bootloader_start implicitly trough a event handler call + // it is a work around for bootloader_start not being public in sdk 8.1 + ble_dfu_t p_dfu; + ble_dfu_evt_t p_evt; + + p_dfu.conn_handle = params->connHandle; + p_evt.ble_dfu_evt_type = BLE_DFU_START; + + dfu_app_on_dfu_evt(&p_dfu, &p_evt); } } - - if (params->handle == microBitDFUServiceFlashCodeCharacteristicHandle) - { - if (params->len >= 4) - { - uint32_t lockCode=0; - memcpy(&lockCode, params->data, 4); - if (lockCode == NRF_FICR->DEVICEID[0]) - { -#if CONFIG_ENABLED(MICROBIT_DBG) - uBit.serial.printf("MicroBitDFU: FLASHCODE AUTHENTICATED\n"); -#endif - authenticated = true; - }else{ - authenticated = false; - } - } - } -} - -/** - * Displays the device's ID code as a histogram on the LED matrix display. - */ -void MicroBitDFUService::showTick() -{ - uBit.display.stopAnimation(); - - 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() -{ - uBit.display.stopAnimation(); - - 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.gattServer().notify(microBitDFUServiceFlashCodeCharacteristicHandle,(uint8_t *)&flashCode, sizeof(uint32_t)); -} - -/** - * UUID definitions for BLE Services and Characteristics. - */ + * UUID definitions for BLE Services and Characteristics. + */ const uint8_t MicroBitDFUServiceUUID[] = { 0xe9,0x5d,0x93,0xb0,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 @@ -216,6 +108,3 @@ const uint8_t MicroBitDFUServiceControlCharacteristicUUID[] = { 0xe9,0x5d,0x93,0xb1,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 }; -const uint8_t MicroBitDFUServiceFlashCodeCharacteristicUUID[] = { - 0xe9,0x5d,0x93,0xb2,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 -}; diff --git a/source/ble-services/MicroBitEventService.cpp b/source/ble-services/MicroBitEventService.cpp index 6f76f04..a7ac3b0 100644 --- a/source/ble-services/MicroBitEventService.cpp +++ b/source/ble-services/MicroBitEventService.cpp @@ -34,6 +34,12 @@ MicroBitEventService::MicroBitEventService(BLEDevice &_ble) : messageBusListenerOffset = 0; + // Set default security requirements + microBitEventCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + clientEventCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + clientRequirementsCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + microBitRequirementsCharacteristic->requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + GattCharacteristic *characteristics[] = {µBitEventCharacteristic, &clientEventCharacteristic, &clientRequirementsCharacteristic, microBitRequirementsCharacteristic}; GattService service(MicroBitEventServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); diff --git a/source/ble-services/MicroBitIOPinService.cpp b/source/ble-services/MicroBitIOPinService.cpp index a48e5cd..167c7e2 100644 --- a/source/ble-services/MicroBitIOPinService.cpp +++ b/source/ble-services/MicroBitIOPinService.cpp @@ -30,6 +30,11 @@ MicroBitIOPinService::MicroBitIOPinService(BLEDevice &_ble) : ioPinServiceADCharacteristicBuffer = 0; ioPinServiceIOCharacteristicBuffer = 0; memset(ioPinServiceIOData, 0, sizeof(ioPinServiceIOData)); + + // Set default security requirements + ioPinServiceADCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + ioPinServiceIOCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + ioPinServiceDataCharacteristic->requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); GattCharacteristic *characteristics[] = {&ioPinServiceADCharacteristic, &ioPinServiceIOCharacteristic, ioPinServiceDataCharacteristic}; GattService service(MicroBitIOPinServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); diff --git a/source/ble-services/MicroBitLEDService.cpp b/source/ble-services/MicroBitLEDService.cpp index 5295615..0a767d7 100644 --- a/source/ble-services/MicroBitLEDService.cpp +++ b/source/ble-services/MicroBitLEDService.cpp @@ -32,6 +32,11 @@ MicroBitLEDService::MicroBitLEDService(BLEDevice &_ble) : matrixCharacteristic.setReadAuthorizationCallback(this, &MicroBitLEDService::onDataRead); + // Set default security requirements + matrixCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + textCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + scrollingSpeedCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + GattCharacteristic *characteristics[] = {&matrixCharacteristic, &textCharacteristic, &scrollingSpeedCharacteristic}; GattService service(MicroBitLEDServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); diff --git a/source/ble-services/MicroBitMagnetometerService.cpp b/source/ble-services/MicroBitMagnetometerService.cpp index 067b7bd..581a28f 100644 --- a/source/ble-services/MicroBitMagnetometerService.cpp +++ b/source/ble-services/MicroBitMagnetometerService.cpp @@ -33,7 +33,12 @@ MicroBitMagnetometerService::MicroBitMagnetometerService(BLEDevice &_ble) : magnetometerDataCharacteristicBuffer[2] = 0; magnetometerBearingCharacteristicBuffer = 0; magnetometerPeriodCharacteristicBuffer = uBit.compass.getPeriod(); - + + // Set default security requirements + magnetometerDataCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + magnetometerBearingCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + magnetometerPeriodCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + GattCharacteristic *characteristics[] = {&magnetometerDataCharacteristic, &magnetometerBearingCharacteristic, &magnetometerPeriodCharacteristic}; GattService service(MicroBitMagnetometerServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); diff --git a/source/ble-services/MicroBitTemperatureService.cpp b/source/ble-services/MicroBitTemperatureService.cpp index 241c9c5..a4440f6 100644 --- a/source/ble-services/MicroBitTemperatureService.cpp +++ b/source/ble-services/MicroBitTemperatureService.cpp @@ -26,7 +26,11 @@ MicroBitTemperatureService::MicroBitTemperatureService(BLEDevice &_ble) : // Initialise our characteristic values. temperatureDataCharacteristicBuffer = 0; temperaturePeriodCharacteristicBuffer = uBit.thermometer.getPeriod(); - + + // Set default security requirements + temperatureDataCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + temperaturePeriodCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); + GattCharacteristic *characteristics[] = {&temperatureDataCharacteristic, &temperaturePeriodCharacteristic}; GattService service(MicroBitTemperatureServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *));