updates
Dependencies: BLE_API mbed-dev-bin nRF51822
Fork of microbit-dal-eddystone by
Revision 1:8aa5cdb4ab67, committed 2016-04-07
- Comitter:
- Jonathan Austin
- Date:
- Thu Apr 07 01:33:22 2016 +0100
- Parent:
- 0:fb15f7887843
- Child:
- 2:557d9ac9e959
- Commit message:
- Synchronized with git rev 55cb9199
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AUTHORS Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,9 @@ +Joe Finney (@finneyj) +James Devine (@jamesadevine) +Martin Woolley (@bluetooth-mdw) +Michał Moskal (@mmoskal) +Robert May (@remay) +Jonathan Protzenko (@msprotz) +James Sheppard (@jamessheppard) +Damien George (@dpgeorge) +Mathew Else (@matthewelse)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,39 @@ +# microbit-dal + +The core set of drivers, mechanisms and types that make up the micro:bit runtime. + +## Overview + +The micro:bit runtime provides an easy to use environment for programming the BBC micro:bit in the C/C++ language, written by Lancaster University. It contains device drivers for all the hardware capabilities of the micro:bit, and also a suite of runtime mechanisms to make programming the micro:bit easier and more flexible. These range from control of the LED matrix display to peer-to-peer radio communication and secure Bluetooth Low Energy services. The micro:bit runtime is proudly built on the ARM mbed and Nordic nrf51 platforms. + +In addition to supporting development in C/C++, the runtime is also designed specifically to support higher level languages provided by our partners that target the micro:bit. It is currently used as a support library for all the languages on the BBC www.microbit.co.uk website, including Microsoft Block, Microsoft TouchDevelop, Code Kingdoms JavaScript and Micropython languages. + +## Links + +[micro:bit runtime docs](http://lancaster-university.github.io/microbit-docs/) | [uBit](https://github.com/lancaster-university/microbit) | [samples](https://github.com/lancaster-university/microbit-samples) + +## Build Environments + +| Build Environment | Documentation | +| ------------- |-------------| +| ARM mbed online | http://lancaster-university.github.io/microbit-docs/online-toolchains/#mbed | +| yotta | http://lancaster-university.github.io/microbit-docs/offline-toolchains/#yotta | + + + +## Hello World! + +```cpp +#include "MicroBitDisplay.h" + +MicroBitDisplay display; + +int main() +{ + display.scroll("Hello world!"); +} +``` + +## BBC Community Guidelines + +[BBC Community Guidelines](https://www.microbit.co.uk/help#sect_cg)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/ExternalEvents.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,38 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef EXTERNAL_EVENTS_H +#define EXTERNAL_EVENTS_H + +/** + * This header file contains IDs and event codes use for Bluetooth communication. + */ + +#define MICROBIT_ID_BLE 1000 +#define MICROBIT_ID_BLE_UART 1001 + +#include "MESEvents.h" + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MESEvents.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,117 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MES_EVENTS_H +#define MES_EVENTS_H + +// +// MicroBit Event Service Event ID's and values +// + +// +// Events that master devices respond to: +// +#define MES_REMOTE_CONTROL_ID 1001 +#define MES_REMOTE_CONTROL_EVT_PLAY 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 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 1 +#define MES_ALERT_EVT_VIBRATE 2 +#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 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_DEVICE_INFO_ID 1103 +#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 + + +#define MES_DPAD_CONTROLLER_ID 1104 +#define MES_DPAD_BUTTON_A_DOWN 1 +#define MES_DPAD_BUTTON_A_UP 2 +#define MES_DPAD_BUTTON_B_DOWN 3 +#define MES_DPAD_BUTTON_B_UP 4 +#define MES_DPAD_BUTTON_C_DOWN 5 +#define MES_DPAD_BUTTON_C_UP 6 +#define MES_DPAD_BUTTON_D_DOWN 7 +#define MES_DPAD_BUTTON_D_UP 8 +#define MES_DPAD_BUTTON_1_DOWN 9 +#define MES_DPAD_BUTTON_1_UP 10 +#define MES_DPAD_BUTTON_2_DOWN 11 +#define MES_DPAD_BUTTON_2_UP 12 +#define MES_DPAD_BUTTON_3_DOWN 13 +#define MES_DPAD_BUTTON_3_UP 14 +#define MES_DPAD_BUTTON_4_DOWN 15 +#define MES_DPAD_BUTTON_4_UP 16 + +// +// Events that typically use radio broadcast: +// +#define MES_BROADCAST_GENERAL_ID 2000 + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitAccelerometerService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,83 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_ACCELEROMETER_SERVICE_H +#define MICROBIT_ACCELEROMETER_SERVICE_H + +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "MicroBitAccelerometer.h" +#include "EventModel.h" + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitAccelerometerServiceUUID[]; +extern const uint8_t MicroBitAccelerometerServiceDataUUID[]; +extern const uint8_t MicroBitAccelerometerServicePeriodUUID[]; + + +/** + * Class definition for a MicroBit BLE Accelerometer Service. + * Provides access to live accelerometer data via Bluetooth, and provides basic configuration options. + */ +class MicroBitAccelerometerService +{ + public: + + /** + * Constructor. + * Create a representation of the AccelerometerService + * @param _ble The instance of a BLE device that we're running on. + * @param _accelerometer An instance of MicroBitAccelerometer. + */ + MicroBitAccelerometerService(BLEDevice &_ble, MicroBitAccelerometer &_acclerometer); + + + private: + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Accelerometer update callback + */ + void accelerometerUpdate(MicroBitEvent e); + + // Bluetooth stack we're running on. + BLEDevice &ble; + MicroBitAccelerometer &accelerometer; + + // memory for our 8 bit control characteristics. + uint16_t accelerometerDataCharacteristicBuffer[3]; + uint16_t accelerometerPeriodCharacteristicBuffer; + + // Handles to access each characteristic when they are held by Soft Device. + GattAttribute::Handle_t accelerometerDataCharacteristicHandle; + GattAttribute::Handle_t accelerometerPeriodCharacteristicHandle; +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitBLEManager.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,215 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_BLE_MANAGER_H +#define MICROBIT_BLE_MANAGER_H + +#include "mbed.h" +#include "MicroBitConfig.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 "MicroBitButton.h" +#include "MicroBitStorage.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 + +#define MICROBIT_BLE_PAIRING_TIMEOUT 90 +#define MICROBIT_BLE_POWER_LEVELS 8 +#define MICROBIT_BLE_MAXIMUM_BONDS 4 +#define MICROBIT_BLE_ENABLE_BONDING true + +extern const int8_t MICROBIT_BLE_POWER_LEVEL[]; + +struct BLESysAttribute +{ + uint8_t sys_attr[8] = { 0 }; +}; + +struct BLESysAttributeStore +{ + BLESysAttribute sys_attrs[MICROBIT_BLE_MAXIMUM_BONDS]; +}; + +/** + * Class definition for the MicroBitBLEManager. + * + */ +class MicroBitBLEManager : MicroBitComponent +{ + public: + + // The mbed abstraction of the BlueTooth Low Energy (BLE) hardware + BLEDevice *ble; + + //an instance of MicroBitStorage used to store sysAttrs from softdevice + MicroBitStorage* storage; + + /** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * + * @param _storage an instance of MicroBitStorage used to persist sys attribute information. (This is required for compatability with iOS). + * + * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). + * Hence, the init() member function should be used to initialise the BLE stack. + */ + MicroBitBLEManager(MicroBitStorage& _storage); + + /** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * + * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). + * Hence, the init() member function should be used to initialise the BLE stack. + */ + MicroBitBLEManager(); + + /** + * Post constructor initialisation method as the BLE stack cannot be brought + * up in a static context. + * + * @param deviceName The name used when advertising + * @param serialNumber The serial number exposed by the device information service + * @param messageBus An instance of an EventModel, used during pairing. + * @param enableBonding If true, the security manager enabled bonding. + * + * @code + * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true); + * @endcode + */ + void init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding); + + /** + * Change the output power level of the transmitter to the given value. + * + * @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range. + * + * @code + * // maximum transmission power. + * bleManager.setTransmitPower(7); + * @endcode + */ + int setTransmitPower(int power); + + /** + * 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 An instance of MicroBitDisplay used when displaying pairing information. + * @param authorizationButton The button to use to authorise a pairing request. + * + * @code + * // initiate pairing mode + * bleManager.pairingMode(uBit.display, uBit.buttonA); + * @endcode + */ + void pairingMode(MicroBitDisplay &display, MicroBitButton &authorisationButton); + + /** + * When called, the micro:bit will begin advertising for a predefined period, + * MICROBIT_BLE_ADVERTISING_TIMEOUT seconds to bonded devices. + */ + void advertise(); + + /** + * Determines the number of devices currently bonded with this micro:bit. + * @return The number of active bonds. + */ + int getBondCount(); + + /** + * A request to pair has been received from a BLE device. + * If we're in pairing mode, display the passkey to the user. + * Also, purge the bonding table if it has reached capacity. + * + * @note for internal use only. + */ + void pairingRequested(ManagedString passKey); + + /** + * A pairing request has been sucessfully completed. + * If we're in pairing mode, display a success or failure message. + * + * @note for internal use only. + */ + void pairingComplete(bool success); + + /** + * Periodic callback in thread context. + * We use this here purely to safely issue a disconnect operation after a pairing operation is complete. + */ + void idleTick(); + + private: + + /** + * Displays the device's ID code as a histogram on the provided MicroBitDisplay instance. + * + * @param display The display instance used for displaying the histogram. + */ + void showNameHistogram(MicroBitDisplay &display); + + int pairingStatus; + ManagedString passKey; + ManagedString deviceName; + +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitButtonService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,80 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_BUTTON_SERVICE_H +#define MICROBIT_BUTTON_SERVICE_H + +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "EventModel.h" + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitButtonServiceUUID[]; +extern const uint8_t MicroBitButtonAServiceDataUUID[]; +extern const uint8_t MicroBitButtonBServiceDataUUID[]; + + +/** + * Class definition for a MicroBit BLE Button Service. + * Provides access to live button data via BLE, and provides basic configuration options. + */ +class MicroBitButtonService +{ + public: + + /** + * Constructor. + * Create a representation of the ButtonService + * @param _ble The instance of a BLE device that we're running on. + */ + MicroBitButtonService(BLEDevice &_ble); + + + private: + + /** + * Button A update callback + */ + void buttonAUpdate(MicroBitEvent e); + + /** + * Button B update callback + */ + void buttonBUpdate(MicroBitEvent e); + + // Bluetooth stack we're running on. + BLEDevice &ble; + + // memory for our 8 bit control characteristics. + uint8_t buttonADataCharacteristicBuffer; + uint8_t buttonBDataCharacteristicBuffer; + + // Handles to access each characteristic when they are held by Soft Device. + GattAttribute::Handle_t buttonADataCharacteristicHandle; + GattAttribute::Handle_t buttonBDataCharacteristicHandle; +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitDFUService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,103 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_DFU_SERVICE_H +#define MICROBIT_DFU_SERVICE_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "MicroBitEvent.h" + +// MicroBit ControlPoint OpCodes +// Requests transfer to the Nordic DFU bootloader. +#define MICROBIT_DFU_OPCODE_START_DFU 1 + +// visual ID code constants +#define MICROBIT_DFU_HISTOGRAM_WIDTH 5 +#define MICROBIT_DFU_HISTOGRAM_HEIGHT 5 + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitDFUServiceUUID[]; +extern const uint8_t MicroBitDFUServiceControlCharacteristicUUID[]; +extern const uint8_t MicroBitDFUServiceFlashCodeCharacteristicUUID[]; + +// Handle on the memory resident Nordic bootloader. +extern "C" void bootloader_start(void); + +/** + * Class definition for a MicroBit Device Firmware Update loader. + * This service allows hexes to be flashed remotely from another Bluetooth + * device. + */ +class MicroBitDFUService +{ + public: + + /** + * Constructor. + * Initialise the Device Firmware Update service. + * @param _ble The instance of a BLE device that we're running on. + */ + MicroBitDFUService(BLEDevice &_ble); + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + virtual void onDataWritten(const GattWriteCallbackParams *params); + + private: + + // State of paiting process. + bool authenticated; + bool flashCodeRequested; + + // Bluetooth stack we're running on. + BLEDevice &ble; + + // memory for our 8 bit control characteristic. + uint8_t controlByte; + + // BLE pairing name of this device, encoded as an integer. + uint32_t flashCode; + + GattAttribute::Handle_t microBitDFUServiceControlCharacteristicHandle; + GattAttribute::Handle_t microBitDFUServiceFlashCodeCharacteristicHandle; + + // Displays the device's ID code as a histogram on the LED matrix display. + void showNameHistogram(); + + // Displays an acknowledgement on the LED matrix display. + void showTick(); + + // Update BLE characteristic to release our flash code. + void releaseFlashCode(); + + // Event handlers for button clicks. + void onButtonA(MicroBitEvent e); + void onButtonB(MicroBitEvent e); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitEventService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,111 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_EVENT_SERVICE_H +#define MICROBIT_EVENT_SERVICE_H + +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "MicroBitEvent.h" +#include "EventModel.h" + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitEventServiceUUID[]; +extern const uint8_t MicroBitEventServiceMicroBitEventCharacteristicUUID[]; +extern const uint8_t MicroBitEventServiceClientEventCharacteristicUUID[]; +extern const uint8_t MicroBitEventServiceMicroBitRequirementsCharacteristicUUID[]; +extern const uint8_t MicroBitEventServiceClientRequirementsCharacteristicUUID[]; + +struct EventServiceEvent +{ + uint16_t type; + uint16_t reason; +}; + + +/** + * Class definition for a MicroBit BLE Event Service. + * Provides a BLE gateway onto an Event Model. + */ +class MicroBitEventService : public MicroBitComponent +{ + public: + + /** + * Constructor. + * Create a representation of the EventService + * @param _ble The instance of a BLE device that we're running on. + * @param _messageBus An instance of an EventModel which events will be mirrored from. + */ + MicroBitEventService(BLEDevice &_ble, EventModel &_messageBus); + + /** + * Periodic callback from MicroBit scheduler. + * If we're no longer connected, remove any registered Message Bus listeners. + */ + virtual void idleTick(); + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Callback. Invoked when any events are sent on the microBit message bus. + */ + void onMicroBitEvent(MicroBitEvent evt); + + /** + * Read callback on microBitRequirements characteristic. + * + * Used to iterate through the events that the code on this micro:bit is interested in. + */ + void onRequirementsRead(GattReadAuthCallbackParams *params); + + private: + + // Bluetooth stack we're running on. + BLEDevice &ble; + EventModel &messageBus; + + // memory for our event characteristics. + EventServiceEvent clientEventBuffer; + EventServiceEvent microBitEventBuffer; + EventServiceEvent microBitRequirementsBuffer; + EventServiceEvent clientRequirementsBuffer; + + // handles on this service's characterisitics. + GattAttribute::Handle_t microBitEventCharacteristicHandle; + GattAttribute::Handle_t clientRequirementsCharacteristicHandle; + GattAttribute::Handle_t clientEventCharacteristicHandle; + GattCharacteristic *microBitRequirementsCharacteristic; + + // Message bus offset last sent to the client... + uint16_t messageBusListenerOffset; + +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitIOPinService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,143 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_IO_PIN_SERVICE_H +#define MICROBIT_IO_PIN_SERVICE_H + +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "MicroBitIO.h" + +#define MICROBIT_IO_PIN_SERVICE_PINCOUNT 20 +#define MICROBIT_IO_PIN_SERVICE_DATA_SIZE 10 + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitIOPinServiceUUID[]; +extern const uint8_t MicroBitIOPinServiceADConfigurationUUID[]; +extern const uint8_t MicroBitIOPinServiceIOConfigurationUUID[]; +extern const uint8_t MicroBitIOPinServiceDataUUID[]; +extern MicroBitPin * const MicroBitIOPins[]; + +/** + * Name value pair definition, as used to read and write pin values over BLE. + */ +struct IOData +{ + uint8_t pin; + uint8_t value; +}; + +/** + * Class definition for the custom MicroBit IOPin Service. + * Provides a BLE service to remotely read the state of the I/O Pin, and configure its behaviour. + */ +class MicroBitIOPinService : public MicroBitComponent +{ + public: + + /** + * Constructor. + * Create a representation of the IOPinService + * @param _ble The instance of a BLE device that we're running on. + * @param _io An instance of MicroBitIO that this service will use to perform + * I/O operations. + */ + MicroBitIOPinService(BLEDevice &_ble, MicroBitIO &_io); + + /** + * Periodic callback from MicroBit scheduler. + * + * Check if any of the pins we're watching need updating. Notify any connected + * device with any changes. + */ + virtual void idleTick(); + + private: + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Callback. invoked when the BLE data characteristic is read. + * + * Reads all the pins marked as inputs, and updates the data stored in the characteristic. + */ + void onDataRead(GattReadAuthCallbackParams *params); + + /** + * Determines if the given pin was configured as a digital pin by the BLE ADPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as digital, 0 otherwise + */ + int isDigital(int i); + + /** + * Determines if the given pin was configured as an analog pin by the BLE ADPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as analog, 0 otherwise + */ + int isAnalog(int i); + + /** + * Determines if the given pin was configured as an input by the BLE IOPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as an input, 0 otherwise + */ + int isInput(int i); + + /** + * Determines if the given pin was configured as output by the BLE IOPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as an output, 0 otherwise + */ + int isOutput(int i); + + + // Bluetooth stack we're running on. + BLEDevice &ble; + MicroBitIO &io; + + // memory for our 8 bit control characteristics. + uint32_t ioPinServiceADCharacteristicBuffer; + uint32_t ioPinServiceIOCharacteristicBuffer; + IOData ioPinServiceDataCharacteristicBuffer[MICROBIT_IO_PIN_SERVICE_DATA_SIZE]; + + // Historic information about our pin data data. + uint8_t ioPinServiceIOData[MICROBIT_IO_PIN_SERVICE_PINCOUNT]; + + // Handles to access each characteristic when they are held by Soft Device. + GattAttribute::Handle_t ioPinServiceADCharacteristicHandle; + GattAttribute::Handle_t ioPinServiceIOCharacteristicHandle; + GattCharacteristic *ioPinServiceDataCharacteristic; +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitLEDService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,91 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_LED_SERVICE_H +#define MICROBIT_LED_SERVICE_H + +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "MicroBitDisplay.h" + +// Defines the buffer size for scrolling text over BLE, hence also defines +// the maximum string length that can be scrolled via the BLE service. +#define MICROBIT_BLE_MAXIMUM_SCROLLTEXT 20 + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitLEDServiceUUID[]; +extern const uint8_t MicroBitLEDServiceMatrixUUID[]; +extern const uint8_t MicroBitLEDServiceTextUUID[]; +extern const uint8_t MicroBitLEDServiceScrollingSpeedUUID[]; + + +/** + * Class definition for the custom MicroBit LED Service. + * Provides a BLE service to remotely read and write the state of the LED display. + */ +class MicroBitLEDService +{ + public: + + /** + * Constructor. + * Create a representation of the LEDService + * @param _ble The instance of a BLE device that we're running on. + * @param _display An instance of MicroBitDisplay to interface with. + */ + MicroBitLEDService(BLEDevice &_ble, MicroBitDisplay &_display); + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Callback. Invoked when any of our attributes are read via BLE. + */ + void onDataRead(GattReadAuthCallbackParams *params); + + private: + + // Bluetooth stack we're running on. + BLEDevice &ble; + MicroBitDisplay &display; + + // memory for our 8 bit control characteristics. + uint8_t matrixCharacteristicBuffer[5]; + uint16_t scrollingSpeedCharacteristicBuffer; + uint8_t textCharacteristicBuffer[MICROBIT_BLE_MAXIMUM_SCROLLTEXT]; + + // Handles to access each characteristic when they are held by Soft Device. + GattAttribute::Handle_t matrixCharacteristicHandle; + GattAttribute::Handle_t textCharacteristicHandle; + GattAttribute::Handle_t scrollingSpeedCharacteristicHandle; + + // We hold a copy of the GattCharacteristic, as mbed's BLE API requires this to provide read callbacks (pity!). + GattCharacteristic matrixCharacteristic; +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitMagnetometerService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,91 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_MAGNETOMETER_SERVICE_H +#define MICROBIT_MAGNETOMETER_SERVICE_H + +#include "ble/BLE.h" +#include "MicroBitConfig.h" +#include "MicroBitCompass.h" +#include "EventModel.h" + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitMagnetometerServiceUUID[]; +extern const uint8_t MicroBitMagnetometerServiceDataUUID[]; +extern const uint8_t MicroBitMagnetometerServiceBearingUUID[]; +extern const uint8_t MicroBitMagnetometerServicePeriodUUID[]; + + +/** + * Class definition for the MicroBit BLE Magnetometer Service. + * Provides access to live magnetometer data via BLE, and provides basic configuration options. + */ +class MicroBitMagnetometerService +{ + public: + + /** + * Constructor. + * Create a representation of the MagnetometerService. + * @param _ble The instance of a BLE device that we're running on. + * @param _compass An instance of MicroBitCompass to use as our Magnetometer source. + */ + MicroBitMagnetometerService(BLEDevice &_ble, MicroBitCompass &_compass); + + private: + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Magnetometer update callback + */ + void magnetometerUpdate(MicroBitEvent e); + + /** + * Sample Period Change Needed callback. + * Reconfiguring the magnetometer can to a REALLY long time (sometimes even seconds to complete) + * So we do this in the background when necessary, through this event handler. + */ + void samplePeriodUpdateNeeded(MicroBitEvent e); + + // Bluetooth stack we're running on. + BLEDevice &ble; + MicroBitCompass &compass; + + // memory for our 8 bit control characteristics. + int16_t magnetometerDataCharacteristicBuffer[3]; + uint16_t magnetometerBearingCharacteristicBuffer; + uint16_t magnetometerPeriodCharacteristicBuffer; + + // Handles to access each characteristic when they are held by Soft Device. + GattAttribute::Handle_t magnetometerDataCharacteristicHandle; + GattAttribute::Handle_t magnetometerBearingCharacteristicHandle; + GattAttribute::Handle_t magnetometerPeriodCharacteristicHandle; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitTemperatureService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,82 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_TEMPERATURE_SERVICE_H +#define MICROBIT_TEMPERATURE_SERVICE_H + +#include "MicroBitConfig.h" +#include "ble/BLE.h" +#include "MicroBitThermometer.h" +#include "EventModel.h" + +// UUIDs for our service and characteristics +extern const uint8_t MicroBitTemperatureServiceUUID[]; +extern const uint8_t MicroBitTemperatureServiceDataUUID[]; +extern const uint8_t MicroBitTemperatureServicePeriodUUID[]; + + +/** + * Class definition for the custom MicroBit Temperature Service. + * Provides a BLE service to remotely read the silicon temperature of the nRF51822. + */ +class MicroBitTemperatureService +{ + public: + + /** + * Constructor. + * Create a representation of the TemperatureService + * @param _ble The instance of a BLE device that we're running on. + * @param _thermometer An instance of MicroBitThermometer to use as our temperature source. + */ + MicroBitTemperatureService(BLEDevice &_ble, MicroBitThermometer &_thermometer); + + /** + * Callback. Invoked when any of our attributes are written via BLE. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * Temperature update callback + */ + void temperatureUpdate(MicroBitEvent e); + + private: + + // Bluetooth stack we're running on. + BLEDevice &ble; + MicroBitThermometer &thermometer; + + // memory for our 8 bit temperature characteristic. + int8_t temperatureDataCharacteristicBuffer; + uint16_t temperaturePeriodCharacteristicBuffer; + + // Handles to access each characteristic when they are held by Soft Device. + GattAttribute::Handle_t temperatureDataCharacteristicHandle; + GattAttribute::Handle_t temperaturePeriodCharacteristicHandle; +}; + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/bluetooth/MicroBitUARTService.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,277 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_UART_SERVICE_H +#define MICROBIT_UART_SERVICE_H + +#include "mbed.h" +#include "ble/UUID.h" +#include "ble/BLE.h" +#include "MicroBitConfig.h" +#include "MicroBitSerial.h" + +#define MICROBIT_UART_S_DEFAULT_BUF_SIZE 20 + +#define MICROBIT_UART_S_EVT_DELIM_MATCH 1 +#define MICROBIT_UART_S_EVT_HEAD_MATCH 2 +#define MICROBIT_UART_S_EVT_RX_FULL 3 + +/** + * Class definition for the custom MicroBit UART Service. + * Provides a BLE service that acts as a UART port, enabling the reception and transmission + * of an arbitrary number of bytes. + */ +class MicroBitUARTService +{ + uint8_t* rxBuffer; + + uint8_t* txBuffer; + + uint8_t rxBufferHead; + uint8_t rxBufferTail; + uint8_t rxBufferSize; + + uint8_t txBufferSize; + + uint32_t txCharacteristicHandle; + + // Bluetooth stack we're running on. + BLEDevice &ble; + + //delimeters used for matching on receive. + ManagedString delimeters; + + //a variable used when a user calls the eventAfter() method. + int rxBuffHeadMatch; + + /** + * A callback function for whenever a Bluetooth device writes to our TX characteristic. + */ + void onDataWritten(const GattWriteCallbackParams *params); + + /** + * An internal method that copies values from a circular buffer to a linear buffer. + * + * @param circularBuff a pointer to the source circular buffer + * @param circularBuffSize the size of the circular buffer + * @param linearBuff a pointer to the destination linear buffer + * @param tailPosition the tail position in the circular buffer you want to copy from + * @param headPosition the head position in the circular buffer you want to copy to + * + * @note this method assumes that the linear buffer has the appropriate amount of + * memory to contain the copy operation + */ + void circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8_t *linearBuff, uint16_t tailPosition, uint16_t headPosition); + + public: + + /** + * Constructor for the UARTService. + * @param _ble an instance of BLEDevice + * @param rxBufferSize the size of the rxBuffer + * @param txBufferSize the size of the txBuffer + * + * @note The default size is MICROBIT_UART_S_DEFAULT_BUF_SIZE (20 bytes). + */ + MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE, uint8_t txBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE); + + /** + * Retreives a single character from our RxBuffer. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read a single character, and return immediately + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, a character or MICROBIT_NO_DATA + */ + int getc(MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * places a single character into our transmission buffer, + * + * @param c the character to transmit + * + * @return the number of characters written (0, or 1). + */ + int putc(char c); + + /** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param buf a buffer containing length number of bytes. + * @param length the size of the buffer. + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ + int send(const uint8_t *buf, int length); + + /** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param s the string to transmit + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ + int send(ManagedString s); + + /** + * Reads a number of characters from the rxBuffer and fills user given buffer. + * + * @param buf a pointer to a buffer of len bytes. + * @param len the size of the user allocated buffer + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return the number of characters digested + */ + int read(uint8_t *buf, int len, MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * Reads a number of characters from the rxBuffer and returns them as a ManagedString + * + * @param len the number of characters to read. + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ + ManagedString read(int len, MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * Reads characters until a character matches one of the given delimeters + * + * @param delimeters the number of characters to match against + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt read the immediate buffer, and look for a match. + * If there isn't, an empty ManagedString will be returned. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all consider the characters in the immediate buffer, + * if a match is not found, it will block on an event, fired when a + * character is matched. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ + ManagedString readUntil(ManagedString delimeters, MicroBitSerialMode mode = SYNC_SLEEP); + + /** + * Configures an event to be fired on a match with one of the delimeters. + * + * @param delimeters the characters to match received characters against e.g. ManagedString("\r\n") + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + * + * @note delimeters are matched on a per byte basis. + */ + int eventOn(ManagedString delimeters, MicroBitSerialMode mode = ASYNC); + + /** + * Configures an event to be fired after "len" characters. + * + * @param len the number of characters to wait before triggering the event + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + */ + int eventAfter(int len, MicroBitSerialMode mode = ASYNC); + + /** + * Determines if we have space in our rxBuff. + * + * @return 1 if we have space, 0 if we do not. + */ + int isReadable(); + + /** + * @return The currently buffered number of bytes in our rxBuff. + */ + int rxBufferedSize(); + + /** + * @return The currently buffered number of bytes in our txBuff. + */ + int txBufferedSize(); +}; + +extern const uint8_t UARTServiceBaseUUID[UUID::LENGTH_OF_LONG_UUID]; +extern const uint16_t UARTServiceShortUUID; +extern const uint16_t UARTServiceTXCharacteristicShortUUID; +extern const uint16_t UARTServiceRXCharacteristicShortUUID; + +extern const uint8_t UARTServiceUUID[UUID::LENGTH_OF_LONG_UUID]; +extern const uint8_t UARTServiceUUID_reversed[UUID::LENGTH_OF_LONG_UUID]; + +extern const uint8_t UARTServiceTXCharacteristicUUID[UUID::LENGTH_OF_LONG_UUID]; +extern const uint8_t UARTServiceRXCharacteristicUUID[UUID::LENGTH_OF_LONG_UUID]; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/ErrorNo.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,86 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef ERROR_NO_H +#define ERROR_NO_H + +#include "MicroBitConfig.h" + +/** + * Error codes used in the micro:bit runtime. + * These may be returned from functions implemented in the micro:bit runtime. + */ +enum ErrorCode{ + + // No error occurred. + MICROBIT_OK = 0, + + // Invalid parameter given. + MICROBIT_INVALID_PARAMETER = -1001, + + // Requested operation is unsupported. + MICROBIT_NOT_SUPPORTED = -1002, + + // Device calibration errors + MICROBIT_CALIBRATION_IN_PROGRESS = -1003, + MICROBIT_CALIBRATION_REQUIRED = -1004, + + // The requested operation could not be performed as the device has run out of some essential resource (e.g. allocated memory) + MICROBIT_NO_RESOURCES = -1005, + + // The requested operation could not be performed as some essential resource is busy (e.g. the display) + MICROBIT_BUSY = -1006, + + // The requested operation was cancelled before it completed. + MICROBIT_CANCELLED = -1007, + + // I2C Communication error occured (typically I2C module on processor has locked up.) + MICROBIT_I2C_ERROR = -1010, + + // The serial bus is currently in use by another fiber. + MICROBIT_SERIAL_IN_USE = -1011, + + // The requested operation had no data to return. + MICROBIT_NO_DATA = -1012 +}; + +/** + * Error codes used in the micro:bit runtime. + */ +enum PanicCode{ + // PANIC Codes. These are not return codes, but are terminal conditions. + // These induce a panic operation, where all code stops executing, and a panic state is + // entered where the panic code is diplayed. + + // Out out memory error. Heap storage was requested, but is not available. + MICROBIT_OOM = 20, + + // Corruption detected in the micro:bit heap space + MICROBIT_HEAP_ERROR = 30, + + // Dereference of a NULL pointer through the ManagedType class, + MICROBIT_NULL_DEREFERENCE = 40, +}; +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/EventModel.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,430 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef EVENT_MODEL_H +#define EVENT_MODEL_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitEvent.h" +#include "MicroBitListener.h" +#include "ErrorNo.h" + +/** + * Class definition for the micro:bit EventModel. + * + * It is common to need to send events from one part of a program (or system) to another. + * The way that these events are stored and delivered is known as an Event Model... + * + * The micro:bit can be programmed in a number of languages, and it not be good to + * constrain those languages to any particular event model (e.g. they may have their own already). + * + * This class defines the functionality an event model needs to have to be able to interact + * with events generated and/or used by the micro:bit runtime. Programmer may choose to implement + * such funcitonality to integrate their own event models. + * + * This is an example of a key principle in computing - ABSTRACTION. This is now part of the + * UK's Computing curriculum in schools... so ask your teacher about it. :-) + * + * An EventModel implementation is provided in the MicroBitMessageBus class. + */ +class EventModel +{ + public: + + static EventModel *defaultEventBus; + + /** + * Queues the given event to be sent to all registered recipients. + * The method of delivery will vary depending on the underlying implementation. + * + * @param The event to send. + * + * @return This default implementation simply returns MICROBIT_NOT_SUPPORTED. + */ + virtual int send(MicroBitEvent evt) + { + (void) evt; + return MICROBIT_NOT_SUPPORTED; + } + + /** + * Add the given MicroBitListener to the list of event handlers, unconditionally. + * + * @param listener The MicroBitListener to validate. + * + * @return This default implementation simply returns MICROBIT_NOT_SUPPORTED. + */ + virtual int add(MicroBitListener *listener) + { + (void) listener; + return MICROBIT_NOT_SUPPORTED; + } + + /** + * Remove the given MicroBitListener from the list of event handlers. + * + * @param listener The MicroBitListener to remove. + * + * @return This default implementation simply returns MICROBIT_NOT_SUPPORTED. + */ + virtual int remove(MicroBitListener *listener) + { + (void) listener; + return MICROBIT_NOT_SUPPORTED; + } + + /** + * Returns the MicroBitListener at the given position in the list. + * + * @param n The index of the desired MicroBitListener. + * + * @return This default implementation simply returns NULL. + */ + MicroBitListener *elementAt(int n) + { + (void) n; + return NULL; + } + + /** + * Define the default EventModel to use for events raised and consumed by the microbit-dal runtime. + * The default EventModel may be changed at any time. + * + * @param model A new instance of an EventModel to use as the default. + * + * @return MICROBIT_OK on success. + * + * Example: + * @code + * MicroBitMessageBus b(); + * EventModel:setDefaultEventModel(b); + * @endcode + */ + static int setDefaultEventModel(EventModel &model) + { + EventModel::defaultEventBus = &model; + return MICROBIT_OK; + } + + /** + * Register a listener function. + * + * An EventModel implementing this interface may optionally choose to override this method, + * if that EventModel supports asynchronous callbacks to user code, but there is no + * requirement to do so. + * + * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_EVT_ANY to receive events of any value. + * + * @param handler The function to call when an event is received. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + * + * @return MICROBIT_OK on success, or any valid error code defined in "ErrNo.h". The default implementation + * simply returns MICROBIT_NOT_SUPPORTED. + * + * @code + * void onButtonBClicked(MicroBitEvent) + * { + * //do something + * } + * + * // call onButtonBClicked when ever a click event from buttonB is detected. + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * @endcode + */ + int listen(int id, int value, void (*handler)(MicroBitEvent), uint16_t flags = EVENT_LISTENER_DEFAULT_FLAGS) + { + if (handler == NULL) + return MICROBIT_INVALID_PARAMETER; + + MicroBitListener *newListener = new MicroBitListener(id, value, handler, flags); + + if(add(newListener) == MICROBIT_OK) + return MICROBIT_OK; + + delete newListener; + + return MICROBIT_NOT_SUPPORTED; + } + + /** + * Register a listener function. + * + * An EventModel implementing this interface may optionally choose to override this method, + * if that EventModel supports asynchronous callbacks to user code, but there is no + * requirement to do so. + * + * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_EVT_ANY to receive events of any value. + * + * @param handler The function to call when an event is received. + * + * @param arg Provide the callback with in an additional argument. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + * + * @return MICROBIT_OK on success, or any valid error code defined in "ErrNo.h". The default implementation + * simply returns MICROBIT_NOT_SUPPORTED. + * + * @code + * void onButtonBClicked(MicroBitEvent, void* data) + * { + * //do something + * } + * + * // call onButtonBClicked when ever a click event from buttonB is detected. + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * @endcode + */ + int listen(int id, int value, void (*handler)(MicroBitEvent, void*), void* arg, uint16_t flags = EVENT_LISTENER_DEFAULT_FLAGS) + { + if (handler == NULL) + return MICROBIT_INVALID_PARAMETER; + + MicroBitListener *newListener = new MicroBitListener(id, value, handler, arg, flags); + + if(add(newListener) == MICROBIT_OK) + return MICROBIT_OK; + + delete newListener; + + return MICROBIT_NOT_SUPPORTED; + } + + /** + * Register a listener function. + * + * @param id The source of messages to listen for. Events sent from any other IDs will be filtered. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_EVT_ANY to receive events of any value. + * + * @param hander The function to call when an event is received. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER if the handler or object + * pointers are NULL. + * + * @code + * void SomeClass::onButtonBClicked(MicroBitEvent) + * { + * //do something + * } + * + * SomeClass s = new SomeClass(); + * + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * @endcode + */ + template <typename T> + int listen(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent), uint16_t flags = EVENT_LISTENER_DEFAULT_FLAGS); + + + /** + * Unregister a listener function. + * Listeners 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. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER if the handler + * given is NULL. + * + * Example: + * @code + * void onButtonBClick(MicroBitEvent) + * { + * //do something + * } + * + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * + * // the previously created listener is now ignored. + * uBit.messageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * @endcode + */ + int ignore(int id, int value, void (*handler)(MicroBitEvent)) + { + if (handler == NULL) + return MICROBIT_INVALID_PARAMETER; + + MicroBitListener listener(id, value, handler); + remove(&listener); + + return MICROBIT_OK; + } + + /** + * Unregister a listener function. + * Listeners 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. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER if the handler + * given is NULL. + * + * Example: + * @code + * void onButtonBClick(MicroBitEvent, void* data) + * { + * //do something + * } + * + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * + * // the previously created listener is now ignored. + * uBit.messageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonBClick); + * @endcode + */ + int ignore(int id, int value, void (*handler)(MicroBitEvent, void*)) + { + if (handler == NULL) + return MICROBIT_INVALID_PARAMETER; + + MicroBitListener listener(id, value, handler, NULL); + remove(&listener); + + return MICROBIT_OK; + } + + /** + * 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. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER if the handler or object + * pointers are NULL. + * + * Example: + * @code + * + * void SomeClass::onButtonBClick() + * { + * //do something + * } + * + * SomeClass s = new SomeClass(); + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * + * // the previously created listener is now ignored. + * uBit.messageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * @endcode + */ + template <typename T> + int ignore(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent)); + +}; + +/** + * A registration function to allow C++ member functions (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. + * Use MICROBIT_ID_ANY to receive events from all components. + * + * @param value The value of messages to listen for. Events with any other values will be filtered. + * Use MICROBIT_EVT_ANY to receive events of any value. + * + * @param object The object on which the method should be invoked. + * + * @param handler The method to call when an event is received. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER if the handler or object + * pointers are NULL. + */ +template <typename T> +int EventModel::listen(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent), uint16_t flags) +{ + if (object == NULL || handler == NULL) + return MICROBIT_INVALID_PARAMETER; + + MicroBitListener *newListener = new MicroBitListener(id, value, object, handler, flags); + + if(add(newListener) == MICROBIT_OK) + return MICROBIT_OK; + + delete newListener; + return MICROBIT_NOT_SUPPORTED; +} + +/** + * 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. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER if the handler or object + * pointers are NULL. + * + * Example: + * @code + * + * void SomeClass::onButtonBClick() + * { + * //do something + * } + * + * SomeClass s = new SomeClass(); + * uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * + * // the previously created listener is now ignored. + * uBit.messageBus.ignore(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, s, &SomeClass::onButtonBClick); + * @endcode + */ +template <typename T> +int EventModel::ignore(uint16_t id, uint16_t value, T* object, void (T::*handler)(MicroBitEvent)) +{ + if (handler == NULL) + return MICROBIT_INVALID_PARAMETER; + + MicroBitListener listener(id, value, object, handler); + remove(&listener); + + return MICROBIT_OK; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MemberFunctionCallback.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,114 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MEMBER_FUNCTION_CALLBACK_H +#define MEMBER_FUNCTION_CALLBACK_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitEvent.h" +#include "MicroBitCompat.h" + +/** + * Class definition for a MemberFunctionCallback. + * + * C++ member functions (also known as methods) have a more complex + * representation than normal C functions. This class allows a reference to + * a C++ member function to be stored then called at a later date. + * + * This class is used extensively by the MicroBitMessageBus to deliver + * events to C++ methods. + */ +class MemberFunctionCallback +{ + private: + void* object; + uint32_t method[4]; + void (*invoke)(void *object, uint32_t *method, MicroBitEvent e); + template <typename T> static void methodCall(void* object, uint32_t*method, MicroBitEvent e); + + public: + + /** + * Constructor. Creates a MemberFunctionCallback based on a pointer to given method. + * + * @param object The object the callback method should be invooked on. + * + * @param method The method to invoke. + */ + template <typename T> MemberFunctionCallback(T* object, void (T::*method)(MicroBitEvent e)); + + /** + * A comparison of two MemberFunctionCallback objects. + * + * @return true if the given MemberFunctionCallback is equivalent to this one, false otherwise. + */ + bool operator==(const MemberFunctionCallback &mfc); + + /** + * Calls the method reference held by this MemberFunctionCallback. + * + * @param e The event to deliver to the method + */ + void fire(MicroBitEvent e); +}; + +/** + * Constructor. Creates a MemberFunctionCallback based on a pointer to given method. + * + * @param object The object the callback method should be invooked on. + * + * @param method The method to invoke. + */ +template <typename T> +MemberFunctionCallback::MemberFunctionCallback(T* object, void (T::*method)(MicroBitEvent e)) +{ + this->object = object; + memclr(this->method, sizeof(this->method)); + memcpy(this->method, &method, sizeof(method)); + invoke = &MemberFunctionCallback::methodCall<T>; +} + +/** + * A template used to create a static method capable of invoking a C++ member function (method) + * based on the given parameters. + * + * @param object The object the callback method should be invooked on. + * + * @param method The method to invoke. + * + * @param method The MicroBitEvent to supply to the given member function. + */ +template <typename T> +void MemberFunctionCallback::methodCall(void *object, uint32_t *method, MicroBitEvent e) +{ + T* o = (T*)object; + void (T::*m)(MicroBitEvent); + memcpy(&m, method, sizeof(m)); + + (o->*m)(e); +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitCompat.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,111 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * This file contains functions used to maintain compatability and portability. + * It also contains constants that are used elsewhere in the DAL. + */ + +#ifndef MICROBIT_COMPAT_H +#define MICROBIT_COMPAT_H + +#include "mbed.h" +#include "MicroBitConfig.h" + +#define PI 3.14159265359 + +/** + * Determines the smallest of the two numbers + * + * @param a the first number + * + * @param b the second number + * + * @return The smallest of the two given values. + */ +inline int min(int a, int b) +{ + return (a < b ? a : b); +} + +/** + * Determines the largest of the two numbers + * + * @param a the first number + * + * @param b the second number + * + * @return The larger of the two given values. + */ +inline int max(int a, int b) +{ + return (a > b ? a : b); +} + +/** + * Sets a given area of memory to zero. + * + * @param a the pointer to the beginning of the memory to clear + * + * @param b the number of bytes to clear. + */ +inline void *memclr(void *a, size_t b) +{ + return memset(a,0,b); +} + +/** + * Determines if the given character is a printable ASCII/UTF8 decimal digit (0..9). + * + * @param c the character to check + * + * @return true if the character is a digit, false otherwise. + */ +inline bool isdigit(char c) +{ + return (c > 47 && c < 58); +} + +/** + * Performs an in buffer reverse of a given char array. + * + * @param s the string to reverse. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int string_reverse(char *s); + +/** + * Converts a given integer into a string representation. + * + * @param n The number to convert. + * + * @param s A pointer to the buffer where the resulting string will be stored. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int itoa(int n, char *s); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitComponent.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,152 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_COMPONENT_H +#define MICROBIT_COMPONENT_H + +#include "MicroBitConfig.h" + +// Enumeration of core components. +#define MICROBIT_ID_BUTTON_A 1 +#define MICROBIT_ID_BUTTON_B 2 +#define MICROBIT_ID_BUTTON_RESET 3 +#define MICROBIT_ID_ACCELEROMETER 4 +#define MICROBIT_ID_COMPASS 5 +#define MICROBIT_ID_DISPLAY 6 + +//EDGE connector events +#define MICROBIT_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_P6 13 //ROW2 +#define MICROBIT_ID_IO_P7 14 //ROW1 +#define MICROBIT_ID_IO_P8 15 //PIN 18 +#define MICROBIT_ID_IO_P9 16 //ROW3 +#define MICROBIT_ID_IO_P10 17 //COL3 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P11 18 //BTN_B +#define MICROBIT_ID_IO_P12 19 //PIN 20 +#define MICROBIT_ID_IO_P13 20 //SCK +#define MICROBIT_ID_IO_P14 21 //MISO +#define MICROBIT_ID_IO_P15 22 //MOSI +#define MICROBIT_ID_IO_P16 23 //PIN 16 +#define MICROBIT_ID_IO_P19 24 //SCL +#define MICROBIT_ID_IO_P20 25 //SDA + +#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton +#define MICROBIT_ID_GESTURE 27 // Gesture events + +#define MICROBIT_ID_THERMOMETER 28 +#define MICROBIT_ID_RADIO 29 +#define MICROBIT_ID_RADIO_DATA_READY 30 +#define MICROBIT_ID_MULTIBUTTON_ATTACH 31 +#define MICROBIT_ID_SERIAL 32 + +#define MICROBIT_ID_MESSAGE_BUS_LISTENER 1021 // Message bus indication that a handler for a given ID has been registered. +#define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation +#define MICROBIT_ID_NOTIFY 1023 // Notfication channel, for general purpose synchronisation + +// Universal flags used as part of the status field +#define MICROBIT_COMPONENT_RUNNING 0x01 + + +/** + * Class definition for MicroBitComponent. + * + * All components should inherit from this class. + * + * If a component requires regular updates, then you should add the component + * to the systemTick and idleTick queues. + * + * The system timer will call systemTick() once the component has been added to + * the array of system components using system_timer_add_component. This callback + * will be in interrupt context. + * + * The idle thread will call idleTick() once the component has been added to the array + * of idle components using fiber_add_idle_component. Updates are determined by + * the isIdleCallbackNeeded() member function. + */ +class MicroBitComponent +{ + protected: + + uint16_t id; // Event Bus ID + uint8_t status; // keeps track of various component state, and also indicates if data is ready. + + public: + + /** + * The default constructor of a MicroBitComponent + */ + MicroBitComponent() + { + this->id = 0; + this->status = 0; + } + + /** + * The system timer will call this member function once the component has been added to + * the array of system components using system_timer_add_component. This callback + * will be in interrupt context. + */ + virtual void systemTick(){ + + } + + /** + * The idle thread will call this member function once the component has been added to the array + * of idle components using fiber_add_idle_component. Updates are determined by + * the isIdleCallbackNeeded() member function. + */ + virtual void idleTick() + { + + } + + /** + * When added to the idleThreadComponents array, this function will be called to determine + * if and when data is ready. + * + * @note override this if you want to request to be scheduled as soon as possible. + */ + virtual int isIdleCallbackNeeded() + { + return 0; + } + + /** + * If you have added your component to the idle or system tick component arrays, + * you must remember to remove your component from them if your component is destructed. + */ + virtual ~MicroBitComponent() + { + } +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitConfig.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,373 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Compile time configuration options for the micro:bit runtime. + */ + +#ifndef MICROBIT_CONFIG_H +#define MICROBIT_CONFIG_H + +#include "mbed.h" + +// +// Memory configuration +// + +// The start address of usable RAM memory. +#ifndef MICROBIT_SRAM_BASE +#define MICROBIT_SRAM_BASE 0x20000008 +#endif + +// Physical address of the top of SRAM. +#ifndef MICROBIT_SRAM_END +#define MICROBIT_SRAM_END 0x20004000 +#endif + +// The end address of memory normally reserved for Soft Device. +#ifndef MICROBIT_SD_LIMIT +#define MICROBIT_SD_LIMIT 0x20002000 +#endif + +// The physical address in memory of the Soft Device GATT table. +#ifndef MICROBIT_SD_GATT_TABLE_START +#define MICROBIT_SD_GATT_TABLE_START 0x20001900 +#endif + +// Physical address of the top of the system stack (on mbed-classic this is the top of SRAM) +#ifndef CORTEX_M0_STACK_BASE +#define CORTEX_M0_STACK_BASE MICROBIT_SRAM_END +#endif + +// Amount of memory reserved for the stack at the end of memory (bytes). +#ifndef MICROBIT_STACK_SIZE +#define MICROBIT_STACK_SIZE 2048 +#endif + +// Physical address of the end of mbed heap space. +#ifndef MICROBIT_HEAP_END +#define MICROBIT_HEAP_END (CORTEX_M0_STACK_BASE - MICROBIT_STACK_SIZE) +#endif + +// Enables or disables the MicroBitHeapllocator. Note that if disabled, no reuse of the SRAM normally +// reserved for SoftDevice is possible, and out of memory condition will no longer be trapped... +// i.e. panic() will no longer be triggered on memory full conditions. +#ifndef MICROBIT_HEAP_ALLOCATOR +#define MICROBIT_HEAP_ALLOCATOR 1 +#endif + +// Block size used by the allocator in bytes. +// n.b. Currently only 32 bits (4 bytes) is supported. +#ifndef MICROBIT_HEAP_BLOCK_SIZE +#define MICROBIT_HEAP_BLOCK_SIZE 4 +#endif + +// The proportion of SRAM available on the mbed heap to reserve for the micro:bit heap. +#ifndef MICROBIT_NESTED_HEAP_SIZE +#define MICROBIT_NESTED_HEAP_SIZE 0.75 +#endif + +// If defined, reuse any unused SRAM normally reserved for SoftDevice (Nordic's memory resident BLE stack) as heap memory. +// The amount of memory reused depends upon whether or not BLE is enabled using MICROBIT_BLE_ENABLED. +// Set '1' to enable. +#ifndef MICROBIT_HEAP_REUSE_SD +#define MICROBIT_HEAP_REUSE_SD 1 +#endif + +// The amount of memory allocated to Soft Device to hold its BLE GATT table. +// For standard S110 builds, this should be word aligned and in the range 0x300 - 0x700. +// Any unused memory will be automatically reclaimed as HEAP memory if both MICROBIT_HEAP_REUSE_SD and MICROBIT_HEAP_ALLOCATOR are enabled. +#ifndef MICROBIT_SD_GATT_TABLE_SIZE +#define MICROBIT_SD_GATT_TABLE_SIZE 0x300 +#endif + +// +// Fiber scheduler configuration +// + +// Scheduling quantum (milliseconds) +// Also used to drive the micro:bit runtime system ticker. +#ifndef SYSTEM_TICK_PERIOD_MS +#define SYSTEM_TICK_PERIOD_MS 6 +#endif + +// +// Message Bus: +// Default behaviour for event handlers, if not specified in the listen() call +// +// Permissable values are: +// MESSAGE_BUS_LISTENER_REENTRANT +// MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY +// MESSAGE_BUS_LISTENER_DROP_IF_BUSY +// MESSAGE_BUS_LISTENER_IMMEDIATE + +#ifndef EVENT_LISTENER_DEFAULT_FLAGS +#define EVENT_LISTENER_DEFAULT_FLAGS MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY +#endif + +// +// Maximum event queue depth. If a queue exceeds this depth, further events will be dropped. +// Used to prevent message queues growing uncontrollably due to badly behaved user code and causing panic conditions. +// +#ifndef MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH +#define MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH 10 +#endif + +// +// Core micro:bit services +// + +// To reduce memory cost and complexity, the micro:bit allows components to register for +// periodic callback events during interrupt context, which occur every scheduling quantum (FIBER_TICK_PERIOD_MS) +// This defines the maximum size of interrupt callback list. +#ifndef MICROBIT_SYSTEM_COMPONENTS +#define MICROBIT_SYSTEM_COMPONENTS 10 +#endif + +// To reduce memory cost and complexity, the micro:bit allows components to register for +// periodic callback events when the processor is idle. +// This defines the maximum size of the idle callback list. +#ifndef MICROBIT_IDLE_COMPONENTS +#define MICROBIT_IDLE_COMPONENTS 6 +#endif + +// +// BLE options +// +// The BLE stack is very memory hungry. Each service can therefore be compiled in or out +// 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 pairing mode regardless of the settings below. +// + +// Enable/Disable BLE during normal operation. +// Set '1' to enable. +#ifndef MICROBIT_BLE_ENABLED +#define MICROBIT_BLE_ENABLED 1 +#endif + +// Enable/Disable BLE pairing mode mode at power up. +// Set '1' to enable. +#ifndef MICROBIT_BLE_PAIRING_MODE +#define MICROBIT_BLE_PAIRING_MODE 1 +#endif + +// Enable/Disable the use of private resolvable addresses. +// Set '1' to enable. +// n.b. This is known to be a feature that suffers compatibility issues with many BLE central devices. +#ifndef MICROBIT_BLE_PRIVATE_ADDRESSES +#define MICROBIT_BLE_PRIVATE_ADDRESSES 0 +#endif + +// Convenience option to enable / disable BLE security entirely +// Open BLE links are not secure, but commonly used during the development of BLE services +// Set '1' to disable all secuity +#ifndef MICROBIT_BLE_OPEN +#define MICROBIT_BLE_OPEN 0 +#endif + +// Configure for open BLE operation if so configured +#if (MICROBIT_BLE_OPEN == 1) +#define MICROBIT_BLE_SECURITY_LEVEL SECURITY_MODE_ENCRYPTION_OPEN_LINK +#define MICROBIT_BLE_WHITELIST 0 +#define MICROBIT_BLE_ADVERTISING_TIMEOUT 0 +#define MICROBIT_BLE_DEFAULT_TX_POWER 6 +#endif + + +// Define the default, global BLE security requirements for MicroBit BLE services +// May be one of the following options (see mbed's SecurityManager class implementaiton detail) +// SECURITY_MODE_ENCRYPTION_OPEN_LINK: No bonding, encryption, or whitelisting required. +// SECURITY_MODE_ENCRYPTION_NO_MITM: Bonding, encyption and whitelisting but no passkey. +// SECURITY_MODE_ENCRYPTION_WITH_MITM: Bonding, encrytion and whitelisting with passkey authentication. +// +#ifndef MICROBIT_BLE_SECURITY_LEVEL +#define MICROBIT_BLE_SECURITY_LEVEL SECURITY_MODE_ENCRYPTION_WITH_MITM +#endif + +// Enable/Disable the use of BLE whitelisting. +// If enabled, the micro:bit will only respond to connection requests from +// known, bonded devices. +#ifndef MICROBIT_BLE_WHITELIST +#define MICROBIT_BLE_WHITELIST 1 +#endif + +// Define the period of time for which the BLE stack will advertise (seconds) +// Afer this period, advertising will cease. Set to '0' for no timeout (always advertise). +#ifndef MICROBIT_BLE_ADVERTISING_TIMEOUT +#define MICROBIT_BLE_ADVERTISING_TIMEOUT 0 +#endif + +// Defines default power level of the BLE radio transmitter. +// Valid values are in the range 0..7 inclusive, with 0 being the lowest power and 7 the highest power. +// Based on trials undertaken by the BBC, the radio is normally set to its lowest power level +// to best protect children's privacy. +#ifndef MICROBIT_BLE_DEFAULT_TX_POWER +#define MICROBIT_BLE_DEFAULT_TX_POWER 0 +#endif + +// Enable/Disable BLE Service: MicroBitDFU +// This allows over the air programming during normal operation. +// Set '1' to enable. +#ifndef MICROBIT_BLE_DFU_SERVICE +#define MICROBIT_BLE_DFU_SERVICE 1 +#endif + +// Enable/Disable BLE Service: MicroBitEventService +// This allows routing of events from the micro:bit message bus over BLE. +// Set '1' to enable. +#ifndef MICROBIT_BLE_EVENT_SERVICE +#define MICROBIT_BLE_EVENT_SERVICE 1 +#endif + +// Enable/Disable BLE Service: MicroBitDeviceInformationService +// This enables the standard BLE device information service. +// Set '1' to enable. +#ifndef MICROBIT_BLE_DEVICE_INFORMATION_SERVICE +#define MICROBIT_BLE_DEVICE_INFORMATION_SERVICE 1 +#endif + +// +// Accelerometer options +// + +// Enable this to read 10 bits of data from the acclerometer. +// Otherwise, 8 bits are used. +// Set '1' to enable. +#ifndef USE_ACCEL_LSB +#define USE_ACCEL_LSB 0 +#endif + +// +// Display options +// + +// Selects the matrix configuration for the display driver. +// Known, acceptable options are: +// +#define MICROBUG_REFERENCE_DEVICE 1 +#define MICROBIT_3X9 2 +#define MICROBIT_SB1 3 +#define MICROBIT_SB2 4 + +#ifndef MICROBIT_DISPLAY_TYPE +#define MICROBIT_DISPLAY_TYPE MICROBIT_SB2 +#endif + +// Selects the minimum permissable brightness level for the device +// in the region of 0 (off) to 255 (full brightness) +#ifndef MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS +#define MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS 1 +#endif + +// Selects the maximum permissable brightness level for the device +// in the region of 0 (off) to 255 (full brightness) +#ifndef MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS +#define MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS 255 +#endif + +// 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 +#endif + +// Selects the default scroll speed for the display. +// The time taken to move a single pixel (ms). +#ifndef MICROBIT_DEFAULT_SCROLL_SPEED +#define MICROBIT_DEFAULT_SCROLL_SPEED 120 +#endif + +// Selects the number of pixels a scroll will move in each quantum. +#ifndef MICROBIT_DEFAULT_SCROLL_STRIDE +#define MICROBIT_DEFAULT_SCROLL_STRIDE -1 +#endif + +// Selects the time each character will be shown on the display during print operations. +// The time each character is shown on the screen (ms). +#ifndef MICROBIT_DEFAULT_PRINT_SPEED +#define MICROBIT_DEFAULT_PRINT_SPEED 400 +#endif + +//Configures the default serial mode used by serial read and send calls. +#ifndef MICROBIT_DEFAULT_SERIAL_MODE +#define MICROBIT_DEFAULT_SERIAL_MODE SYNC_SLEEP +#endif + + +// +// Panic options +// + +// Enable this to invoke a panic on out of memory conditions. +// Set '1' to enable. +#ifndef MICROBIT_PANIC_HEAP_FULL +#define MICROBIT_PANIC_HEAP_FULL 1 +#endif + +// +// Debug options +// + +// Enable this to route debug messages through the USB serial interface. +// n.b. This also disables the user serial port 'uBit.serial'. +// Set '1' to enable. +#ifndef MICROBIT_DBG +#define MICROBIT_DBG 0 +#endif + +// Enable this to receive diagnostic messages from the heap allocator via the USB serial interface. +// n.b. This requires MICROBIT_DBG to be defined. +// Set '1' to enable. +#ifndef MICROBIT_HEAP_DBG +#define MICROBIT_HEAP_DBG 0 +#endif + +// Versioning options. +// We use semantic versioning (http://semver.org/) to identify differnet versions of the micro:bit runtime. +// Where possible we use yotta (an ARM mbed build tool) to help us track versions. +// if this isn't available, it can be defined manually as a configuration option. +// +#ifndef MICROBIT_DAL_VERSION +#define MICROBIT_DAL_VERSION "unknown" +#endif + + +// +// Helper macro used by the micro:bit runtime to determine if a boolean configuration option is set. +// +#define CONFIG_ENABLED(X) (X == 1) +#define CONFIG_DISABLED(X) (X != 1) + +#if CONFIG_ENABLED(MICROBIT_HEAP_ALLOCATOR) +#include "MicroBitHeapAllocator.h" +#endif + +#if CONFIG_ENABLED(MICROBIT_DBG) +extern RawSerial* SERIAL_DEBUG; +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitDevice.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Device specific funcitons for the nrf51822 device. + * + * Provides a degree of platform independence for microbit-dal functionality. + * + * TODO: Determine if any of this belongs in an mbed target definition. + * TODO: Review microbit-dal to place all such functions here. + */ +#ifndef MICROBIT_DEVICE_H +#define MICROBIT_DEVICE_H + +#define MICROBIT_NAME_LENGTH 5 +#define MICROBIT_NAME_CODE_LETTERS 5 +#define MICROBIT_PANIC_ERROR_CHARS 4 + +#include "MicroBitConfig.h" +#include "MicroBitMatrixMaps.h" + +/** + * Determines if a BLE stack is currently running. + * + * @return true is a bluetooth stack is operational, false otherwise. + */ +bool ble_running(); + +/** + * Derive a unique, consistent serial number of this device from internal data. + * + * @return the serial number of this device. + */ +uint32_t microbit_serial_number(); + +/** + * Derive the friendly name for this device, based on its serial number. + * + * @return the serial number of this device. + */ +char* microbit_friendly_name(); + +/** + * Perform a hard reset of the micro:bit. + */ +void microbit_reset(); + +/** + * Determine the version of microbit-dal currently running. + * @return a pointer to a character buffer containing a representation of the semantic version number. + */ +const char * microbit_dal_version(); + +/** + * Disables all interrupts and user processing. + * Displays "=(" and an accompanying status code on the default display. + * @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255. + * + * @code + * microbit_panic(20); + * @endcode + */ +void microbit_panic(int statusCode); + +/** + * Defines the length of time that the device will remain in a error state before resetting. + * + * @param iteration The number of times the error code will be displayed before resetting. Set to zero to remain in error state forever. + * + * @code + * microbit_panic_timeout(4); + * @endcode + */ +void microbit_panic_timeout(int iterations); + +/** + * Generate a random number in the given range. + * We use a simple Galois LFSR random number generator here, + * as a Galois LFSR is sufficient for our applications, and much more lightweight + * than the hardware random number generator built int the processor, which takes + * a long time and uses a lot of energy. + * + * KIDS: You shouldn't use this is the real world to generte cryptographic keys though... + * have a think why not. :-) + * + * @param max the upper range to generate a number for. This number cannot be negative. + * + * @return A random, natural number between 0 and the max-1. Or MICROBIT_INVALID_VALUE if max is <= 0. + * + * @code + * microbit_random(200); //a number between 0 and 199 + * @endcode + */ +int microbit_random(int max); + +/** + * Seed the random number generator (RNG). + * + * This function uses the NRF51822's in built cryptographic random number generator to seed a Galois LFSR. + * We do this as the hardware RNG is relatively high power, and is locked out by the BLE stack internally, + * with a less than optimal application interface. A Galois LFSR is sufficient for our + * applications, and much more lightweight. + */ +void microbit_seed_random(); + +/** + * Seed the pseudo random number generator (RNG) using the given 32-bit value. + * This function does not use the NRF51822's in built cryptographic random number generator. + * + * @param seed The value to use as a seed. + */ +void microbit_seed_random(uint32_t seed); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitFiber.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,401 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Functionality definitions for the MicroBit Fiber scheduler. + * + * This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes: + * + * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). + * 2) To provide ISR decoupling for EventModel events generated in an ISR context. + * + * TODO: Consider a split mode scheduler, that monitors used stack size, and maintains a dedicated, persistent + * stack for any long lived fibers with large stack + */ +#ifndef MICROBIT_FIBER_H +#define MICROBIT_FIBER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitEvent.h" +#include "EventModel.h" + +// Fiber Scheduler Flags +#define MICROBIT_SCHEDULER_RUNNING 0x01 + +// Fiber Flags +#define MICROBIT_FIBER_FLAG_FOB 0x01 +#define MICROBIT_FIBER_FLAG_PARENT 0x02 +#define MICROBIT_FIBER_FLAG_CHILD 0x04 +#define MICROBIT_FIBER_FLAG_DO_NOT_PAGE 0x08 + +/** + * Thread Context for an ARM Cortex M0 core. + * + * This is probably overkill, but the ARMCC compiler uses a lot register optimisation + * in its calling conventions, so better safe than sorry! + */ +struct Cortex_M0_TCB +{ + uint32_t R0; + uint32_t R1; + uint32_t R2; + uint32_t R3; + uint32_t R4; + uint32_t R5; + uint32_t R6; + uint32_t R7; + uint32_t R8; + uint32_t R9; + uint32_t R10; + uint32_t R11; + uint32_t R12; + uint32_t SP; + uint32_t LR; + uint32_t stack_base; +}; + +/** + * Representation of a single Fiber + */ +struct Fiber +{ + Cortex_M0_TCB tcb; // Thread context when last scheduled out. + uint32_t stack_bottom; // The start address of this Fiber's stack. The stack is heap allocated, and full descending. + uint32_t stack_top; // The end address of this Fiber's stack. + uint32_t context; // Context specific information. + uint32_t flags; // Information about this fiber. + Fiber **queue; // The queue this fiber is stored on. + Fiber *next, *prev; // Position of this Fiber on the run queue. +}; + +extern Fiber *currentFiber; + + +/** + * Initialises the Fiber scheduler. + * Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread. + * + * This function must be called once only from the main thread, and before any other Fiber operation. + * + * @param _messageBus An event model, used to direct the priorities of the scheduler. + */ +void scheduler_init(EventModel &_messageBus); + +/** + * Determines if the fiber scheduler is operational. + * + * @return 1 if the fber scheduler is running, 0 otherwise. + */ +int fiber_scheduler_running(); + +/** + * Exit point for all fibers. + * + * Any fiber reaching the end of its entry function will return here for recycling. + */ +void release_fiber(void); +void release_fiber(void *param); + +/** + * Launches a fiber. + * + * @param ep the entry point for the fiber. + * + * @param cp the completion routine after ep has finished execution + */ +void launch_new_fiber(void (*ep)(void), void (*cp)(void)) +#ifdef __GCC__ + __attribute__((naked)) +#endif +; + +/** + * Launches a fiber with a parameter + * + * @param ep the entry point for the fiber. + * + * @param cp the completion routine after ep has finished execution + * + * @param pm the parameter to provide to ep and cp. + */ +void launch_new_fiber_param(void (*ep)(void *), void (*cp)(void *), void *pm) +#ifdef __GCC__ + __attribute__((naked)) +#endif +; + +/** + * Creates a new Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * + * @param completion_fn The function called when the thread completes execution of entry_fn. + * Defaults to release_fiber. + * + * @return The new Fiber, or NULL if the operation could not be completed. + */ +Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void) = release_fiber); + + +/** + * Creates a new parameterised Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * + * @param param an untyped parameter passed into the entry_fn and completion_fn. + * + * @param completion_fn The function called when the thread completes execution of entry_fn. + * Defaults to release_fiber. + * + * @return The new Fiber, or NULL if the operation could not be completed. + */ +Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *) = release_fiber); + + +/** + * Calls the Fiber scheduler. + * The calling Fiber will likely be blocked, and control given to another waiting fiber. + * Call this function to yield control of the processor when you have nothing more to do. + */ +void schedule(); + +/** + * Blocks the calling thread for the given period of time. + * The calling thread will be immediateley descheduled, and placed onto a + * wait queue until the requested amount of time has elapsed. + * + * @param t The period of time to sleep, in milliseconds. + * + * @note the fiber will not be be made runnable until after the elapsed time, but there + * are no guarantees precisely when the fiber will next be scheduled. + */ +void fiber_sleep(unsigned long t); + +/** + * The timer callback, called from interrupt context once every SYSTEM_TICK_PERIOD_MS milliseconds. + * This function checks to determine if any fibers blocked on the sleep queue need to be woken up + * and made runnable. + */ +void scheduler_tick(); + +/** + * Blocks the calling thread until the specified event is raised. + * The calling thread will be immediateley descheduled, and placed onto a + * wait queue until the requested event is received. + * + * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) + * + * @param value The value of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) + * + * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. + * + * @code + * fiber_wait_for_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * @endcode + * + * @note the fiber will not be be made runnable until after the event is raised, but there + * are no guarantees precisely when the fiber will next be scheduled. + */ +int fiber_wait_for_event(uint16_t id, uint16_t value); + +/** + * Configures the fiber context for the current fiber to block on an event ID + * and value, but does not deschedule the fiber. + * + * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) + * + * @param value The value of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) + * + * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. + * + * @code + * fiber_wake_on_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * + * //perform some time critical operation. + * + * //deschedule the current fiber manually, waiting for the previously configured event. + * schedule(); + * @endcode + */ +int fiber_wake_on_event(uint16_t id, uint16_t value); + +/** + * Executes the given function asynchronously if necessary. + * + * Fibers are often used to run event handlers, however many of these event handlers are very simple functions + * that complete very quickly, bringing unecessary RAM overhead. + * + * This function takes a snapshot of the current processor context, then attempts to optimistically call the given function directly. + * We only create an additional fiber if that function performs a block operation. + * + * @param entry_fn The function to execute. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int invoke(void (*entry_fn)(void)); + +/** + * Executes the given function asynchronously if necessary, and offers the ability to provide a parameter. + * + * Fibers are often used to run event handlers, however many of these event handlers are very simple functions + * that complete very quickly, bringing unecessary RAM. overhead + * + * This function takes a snapshot of the current fiber context, then attempt to optimistically call the given function directly. + * We only create an additional fiber if that function performs a block operation. + * + * @param entry_fn The function to execute. + * + * @param param an untyped parameter passed into the entry_fn and completion_fn. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int invoke(void (*entry_fn)(void *), void *param); + +/** + * Resizes the stack allocation of the current fiber if necessary to hold the system stack. + * + * If the stack allocation is large enough to hold the current system stack, then this function does nothing. + * Otherwise, the the current allocation of the fiber is freed, and a larger block is allocated. + * + * @param f The fiber context to verify. + * + * @return The stack depth of the given fiber. + */ +inline void verify_stack_size(Fiber *f); + +/** + * Event callback. Called from an instance of MicroBitMessageBus whenever an event is raised. + * + * This function checks to determine if any fibers blocked on the wait queue need to be woken up + * and made runnable due to the event. + * + * @param evt the event that has just been raised on an instance of MicroBitMessageBus. + */ +void scheduler_event(MicroBitEvent evt); + +/** + * Determines if any fibers are waiting to be scheduled. + * + * @return The number of fibers currently on the run queue + */ +int scheduler_runqueue_empty(); + +/** + * Utility function to add the currenty running fiber to the given queue. + * + * Perform a simple add at the head, to avoid complexity, + * + * Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of + * brute force searching. + * + * @param f The fiber to add to the queue + * + * @param queue The run queue to add the fiber to. + */ +void queue_fiber(Fiber *f, Fiber **queue); + +/** + * Utility function to the given fiber from whichever queue it is currently stored on. + * + * @param f the fiber to remove. + */ +void dequeue_fiber(Fiber *f); + +/** + * Set of tasks to perform when idle. + * Service any background tasks that are required, and attempt a power efficient sleep. + */ +void idle(); + +/** + * The idle task, which is called when the runtime has no fibers that require execution. + * + * This function typically calls idle(). + */ +void idle_task(); + +/** + * Adds a component to the array of idle thread components, which are processed + * when the run queue is empty. + * + * The system timer will poll isIdleCallbackNeeded on each component to determine + * if the scheduler should schedule the idle_task imminently. + * + * @param component The component to add to the array. + * + * @return MICROBIT_OK on success or MICROBIT_NO_RESOURCES if the fiber components array is full. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * // heap allocated - otherwise it will be paged out! + * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); + * + * fiber_add_idle_component(accelerometer); + * @endcode + */ +int fiber_add_idle_component(MicroBitComponent *component); + +/** + * Remove a component from the array of idle thread components + * + * @param component The component to remove from the idle component array. + * + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previously added. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * // heap allocated - otherwise it will be paged out! + * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); + * + * fiber_add_idle_component(accelerometer); + * + * fiber_remove_idle_component(accelerometer); + * @endcode + */ +int fiber_remove_idle_component(MicroBitComponent *component); + +/** + * Determines if the processor is executing in interrupt context. + * + * @return true if any the processor is currently executing any interrupt service routine. False otherwise. + */ +inline int inInterruptContext() +{ + return (((int)__get_IPSR()) & 0x003F) > 0; +} + +/** + * Assembler Context switch routing. + * Defined in CortexContextSwitch.s. + */ +extern "C" void swap_context(Cortex_M0_TCB *from, Cortex_M0_TCB *to, uint32_t from_stack, uint32_t to_stack); +extern "C" void save_context(Cortex_M0_TCB *tcb, uint32_t stack); +extern "C" void save_register_context(Cortex_M0_TCB *tcb); +extern "C" void restore_register_context(Cortex_M0_TCB *tcb); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitFont.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,100 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_FONT_H +#define MICROBIT_FONT_H + +#include "mbed.h" +#include "MicroBitConfig.h" + +#define MICROBIT_FONT_WIDTH 5 +#define MICROBIT_FONT_HEIGHT 5 +#define MICROBIT_FONT_ASCII_START 32 +#define MICROBIT_FONT_ASCII_END 126 + +/** + * Class definition for a MicrobitFont + * This class represents a font that can be used by the display to render text. + * + * A MicroBitFont is 5x5. + * Each Row is represented by a byte in the array. + * + * Row Format: + * ================================================================ + * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | + * ================================================================ + * | N/A | N/A | N/A | Col 1 | Col 2 | Col 3 | Col 4 | Col 5 | + * | 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01 | + * + * Example: { 0x08, 0x08, 0x08, 0x0, 0x08 } + * + * The above will produce an exclaimation mark on the second column in form the left. + * + * We could compress further, but the complexity of decode would likely outweigh the gains. + */ +class MicroBitFont +{ + public: + + static const unsigned char* defaultFont; + static MicroBitFont systemFont; + + const unsigned char* characters; + + int asciiEnd; + + /** + * Constructor. + * + * Sets the font represented by this font object. + * + * @param font A pointer to the beginning of the new font. + * + * @param asciiEnd the char value at which this font finishes. + */ + MicroBitFont(const unsigned char* font, int asciiEnd = MICROBIT_FONT_ASCII_END); + + /** + * Default Constructor. + * + * Configures the default font for the display to use. + */ + MicroBitFont(); + + /** + * Modifies the current system font to the given instance of MicroBitFont. + * + * @param font the new font that will be used to render characters on the display. + */ + static void setSystemFont(MicroBitFont font); + + /** + * Retreives the font object used for rendering characters on the display. + */ + static MicroBitFont getSystemFont(); + +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitHeapAllocator.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,167 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * A simple 32 bit block based memory allocator. This allows one or more memory segments to + * be designated as heap storage, and is designed to run in a static memory area or inside the standard C + * heap for use by the micro:bit runtime. This is required for several reasons: + * + * 1) It reduces memory fragmentation due to the high churn sometime placed on the heap + * by ManagedTypes, fibers and user code. Underlying heap implentations are often have very simplistic + * allocation pilicies and suffer from fragmentation in prolonged use - which can cause programs to + * stop working after a period of time. The algorithm implemented here is simple, but highly tolerant to + * large amounts of churn. + * + * 2) It allows us to reuse the 8K of SRAM set aside for SoftDevice as additional heap storage + * when BLE is not in use. + * + * 3) It gives a simple example of how memory allocation works! :-) + * + * P.S. This is a very simple allocator, therefore not without its weaknesses. Why don't you consider + * what these are, and consider the tradeoffs against simplicity... + * + * @note The need for this should be reviewed in the future, if a different memory allocator is + * made availiable in the mbed platform. + * + * TODO: Consider caching recently freed blocks to improve allocation time. + */ + +#ifndef MICROBIT_HEAP_ALLOCTOR_H +#define MICROBIT_HEAP_ALLOCTOR_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include <new> + +// The maximum number of heap segments that can be created. +#define MICROBIT_MAXIMUM_HEAPS 2 + +// Flag to indicate that a given block is FREE/USED +#define MICROBIT_HEAP_BLOCK_FREE 0x80000000 + +/** + * Create and initialise a given memory region as for heap storage. + * After this is called, any future calls to malloc, new, free or delete may use the new heap. + * The heap allocator will attempt to allocate memory from heaps in the order that they are created. + * i.e. memory will be allocated from first heap created until it is full, then the second heap, and so on. + * + * @param start The start address of memory to use as a heap region. + * + * @param end The end address of memory to use as a heap region. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the heap could not be allocated. + * + * @note Only code that #includes MicroBitHeapAllocator.h will use this heap. This includes all micro:bit runtime + * code, and user code targetting the runtime. External code can choose to include this file, or + * simply use the standard heap. + */ +int microbit_create_heap(uint32_t start, uint32_t end); + +/** + * Create and initialise a heap region within the current the heap region specified + * by the linker script. + * + * If the requested amount is not available, then the amount requested will be reduced + * automatically to fit the space available. + * + * @param ratio The proportion of the underlying heap to allocate. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the heap could not be allocated. + */ +int microbit_create_nested_heap(float ratio); + +/** + * Attempt to allocate a given amount of memory from any of our configured heap areas. + * + * @param size The amount of memory, in bytes, to allocate. + * + * @return A pointer to the allocated memory, or NULL if insufficient memory is available. + */ +void *microbit_malloc(size_t size); + + +/** + * Release a given area of memory from the heap. + * + * @param mem The memory area to release. + */ +void microbit_free(void *mem); + +/* + * Wrapper function to ensure we have an explicit handle on the heap allocator provided + * by our underlying platform. + * + * @param size The amount of memory, in bytes, to allocate. + * + * @return A pointer to the memory allocated. NULL if no memory is available. + */ +inline void *native_malloc(size_t size) +{ + return malloc(size); +} + +/* + * Wrapper function to ensure we have an explicit handle on the heap allocator provided + * by our underlying platform. + * + * @param p Pointer to the memory to be freed. + */ +inline void native_free(void *p) +{ + free(p); +} + +/** + * Overrides the 'new' operator globally, and redirects calls to the micro:bit heap allocator. + */ +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. + */ +inline void operator delete(void *ptr) throw() +{ + microbit_free(ptr); +} + + +// Macros to override overrides the 'malloc' and 'delete' functions globally, and redirects calls +// to the micro:bit theap allocator. + +#define malloc(X) microbit_malloc( X ) +#define free(X) microbit_free( X ) + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitListener.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,168 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_LISTENER_H +#define MICROBIT_LISTENER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitEvent.h" +#include "MemberFunctionCallback.h" +#include "MicroBitConfig.h" + +// MicroBitListener flags... +#define MESSAGE_BUS_LISTENER_PARAMETERISED 0x0001 +#define MESSAGE_BUS_LISTENER_METHOD 0x0002 +#define MESSAGE_BUS_LISTENER_BUSY 0x0004 +#define MESSAGE_BUS_LISTENER_REENTRANT 0x0008 +#define MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY 0x0010 +#define MESSAGE_BUS_LISTENER_DROP_IF_BUSY 0x0020 +#define MESSAGE_BUS_LISTENER_NONBLOCKING 0x0040 +#define MESSAGE_BUS_LISTENER_URGENT 0x0080 +#define MESSAGE_BUS_LISTENER_DELETING 0x8000 + +#define MESSAGE_BUS_LISTENER_IMMEDIATE (MESSAGE_BUS_LISTENER_NONBLOCKING | MESSAGE_BUS_LISTENER_URGENT) + +/** + * This structure defines a MicroBitListener used to invoke functions, or member + * functions if an instance of EventModel receives an event whose id and value + * match this MicroBitListener's id and value. + */ +struct MicroBitListener +{ + uint16_t id; // The ID of the component that this listener is interested in. + uint16_t value; // Value this listener is interested in receiving. + uint16_t flags; // Status and configuration options codes for this listener. + + union + { + void (*cb)(MicroBitEvent); + void (*cb_param)(MicroBitEvent, void *); + MemberFunctionCallback *cb_method; + }; + + void* cb_arg; // Optional argument to be passed to the caller. + + MicroBitEvent evt; + MicroBitEventQueueItem *evt_queue; + + MicroBitListener *next; + + /** + * Constructor. + * + * Create a new Message Bus Listener. + * + * @param id The ID of the component you want to listen to. + * + * @param value The event value you would like to listen to from that component + * + * @param handler A function pointer to call when the event is detected. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + */ + MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent), uint16_t flags = EVENT_LISTENER_DEFAULT_FLAGS); + + /** + * Constructor. + * + * Create a new Message Bus Listener, this constructor accepts an additional + * parameter "arg", which is passed to the handler. + * + * @param id The ID of the component you want to listen to. + * + * @param value The event value you would like to listen to from that component + * + * @param handler A function pointer to call when the event is detected. + * + * @param arg A pointer to some data that will be given to the handler. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + */ + MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent, void *), void* arg, uint16_t flags = EVENT_LISTENER_DEFAULT_FLAGS); + + + /** + * Constructor. + * + * Create a new Message Bus Listener, with a callback to a C++ member function. + * + * @param id The ID of the component you want to listen to. + * + * @param value The event value you would like to listen to from that component + * + * @param object The C++ object on which to call the event handler. + * + * @param method The method within the C++ object to call. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + */ + template <typename T> + MicroBitListener(uint16_t id, uint16_t value, T* object, void (T::*method)(MicroBitEvent), uint16_t flags = EVENT_LISTENER_DEFAULT_FLAGS); + + /** + * Destructor. Ensures all resources used by this listener are freed. + */ + ~MicroBitListener(); + + /** + * Queues and event up to be processed. + * + * @param e The event to queue + */ + void queue(MicroBitEvent e); +}; + +/** + * Constructor. + * + * Create a new Message Bus Listener, with a callback to a C++ member function. + * + * @param id The ID of the component you want to listen to. + * + * @param value The event value you would like to listen to from that component + * + * @param object The C++ object on which to call the event handler. + * + * @param method The method within the C++ object to call. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + */ +template <typename T> +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, T* object, void (T::*method)(MicroBitEvent), uint16_t flags) +{ + this->id = id; + this->value = value; + this->cb_method = new MemberFunctionCallback(object, method); + this->cb_arg = NULL; + this->flags = flags | MESSAGE_BUS_LISTENER_METHOD; + this->next = NULL; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitSystemTimer.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,149 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Definitions for the MicroBit system timer. + * + * This module provides: + * + * 1) a concept of global system time since power up + * 2) a simple periodic multiplexing API for the underlying mbed implementation. + * + * The latter is useful to avoid costs associated with multiple mbed Ticker instances + * in microbit-dal components, as each incurs a significant additional RAM overhead (circa 80 bytes). + */ + +#ifndef MICROBIT_SYSTEM_TIMER_H +#define MICROBIT_SYSTEM_TIMER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" + +/** + * Initialises the system wide timer. + * + * This must be called before any components register to receive periodic periodic callbacks. + * + * @param timer_period The initial period between interrupts, in millseconds. + * + * @return MICROBIT_OK on success. + */ +int system_timer_init(int period); + +/** + * Reconfigures the system wide timer to the given period in milliseconds. + * + * @param period the new period of the timer in milliseconds + * + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if period < 1 + */ +int system_timer_set_period(int period); + +/** + * Accessor to obtain the current tick period in milliseconds + * + * @return the current tick period in milliseconds + */ +int system_timer_get_period(); + +/** + * Determines the time since the device was powered on. + * + * @return the current time since power on in milliseconds + */ +unsigned long system_timer_current_time(); + +/** + * Timer callback. Called from interrupt context, once per period. + * + * Simply checks to determine if any fibers blocked on the sleep queue need to be woken up + * and made runnable. + */ +void system_timer_tick(); + +/** + * Add a component to the array of system components. This component will then receive + * periodic callbacks, once every tick period in interrupt context. + * + * @param component The component to add. + * + * @return MICROBIT_OK on success or MICROBIT_NO_RESOURCES if the component array is full. + * + * @code + * // heap allocated - otherwise it will be paged out! + * MicroBitDisplay* display = new MicroBitDisplay(); + * + * system_timer_add_component(display); + * @endcode + */ +int system_timer_add_component(MicroBitComponent *component); + +/** + * Remove a component from the array of system components. This component will no longer receive + * periodic callbacks. + * + * @param component The component to remove. + * + * @return MICROBIT_OK on success or MICROBIT_INVALID_PARAMETER is returned if the given component has not been previously added. + * + * @code + * // heap allocated - otherwise it will be paged out! + * MicroBitDisplay* display = new MicroBitDisplay(); + * + * system_timer_add_component(display); + * + * system_timer_remove_component(display); + * @endcode + */ +int system_timer_remove_component(MicroBitComponent *component); + +/** + * A simple C/C++ wrapper to allow periodic callbacks to standard C functions transparently. + */ +class MicroBitSystemTimerCallback : MicroBitComponent +{ + void (*fn)(void); + + /** + * Creates an object that receives periodic callbacks from the system timer, + * and, in turn, calls a plain C function as provided as a parameter. + * + * @param function the function to invoke upon a systemTick. + */ + public: + MicroBitSystemTimerCallback(void (*function)(void)) + { + fn = function; + system_timer_add_component(this); + } + + void systemTick() + { + fn(); + } +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/NotifyEvents.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,37 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef NOTIFY_EVENTS_H +#define NOTIFY_EVENTS_H + +/** + * This file contains events used on the general purpose Eventing channel + * MICROBIT_ID_NOTIFY, new events should be added here, to prevent duplication. + */ +#define MICROBIT_DISPLAY_EVT_FREE 1 +#define MICROBIT_SERIAL_EVT_TX_EMPTY 2 +#define MICROBIT_UART_S_EVT_TX_EMPTY 3 + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/DynamicPwm.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,216 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "mbed.h" +#include "MicroBitConfig.h" + +#ifndef MICROBIT_DYNAMIC_PWM_H +#define MICROBIT_DYNAMIC_PWM_H + +#define NO_PWMS 3 +#define MICROBIT_DEFAULT_PWM_PERIOD 20000 + +enum PwmPersistence +{ + PWM_PERSISTENCE_TRANSIENT = 1, + PWM_PERSISTENCE_PERSISTENT = 2, +}; + +/** + * Class definition for DynamicPwm. + * + * This class addresses a few issues found in the underlying libraries. + * This provides the ability for a neat, clean swap between PWM channels. + */ +class DynamicPwm : public PwmOut +{ + private: + static DynamicPwm* pwms[NO_PWMS]; + static uint8_t lastUsed; + static uint16_t sharedPeriod; + uint8_t flags; + float lastValue; + + + + /** + * An internal constructor used when allocating a new DynamicPwm instance. + * + * @param pin the name of the pin for the pwm to target + * + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + */ + DynamicPwm(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT); + + public: + + /** + * Redirects the pwm channel to point at a different pin. + * + * @param pin the desired pin to output a PWM wave. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->redirect(p0); // pwm is now produced on p0 + * @endcode + */ + void redirect(PinName pin); + + + /** + * Creates a new DynamicPwm instance, or reuses an existing instance that + * has a persistence level of PWM_PERSISTENCE_TRANSIENT. + * + * @param pin the name of the pin for the pwm to target + * + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + * + * @return a pointer to the first available free pwm channel - or the first one that can be reallocated. If + * no channels are available, NULL is returned. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * @endcode + */ + static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT); + + /** + * Frees this DynamicPwm instance for reuse. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->release(); + * @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 + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->write(0.5); + * @endcode + */ + int write(float value); + + /** + * Retreives the PinName associated with this DynamicPwm instance. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * + * // returns the PinName n. + * pwm->getPinName(); + * @endcode + * + * @note This should be used to check that the DynamicPwm instance has not + * been reallocated for use in another part of a program. + */ + PinName getPinName(); + + /** + * Retreives the last value that has been written to this DynamicPwm instance. + * in the range 0 - 1023 inclusive. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->write(0.5); + * + * // will return 512. + * pwm->getValue(); + * @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->setPeriodUs(20000); + * + * // will return 20000 + * pwm->getPeriod(); + * @endcode + */ + int getPeriod(); + + /** + * Sets the period used by the WHOLE PWM module. + * + * @param period the desired period in microseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if period is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * + * // period now is 20ms + * pwm->setPeriodUs(20000); + * @endcode + * + * @note Any changes to the period will AFFECT ALL CHANNELS. + */ + 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 period is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * + * // period now is 20ms + * pwm->setPeriod(20); + * @endcode + */ + int setPeriod(int period); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitAccelerometer.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,467 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_ACCELEROMETER_H +#define MICROBIT_ACCELEROMETER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitCoordinateSystem.h" +#include "MicroBitI2C.h" + +/** + * Relevant pin assignments + */ +#define MICROBIT_PIN_ACCEL_DATA_READY P0_28 + +/** + * Status flags + */ +#define MICROBIT_ACCEL_PITCH_ROLL_VALID 0x02 +#define MICROBIT_ACCEL_ADDED_TO_IDLE 0x04 + +/** + * I2C constants + */ +#define MMA8653_DEFAULT_ADDR 0x3A + +/** + * MMA8653 Register map (partial) + */ +#define MMA8653_STATUS 0x00 +#define MMA8653_OUT_X_MSB 0x01 +#define MMA8653_WHOAMI 0x0D +#define MMA8653_XYZ_DATA_CFG 0x0E +#define MMA8653_CTRL_REG1 0x2A +#define MMA8653_CTRL_REG2 0x2B +#define MMA8653_CTRL_REG3 0x2C +#define MMA8653_CTRL_REG4 0x2D +#define MMA8653_CTRL_REG5 0x2E + + +/** + * MMA8653 constants + */ +#define MMA8653_WHOAMI_VAL 0x5A + +#define MMA8653_SAMPLE_RANGES 3 +#define MMA8653_SAMPLE_RATES 8 + +/** + * Accelerometer events + */ +#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 +{ + int16_t x; + int16_t y; + int16_t z; +}; + +struct MMA8653SampleRateConfig +{ + uint32_t sample_period; + uint8_t ctrl_reg1; +}; + +struct MMA8653SampleRangeConfig +{ + uint8_t sample_range; + 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 MicroBitAccelerometer : public MicroBitComponent +{ + 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. + MicroBitI2C& i2c; // The I2C interface to use. + 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 a software abstraction of an accelerometer. + * + * @param _i2c an instance of MicroBitI2C used to communicate with the onboard accelerometer. + * + * @param address the default I2C address of the accelerometer. Defaults to: MMA8653_DEFAULT_ADDR. + * + * @param id the unique EventModel id of this component. Defaults to: MICROBIT_ID_ACCELEROMETER + * + * @code + * MicroBitI2C i2c = MicroBitI2C(I2C_SDA0, I2C_SCL0); + * + * MicroBitAccelerometer accelerometer = MicroBitAccelerometer(i2c); + * @endcode + */ + MicroBitAccelerometer(MicroBitI2C &_i2c, uint16_t address = MMA8653_DEFAULT_ADDR, uint16_t id = MICROBIT_ID_ACCELEROMETER); + + /** + * 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 configure(); + + /** + * Reads the acceleration data from the accelerometer, and stores it in our buffer. + * This only happens if the accelerometer indicates that it has new data via int1. + * + * On first use, this member function will attempt to add this component to the + * list of fiber components in order to constantly update the values stored + * by this object. + * + * This technique is called lazy instantiation, and it means that we do not + * obtain the overhead from non-chalantly adding this component to fiber components. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the read request fails. + */ + int updateSample(); + + /** + * Attempts to set the sample rate of the accelerometer to the specified value (in ms). + * + * @param period the requested time between samples, in milliseconds. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the request fails. + * + * @code + * // sample rate is now 20 ms. + * accelerometer.setPeriod(20); + * @endcode + * + * @note The requested rate may not be possible on the hardware. In this case, the + * nearest lower rate is chosen. + */ + int setPeriod(int period); + + /** + * Reads the currently configured sample rate of the accelerometer. + * + * @return The time between samples, in milliseconds. + */ + int getPeriod(); + + /** + * Attempts to set the sample range of the accelerometer to the specified value (in g). + * + * @param range The requested sample range of samples, in g. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the request fails. + * + * @code + * // the sample range of the accelerometer is now 8G. + * accelerometer.setRange(8); + * @endcode + * + * @note The requested range may not be possible on the hardware. In this case, the + * nearest lower range is chosen. + */ + int setRange(int range); + + /** + * Reads the currently configured sample range of the accelerometer. + * + * @return The sample range, in g. + */ + int getRange(); + + /** + * Attempts to read the 8 bit ID from the accelerometer, this can be used for + * validation purposes. + * + * @return the 8 bit ID returned by the accelerometer, or MICROBIT_I2C_ERROR if the request fails. + * + * @code + * accelerometer.whoAmI(); + * @endcode + */ + int whoAmI(); + + /** + * Reads the value of the X axis from the latest update retrieved from the accelerometer. + * + * @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. + * + * @code + * accelerometer.getX(); + * @endcode + */ + int getX(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Reads the value of the Y axis from the latest update retrieved from the accelerometer. + * + * @return The force measured in the Y axis, in milli-g. + * + * @code + * accelerometer.getY(); + * @endcode + */ + int getY(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Reads the value of the Z axis from the latest update retrieved from the accelerometer. + * + * @return The force measured in the Z axis, in milli-g. + * + * @code + * accelerometer.getZ(); + * @endcode + */ + int getZ(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Provides a rotation compensated pitch of the device, based on the latest update retrieved from the accelerometer. + * + * @return The pitch of the device, in degrees. + * + * @code + * accelerometer.getPitch(); + * @endcode + */ + int getPitch(); + + /** + * Provides a rotation compensated pitch of the device, based on the latest update retrieved from the accelerometer. + * + * @return The pitch of the device, in radians. + * + * @code + * accelerometer.getPitchRadians(); + * @endcode + */ + float getPitchRadians(); + + /** + * Provides a rotation compensated roll of the device, based on the latest update retrieved from the accelerometer. + * + * @return The roll of the device, in degrees. + * + * @code + * accelerometer.getRoll(); + * @endcode + */ + int getRoll(); + + /** + * Provides a rotation compensated roll of the device, based on the latest update retrieved from the accelerometer. + * + * @return The roll of the device, in radians. + * + * @code + * accelerometer.getRollRadians(); + * @endcode + */ + float getRollRadians(); + + /** + * Retrieves the last recorded gesture. + * + * @return The last gesture that was detected. + * + * Example: + * @code + * MicroBitDisplay display; + * + * if (accelerometer.getGesture() == SHAKE) + * display.scroll("SHAKE!"); + * @endcode + */ + BasicGesture getGesture(); + + /** + * A periodic callback invoked by the fiber scheduler idle thread. + * + * Internally calls updateSample(). + */ + virtual void idleTick(); + + /** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + * + * We check if any data is ready for reading by checking the interrupt flag on the accelerometer. + */ + virtual int isIdleCallbackNeeded(); + + /** + * Destructor for MicroBitButton, where we deregister this instance from the array of fiber components. + */ + ~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. + */ + int writeCommand(uint8_t reg, uint8_t value); + + /** + * Issues a read command, copying data 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. + * + * @note 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(); + + /** + * A service function. + * It 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 a 'best guess' posture of the device based on instantaneous data. + * + * This makes no use of historic data, and forms this input to the filter implemented in updateGesture(). + * + * @return A 'best guess' of the current posture of the device, based on instanataneous data. + */ + BasicGesture instantaneousPosture(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitButton.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,145 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_BUTTON_H +#define MICROBIT_BUTTON_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitEvent.h" + +#define MICROBIT_PIN_BUTTON_A P0_17 +#define MICROBIT_PIN_BUTTON_B P0_26 +#define MICROBIT_PIN_BUTTON_RESET P0_19 + +#define MICROBIT_BUTTON_EVT_DOWN 1 +#define MICROBIT_BUTTON_EVT_UP 2 +#define MICROBIT_BUTTON_EVT_CLICK 3 +#define MICROBIT_BUTTON_EVT_LONG_CLICK 4 +#define MICROBIT_BUTTON_EVT_HOLD 5 +#define MICROBIT_BUTTON_EVT_DOUBLE_CLICK 6 + +#define MICROBIT_BUTTON_LONG_CLICK_TIME 1000 +#define MICROBIT_BUTTON_HOLD_TIME 1500 + +#define MICROBIT_BUTTON_STATE 1 +#define MICROBIT_BUTTON_STATE_HOLD_TRIGGERED 2 +#define MICROBIT_BUTTON_STATE_CLICK 4 +#define MICROBIT_BUTTON_STATE_LONG_CLICK 8 + +#define MICROBIT_BUTTON_SIGMA_MIN 0 +#define MICROBIT_BUTTON_SIGMA_MAX 12 +#define MICROBIT_BUTTON_SIGMA_THRESH_HI 8 +#define MICROBIT_BUTTON_SIGMA_THRESH_LO 2 +#define MICROBIT_BUTTON_DOUBLE_CLICK_THRESH 50 + +enum MicroBitButtonEventConfiguration +{ + MICROBIT_BUTTON_SIMPLE_EVENTS, + MICROBIT_BUTTON_ALL_EVENTS +}; + + +/** + * Class definition for MicroBit Button. + * + * Represents a single, generic button on the device. + */ +class MicroBitButton : public MicroBitComponent +{ + PinName name; // mbed pin name for this button. + 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. + * + * Create a software representation of a button. + * + * @param name the physical pin on the processor that should be used as input. + * + * @param id the ID of the new MicroBitButton object. + * + * @param eventConfiguration Configures the events that will be generated by this MicroBitButton instance. + * Defaults to MICROBIT_BUTTON_ALL_EVENTS. + * + * @param mode the configuration of internal pullups/pulldowns, as defined in the mbed PinMode class. PullNone by default. + * + * @code + * buttonA(MICROBIT_PIN_BUTTON_A, MICROBIT_ID_BUTTON_A); + * @endcode + */ + MicroBitButton(PinName name, uint16_t id, MicroBitButtonEventConfiguration eventConfiguration = MICROBIT_BUTTON_ALL_EVENTS, PinMode mode = PullNone); + + /** + * Tests if this Button is currently pressed. + * + * @code + * if(buttonA.isPressed()) + * display.scroll("Pressed!"); + * @endcode + * + * @return 1 if this button is pressed, 0 otherwise. + */ + int isPressed(); + + /** + * Changes the event configuration used by this button to the given MicroBitButtonEventConfiguration. + * + * All subsequent events generated by this button will then be informed by this configuraiton. + * + * @param config The new configuration for this button. Legal values are MICROBIT_BUTTON_ALL_EVENTS or MICROBIT_BUTTON_SIMPLE_EVENTS. + * + * Example: + * @code + * // Configure a button to generate all possible events. + * buttonA.setEventConfiguration(MICROBIT_BUTTON_ALL_EVENTS); + * + * // Configure a button to suppress MICROBIT_BUTTON_EVT_CLICK and MICROBIT_BUTTON_EVT_LONG_CLICK events. + * buttonA.setEventConfiguration(MICROBIT_BUTTON_SIMPLE_EVENTS); + * @endcode + */ + void setEventConfiguration(MicroBitButtonEventConfiguration config); + + /** + * periodic callback from MicroBit system timer. + * + * Check for state change for this button, and fires various events on a state change. + */ + virtual void systemTick(); + + /** + * Destructor for MicroBitButton, where we deregister this instance from the array of fiber components. + */ + ~MicroBitButton(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitCompass.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,518 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_COMPASS_H +#define MICROBIT_COMPASS_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitCoordinateSystem.h" +#include "MicroBitAccelerometer.h" +#include "MicroBitStorage.h" + +/** + * Relevant pin assignments + */ +#define MICROBIT_PIN_COMPASS_DATA_READY P0_29 + +/** + * I2C constants + */ +#define MAG3110_DEFAULT_ADDR 0x1D + +/** + * MAG3110 Register map + */ +#define MAG_DR_STATUS 0x00 +#define MAG_OUT_X_MSB 0x01 +#define MAG_OUT_X_LSB 0x02 +#define MAG_OUT_Y_MSB 0x03 +#define MAG_OUT_Y_LSB 0x04 +#define MAG_OUT_Z_MSB 0x05 +#define MAG_OUT_Z_LSB 0x06 +#define MAG_WHOAMI 0x07 +#define MAG_SYSMOD 0x08 +#define MAG_OFF_X_MSB 0x09 +#define MAG_OFF_X_LSB 0x0A +#define MAG_OFF_Y_MSB 0x0B +#define MAG_OFF_Y_LSB 0x0C +#define MAG_OFF_Z_MSB 0x0D +#define MAG_OFF_Z_LSB 0x0E +#define MAG_DIE_TEMP 0x0F +#define MAG_CTRL_REG1 0x10 +#define MAG_CTRL_REG2 0x11 + +/** + * Configuration options + */ +struct MAG3110SampleRateConfig +{ + uint32_t sample_period; + uint8_t ctrl_reg1; +}; + +extern const MAG3110SampleRateConfig MAG3110SampleRate[]; + +#define MAG3110_SAMPLE_RATES 11 + +/** + * Compass events + */ +#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 2 +#define MICROBIT_COMPASS_STATUS_CALIBRATING 4 +#define MICROBIT_COMPASS_STATUS_ADDED_TO_IDLE 8 + +/** + * Term to convert sample data into SI units + */ +#define MAG3110_NORMALIZE_SAMPLE(x) (100*x) + +/** + * MAG3110 MAGIC ID value + * Returned from the MAG_WHO_AM_I register for ID purposes. + */ +#define MAG3110_WHOAMI_VAL 0xC4 + +struct CompassSample +{ + int x; + int y; + int z; + + CompassSample() + { + this->x = 0; + this->y = 0; + this->z = 0; + } + + CompassSample(int x, int y, int z) + { + this->x = x; + this->y = y; + this->z = z; + } + + bool operator==(const CompassSample& other) const + { + return x == other.x && y == other.y && z == other.z; + } + + bool operator!=(const CompassSample& other) const + { + return !(x == other.x && y == other.y && z == other.z); + } +}; + +/** + * Class definition for MicroBit Compass. + * + * Represents an implementation of the Freescale MAG3110 I2C Magnetmometer. + * Also includes basic caching, calibration and on demand activation. + */ +class MicroBitCompass : public MicroBitComponent +{ + 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. + MicroBitI2C& i2c; // The I2C interface the sensor is connected to. + MicroBitAccelerometer* accelerometer; // The accelerometer to use for tilt compensation. + MicroBitStorage* storage; // An instance of MicroBitStorage used for persistence. + + public: + + /** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param _accelerometer an instance of the accelerometer, used for tilt compensation. + * + * @param _storage an instance of MicroBitStorage, used to persist calibration data across resets. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitAccelerometer accelerometer(i2c); + * + * MicroBitStorage storage; + * + * MicroBitCompass compass(i2c, accelerometer, storage); + * @endcode + */ + MicroBitCompass(MicroBitI2C& _i2c, MicroBitAccelerometer& _accelerometer, MicroBitStorage& _storage, uint16_t address = MAG3110_DEFAULT_ADDR, uint16_t id = MICROBIT_ID_COMPASS); + + /** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param _accelerometer an instance of the accelerometer, used for tilt compensation. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitAccelerometer accelerometer(i2c); + * + * MicroBitCompass compass(i2c, accelerometer, storage); + * @endcode + */ + MicroBitCompass(MicroBitI2C& _i2c, MicroBitAccelerometer& _accelerometer, uint16_t address = MAG3110_DEFAULT_ADDR, uint16_t id = MICROBIT_ID_COMPASS); + + /** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param _storage an instance of MicroBitStorage, used to persist calibration data across resets. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitStorage storage; + * + * MicroBitCompass compass(i2c, storage); + * @endcode + */ + MicroBitCompass(MicroBitI2C& _i2c, MicroBitStorage& _storage, uint16_t address = MAG3110_DEFAULT_ADDR, uint16_t id = MICROBIT_ID_COMPASS); + + /** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitCompass compass(i2c); + * @endcode + */ + MicroBitCompass(MicroBitI2C& _i2c, uint16_t address = MAG3110_DEFAULT_ADDR, uint16_t id = MICROBIT_ID_COMPASS); + + /** + * Configures the compass for the 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 or MICROBIT_I2C_ERROR if the magnetometer could not be configured. + */ + int configure(); + + /** + * Attempts to set the sample rate of the compass to the specified value (in ms). + * + * @param period the requested time between samples, in milliseconds. + * + * @return MICROBIT_OK or MICROBIT_I2C_ERROR if the magnetometer could not be updated. + * + * @code + * // sample rate is now 20 ms. + * compass.setPeriod(20); + * @endcode + * + * @note The requested rate may not be possible on the hardware. In this case, the + * nearest lower rate is chosen. + */ + int setPeriod(int period); + + /** + * 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. + * + * 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. + * + * @code + * compass.heading(); + * @endcode + */ + int heading(); + + /** + * Attempts to read the 8 bit ID from the magnetometer, this can be used for + * validation purposes. + * + * @return the 8 bit ID returned by the magnetometer, or MICROBIT_I2C_ERROR if the request fails. + * + * @code + * compass.whoAmI(); + * @endcode + */ + int whoAmI(); + + /** + * Reads the value of the X axis from the latest update retrieved from the magnetometer. + * + * @param system The coordinate system to use. By default, a simple cartesian system is provided. + * + * @return The magnetic force measured in the X axis, in nano teslas. + * + * @code + * compass.getX(); + * @endcode + */ + int getX(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Reads the value of the Y axis from the latest update retrieved from the magnetometer. + * + * @param system The coordinate system to use. By default, a simple cartesian system is provided. + * + * @return The magnetic force measured in the Y axis, in nano teslas. + * + * @code + * compass.getY(); + * @endcode + */ + int getY(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Reads the value of the Z axis from the latest update retrieved from the magnetometer. + * + * @param system The coordinate system to use. By default, a simple cartesian system is provided. + * + * @return The magnetic force measured in the Z axis, in nano teslas. + * + * @code + * compass.getZ(); + * @endcode + */ + int getZ(MicroBitCoordinateSystem system = SIMPLE_CARTESIAN); + + /** + * Determines the overall magnetic field strength based on the latest update from the magnetometer. + * + * @return The magnetic force measured across all axis, in nano teslas. + * + * @code + * 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 temperature reading could not be retreived + * from the accelerometer. + */ + 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. + * + * 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 successfully. + * + * @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS + */ + int calibrate(); + + /** + * Configure the compass to use the calibration data that is supplied to this call. + * + * 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 calibration A CompassSample containing the offsets for the x, y and z axis. + */ + 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 calibration A CompassSample containing the offsets for the x, y and z axis. + */ + CompassSample getCalibration(); + + /** + * Updates the local sample, only if the compass indicates that + * data is stale. + * + * @note Can be used to trigger manual updates, if the device is running without a scheduler. + * Also called internally by all get[X,Y,Z]() member functions. + */ + int updateSample(); + + /** + * Periodic callback from MicroBit idle thread. + * + * Calls updateSample(). + */ + 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, where we deregister this instance from the array of fiber components. + */ + ~MicroBitCompass(); + + 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. + */ + int writeCommand(uint8_t reg, uint8_t value); + + /** + * Issues a read command, copying data 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); + + /** + * Issues a read of a given address, and returns the value. + * + * Blocks the calling thread until complete. + * + * @param reg The address of the 16 bit register to access. + * + * @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. + * + * @param reg The address of the 16 bit register to access. + * + * @return The register value, interpreted as a 8 bit unsigned value, or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. + */ + int read8(uint8_t reg); + + /** + * Calculates a tilt compensated bearing of the device, using the accelerometer. + */ + int tiltCompensatedBearing(); + + /** + * Calculates a non-tilt compensated bearing of the device. + */ + int basicBearing(); + + /** + * An initialisation member function used by the many constructors of MicroBitCompass. + * + * @param id the unique identifier for this compass instance. + * + * @param address the base address of the magnetometer on the i2c bus. + */ + void init(uint16_t id, uint16_t address); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitCompassCalibrator.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,84 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_COMPASS_CALIBRATOR_H +#define MICROBIT_COMPASS_CALIBRATOR_H + +#include "MicroBitConfig.h" +#include "MicroBitCompass.h" +#include "MicroBitAccelerometer.h" +#include "MicroBitDisplay.h" + + +/** + * Class definition for an interactive compass calibration algorithm. + * + * The algorithm uses an accelerometer to ensure that a broad range of sample data has been gathered + * from the compass module, then performs a least mean squares optimisation of the + * results to determine the calibration data for the compass. + * + * The LED matrix display is used to provide feedback to the user on the gestures required. + * + * This class listens for calibration requests from the compass (on the default event model), + * and automatically initiates a calibration sequence as necessary. + */ +class MicroBitCompassCalibrator +{ + MicroBitCompass& compass; + MicroBitAccelerometer& accelerometer; + MicroBitDisplay& display; + + public: + + /** + * Constructor. + * + * Create an object capable of calibrating the compass. + * + * The algorithm uses an accelerometer to ensure that a broad range of sample data has been gathered + * from the compass module, then performs a least mean squares optimisation of the + * results to determine the calibration data for the compass. + * + * The LED matrix display is used to provide feedback to the user on the gestures required. + * + * @param compass The compass instance to calibrate. + * + * @param accelerometer The accelerometer to gather contextual data from. + * + * @param display The LED matrix to display user feedback on. + */ + MicroBitCompassCalibrator(MicroBitCompass& _compass, MicroBitAccelerometer& _accelerometer, MicroBitDisplay& _display); + + /** + * 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 calibrate(MicroBitEvent); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitDisplay.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,639 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_DISPLAY_H +#define MICROBIT_DISPLAY_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "ManagedString.h" +#include "MicroBitComponent.h" +#include "MicroBitImage.h" +#include "MicroBitFont.h" +#include "MicroBitMatrixMaps.h" +#include "MicroBitLightSensor.h" + +/** + * Event codes raised by MicroBitDisplay + */ +#define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1 +#define MICROBIT_DISPLAY_EVT_LIGHT_SENSE 2 + +// +// Internal constants +// + +#define MICROBIT_DISPLAY_SPACING 1 +#define MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH 8 +#define MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS -255 + +enum AnimationMode { + ANIMATION_MODE_NONE, + ANIMATION_MODE_STOPPED, + ANIMATION_MODE_SCROLL_TEXT, + ANIMATION_MODE_PRINT_TEXT, + ANIMATION_MODE_SCROLL_IMAGE, + ANIMATION_MODE_ANIMATE_IMAGE, + ANIMATION_MODE_PRINT_CHARACTER +}; + +enum DisplayMode { + DISPLAY_MODE_BLACK_AND_WHITE, + DISPLAY_MODE_GREYSCALE, + DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE +}; + +enum DisplayRotation { + MICROBIT_DISPLAY_ROTATION_0, + MICROBIT_DISPLAY_ROTATION_90, + MICROBIT_DISPLAY_ROTATION_180, + MICROBIT_DISPLAY_ROTATION_270 +}; + +/** + * Class definition for MicroBitDisplay. + * + * A MicroBitDisplay represents the LED matrix array on the micro:bit. + */ +class MicroBitDisplay : public MicroBitComponent +{ + uint8_t width; + uint8_t height; + uint8_t brightness; + uint8_t strobeRow; + uint8_t rotation; + uint8_t mode; + uint8_t greyscaleBitMsk; + uint8_t timingCount; + uint32_t col_mask; + + Timeout renderTimer; + PortOut *LEDMatrix; + + // + // State used by all animation routines. + // + + // The animation mode that's currently running (if any) + volatile AnimationMode animationMode; + + // The time in milliseconds between each frame update. + uint16_t animationDelay; + + // The time in milliseconds since the frame update. + uint16_t animationTick; + + // Stop playback of any animations + void stopAnimation(int delay); + + // + // State for scrollString() method. + // This is a surprisingly intricate method. + // + // The text being displayed. + ManagedString scrollingText; + + // The index of the character currently being displayed. + uint16_t scrollingChar; + + // The number of pixels the current character has been shifted on the display. + uint8_t scrollingPosition; + + // + // State for printString() method. + // + // The text being displayed. NULL if no message is scheduled for playback. + // We *could* get some reuse in here with the scroll* variables above, + // but best to keep it clean in case kids try concurrent operation (they will!), + // given the small RAM overhead needed to maintain orthogonality. + ManagedString printingText; + + // The index of the character currently being displayed. + uint16_t printingChar; + + // + // State for scrollImage() method. + // + // The image being displayed. + MicroBitImage scrollingImage; + + // The number of pixels the image has been shifted on the display. + int16_t scrollingImagePosition; + + // The number of pixels the image is shifted on the display in each quantum. + int8_t scrollingImageStride; + + // A pointer to an instance of light sensor, if in use + MicroBitLightSensor* lightSensor; + + // Flag to indicate if image has been rendered to screen yet (or not) + bool scrollingImageRendered; + + const MatrixMap &matrixMap; + + // Internal methods to handle animation. + + /** + * Periodic callback, that we use to perform any animations we have running. + */ + void animationUpdate(); + + /** + * Called by the display in an interval determined by the brightness of the display, to give an impression + * of brightness. + */ + void renderFinish(); + + /** + * Translates a bit mask to a bit mask suitable for the nrf PORT0 and PORT1. + * Brightness has two levels on, or off. + */ + void render(); + + /** + * Renders the current image, and drops the fourth frame to allow for + * sensors that require the display to operate. + */ + void renderWithLightSense(); + + /** + * Translates a bit mask into a timer interrupt that gives the appearence of greyscale. + */ + void renderGreyscale(); + + /** + * Internal scrollText update method. + * Shift the screen image by one pixel to the left. If necessary, paste in the next char. + */ + void updateScrollText(); + + /** + * Internal printText update method. + * Paste the next character in the string. + */ + void updatePrintText(); + + /** + * Internal scrollImage update method. + * Paste the stored bitmap at the appropriate point. + */ + void updateScrollImage(); + + /** + * Internal animateImage update method. + * Paste the stored bitmap at the appropriate point and stop on the last frame. + */ + void updateAnimateImage(); + + /** + * Broadcasts an event onto the defult EventModel indicating that the + * current animation has completed. + */ + void sendAnimationCompleteEvent(); + + /** + * Blocks the current fiber until the display is available (i.e. does not effect is being displayed). + * Animations are queued until their time to display. + */ + void waitForFreeDisplay(); + + /** + * Blocks the current fiber until the current animation has finished. + * If the scheduler is not running, this call will essentially perform a spinning wait. + */ + void fiberWait(); + + /** + * Enables or disables the display entirely, and releases the pins for other uses. + * + * @param enableDisplay true to enabled the display, or false to disable it. + */ + void setEnable(bool enableDisplay); + +public: + // The mutable bitmap buffer being rendered to the LED matrix. + MicroBitImage image; + + /** + * Constructor. + * + * Create a software representation the micro:bit's 5x5 LED matrix. + * The display is initially blank. + * + * @param id The id the display should use when sending events on the MessageBus. Defaults to MICROBIT_ID_DISPLAY. + * + * @param map The mapping information that relates pin inputs/outputs to physical screen coordinates. + * Defaults to microbitMatrixMap, defined in MicroBitMatrixMaps.h. + * + * @code + * MicroBitDisplay display; + * @endcode + */ + MicroBitDisplay(uint16_t id = MICROBIT_ID_DISPLAY, const MatrixMap &map = microbitMatrixMap); + + /** + * Stops any currently running animation, and any that are waiting to be displayed. + */ + void stopAnimation(); + + /** + * Frame update method, invoked periodically to strobe the display. + */ + virtual void systemTick(); + + /** + * Prints the given character to the display, if it is not in use. + * + * @param c The character to display. + * + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever, + * or until the Displays next use. + * + * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync('p'); + * display.printAsync('p',100); + * @endcode + */ + int printCharAsync(char c, int delay = 0); + + /** + * Prints the given ManagedString to the display, one character at a time. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Must be > 0. + * Defaults to: MICROBIT_DEFAULT_PRINT_SPEED. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync("abc123",400); + * @endcode + */ + int printAsync(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + + /** + * Prints the given image to the display, if the display is not in use. + * Returns immediately, and executes the animation asynchronously. + * + * @param i The image to display. + * + * @param x The horizontal position on the screen to display the image. Defaults to 0. + * + * @param y The vertical position on the screen to display the image. Defaults to 0. + * + * @param alpha Treats the brightness level '0' as transparent. Defaults to 0. + * + * @param delay The time to delay between characters, in milliseconds. Defaults to 0. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.print(i,400); + * @endcode + */ + int printAsync(MicroBitImage i, int x = 0, int y = 0, int alpha = 0, int delay = 0); + + /** + * Prints the given character to the display. + * + * @param c The character to display. + * + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever, + * or until the Displays next use. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync('p'); + * display.printAsync('p',100); + * @endcode + */ + int printChar(char c, int delay = 0); + + /** + * Prints the given string to the display, one character at a time. + * + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_PRINT_SPEED. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.print("abc123",400); + * @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 image has been displayed. + * + * @param i The image to display. + * + * @param x The horizontal position on the screen to display the image. Defaults to 0. + * + * @param y The vertical position on the screen to display the image. Defaults to 0. + * + * @param alpha Treats the brightness level '0' as transparent. Defaults to 0. + * + * @param delay The time to display the image for, or zero to show the image forever. Defaults to 0. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.print(i,400); + * @endcode + */ + int print(MicroBitImage i, int x = 0, int y = 0, int alpha = 0, int delay = 0); + + /** + * Scrolls the given string to the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.scrollAsync("abc123",100); + * @endcode + */ + int scrollAsync(ManagedString s, int delay = MICROBIT_DEFAULT_SCROLL_SPEED); + + /** + * Scrolls the given image across the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * + * @param image The image to display. + * + * @param delay The time between updates, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @param stride The number of pixels to shift by in each update. Defaults to MICROBIT_DEFAULT_SCROLL_STRIDE. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.scrollAsync(i,100,1); + * @endcode + */ + int scrollAsync(MicroBitImage image, int delay = MICROBIT_DEFAULT_SCROLL_SPEED, int stride = MICROBIT_DEFAULT_SCROLL_STRIDE); + + /** + * Scrolls the given string across the display, from right to left. + * Blocks the calling thread until all text has been displayed. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.scroll("abc123",100); + * @endcode + */ + int scroll(ManagedString s, int delay = MICROBIT_DEFAULT_SCROLL_SPEED); + + /** + * Scrolls the given image across the display, from right to left. + * Blocks the calling thread until all the text has been displayed. + * + * @param image The image to display. + * + * @param delay The time between updates, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @param stride The number of pixels to shift by in each update. Defaults to MICROBIT_DEFAULT_SCROLL_STRIDE. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.scroll(i,100,1); + * @endcode + */ + int scroll(MicroBitImage image, int delay = MICROBIT_DEFAULT_SCROLL_SPEED, int stride = MICROBIT_DEFAULT_SCROLL_STRIDE); + + /** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Returns immediately. + * + * @param image The image to display. + * + * @param delay The time to delay between each update of the display, in milliseconds. + * + * @param stride The number of pixels to shift by in each update. + * + * @param startingPosition the starting position on the display for the animation + * to begin at. Defaults to MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * display.animateAsync(i,100,5); + * @endcode + */ + int animateAsync(MicroBitImage image, int delay, int stride, int startingPosition = MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS); + + /** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Blocks the calling thread until the animation is complete. + * + * + * @param delay The time to delay between each update of the display, in milliseconds. + * + * @param stride The number of pixels to shift by in each update. + * + * @param startingPosition the starting position on the display for the animation + * to begin at. Defaults to MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * display.animate(i,100,5); + * @endcode + */ + int animate(MicroBitImage image, int delay, int stride, int startingPosition = MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS); + + /** + * Configures the brightness of the display. + * + * @param b The brightness to set the brightness to, in the range 0 - 255. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER + * + * @code + * display.setBrightness(255); //max brightness + * @endcode + */ + int setBrightness(int b); + + /** + * Configures the mode of the display. + * + * @param mode The mode to swap the display into. One of: DISPLAY_MODE_GREYSCALE, + * DISPLAY_MODE_BLACK_AND_WHITE, DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE + * + * @code + * display.setDisplayMode(DISPLAY_MODE_GREYSCALE); //per pixel brightness + * @endcode + */ + void setDisplayMode(DisplayMode mode); + + /** + * Retrieves the mode of the display. + * + * @return the current mode of the display + */ + int getDisplayMode(); + + /** + * Fetches the current brightness of this display. + * + * @return the brightness of this display, in the range 0..255. + * + * @code + * display.getBrightness(); //the current brightness + * @endcode + */ + int getBrightness(); + + /** + * Rotates the display to the given position. + * + * Axis aligned values only. + * + * @code + * display.rotateTo(MICROBIT_DISPLAY_ROTATION_180); //rotates 180 degrees from original orientation + * @endcode + */ + void rotateTo(DisplayRotation position); + + /** + * Enables the display, should only be called if the display is disabled. + * + * @code + * display.enable(); //Enables the display mechanics + * @endcode + * + * @note Only enables the display if the display is currently disabled. + */ + void enable(); + + /** + * Disables the display, which releases control of the GPIO pins used by the display, + * which are exposed on the edge connector. + * + * @code + * display.disable(); //disables the display + * @endcode + * + * @note Only disables the display if the display is currently enabled. + */ + void disable(); + + /** + * Clears the display of any remaining pixels. + * + * `display.image.clear()` can also be used! + * + * @code + * display.clear(); //clears the display + * @endcode + */ + void clear(); + + /** + * Updates the font that will be used for display operations. + * + * @param font the new font that will be used to render characters. + * + * @note DEPRECATED! Please use MicroBitFont::setSystemFont() instead. + */ + void setFont(MicroBitFont font); + + /** + * Retrieves the font object used for rendering characters on the display. + * + * @note DEPRECATED! Please use MicroBitFont::getSystemFont() instead. + */ + MicroBitFont getFont(); + + /** + * Captures the bitmap currently being rendered on the display. + * + * @return a MicroBitImage containing the captured data. + */ + MicroBitImage screenShot(); + + /** + * Gives a representative figure of the light level in the current environment + * where are micro:bit is situated. + * + * Internally, it constructs an instance of a MicroBitLightSensor if not already configured + * and sets the display mode to DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE. + * + * This also changes the tickPeriod to MICROBIT_LIGHT_SENSOR_TICK_SPEED so + * that the display does not suffer from artifacts. + * + * @return an indicative light level in the range 0 - 255. + * + * @note this will return 0 on the first call to this method, a light reading + * will be available after the display has activated the light sensor for the + * first time. + */ + int readLightLevel(); + + /** + * Destructor for MicroBitDisplay, where we deregister this instance from the array of system components. + */ + ~MicroBitDisplay(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitI2C.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,107 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_I2C_H +#define MICROBIT_I2C_H + +#include "mbed.h" +#include "MicroBitConfig.h" + +#define MICROBIT_I2C_MAX_RETRIES 9 + +/** + * Class definition for MicroBitI2C. + * + * Presents a wrapped mbed call to capture failed I2C operations caused by a known silicon bug in the nrf51822. + * Attempts to automatically reset and restart the I2C hardware if this case is detected. + * + * For reference see PAN56 in: + * + * https://www.nordicsemi.com/eng/nordic/Products/nRF51822/PAN-nRF51822/24634 + * + * v2.0 through to v2.4 + */ +class MicroBitI2C : public I2C +{ + uint8_t retries; + + public: + + /** + * Constructor. + * + * Create an instance of MicroBitI2C for I2C communication. + * + * @param sda the Pin to be used for SDA + * + * @param scl the Pin to be used for SCL + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * @endcode + * + * @note This class presents a wrapped mbed call to capture failed I2C operations caused by a known silicon bug in the nrf51822. + * Attempts to automatically reset and restart the I2C hardware if this case is detected. + * + * For reference see PAN56 in: + * + * https://www.nordicsemi.com/eng/nordic/Products/nRF51822/PAN-nRF51822/24634 + * + * v2.0 through to v2.4 + */ + MicroBitI2C(PinName sda, PinName scl); + + /** + * Performs a complete read transaction. The bottom bit of the address is forced to 1 to indicate a read. + * + * @param address 8-bit I2C slave address [ addr | 1 ] + * + * @param data A pointer to a byte buffer used for storing retrieved data. + * + * @param length Number of bytes to read. + * + * @param repeated if true, stop is not sent at the end. Defaults to false. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if an unresolved read failure is detected. + */ + int read(int address, char *data, int length, bool repeated = false); + + /** + * Performs a complete write transaction. The bottom bit of the address is forced to 0 to indicate a write. + * + * @param address 8-bit I2C slave address [ addr | 0 ] + * + * @param data A pointer to a byte buffer containing the data to write. + * + * @param length Number of bytes to write + * + * @param repeated if true, stop is not sent at the end. Defaults to false. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if an unresolved write failure is detected. + */ + int write(int address, const char *data, int length, bool repeated = false); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitIO.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,81 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_IO_H +#define MICROBIT_IO_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitPin.h" + +/** + * Class definition for MicroBit IO. + * + * Represents a collection of all I/O pins on the edge connector. + */ +class MicroBitIO +{ + public: + + MicroBitPin pin[0]; + MicroBitPin P0; + MicroBitPin P1; + MicroBitPin P2; + MicroBitPin P3; + MicroBitPin P4; + MicroBitPin P5; + MicroBitPin P6; + MicroBitPin P7; + MicroBitPin P8; + MicroBitPin P9; + MicroBitPin P10; + MicroBitPin P11; + MicroBitPin P12; + MicroBitPin P13; + MicroBitPin P14; + MicroBitPin P15; + MicroBitPin P16; + MicroBitPin P19; + MicroBitPin P20; + + /** + * Constructor. + * + * Create a representation of all given I/O pins on the edge connector + * + * Accepts a sequence of unique ID's used to distinguish events raised + * by MicroBitPin instances on the default EventModel. + */ + MicroBitIO(int ID_P0, int ID_P1, int ID_P2, + int ID_P3, int ID_P4, int ID_P5, + int ID_P6, int ID_P7, int ID_P8, + int ID_P9, int ID_P10,int ID_P11, + int ID_P12,int ID_P13,int ID_P14, + int ID_P15,int ID_P16,int ID_P19, + int ID_P20); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitLightSensor.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,137 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_LIGHT_SENSOR_H +#define MICROBIT_LIGHT_SENSOR_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "EventModel.h" +#include "MicroBitMatrixMaps.h" + +#define MICROBIT_LIGHT_SENSOR_CHAN_NUM 3 +#define MICROBIT_LIGHT_SENSOR_AN_SET_TIME 4000 +#define MICROBIT_LIGHT_SENSOR_TICK_PERIOD 5 + +#define MICROBIT_LIGHT_SENSOR_MAX_VALUE 338 +#define MICROBIT_LIGHT_SENSOR_MIN_VALUE 75 + +/** + * Class definition for MicroBitLightSensor. + * + * This is an object that interleaves light sensing with MicroBitDisplay. + */ +class MicroBitLightSensor +{ + + //contains the results from each section of the display + int results[MICROBIT_LIGHT_SENSOR_CHAN_NUM] = { 0 }; + + //holds the current channel (also used to index the results array) + uint8_t chan; + + //a Timeout which triggers our analogReady() call + Timeout analogTrigger; + + //a pointer the currently sensed pin, represented as an AnalogIn + AnalogIn* sensePin; + + const MatrixMap &matrixMap; + + /** + * After the startSensing method has been called, this method will be called + * MICROBIT_LIGHT_SENSOR_AN_SET_TIME after. + * + * It will then read from the currently selected channel using the AnalogIn + * that was configured in the startSensing method. + */ + void analogReady(); + + /** + * Forcibly disables the AnalogIn, otherwise it will remain in possession + * of the GPIO channel it is using, meaning that the display will not be + * able to use a channel (COL). + * + * This is required as per PAN 3, details of which can be found here: + * + * https://www.nordicsemi.com/eng/nordic/download_resource/24634/5/88440387 + */ + void analogDisable(); + + public: + + /** + * Constructor. + * + * Create a representation of the light sensor. + * + * @param map The mapping information that relates pin inputs/outputs to physical screen coordinates. + * Defaults to microbitMatrixMap, defined in MicroBitMatrixMaps.h. + */ + MicroBitLightSensor(const MatrixMap &map); + + /** + * This method returns a summed average of the three sections of the display. + * + * A section is defined as: + * ___________________ + * | 1 | | 2 | | 3 | + * |___|___|___|___|___| + * | | | | | | + * |___|___|___|___|___| + * | 2 | | 3 | | 1 | + * |___|___|___|___|___| + * | | | | | | + * |___|___|___|___|___| + * | 3 | | 1 | | 2 | + * |___|___|___|___|___| + * + * Where each number represents a different section on the 5 x 5 matrix display. + * + * @return returns a value in the range 0 - 255 where 0 is dark, and 255 + * is very bright + */ + int read(); + + /** + * The method that is invoked by sending MICROBIT_DISPLAY_EVT_LIGHT_SENSE + * using the id MICROBIT_ID_DISPLAY. + * + * @note this can be manually driven by calling this member function, with + * a MicroBitEvent using the CREATE_ONLY option of the MicroBitEvent + * constructor. + */ + void startSensing(MicroBitEvent); + + /** + * A destructor for MicroBitLightSensor. + * + * The destructor removes the listener, used by MicroBitLightSensor from the default EventModel. + */ + ~MicroBitLightSensor(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitMatrixMaps.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,157 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Definition of the LED Matrix maps supported. + * Each map represents the layou of a different device. + * + * Ensure only one of these is selected. + */ + +#ifndef MICROBIT_MATRIX_MAPS_H +#define MICROBIT_MATRIX_MAPS_H + +#include "MicroBitConfig.h" +#include "mbed.h" + +#define NO_CONN 0 + +/** + * Provides the mapping from Matrix ROW/COL to a linear X/Y buffer. + * It's arranged such that matrixMap[col, row] provides the [x,y] screen co-ord. + */ + +struct MatrixPoint +{ + uint8_t x; + uint8_t y; +}; + +/** + * This struct presumes rows and columns are arranged contiguously... + */ +struct MatrixMap +{ + int width; // The physical width of the LED matrix, in pixels. + int height; // The physical height of the LED matrix, in pixels. + int rows; // The number of drive pins connected to LEDs. + int columns; // The number of sink pins connected to the LEDs. + + PinName rowStart; // ID of the first drive pin. + PinName columnStart; // ID of the first sink pink. + + const MatrixPoint *map; // Table mapping logical LED positions to physical positions. +}; + +/* + * Dimensions for well known micro:bit LED configurations + */ +#define MICROBIT_DISPLAY_WIDTH 5 +#define MICROBIT_DISPLAY_HEIGHT 5 + +#if MICROBIT_DISPLAY_TYPE == MICROBUG_REFERENCE_DEVICE + +#define MICROBIT_DISPLAY_COLUMN_COUNT 5 +#define MICROBIT_DISPLAY_ROW_COUNT 5 + + const MatrixPoint microbitDisplayMap[MICROBIT_DISPLAY_ROW_COUNT * MICROBIT_DISPLAY_COLUMN_COUNT] = + { + {0,0},{0,1},{0,2},{0,3},{0,4}, + {1,0},{1,1},{1,2},{1,3},{1,4}, + {2,0},{2,1},{2,2},{2,3},{2,4}, + {3,0},{3,1},{3,2},{3,3},{3,4}, + {4,0},{4,1},{4,2},{4,3},{4,4} + }; + +#endif + +#if MICROBIT_DISPLAY_TYPE == MICROBIT_3X9 + +#define MICROBIT_DISPLAY_COLUMN_COUNT 9 +#define MICROBIT_DISPLAY_ROW_COUNT 3 + + const MatrixPoint microbitDisplayMap[MICROBIT_DISPLAY_ROW_COUNT * MICROBIT_DISPLAY_COLUMN_COUNT] = + { + {0,4},{0,3},{1,1}, + {1,4},{4,2},{0,1}, + {2,4},{3,2},{4,0}, + {3,4},{2,2},{3,0}, + {4,4},{1,2},{2,0}, + {4,3},{0,2},{1,0}, + {3,3},{4,1},{0,0}, + {2,3},{3,1},{NO_CONN,NO_CONN}, + {1,3},{2,1},{NO_CONN,NO_CONN} + }; + +#endif + +#if MICROBIT_DISPLAY_TYPE == MICROBIT_SB1 + +#define MICROBIT_DISPLAY_COLUMN_COUNT 3 +#define MICROBIT_DISPLAY_ROW_COUNT 9 + + const MatrixPoint microbitDisplayMap[MICROBIT_DISPLAY_ROW_COUNT * MICROBIT_DISPLAY_COLUMN_COUNT] = + { + {0,4},{1,4},{2,4},{3,4},{4,4},{4,3},{3,3},{2,3},{1,3}, + {0,3},{4,2},{3,2},{2,2},{1,2},{0,2},{4,1},{3,1},{2,1}, + {1,1},{0,1},{4,0},{3,0},{2,0},{1,0},{0,0},{NO_CONN,NO_CONN},{NO_CONN,NO_CONN} + }; + +#endif + +#if MICROBIT_DISPLAY_TYPE == MICROBIT_SB2 + +#define MICROBIT_DISPLAY_COLUMN_COUNT 9 +#define MICROBIT_DISPLAY_ROW_COUNT 3 + + const MatrixPoint microbitDisplayMap[MICROBIT_DISPLAY_ROW_COUNT * MICROBIT_DISPLAY_COLUMN_COUNT] = + { + {0,0},{4,2},{2,4}, + {2,0},{0,2},{4,4}, + {4,0},{2,2},{0,4}, + {4,3},{1,0},{0,1}, + {3,3},{3,0},{1,1}, + {2,3},{3,4},{2,1}, + {1,3},{1,4},{3,1}, + {0,3},{NO_CONN,NO_CONN},{4,1}, + {1,2},{NO_CONN,NO_CONN},{3,2} + }; + +#endif + +//ROW1 and COL1 are defined in mbed classic: +//https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/TARGET_NRF51_MICROBIT/PinNames.h +const MatrixMap microbitMatrixMap = +{ + MICROBIT_DISPLAY_WIDTH, + MICROBIT_DISPLAY_HEIGHT, + MICROBIT_DISPLAY_ROW_COUNT, + MICROBIT_DISPLAY_COLUMN_COUNT, + ROW1, + COL1, + microbitDisplayMap +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitMessageBus.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,188 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_MESSAGE_BUS_H +#define MICROBIT_MESSAGE_BUS_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitEvent.h" +#include "MicroBitListener.h" +#include "EventModel.h" + +/** + * Class definition for the MicroBitMessageBus. + * + * The MicroBitMessageBus is the common mechanism to deliver asynchronous events on the + * MicroBit platform. It serves a number of purposes: + * + * 1) It provides an eventing abstraction that is independent of the underlying substrate. + * + * 2) It provides a mechanism to decouple user code from trusted system code + * i.e. the basis of a message passing nano kernel. + * + * 3) It allows a common high level eventing abstraction across a range of hardware types.e.g. buttons, BLE... + * + * 4) It provides a mechanim for extensibility - new devices added via I/O pins can have OO based + * drivers and communicate via the message bus with minima impact on user level languages. + * + * 5) It allows for the possiblility of event / data aggregation, which in turn can save energy. + * + * It has the following design principles: + * + * 1) Maintain a low RAM footprint where possible + * + * 2) Make few assumptions about the underlying platform, but allow optimizations where possible. + */ +class MicroBitMessageBus : public EventModel, public MicroBitComponent +{ + public: + + /** + * Default constructor. + * + * Adds itself as a fiber component, and also configures itself to be the + * default EventModel if defaultEventBus is NULL. + */ + MicroBitMessageBus(); + + /** + * Queues the given event to be sent to all registered recipients. + * + * @param evt The event to send. + * + * @code + * MicroBitMessageBus bus; + * + * // Creates and sends the MicroBitEvent using bus. + * MicrobitEvent evt(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * + * // Creates the MicrobitEvent, but delays the sending of that event. + * MicrobitEvent evt1(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, CREATE_ONLY); + * + * bus.send(evt1); + * + * // This has the same effect! + * evt1.fire() + * @endcode + */ + virtual int send(MicroBitEvent evt); + + /** + * Internal function, used to deliver the given event to all relevant recipients. + * Normally, this is called once an event has been removed from the event queue. + * + * @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. Defaults to false. + * + * @return 1 if all matching listeners were processed, 0 if further processing is required. + * + * @note It is recommended that all external code uses the send() function instead of this function, + * or the constructors provided by MicrobitEvent. + */ + int process(MicroBitEvent &evt, bool urgent = false); + + /** + * Returns the microBitListener with the given position in our list. + * + * @param n The position in the list to return. + * + * @return the MicroBitListener at postion n in the list, or NULL if the position is invalid. + */ + virtual MicroBitListener *elementAt(int n); + + /** + * Destructor for MicroBitMessageBus, where we deregister this instance from the array of fiber components. + */ + ~MicroBitMessageBus(); + + /** + * Add the given MicroBitListener to the list of event handlers, unconditionally. + * + * @param listener The MicroBitListener to add. + * + * @return MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise. + */ + virtual int add(MicroBitListener *newListener); + + /** + * Remove the given MicroBitListener from the list of event handlers. + * + * @param listener The MicroBitListener to remove. + * + * @return MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise. + */ + virtual int remove(MicroBitListener *newListener); + + private: + + MicroBitListener *listeners; // Chain of active listeners. + MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed. + MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed. + uint16_t nonce_val; // The last nonce issued. + uint16_t queueLength; // The number of events currently waiting to be processed. + + /** + * Cleanup any MicroBitListeners marked for deletion from the list. + * + * @return The number of listeners removed from the list. + */ + int deleteMarkedListeners(); + + /** + * 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. + */ + void queueEvent(MicroBitEvent &evt); + + /** + * Extract the next event from the front of the event queue (if present). + * + * @return a pointer to the MicroBitEventQueueItem that is at the head of the list. + */ + MicroBitEventQueueItem* dequeueEvent(); + + /** + * 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. + */ + virtual void idleTick(); + + /** + * Indicates whether or not we have any background work to do. + * + * @return 1 if there are any events waitingto be processed, 0 otherwise. + */ + virtual int isIdleCallbackNeeded(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitMultiButton.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,176 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_MULTI_BUTTON_H +#define MICROBIT_MULTI_BUTTON_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitButton.h" +#include "EventModel.h" + +#define MICROBIT_MULTI_BUTTON_STATE_1 0x01 +#define MICROBIT_MULTI_BUTTON_STATE_2 0x02 +#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1 0x04 +#define MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2 0x08 +#define MICROBIT_MULTI_BUTTON_SUPRESSED_1 0X10 +#define MICROBIT_MULTI_BUTTON_SUPRESSED_2 0x20 +#define MICROBIT_MULTI_BUTTON_ATTACHED 0x40 + +/** + * Class definition for MicroBitMultiButton. + * + * Represents a virtual button, capable of reacting to simultaneous presses of two + * other buttons. + */ +class MicroBitMultiButton : public MicroBitComponent +{ + uint16_t button1; // ID of the first button we're monitoring + uint16_t button2; // ID of the second button we're monitoring + MicroBitButtonEventConfiguration eventConfiguration; // Do we want to generate high level event (clicks), or defer this to another service. + + /** + * Retrieves the button id for the alternate button id given. + * + * @param b the id of the button whose state we would like to retrieve. + * + * @return the other sub button id. + */ + uint16_t otherSubButton(uint16_t b); + + /** + * Determines if the given button id is marked as pressed. + * + * @param button the id of the button whose state we would like to retrieve. + * + * @return 1 if pressed, 0 if not. + */ + int isSubButtonPressed(uint16_t button); + + /** + * Determines if the given button id is marked as held. + * + * @param button the id of the button whose state we would like to retrieve. + * + * @return 1 if held, 0 if not. + */ + int isSubButtonHeld(uint16_t button); + + /** + * Determines if the given button id is marked as supressed. + * + * @param button the id of the button whose state we would like to retrieve. + * + * @return 1 if supressed, 0 if not. + */ + int isSubButtonSupressed(uint16_t button); + + /** + * Configures the button pressed state for the given button id. + * + * @param button the id of the button whose state requires updating. + * + * @param value the value to set for this buttons state. (Transformed into a logical 0 or 1). + */ + void setButtonState(uint16_t button, int value); + + /** + * Configures the button held state for the given button id. + * + * @param button the id of the button whose state requires updating. + * + * @param value the value to set for this buttons state. (Transformed into a logical 0 or 1). + */ + void setHoldState(uint16_t button, int value); + + /** + * Configures the button suppressed state for the given button id. + * + * @param button the id of the button whose state requires updating. + * + * @param value the value to set for this buttons state. (Transformed into a logical 0 or 1). + */ + void setSupressedState(uint16_t button, int value); + + public: + + /** + * Constructor. + * + * Create a representation of a virtual button, that generates events based upon the combination + * of two given buttons. + * + * @param button1 the unique ID of the first button to watch. + * + * @param button2 the unique ID of the second button to watch. + * + * @param id the unique EventModel id of this MicroBitMultiButton instance. + * + * @code + * multiButton(MICROBIT_ID_BUTTON_A, MICROBIT_ID_BUTTON_B, MICROBIT_ID_BUTTON_AB); + * @endcode + */ + MicroBitMultiButton(uint16_t button1, uint16_t button2, uint16_t id); + + /** + * Tests if this MicroBitMultiButton instance is virtually pressed. + * + * @return 1 if both physical buttons are pressed simultaneously. + * + * @code + * if(buttonAB.isPressed()) + * display.scroll("Pressed!"); + * @endcode + */ + int isPressed(); + + /** + * Changes the event configuration of this button to the given MicroBitButtonEventConfiguration. + * All subsequent events generated by this button will then be informed by this configuraiton. + * + * @param config The new configuration for this button. Legal values are MICROBIT_BUTTON_ALL_EVENTS or MICROBIT_BUTTON_SIMPLE_EVENTS. + * + * @code + * // Configure a button to generate all possible events. + * buttonAB.setEventConfiguration(MICROBIT_BUTTON_ALL_EVENTS); + * + * // Configure a button to suppress MICROBIT_BUTTON_EVT_CLICK and MICROBIT_BUTTON_EVT_LONG_CLICK events. + * buttonAB.setEventConfiguration(MICROBIT_BUTTON_SIMPLE_EVENTS); + * @endcode + */ + void setEventConfiguration(MicroBitButtonEventConfiguration config); + + private: + + /** + * A member function that is invoked when any event is detected from the two + * button IDs this MicrobitMultiButton instance was constructed with. + * + * @param evt the event received from the default EventModel. + */ + void onButtonEvent(MicroBitEvent evt); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitPin.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,297 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_PIN_H +#define MICROBIT_PIN_H + +#include "mbed.h" +#include "MicroBitConfig.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 +#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_5 //COL2 (ANALOG/DIGITAL) +#define MICROBIT_PIN_P5 P0_17 //BTN_A +#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_SERVO_RANGE 180 +#define MICROBIT_PIN_DEFAULT_SERVO_RANGE 2000 +#define MICROBIT_PIN_DEFAULT_SERVO_CENTER 1500 + + +/** + * Pin capabilities enum. + * Used to determine the capabilities of each Pin as some can only be digital, or can be both digital and analogue. + */ +enum PinCapability{ + PIN_CAPABILITY_DIGITAL = 0x01, + PIN_CAPABILITY_ANALOG = 0x02, + PIN_CAPABILITY_TOUCH = 0x04, + PIN_CAPABILITY_AD = PIN_CAPABILITY_DIGITAL | PIN_CAPABILITY_ANALOG, + PIN_CAPABILITY_ALL = PIN_CAPABILITY_DIGITAL | PIN_CAPABILITY_ANALOG | PIN_CAPABILITY_TOUCH + +}; + +/** + * Class definition for MicroBitPin. + * + * Commonly represents an I/O pin on the edge connector. + */ +class MicroBitPin : public MicroBitComponent +{ + // The mbed object looking after this pin at any point in time (untyped due to dynamic behaviour). + void *pin; + + PinCapability capability; + + /** + * Disconnect any attached mBed IO from this pin. + * + * Used only when pin changes mode (i.e. Input/Output/Analog/Digital) + */ + void disconnect(); + + /** + * 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: + + // mbed PinName of this pin. + PinName name; + + /** + * Constructor. + * Create a MicroBitPin instance, generally used to represent a pin on the edge connector. + * + * @param id the unique EventModel id of this component. + * + * @param name the mbed PinName for this MicroBitPin instance. + * + * @param capability the capabilities this MicroBitPin instance should have. + * (PIN_CAPABILITY_DIGITAL, PIN_CAPABILITY_ANALOG, PIN_CAPABILITY_TOUCH, PIN_CAPABILITY_AD, PIN_CAPABILITY_ALL) + * + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); + * @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. + * + * @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. + * + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.getDigitalValue(); // P0 is either 0 or 1; + * @endcode + */ + int getDigitalValue(); + + /** + * Configures this IO pin as an 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 + * + * @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 setAnalogValue(int value); + + /** + * Configures this IO pin as an analog/pwm output (if necessary) and configures the period to be 20ms, + * with a duty cycle between 500 us and 2500 us. + * + * A value of 180 sets the duty cycle to be 2500us, and a value of 0 sets the duty cycle to be 500us 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. the 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 samples the Pin for its analog value. + * + * @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. + * + * @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 + */ + int getAnalogValue(); + + /** + * Determines if this IO pin is currently configured as an input. + * + * @return 1 if pin is an analog or digital input, 0 otherwise. + */ + int isInput(); + + /** + * Determines if this IO pin is currently configured as an output. + * + * @return 1 if pin is an analog or digital output, 0 otherwise. + */ + int isOutput(); + + /** + * Determines if this IO pin is currently configured for digital use. + * + * @return 1 if pin is digital, 0 otherwise. + */ + int isDigital(); + + /** + * Determines if this IO pin is currently configured for analog use. + * + * @return 1 if pin is analog, 0 otherwise. + */ + int isAnalog(); + + /** + * Configures this IO pin as a "makey makey" style touch sensor (if necessary) + * and tests its current debounced state. + * + * Users can also subscribe to MicroBitButton events generated from this pin. + * + * @return 1 if pin is touched, 0 if not, or MICROBIT_NOT_SUPPORTED if this pin does not support touch capability. + * + * @code + * MicroBitMessageBus bus; + * + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); + * if(P0.isTouched()) + * { + * //do something! + * } + * + * // subscribe to events generated by this pin! + * bus.listen(MICROBIT_ID_IO_P0, MICROBIT_BUTTON_EVT_CLICK, someFunction); + * @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 + * 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 + * given pin is not configured as an analog output. + */ + int setAnalogPeriodUs(int period); + + /** + * Obtains the PWM period of the analog output in microseconds. + * + * @return the period on success, or MICROBIT_NOT_SUPPORTED if the + * given pin is not configured as an analog output. + */ + int getAnalogPeriodUs(); + + /** + * Obtains the PWM period of the analog output in milliseconds. + * + * @return the period on success, or MICROBIT_NOT_SUPPORTED if the + * given pin is not configured as an analog output. + */ + int getAnalogPeriod(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitRadio.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,227 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_RADIO_H +#define MICROBIT_RADIO_H + +class MicroBitRadio; +struct FrameBuffer; + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "PacketBuffer.h" +#include "MicroBitRadioDatagram.h" +#include "MicroBitRadioEvent.h" + +/** + * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module. + * + * The nrf51822 RADIO module supports a number of proprietary modes of operation in addition to the typical BLE usage. + * This class uses one of these modes to enable simple, point to multipoint communication directly between micro:bits. + * + * TODO: The protocols implemented here do not currently perform any significant form of energy management, + * which means that they will consume far more energy than their BLE equivalent. Later versions of the protocol + * should look to address this through energy efficient broadcast techniques / sleep scheduling. In particular, the GLOSSY + * approach to efficienct rebroadcast and network synchronisation would likely provide an effective future step. + * + * TODO: Meshing should also be considered - again a GLOSSY approach may be effective here, and highly complementary to + * the master/slave arachitecture of BLE. + * + * TODO: This implementation only operates whilst the BLE stack is disabled. The nrf51822 provides a timeslot API to allow + * BLE to cohabit with other protocols. Future work to allow this colocation would be benefical, and would also allow for the + * creation of wireless BLE bridges. + * + * NOTE: This API does not contain any form of encryption, authentication or authorization. It's purpose is solely for use as a + * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place. + * For serious applications, BLE should be considered a substantially more secure alternative. + */ + +// Status Flags +#define MICROBIT_RADIO_STATUS_INITIALISED 0x0001 + +// Default configuration values +#define MICROBIT_RADIO_BASE_ADDRESS 0x75626974 +#define MICROBIT_RADIO_DEFAULT_GROUP 0 +#define MICROBIT_RADIO_DEFAULT_TX_POWER 6 +#define MICROBIT_RADIO_DEFAULT_FREQUENCY 7 +#define MICROBIT_RADIO_MAX_PACKET_SIZE 32 +#define MICROBIT_RADIO_HEADER_SIZE 4 +#define MICROBIT_RADIO_MAXIMUM_RX_BUFFERS 4 + +// Known Protocol Numbers +#define MICROBIT_RADIO_PROTOCOL_DATAGRAM 1 // A simple, single frame datagram. a little like UDP but with smaller packets. :-) +#define MICROBIT_RADIO_PROTOCOL_EVENTBUS 2 // Transparent propogation of events from one micro:bit to another. + +// Events +#define MICROBIT_RADIO_EVT_DATAGRAM 1 // Event to signal that a new datagram has been received. + + +struct FrameBuffer +{ + uint8_t length; // The length of the remaining bytes in the packet. includes protocol/version/group fields, excluding the length field itself. + uint8_t version; // Protocol version code. + uint8_t group; // ID of the group to which this packet belongs. + uint8_t protocol; // Inner protocol number c.f. those issued by IANA for IP protocols + + uint8_t payload[MICROBIT_RADIO_MAX_PACKET_SIZE]; // User / higher layer protocol data + FrameBuffer *next; // Linkage, to allow this and other protocols to queue packets pending processing. + uint8_t rssi; // Received signal strength of this frame. +}; + + +class MicroBitRadio : MicroBitComponent +{ + uint8_t group; // The radio group to which this micro:bit belongs. + uint8_t queueDepth; // The number of packets in the receiver queue. + uint8_t rssi; + FrameBuffer *rxQueue; // A linear list of incoming packets, queued awaiting processing. + FrameBuffer *rxBuf; // A pointer to the buffer being actively used by the RADIO hardware. + + public: + MicroBitRadioDatagram datagram; // A simple datagram service. + MicroBitRadioEvent event; // A simple event handling service. + static MicroBitRadio *instance; // A singleton reference, used purely by the interrupt service routine. + + /** + * Constructor. + * + * Initialise the MicroBitRadio. + * + * @note This class is demand activated, as a result most resources are only + * committed if send/recv or event registrations calls are made. + */ + MicroBitRadio(uint16_t id = MICROBIT_ID_RADIO); + + /** + * Change the output power level of the transmitter to the given value. + * + * @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range. + */ + int setTransmitPower(int power); + + /** + * Change the transmission and reception band of the radio to the given channel + * + * @param band a frequency band in the range 0 - 100. Each step is 1MHz wide, based at 2400MHz. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range, + * or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ + int setFrequencyBand(int band); + + /** + * Retrieve a pointer to the currently allocated receive buffer. This is the area of memory + * actively being used by the radio hardware to store incoming data. + * + * @return a pointer to the current receive buffer. + */ + FrameBuffer * getRxBuf(); + + /** + * Attempt to queue a buffer received by the radio hardware, if sufficient space is available. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if a replacement receiver buffer + * could not be allocated (either by policy or memory exhaustion). + */ + int queueRxBuf(); + + /** + * Sets the RSSI for the most recent packet. + * + * @param rssi the new rssi value. + * + * @note should only be called from RADIO_IRQHandler... + */ + int setRSSI(uint8_t rssi); + + /** + * Retrieves the current RSSI for the most recent packet. + * + * @return the most recent RSSI value or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ + int getRSSI(); + + /** + * Initialises the radio for use as a multipoint sender/receiver + * + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ + int enable(); + + /** + * Disables the radio for use as a multipoint sender/receiver. + * + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ + int disable(); + + /** + * Sets the radio to listen to packets sent with the given group id. + * + * @param group The group to join. A micro:bit can only listen to one group ID at any time. + * + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ + int setGroup(uint8_t group); + + /** + * A background, low priority callback that is triggered whenever the processor is idle. + * Here, we empty our queue of received packets, and pass them onto higher level protocol handlers. + */ + virtual void idleTick(); + + /** + * Determines the number of packets ready to be processed. + * + * @return The number of packets in the receive buffer. + */ + int dataReady(); + + /** + * Retrieves the next packet from the receive buffer. + * If a data packet is available, then it will be returned immediately to + * the caller. This call will also dequeue the buffer. + * + * @return The buffer containing the the packet. If no data is available, NULL is returned. + * + * @note Once recv() has been called, it is the callers resposibility to + * delete the buffer when appropriate. + */ + FrameBuffer* recv(); + + /** + * Transmits the given buffer onto the broadcast radio. + * The call will wait until the transmission of the packet has completed before returning. + * + * @param data The packet contents to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ + int send(FrameBuffer *buffer); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitRadioDatagram.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,135 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_RADIO_DATAGRAM_H +#define MICROBIT_RADIO_DATAGRAM_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitRadio.h" +#include "ManagedString.h" + +/** + * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module. + * + * This class provides the ability to broadcast simple text or binary messages to other micro:bits in the vicinity + * It is envisaged that this would provide the basis for children to experiment with building their own, simple, + * custom protocols. + * + * @note This API does not contain any form of encryption, authentication or authorisation. Its purpose is solely for use as a + * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place. + * For serious applications, BLE should be considered a substantially more secure alternative. + */ +class MicroBitRadioDatagram +{ + MicroBitRadio &radio; // The underlying radio module used to send and receive data. + FrameBuffer *rxQueue; // A linear list of incoming packets, queued awaiting processing. + + public: + + /** + * Constructor. + * + * Creates an instance of a MicroBitRadioDatagram which offers the ability + * to broadcast simple text or binary messages to other micro:bits in the vicinity + * + * @param r The underlying radio module used to send and receive data. + */ + MicroBitRadioDatagram(MicroBitRadio &r); + + /** + * Retrieves packet payload data into the given buffer. + * + * If a data packet is already available, then it will be returned immediately to the caller. + * If no data is available then MICROBIT_INVALID_PARAMETER is returned. + * + * @param buf A pointer to a valid memory location where the received data is to be stored + * + * @param len The maximum amount of data that can safely be stored in 'buf' + * + * @return The length of the data stored, or MICROBIT_INVALID_PARAMETER if no data is available, or the memory regions provided are invalid. + */ + int recv(uint8_t *buf, int len); + + /** + * Retreives packet payload data into the given buffer. + * + * If a data packet is already available, then it will be returned immediately to the caller + * in the form of a PacketBuffer. + * + * @return the data received, or an empty PacketBuffer if no data is available. + */ + PacketBuffer recv(); + + /** + * Transmits the given buffer onto the broadcast radio. + * + * This is a synchronous call that will wait until the transmission of the packet + * has completed before returning. + * + * @param buffer The packet contents to transmit. + * + * @param len The number of bytes to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, + * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. + */ + int send(uint8_t *buffer, int len); + + /** + * Transmits the given string onto the broadcast radio. + * + * This is a synchronous call that will wait until the transmission of the packet + * has completed before returning. + * + * @param data The packet contents to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, + * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. + */ + int send(PacketBuffer data); + + /** + * Transmits the given string onto the broadcast radio. + * + * This is a synchronous call that will wait until the transmission of the packet + * has completed before returning. + * + * @param data The packet contents to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, + * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. + */ + int send(ManagedString data); + + /** + * Protocol handler callback. This is called when the radio receives a packet marked as a datagram. + * + * This function process this packet, and queues it for user reception. + */ + void packetReceived(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitRadioEvent.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,143 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_RADIO_EVENT_H +#define MICROBIT_RADIO_EVENT_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitRadio.h" +#include "EventModel.h" + +/** + * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module. + * + * This class provides the ability to extend the micro:bit's default EventModel to other micro:bits in the vicinity, + * in a very similar way to the MicroBitEventService for BLE interfaces. + * + * It is envisaged that this would provide the basis for children to experiment with building their own, simple, + * custom asynchronous events and actions. + * + * @note This API does not contain any form of encryption, authentication or authorisation. Its purpose is solely for use as a + * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place. + * For serious applications, BLE should be considered a substantially more secure alternative. + */ +class MicroBitRadioEvent +{ + bool suppressForwarding; // A private flag used to prevent event forwarding loops. + MicroBitRadio &radio; // A reference to the underlying radio module to use. + + public: + + /** + * Constructor. + * + * Creates an instance of MicroBitRadioEvent which offers the ability to extend + * the micro:bit's default EventModel to other micro:bits in the vicinity. + * + * @param r The underlying radio module used to send and receive data. + */ + MicroBitRadioEvent(MicroBitRadio &r); + + /** + * Associates the given event with the radio channel. + * + * Once registered, all events matching the given registration sent to this micro:bit's + * default EventModel will be automatically retransmitted on the radio. + * + * @param id The id of the event to register. + * + * @param value the value of the event to register. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if no default EventModel is available. + * + * @note The wildcards MICROBIT_ID_ANY and MICROBIT_EVT_ANY can also be in place of the + * id and value fields. + */ + int listen(uint16_t id, uint16_t value); + + /** + * Associates the given event with the radio channel. + * + * Once registered, all events matching the given registration sent to the given + * EventModel will be automatically retransmitted on the radio. + * + * @param id The id of the events to register. + * + * @param value the value of the event to register. + * + * @param eventBus The EventModel to listen for events on. + * + * @return MICROBIT_OK on success. + * + * @note The wildcards MICROBIT_ID_ANY and MICROBIT_EVT_ANY can also be in place of the + * id and value fields. + */ + int listen(uint16_t id, uint16_t value, EventModel &eventBus); + + /** + * Disassociates the given event with the radio channel. + * + * @param id The id of the events to deregister. + * + * @param value The value of the event to deregister. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the default message bus does not exist. + * + * @note MICROBIT_EVT_ANY can be used to deregister all event values matching the given id. + */ + int ignore(uint16_t id, uint16_t value); + + /** + * Disassociates the given events with the radio channel. + * + * @param id The id of the events to deregister. + * + * @param value The value of the event to deregister. + * + * @param eventBus The EventModel to deregister on. + * + * @return MICROBIT_OK on success. + * + * @note MICROBIT_EVT_ANY can be used to deregister all event values matching the given id. + */ + int ignore(uint16_t id, uint16_t value, EventModel &eventBus); + + /** + * Protocol handler callback. This is called when the radio receives a packet marked as using the event protocol. + * + * This function process this packet, and fires the event contained inside onto the default EventModel. + */ + void packetReceived(); + + /** + * Event handler callback. This is called whenever an event is received matching one of those registered through + * the registerEvent() method described above. Upon receiving such an event, it is wrapped into + * a radio packet and transmitted to any other micro:bits in the same group. + */ + void eventReceived(MicroBitEvent e); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitSerial.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,587 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_SERIAL_H +#define MICROBIT_SERIAL_H + +#include "mbed.h" +#include "ManagedString.h" + +#define MICROBIT_SERIAL_DEFAULT_BAUD_RATE 115200 +#define MICROBIT_SERIAL_DEFAULT_BUFFER_SIZE 20 + +#define MICROBIT_SERIAL_EVT_DELIM_MATCH 1 +#define MICROBIT_SERIAL_EVT_HEAD_MATCH 2 +#define MICROBIT_SERIAL_EVT_RX_FULL 3 + +#define MICROBIT_SERIAL_RX_IN_USE 1 +#define MICROBIT_SERIAL_TX_IN_USE 2 +#define MICROBIT_SERIAL_RX_BUFF_INIT 4 +#define MICROBIT_SERIAL_TX_BUFF_INIT 8 + + +enum MicroBitSerialMode +{ + ASYNC, + SYNC_SPINWAIT, + SYNC_SLEEP +}; + +/** + * Class definition for MicroBitSerial. + * + * Represents an instance of RawSerial which accepts micro:bit specific data types. + */ +class MicroBitSerial : public RawSerial +{ + + //holds that state of the mutex locks for all MicroBitSerial instances. + static uint8_t status; + + //holds the state of the baudrate for all MicroBitSerial instances. + static int baudrate; + + //delimeters used for matching on receive. + ManagedString delimeters; + + //a variable used when a user calls the eventAfter() method. + int rxBuffHeadMatch; + + uint8_t *rxBuff; + uint8_t rxBuffSize; + volatile uint16_t rxBuffHead; + uint16_t rxBuffTail; + + + uint8_t *txBuff; + uint8_t txBuffSize; + uint16_t txBuffHead; + volatile uint16_t txBuffTail; + + /** + * An internal interrupt callback for MicroBitSerial configured for when a + * character is received. + * + * Each time a character is received fill our circular buffer! + */ + void dataReceived(); + + /** + * An internal interrupt callback for MicroBitSerial. + * + * Each time the Serial module's buffer is empty, write a character if we have + * characters to write. + */ + void dataWritten(); + + /** + * An internal method to configure an interrupt on tx buffer and also + * a best effort copy operation to move bytes from a user buffer to our txBuff + * + * @param string a pointer to the first character of the users' buffer. + * + * @param len the length of the string, and ultimately the maximum number of bytes + * that will be copied dependent on the state of txBuff + * + * @return the number of bytes copied into the buffer. + */ + int setTxInterrupt(uint8_t *string, int len); + + /** + * Locks the mutex so that others can't use this serial instance for reception + */ + void lockRx(); + + /** + * Locks the mutex so that others can't use this serial instance for transmission + */ + void lockTx(); + + /** + * Unlocks the mutex so that others can use this serial instance for reception + */ + void unlockRx(); + + /** + * Unlocks the mutex so that others can use this serial instance for transmission + */ + void unlockTx(); + + /** + * We do not want to always have our buffers initialised, especially if users to not + * use them. We only bring them up on demand. + */ + int initialiseRx(); + + /** + * We do not want to always have our buffers initialised, especially if users to not + * use them. We only bring them up on demand. + */ + int initialiseTx(); + + /** + * An internal method that either spin waits if mode is set to SYNC_SPINWAIT + * or puts the fiber to sleep if the mode is set to SYNC_SLEEP + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP + */ + void send(MicroBitSerialMode mode); + + /** + * Reads a single character from the rxBuff + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - A character is read from the rxBuff if available, if there + * are no characters to be read, a value of zero is returned immediately. + * + * SYNC_SPINWAIT - A character is read from the rxBuff if available, if there + * are no characters to be read, this method will spin + * (lock up the processor) until a character is available. + * + * SYNC_SLEEP - A character is read from the rxBuff if available, if there + * are no characters to be read, the calling fiber sleeps + * until there is a character available. + * + * Defaults to SYNC_SLEEP. + * + * @return a character from the circular buffer, or MICROBIT_NO_DATA is there + * are no characters in the buffer. + */ + int getChar(MicroBitSerialMode mode); + + /** + * An internal method that copies values from a circular buffer to a linear buffer. + * + * @param circularBuff a pointer to the source circular buffer + * + * @param circularBuffSize the size of the circular buffer + * + * @param linearBuff a pointer to the destination linear buffer + * + * @param tailPosition the tail position in the circular buffer you want to copy from + * + * @param headPosition the head position in the circular buffer you want to copy to + * + * @note this method assumes that the linear buffer has the appropriate amount of + * memory to contain the copy operation + */ + void circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8_t *linearBuff, uint16_t tailPosition, uint16_t headPosition); + + public: + + /** + * Constructor. + * Create an instance of MicroBitSerial + * + * @param tx the Pin to be used for transmission + * + * @param rx the Pin to be used for receiving data + * + * @param rxBufferSize the size of the buffer to be used for receiving bytes + * + * @param txBufferSize the size of the buffer to be used for transmitting bytes + * + * @code + * MicroBitSerial serial(USBTX, USBRX); + * @endcode + * @note the default baud rate is 115200. More API details can be found: + * -https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/api/SerialBase.h + * -https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/api/RawSerial.h + * + * Buffers aren't allocated until the first send or receive respectively. + */ + MicroBitSerial(PinName tx, PinName rx, uint8_t rxBufferSize = MICROBIT_SERIAL_DEFAULT_BUFFER_SIZE, uint8_t txBufferSize = MICROBIT_SERIAL_DEFAULT_BUFFER_SIZE); + + /** + * Sends a single character over the serial line. + * + * @param c the character to send + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - the character is copied into the txBuff and returns immediately. + * + * SYNC_SPINWAIT - the character is copied into the txBuff and this method + * will spin (lock up the processor) until the character has + * been sent. + * + * SYNC_SLEEP - the character is copied into the txBuff and the fiber sleeps + * until the character has been sent. This allows other fibers + * to continue execution. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of bytes written, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the serial instance for transmission. + */ + int sendChar(char c, MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * Sends a ManagedString over the serial line. + * + * @param s the string to send + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - bytes are copied into the txBuff and returns immediately. + * + * SYNC_SPINWAIT - bytes are copied into the txBuff and this method + * will spin (lock up the processor) until all bytes + * have been sent. + * + * SYNC_SLEEP - bytes are copied into the txBuff and the fiber sleeps + * until all bytes have been sent. This allows other fibers + * to continue execution. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of bytes written, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the serial instance for transmission. + */ + int send(ManagedString s, MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * Sends a buffer of known length over the serial line. + * + * @param buffer a pointer to the first character of the buffer + * + * @param len the number of bytes that are safely available to read. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - bytes are copied into the txBuff and returns immediately. + * + * SYNC_SPINWAIT - bytes are copied into the txBuff and this method + * will spin (lock up the processor) until all bytes + * have been sent. + * + * SYNC_SLEEP - bytes are copied into the txBuff and the fiber sleeps + * until all bytes have been sent. This allows other fibers + * to continue execution. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of bytes written, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the serial instance for transmission. + */ + int send(uint8_t *buffer, int bufferLen, MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * Reads a single character from the rxBuff + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - A character is read from the rxBuff if available, if there + * are no characters to be read, a value of MICROBIT_NO_DATA is returned immediately. + * + * SYNC_SPINWAIT - A character is read from the rxBuff if available, if there + * are no characters to be read, this method will spin + * (lock up the processor) until a character is available. + * + * SYNC_SLEEP - A character is read from the rxBuff if available, if there + * are no characters to be read, the calling fiber sleeps + * until there is a character available. + * + * Defaults to SYNC_SLEEP. + * + * @return a character, MICROBIT_SERIAL_IN_USE if another fiber is using the serial instance for reception, + * MICROBIT_NO_RESOURCES if buffer allocation did not complete successfully, or MICROBIT_NO_DATA if + * the rx buffer is empty and the mode given is ASYNC. + */ + int read(MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * Reads multiple characters from the rxBuff and returns them as a ManagedString + * + * @param size the number of characters to read. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - If the desired number of characters are available, this will return + * a ManagedString with the expected size. Otherwise, it will read however + * many characters there are available. + * + * SYNC_SPINWAIT - If the desired number of characters are available, this will return + * a ManagedString with the expected size. Otherwise, this method will spin + * (lock up the processor) until the desired number of characters have been read. + * + * SYNC_SLEEP - If the desired number of characters are available, this will return + * a ManagedString with the expected size. Otherwise, the calling fiber sleeps + * until the desired number of characters have been read. + * + * Defaults to SYNC_SLEEP. + * + * @return A ManagedString, or an empty ManagedString if an error was encountered during the read. + */ + ManagedString read(int size, MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * Reads multiple characters from the rxBuff and fills a user buffer. + * + * @param buffer a pointer to a user allocated buffer. + * + * @param bufferLen the amount of data that can be safely stored + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - If the desired number of characters are available, this will fill + * the given buffer. Otherwise, it will fill the buffer with however + * many characters there are available. + * + * SYNC_SPINWAIT - If the desired number of characters are available, this will fill + * the given buffer. Otherwise, this method will spin (lock up the processor) + * and fill the buffer until the desired number of characters have been read. + * + * SYNC_SLEEP - If the desired number of characters are available, this will fill + * the given buffer. Otherwise, the calling fiber sleeps + * until the desired number of characters have been read. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of characters read, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the instance for receiving. + */ + int read(uint8_t *buffer, int bufferLen, MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * Reads until one of the delimeters matches a character in the rxBuff + * + * @param delimeters a ManagedString containing a sequence of delimeter characters e.g. ManagedString("\r\n") + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - If one of the delimeters matches a character already in the rxBuff + * this method will return a ManagedString up to the delimeter. + * Otherwise, it will return an Empty ManagedString. + * + * SYNC_SPINWAIT - If one of the delimeters matches a character already in the rxBuff + * this method will return a ManagedString up to the delimeter. + * Otherwise, this method will spin (lock up the processor) until a + * received character matches one of the delimeters. + * + * SYNC_SLEEP - If one of the delimeters matches a character already in the rxBuff + * this method will return a ManagedString up to the delimeter. + * Otherwise, the calling fiber sleeps until a character matching one + * of the delimeters is seen. + * + * Defaults to SYNC_SLEEP. + * + * @return A ManagedString containing the characters up to a delimeter, or an Empty ManagedString, + * if another fiber is currently using this instance for reception. + * + * @note delimeters are matched on a per byte basis. + */ + ManagedString readUntil(ManagedString delimeters, MicroBitSerialMode mode = MICROBIT_DEFAULT_SERIAL_MODE); + + /** + * A wrapper around the inherited method "baud" so we can trap the baud rate + * as it changes and restore it if redirect() is called. + * + * @param baudrate the new baudrate. See: + * - https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/serial_api.c + * for permitted baud rates. + * + * @return MICROBIT_INVALID_PARAMETER if baud rate is less than 0, otherwise MICROBIT_OK. + * + * @note the underlying implementation chooses the first allowable rate at or above that requested. + */ + void baud(int baudrate); + + /** + * A way of dynamically configuring the serial instance to use pins other than USBTX and USBRX. + * + * @param tx the new transmission pin. + * + * @param rx the new reception pin. + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently transmitting or receiving, otherwise MICROBIT_OK. + */ + int redirect(PinName tx, PinName rx); + + /** + * Configures an event to be fired after "len" characters. + * + * @param len the number of characters to wait before triggering the event. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + */ + int eventAfter(int len, MicroBitSerialMode mode = ASYNC); + + /** + * Configures an event to be fired on a match with one of the delimeters. + * + * @param delimeters the characters to match received characters against e.g. ManagedString("\r\n") + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + * + * @note delimeters are matched on a per byte basis. + */ + int eventOn(ManagedString delimeters, MicroBitSerialMode mode = ASYNC); + + /** + * Determines whether there is any data waiting in our Rx buffer. + * + * @return 1 if we have space, 0 if we do not. + * + * @note We do not wrap the super's readable() method as we don't want to + * interfere with communities that use manual calls to serial.readable(). + */ + int isReadable(); + + /** + * Determines if we have space in our txBuff. + * + * @return 1 if we have space, 0 if we do not. + * + * @note We do not wrap the super's writeable() method as we don't want to + * interfere with communities that use manual calls to serial.writeable(). + */ + int isWriteable(); + + /** + * Reconfigures the size of our rxBuff + * + * @param size the new size for our rxBuff + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for reception, otherwise MICROBIT_OK. + */ + int setRxBufferSize(uint8_t size); + + /** + * Reconfigures the size of our txBuff + * + * @param size the new size for our txBuff + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for transmission, otherwise MICROBIT_OK. + */ + int setTxBufferSize(uint8_t size); + + /** + * The size of our rx buffer in bytes. + * + * @return the current size of rxBuff in bytes + */ + int getRxBufferSize(); + + /** + * The size of our tx buffer in bytes. + * + * @return the current size of txBuff in bytes + */ + int getTxBufferSize(); + + /** + * Sets the tail to match the head of our circular buffer for reception, + * effectively clearing the reception buffer. + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for reception, otherwise MICROBIT_OK. + */ + int clearRxBuffer(); + + /** + * Sets the tail to match the head of our circular buffer for transmission, + * effectively clearing the transmission buffer. + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for transmission, otherwise MICROBIT_OK. + */ + int clearTxBuffer(); + + /** + * The number of bytes currently stored in our rx buffer waiting to be digested, + * by the user. + * + * @return The currently buffered number of bytes in our rxBuff. + */ + int rxBufferedSize(); + + /** + * The number of bytes currently stored in our tx buffer waiting to be transmitted + * by the hardware. + * + * @return The currently buffered number of bytes in our txBuff. + */ + int txBufferedSize(); + + /** + * Determines if the serial bus is currently in use by another fiber for reception. + * + * @return The state of our mutex lock for reception. + * + * @note Only one fiber can call read at a time + */ + int rxInUse(); + + /** + * Determines if the serial bus is currently in use by another fiber for transmission. + * + * @return The state of our mutex lock for transmition. + * + * @note Only one fiber can call send at a time + */ + int txInUse(); + + /** + * Detaches a previously configured interrupt + * + * @param interruptType one of Serial::RxIrq or Serial::TxIrq + */ + void detach(Serial::IrqType interuptType); + +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitStorage.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,233 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_STORAGE_H +#define MICROBIT_STORAGE_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "ManagedString.h" +#include "ErrorNo.h" + +#define MICROBIT_STORAGE_MAGIC 0xCAFE + +#define MICROBIT_STORAGE_BLOCK_SIZE 48 +#define MICROBIT_STORAGE_KEY_SIZE 16 +#define MICROBIT_STORAGE_VALUE_SIZE MICROBIT_STORAGE_BLOCK_SIZE - MICROBIT_STORAGE_KEY_SIZE + +#define MICROBIT_STORAGE_STORE_PAGE_OFFSET 17 //Use the page just above the BLE Bond Data. +#define MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET 19 //Use the page just below the BLE Bond Data. + +struct KeyValuePair +{ + uint8_t key[MICROBIT_STORAGE_KEY_SIZE] = { 0 }; + uint8_t value[MICROBIT_STORAGE_VALUE_SIZE] = { 0 }; +}; + +struct KeyValueStore +{ + uint32_t magic; + uint32_t size; + + KeyValueStore(uint32_t magic, uint32_t size) + { + this->magic = magic; + this->size = size; + } + + KeyValueStore() + { + this->magic = 0; + this->size = 0; + } +}; + + +/** + * Class definition for the MicroBitStorage class. + * This allows reading and writing of small blocks of data to FLASH memory. + * + * This class operates as a key value store, it allows the retrieval, addition + * and deletion of KeyValuePairs. + * + * The first 8 bytes are reserved for the KeyValueStore struct which gives core + * information such as the number of KeyValuePairs in the store, and whether the + * store has been initialised. + * + * After the KeyValueStore struct, KeyValuePairs are arranged contiguously until + * the end of the block used as persistent storage. + * + * |-------8-------|--------48-------|-----|---------48--------| + * | KeyValueStore | KeyValuePair[0] | ... | KeyValuePair[N-1] | + * |---------------|-----------------|-----|-------------------| + */ +class MicroBitStorage +{ + /** + * Function for copying words from one location to another. + * + * @param from the address to copy data from. + * + * @param to the address to copy the data to. + * + * @param sizeInWords the number of words to copy + */ + void flashCopy(uint32_t* from, uint32_t* to, int sizeInWords); + + /** + * Function for populating the scratch page with a KeyValueStore. + * + * @param store the KeyValueStore struct to write to the scratch page. + */ + void scratchKeyValueStore(KeyValueStore store); + + /** + * Function for populating the scratch page with a KeyValuePair. + * + * @param pair the KeyValuePair struct to write to the scratch page. + * + * @param flashPointer the pointer in flash where this KeyValuePair resides. This pointer + * is used to determine the offset into the scratch page, where the KeyValuePair should + * be written. + */ + void scratchKeyValuePair(KeyValuePair pair, uint32_t* flashPointer); + + public: + + /** + * Default constructor. + * + * Creates an instance of MicroBitStorage which acts like a KeyValueStore + * that allows the retrieval, addition and deletion of KeyValuePairs. + */ + MicroBitStorage(); + + /** + * Writes the given number of bytes to the address specified. + * + * @param buffer the data to write. + * + * @param address the location in memory to write to. + * + * @param length the number of bytes to write. + * + * @note currently not implemented. + */ + int writeBytes(uint8_t *buffer, uint32_t address, int length); + + /** + * Method for erasing a page in flash. + * + * @param page_address Address of the first word in the page to be erased. + */ + void flashPageErase(uint32_t * page_address); + + /** + * Method for writing a word of data in flash with a value. + * + * @param address Address of the word to change. + * + * @param value Value to be written to flash. + */ + void flashWordWrite(uint32_t * address, uint32_t value); + + /** + * Places a given key, and it's corresponding value into flash at the earliest + * available point. + * + * @param key the unique name that should be used as an identifier for the given data. + * The key is presumed to be null terminated. + * + * @param data a pointer to the beginning of the data to be persisted. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the storage page is full + */ + int put(const char* key, uint8_t* data); + + /** + * Places a given key, and it's corresponding value into flash at the earliest + * available point. + * + * @param key the unique name that should be used as an identifier for the given data. + * + * @param data a pointer to the beginning of the data to be persisted. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the storage page is full + */ + int put(ManagedString key, uint8_t* data); + + /** + * Retreives a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return a pointer to a heap allocated KeyValuePair struct, this pointer will be + * NULL if the key was not found in storage. + * + * @note it is up to the user to free memory after use. + */ + KeyValuePair* get(const char* key); + + /** + * Retreives a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return a pointer to a heap allocated KeyValuePair struct, this pointer will be + * NULL if the key was not found in storage. + * + * @note it is up to the user to free memory after use. + */ + KeyValuePair* get(ManagedString key); + + /** + * Removes a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_DATA if the given key + * was not found in flash. + */ + int remove(const char* key); + + /** + * Removes a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_DATA if the given key + * was not found in flash. + */ + int remove(ManagedString key); + + /** + * The size of the flash based KeyValueStore. + * + * @return the number of entries in the key value store + */ + int size(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/drivers/MicroBitThermometer.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,180 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_THERMOMETER_H +#define MICROBIT_THERMOMETER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitStorage.h" + +#define MICROBIT_THERMOMETER_PERIOD 1000 + + +#define MAG3110_SAMPLE_RATES 11 + +/* + * Temperature events + */ +#define MICROBIT_THERMOMETER_EVT_UPDATE 1 + +#define MICROBIT_THERMOMETER_ADDED_TO_IDLE 2 + +/** + * Class definition for MicroBit Thermometer. + * + * Infers and stores the ambient temoperature based on the surface temperature + * of the various chips on the micro:bit. + * + */ +class MicroBitThermometer : public MicroBitComponent +{ + unsigned long sampleTime; + uint32_t samplePeriod; + int16_t temperature; + int16_t offset; + MicroBitStorage* storage; + + public: + + /** + * Constructor. + * Create new MicroBitThermometer that gives an indication of the current temperature. + * + * @param _storage an instance of MicroBitStorage used to persist temperature offset data + * + * @param id the unique EventModel id of this component. Defaults to MICROBIT_ID_THERMOMETER. + * + * @code + * MicroBitStorage storage; + * MicroBitThermometer thermometer(storage); + * @endcode + */ + MicroBitThermometer(MicroBitStorage& _storage, uint16_t id = MICROBIT_ID_THERMOMETER); + + /** + * Constructor. + * Create new MicroBitThermometer that gives an indication of the current temperature. + * + * @param id the unique EventModel id of this component. Defaults to MICROBIT_ID_THERMOMETER. + * + * @code + * MicroBitThermometer thermometer; + * @endcode + */ + MicroBitThermometer(uint16_t id = MICROBIT_ID_THERMOMETER); + + /** + * Set the sample rate at which the temperatureis read (in ms). + * + * The default sample period is 1 second. + * + * @param period the requested time between samples, in milliseconds. + * + * @note the temperature is always read in the background, and is only updated + * when the processor is idle, or when the temperature is explicitly read. + */ + void setPeriod(int period); + + /** + * Reads the currently configured sample rate of the thermometer. + * + * @return The time between samples, in milliseconds. + */ + int getPeriod(); + + /** + * Set the value that is used to offset the raw silicon temperature. + * + * @param offset the offset for the silicon temperature + * + * @return MICROBIT_OK on success + */ + int setOffset(int offset); + + /** + * Retreive the value that is used to offset the raw silicon temperature. + * + * @return the current offset. + */ + int getOffset(); + + /** + * This member function fetches the raw silicon temperature, and calculates + * the value used to offset the raw silicon temperature based on a given temperature. + * + * @param calibrationTemp the temperature used to calculate the raw silicon temperature + * offset. + * + * @return MICROBIT_OK on success + */ + int setCalibration(int calibrationTemp); + + /** + * Gets the current temperature of the microbit. + * + * @return the current temperature, in degrees celsius. + * + * @code + * thermometer.getTemperature(); + * @endcode + */ + int getTemperature(); + + /** + * Updates the temperature sample of this instance of MicroBitThermometer + * only if isSampleNeeded() indicates that an update is required. + * + * This call also will add the thermometer to fiber components to receive + * periodic callbacks. + * + * @return MICROBIT_OK on success. + */ + int updateSample(); + + /** + * Periodic callback from MicroBit idle thread. + */ + virtual void idleTick(); + + /** + * Indicates if we'd like some processor time to sense the temperature. + * + * @returns 1 if we'd like some processor time, 0 otherwise. + */ + virtual int isIdleCallbackNeeded(); + + private: + + /** + * Determines if we're due to take another temperature reading + * + * @return 1 if we're due to take a temperature reading, 0 otherwise. + */ + int isSampleNeeded(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/ManagedString.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,399 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MANAGED_STRING_H +#define MANAGED_STRING_H + +#include "MicroBitConfig.h" +#include "RefCounted.h" +#include "PacketBuffer.h" + +struct StringData : RefCounted +{ + uint16_t len; + char data[0]; +}; + + +/** + * Class definition for a ManagedString. + * + * Uses basic reference counting to implement a copy-assignable, immutable string. + * + * This maps closely to the constructs found in many high level application languages, + * such as Touch Develop. + * + * Written from first principles here, for several reasons: + * 1) std::shared_ptr is not yet availiable on the ARMCC compiler + * + * 2) to reduce memory footprint - we don't need many of the other features in the std library + * + * 3) it makes an interesting case study for anyone interested in seeing how it works! + * + * 4) we need explicit reference counting to inter-op with low-level application langauge runtimes. + * + * 5) the reference counting needs to also work for read-only, flash-resident strings + */ +class ManagedString +{ + // StringData contains the reference count, the length, follwed by char[] data, all in one block. + // When referece count is 0xffff, then it's read only and should not be counted. + // Otherwise the block was malloc()ed. + // We control access to this to proide immutability and reference counting. + StringData *ptr; + + public: + + /** + * Constructor. + * Create a managed string from a specially prepared string literal. + * + * @param ptr The literal - first two bytes should be 0xff, then the length in little endian, then the literal. The literal has to be 4-byte aligned. + * + * @code + * static const char hello[] __attribute__ ((aligned (4))) = "\xff\xff\x05\x00" "Hello"; + * ManagedString s((StringData*)(void*)hello); + * @endcode + */ + ManagedString(StringData *ptr); + + /** + * Get current ptr, do not decr() it, and set the current instance to empty string. + * + * This is to be used by specialized runtimes which pass StringData around. + */ + StringData *leakData(); + + /** + * Constructor. + * + * Create a managed string from a pointer to an 8-bit character buffer. + * + * The buffer is copied to ensure safe memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * + * @code + * ManagedString s("abcdefg"); + * @endcode + */ + ManagedString(const char *str); + + /** + * Constructor. + * + * Create a managed string from a given integer. + * + * @param value The integer from which to create the ManagedString. + * + * @code + * ManagedString s(20); + * @endcode + */ + ManagedString(const int value); + + + /** + * Constructor. + * Create a managed string from a given char. + * + * @param value The character from which to create the ManagedString. + * + * @code + * ManagedString s('a'); + * @endcode + */ + ManagedString(const char value); + + /** + * Constructor. + * Create a ManagedString from a PacketBuffer. All bytes in the + * PacketBuffer are added to the ManagedString. + * + * @param buffer The PacktBuffer from which to create the ManagedString. + * + * @code + * ManagedString s = radio.datagram.recv(); + * @endcode + */ + ManagedString(PacketBuffer buffer); + + /** + * Constructor. + * Create a ManagedString from a pointer to an 8-bit character buffer of a given length. + * + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * + * @param length The length of the character array + * + * @code + * ManagedString s("abcdefg",7); + * @endcode + */ + ManagedString(const char *str, const int16_t length); + + /** + * Copy constructor. + * Makes a new ManagedString identical to the one supplied. + * + * Shares the character buffer and reference count with the supplied ManagedString. + * + * @param s The ManagedString to copy. + * + * @code + * ManagedString s("abcdefg"); + * ManagedString p(s); + * @endcode + */ + ManagedString(const ManagedString &s); + + /** + * Default constructor. + * + * Create an empty ManagedString. + * + * @code + * ManagedString s(); + * @endcode + */ + ManagedString(); + + /** + * Destructor. + * + * Free this ManagedString, and decrement the reference count to the + * internal character buffer. + * + * If we're holding the last reference, also free the character buffer. + */ + ~ManagedString(); + + /** + * Copy assign operation. + * + * Called when one ManagedString is assigned the value of another. + * + * If the ManagedString being assigned is already refering to a character buffer, + * decrement the reference count and free up the buffer as necessary. + * + * Then, update our character buffer to refer to that of the supplied ManagedString, + * and increase its reference count. + * + * @param s The ManagedString to copy. + * + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * p = s // p now points to s, s' ref is incremented + * @endcode + */ + ManagedString& operator = (const ManagedString& s); + + /** + * Equality operation. + * + * Called when one ManagedString is tested to be equal to another using the '==' operator. + * + * @param s The ManagedString to test ourselves against. + * + * @return true if this ManagedString is identical to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * + * if(p == s) + * display.scroll("We are the same!"); + * else + * display.scroll("We are different!"); //p is not equal to s - this will be called + * @endcode + */ + bool operator== (const ManagedString& s); + + /** + * Inequality operation. + * + * Called when one ManagedString is tested to be less than another using the '<' operator. + * + * @param s The ManagedString to test ourselves against. + * + * @return true if this ManagedString is alphabetically less than to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(s < p) + * display.scroll("a is before b!"); //a is before b + * else + * display.scroll("b is before a!"); + * @endcode + */ + bool operator< (const ManagedString& s); + + /** + * Inequality operation. + * + * Called when one ManagedString is tested to be greater than another using the '>' operator. + * + * @param s The ManagedString to test ourselves against. + * + * @return true if this ManagedString is alphabetically greater than to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(p>a) + * display.scroll("b is after a!"); //b is after a + * else + * display.scroll("a is after b!"); + * @endcode + */ + bool operator> (const ManagedString& s); + + /** + * Extracts a ManagedString from this string, at the position provided. + * + * @param start The index of the first character to extract, indexed from zero. + * + * @param length The number of characters to extract from the start position + * + * @return a ManagedString representing the requested substring. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcdefg"); + * + * display.scroll(s.substring(0,2)) // displays "ab" + * @endcode + */ + ManagedString substring(int16_t start, int16_t length); + + /** + * Concatenates this string with the one provided. + * + * @param s The ManagedString to concatenate. + * + * @return a new ManagedString representing the joined strings. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * ManagedString p("efgh") + * + * display.scroll(s + p) // scrolls "abcdefgh" + * @endcode + */ + ManagedString operator+ (ManagedString& s); + + /** + * Provides a character value at a given position in the string, indexed from zero. + * + * @param index The position of the character to return. + * + * @return the character at posisiton index, zero if index is invalid. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * + * display.scroll(s.charAt(1)) // scrolls "b" + * @endcode + */ + char charAt(int16_t index); + + + /** + * Provides an immutable 8 bit wide character buffer representing this string. + * + * @return a pointer to the character buffer. + */ + const char *toCharArray() const + { + return ptr->data; + } + + /** + * Determines the length of this ManagedString in characters. + * + * @return the length of the string in characters. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * + * display.scroll(s.length()) // scrolls "4" + * @endcode + */ + int16_t length() const + { + return ptr->len; + } + + /** + * Empty String constant + */ + static ManagedString EmptyString; + + private: + + /** + * Internal constructor helper. + * + * Configures this ManagedString to refer to the static EmptyString + */ + void initEmpty(); + + /** + * Internal constructor helper. + * + * Creates this ManagedString based on a given null terminated char array. + */ + void initString(const char *str); + + /** + * Private Constructor. + * + * Create a managed string based on a concat of two strings. + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str1 The first string on which to base the new ManagedString. + * + * @param str2 The second string on which to base the new ManagedString. + */ + ManagedString(const ManagedString &s1, const ManagedString &s2); + +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/ManagedType.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,279 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_MANAGED_TYPE_H +#define MICROBIT_MANAGED_TYPE_H + +#include "MicroBitConfig.h" + +/** + * Class definition for a Generic Managed Type. + * + * Represents a reference counted object. + * + * @note When the destructor is called, delete is called on the object - implicitly calling the given objects destructor. + */ +template <class T> +class ManagedType +{ +protected: + + int *ref; + +public: + + T *object; + + /** + * Constructor for the managed type, given a class space T. + * + * @param object the object that you would like to be ref counted - of class T + * + * @code + * T object = new T(); + * ManagedType<T> mt(t); + * @endcode + */ + ManagedType(T* object); + + /** + * Default constructor for the managed type, given a class space T. + */ + ManagedType(); + + /** + * Copy constructor for the managed type, given a class space T. + * + * @param t another managed type instance of class type T. + * + * @code + * T* object = new T(); + * ManagedType<T> mt(t); + * ManagedType<T> mt1(mt); + * @endcode + */ + ManagedType(const ManagedType<T> &t); + + /** + * Destructor for the managed type, given a class space T. + */ + ~ManagedType(); + + /** + * Copy-assign member function for the managed type, given a class space. + * + * @code + * T* object = new T(); + * ManagedType<T> mt(t); + * ManagedType<T> mt1 = mt; + * @endcode + */ + ManagedType<T>& operator = (const ManagedType<T>&i); + + /** + * Returns the references to this ManagedType. + * + * @code + * T* object = new T(); + * ManagedType<T> mt(t); + * ManagedType<T> mt1(mt); + * + * mt.getReferences // this will be 2! + * @endcode + */ + int getReferences(); + + /** + * De-reference operator overload. This makes modifying ref-counted POD + * easier. + * + * @code + * ManagedType<int> x = 0; + * *x = 1; // mutates the ref-counted integer + * @endcode + */ + T& operator*() { + return *object; + } + + /** + * Method call operator overload. This forwards the call to the underlying + * object. + * + * @code + * ManagedType<T> x = new T(); + * x->m(); // resolves to T::m + */ + T* operator->() { + if (object == NULL) + microbit_panic(MICROBIT_NULL_DEREFERENCE); + return object; + } + + /** + * Shorthand for `x.operator->()` + */ + T* get() { + return object; + } + + /** + * A simple inequality overload to compare two ManagedType instances. + */ + bool operator!=(const ManagedType<T>& x) { + return !(this == x); + } + + /** + * A simple equality overload to compare two ManagedType instances. + */ + bool operator==(const ManagedType<T>& x) { + return this->object == x.object; + } +}; + +/** + * Constructor for the managed type, given a class space T. + * + * @param object the object that you would like to be ref counted - of class T + * + * @code + * T object = new T(); + * ManagedType<T> mt(t); + * @endcode + */ +template<typename T> +ManagedType<T>::ManagedType(T* object) +{ + this->object = object; + ref = (int *)malloc(sizeof(int)); + *ref = 1; +} + +/** + * Default constructor for the managed type, given a class space T. + */ +template<typename T> +ManagedType<T>::ManagedType() +{ + this->object = NULL; + ref = (int *)malloc(sizeof(int)); + *ref = 0; +} + +/** + * Copy constructor for the managed type, given a class space T. + * + * @param t another managed type instance of class type T. + * + * @code + * T* object = new T(); + * ManagedType<T> mt(t); + * ManagedType<T> mt1(mt); + * @endcode + */ +template<typename T> +ManagedType<T>::ManagedType(const ManagedType<T> &t) +{ + this->object = t.object; + this->ref = t.ref; + (*ref)++; +} + +/** + * Destructor for the managed type, given a class space T. + */ +template<typename T> +ManagedType<T>::~ManagedType() +{ + // Special case - we were created using a default constructor, and never assigned a value. + if (*ref == 0) + { + // Simply destroy our reference counter and we're done. + free(ref); + } + + // Normal case - we have a valid piece of data. + // Decrement our reference counter and free all allocated memory if we're deleting the last reference. + else if (--(*ref) == 0) + { + delete object; + free(ref); + } +} + +/** + * Copy-assign member function for the managed type, given a class space. + * + * @code + * T* object = new T(); + * ManagedType<T> mt(t); + * ManagedType<T> mt1 = mt; + * @endcode + */ +template<typename T> +ManagedType<T>& ManagedType<T>::operator = (const ManagedType<T>&t) +{ + if (this == &t) + return *this; + + // Special case - we were created using a default constructor, and never assigned a value. + if (*ref == 0) + { + // Simply destroy our reference counter, as we're about to adopt another. + free(ref); + } + + else if (--(*ref) == 0) + { + delete object; + free(ref); + } + + object = t.object; + ref = t.ref; + + (*ref)++; + + return *this; +} + +/** + * Returns the references to this ManagedType. + * + * @code + * T* object = new T(); + * ManagedType<T> mt(t); + * ManagedType<T> mt1(mt); + * + * mt.getReferences // this will be 2! + * @endcode + */ +template<typename T> +int ManagedType<T>::getReferences() +{ + return (*ref); +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/Matrix4.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,202 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_MATRIX4_H +#define MICROBIT_MATRIX4_H + +#include "MicroBitConfig.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 +{ + float *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. + * + * @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. + * + * @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. + * + * @code + * int c = matrix.width(); + * @endcode + */ + int width(); + + /** + * Determines the number of rows in this matrix. + * + * @return The number of rows in the matrix. + * + * @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. 0 is returned if the given index is out of range. + * + * @code + * float v = matrix.get(1,2); + * @endcode + */ + float 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. + * + * @code + * matrix.set(1,2,42.0); + * @endcode + */ + void set(int row, int col, float v); + + /** + * Transposes this matrix. + * + * @return the resultant matrix. + * + * @code + * matrix.transpose(); + * @endcode + */ + Matrix4 transpose(); + + /** + * Multiplies this matrix with the given matrix (if possible). + * + * @param matrix the matrix to multiply this matrix's values against. + * + * @param transpose Transpose the matrices before multiplication. Defaults to false. + * + * @return the resultant matrix. An empty matrix is returned if the operation canot be completed. + * + * @code + * Matrix result = matrixA.multiply(matrixB); + * @endcode + */ + Matrix4 multiply(Matrix4 &matrix, bool transpose = false); + + /** + * Multiplies the transpose of this matrix with the given matrix (if possible). + * + * @param matrix the matrix to multiply this matrix's values against. + * + * @return the resultant matrix. An empty matrix is returned if the operation canot be completed. + * + * @code + * Matrix result = matrixA.multiplyT(matrixB); + * @endcode + */ + Matrix4 multiplyT(Matrix4 &matrix); + + /** + * 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. + * + * @code + * Matrix result = matrixA.invert(); + * @endcode + */ + Matrix4 invert(); + + /** + * Destructor. + * + * Frees any memory consumed by this Matrix4 instance. + */ + ~Matrix4(); +}; + +/** + * Multiplies the transpose of this matrix with the given matrix (if possible). + * + * @return the resultant matrix. An empty matrix is returned if the operation canot be completed. + * + * @code + * Matrix result = matrixA.multiplyT(matrixB); + * @endcode + */ +inline Matrix4 Matrix4::multiplyT(Matrix4 &matrix) +{ + return multiply(matrix, true); +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/MicroBitCoordinateSystem.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,67 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_COORDINATE_SYSTEM_H +#define MICROBIT_COORDINATE_SYSTEM_H +#include "MicroBitConfig.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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/MicroBitEvent.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,106 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_EVENT_H +#define MICROBIT_EVENT_H + +#include "mbed.h" +#include "MicroBitConfig.h" + +// Wildcard event codes +#define MICROBIT_ID_ANY 0 +#define MICROBIT_EVT_ANY 0 + +enum MicroBitEventLaunchMode +{ + CREATE_ONLY, + CREATE_AND_FIRE +}; + +#define MICROBIT_EVENT_DEFAULT_LAUNCH_MODE CREATE_AND_FIRE + +/** + * Class definition for a MicroBitEvent + * + * It represents a common event that is generated by the various components on the micro:bit. + */ +class MicroBitEvent +{ + public: + + uint16_t source; // ID of the MicroBit Component that generated the event e.g. MICROBIT_ID_BUTTON_A. + uint16_t value; // Component specific code indicating the cause of the event. + uint32_t timestamp; // Time at which the event was generated. ms since power on. + + /** + * Constructor. + * + * @param src The id of the MicroBit Component that generated the event e.g. MICROBIT_ID_BUTTON_A. + * + * @param value A component specific code indicating the cause of the event. + * + * @param mode Optional definition of how the event should be processed after construction (if at all): + * CREATE_ONLY: MicroBitEvent is initialised, and no further processing takes place. + * CREATE_AND_FIRE: MicroBitEvent is initialised, and its event handlers are immediately fired (not suitable for use in interrupts!). + * + * @code + * // Create and launch an event using the default configuration + * MicrobitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); + * + * // Create an event only, do not fire onto an EventModel. + * MicrobitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK,CREATE_AND_FIRE); + * @endcode + */ + MicroBitEvent(uint16_t source, uint16_t value, MicroBitEventLaunchMode mode = MICROBIT_EVENT_DEFAULT_LAUNCH_MODE); + + /** + * Default constructor - initialises all values, and sets timestamp to the current time. + */ + MicroBitEvent(); + + /** + * Fires this MicroBitEvent onto the Default EventModel, or a custom one! + */ + void fire(); +}; + +/** + * Enclosing class to hold a chain of events. + */ +struct MicroBitEventQueueItem +{ + MicroBitEvent evt; + MicroBitEventQueueItem *next; + + /** + * Constructor. + * Create a new MicroBitEventQueueItem. + * + * @param evt The event to be queued. + */ + MicroBitEventQueueItem(MicroBitEvent evt); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/MicroBitImage.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,480 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_IMAGE_H +#define MICROBIT_IMAGE_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "ManagedString.h" +#include "RefCounted.h" + +struct ImageData : RefCounted +{ + uint16_t width; // Width in pixels + uint16_t height; // Height in pixels + uint8_t data[0]; // 2D array representing the bitmap image +}; + +/** + * Class definition for a MicroBitImage. + * + * An MicroBitImage is a simple bitmap representation of an image. + * n.b. This is a mutable, managed type. + */ +class MicroBitImage +{ + ImageData *ptr; // Pointer to payload data + + + /** + * Internal constructor which provides sanity checking and initialises class properties. + * + * @param x the width of the image + * + * @param y the height of the image + * + * @param bitmap an array of integers that make up an image. + */ + void init(const int16_t x, const int16_t y, const uint8_t *bitmap); + + /** + * Internal constructor which defaults to the Empty Image instance variable + */ + void init_empty(); + + public: + static MicroBitImage EmptyImage; // Shared representation of a null image. + + /** + * Get current ptr, do not decr() it, and set the current instance to empty image. + * + * This is to be used by specialized runtimes which pass ImageData around. + */ + ImageData *leakData(); + + /** + * Return a 2D array representing the bitmap image. + */ + uint8_t *getBitmap() + { + return ptr->data; + } + + /** + * Constructor. + * Create an image from a specially prepared constant array, with no copying. Will call ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then width, 0, height, 0, and the bitmap. Width and height are 16 bit. The literal has to be 4-byte aligned. + * + * @code + * static const uint8_t heart[] __attribute__ ((aligned (4))) = { 0xff, 0xff, 10, 0, 5, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i((ImageData*)(void*)heart); + * @endcode + */ + MicroBitImage(ImageData *ptr); + + /** + * Default Constructor. + * Creates a new reference to the empty MicroBitImage bitmap + * + * @code + * MicroBitImage i(); //an empty image instance + * @endcode + */ + MicroBitImage(); + + + /** + * Copy Constructor. + * Add ourselves as a reference to an existing MicroBitImage. + * + * @param image The MicroBitImage to reference. + * + * @code + * MicroBitImage i("0,1,0,1,0\n"); + * MicroBitImage i2(i); //points to i + * @endcode + */ + MicroBitImage(const MicroBitImage &image); + + /** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param s A text based representation of the image given whitespace delimited numeric values. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * @endcode + */ + explicit MicroBitImage(const char *s); + + /** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param x the width of the image. + * + * @param y the height of the image. + * + * Bitmap buffer is linear, with 8 bits per pixel, row by row, + * top to bottom with no word alignment. Stride is therefore the image width in pixels. + * in where w and h are width and height respectively, the layout is therefore: + * + * |[0,0]...[w,o][1,0]...[w,1] ... [[w,h] + * + * A copy of the image is made in RAM, as images are mutable. + */ + MicroBitImage(const int16_t x, const int16_t y); + + /** + * Constructor. + * Create a bitmap representation of a given size, based on a given buffer. + * + * @param x the width of the image. + * + * @param y the height of the image. + * + * @param bitmap a 2D array representing the image. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * @endcode + */ + MicroBitImage(const int16_t x, const int16_t y, const uint8_t *bitmap); + + /** + * Destructor. + * + * Removes buffer resources held by the instance. + */ + ~MicroBitImage(); + + /** + * Copy assign operation. + * + * Called when one MicroBitImage is assigned the value of another using the '=' operator. + * + * Decrement our reference count and free up the buffer as necessary. + * + * Then, update our buffer to refer to that of the supplied MicroBitImage, + * and increase its reference count. + * + * @param s The MicroBitImage to reference. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * MicroBitImage i1(); + * i1 = i; // i1 now references i + * @endcode + */ + MicroBitImage& operator = (const MicroBitImage& i); + + + /** + * Equality operation. + * + * Called when one MicroBitImage is tested to be equal to another using the '==' operator. + * + * @param i The MicroBitImage to test ourselves against. + * + * @return true if this MicroBitImage is identical to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * MicroBitImage i(); + * MicroBitImage i1(); + * + * if(i == i1) //will be true + * display.scroll("true"); + * @endcode + */ + bool operator== (const MicroBitImage& i); + + /** + * Resets all pixels in this image to 0. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.clear(); + * @endcode + */ + void clear(); + + /** + * Sets the pixel at the given co-ordinates to a given value. + * + * @param x The co-ordinate of the pixel to change. + * + * @param y The co-ordinate of the pixel to change. + * + * @param value The new value of the pixel (the brightness level 0-255) + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.setPixelValue(0,0,255); + * @endcode + * + * @note all coordinates originate from the top left of an image. + */ + int setPixelValue(int16_t x , int16_t y, uint8_t value); + + /** + * Retreives the value of a given pixel. + * + * @param x The x co-ordinate of the pixel to read. Must be within the dimensions of the image. + * + * @param y The y co-ordinate of the pixel to read. Must be within the dimensions of the image. + * + * @return The value assigned to the given pixel location (the brightness level 0-255), or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.getPixelValue(0,0); //should be 0; + * @endcode + */ + int getPixelValue(int16_t x , int16_t y); + + /** + * Replaces the content of this image with that of a given 2D array representing + * the image. + * + * @param x the width of the image. Must be within the dimensions of the image. + * + * @param y the width of the image. Must be within the dimensions of the image. + * + * @param bitmap a 2D array representing the image. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(); + * i.printImage(0,0,heart); + * @endcode + * + * @note all coordinates originate from the top left of an image. + */ + int printImage(int16_t x, int16_t y, const uint8_t *bitmap); + + /** + * Pastes a given bitmap at the given co-ordinates. + * + * Any pixels in the relvant area of this image are replaced. + * + * @param image The MicroBitImage to paste. + * + * @param x The leftmost X co-ordinate in this image where the given image should be pasted. Defaults to 0. + * + * @param y The uppermost Y co-ordinate in this image where the given image should be pasted. Defaults to 0. + * + * @param alpha set to 1 if transparency clear pixels in given image should be treated as transparent. Set to 0 otherwise. Defaults to 0. + * + * @return The number of pixels written. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); // a big heart + * i.paste(i, -5, 0); // a small heart + * @endcode + */ + int paste(const MicroBitImage &image, int16_t x = 0, int16_t y = 0, uint8_t alpha = 0); + + /** + * Prints a character to the display at the given location + * + * @param c The character to display. + * + * @param x The x co-ordinate of on the image to place the top left of the character. Defaults to 0. + * + * @param y The y co-ordinate of on the image to place the top left of the character. Defaults to 0. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicroBitImage i(5,5); + * i.print('a'); + * @endcode + */ + int print(char c, int16_t x = 0, int16_t y = 0); + + /** + * Shifts the pixels in this Image a given number of pixels to the left. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); // a big heart + * i.shiftLeft(5); // a small heart + * @endcode + */ + int shiftLeft(int16_t n); + + /** + * Shifts the pixels in this Image a given number of pixels to the right. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); // a big heart + * i.shiftLeft(5); // a small heart + * i.shiftRight(5); // a big heart + * @endcode + */ + int shiftRight(int16_t n); + + /** + * Shifts the pixels in this Image a given number of pixels to upward. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftUp(1); + * @endcode + */ + int shiftUp(int16_t n); + + /** + * Shifts the pixels in this Image a given number of pixels to downward. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftDown(1); + * @endcode + */ + int shiftDown(int16_t n); + + /** + * Gets the width of this image. + * + * @return The width of this image. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getWidth(); // equals 10... + * @endcode + */ + int getWidth() const + { + return ptr->width; + } + + /** + * Gets the height of this image. + * + * @return The height of this image. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getHeight(); // equals 5... + * @endcode + */ + int getHeight() const + { + return ptr->height; + } + + /** + * Gets number of bytes in the bitmap, ie., width * height. + * + * @return The size of the bitmap. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getSize(); // equals 50... + * @endcode + */ + int getSize() const + { + return ptr->width * ptr->height; + } + + /** + * Converts the bitmap to a csv ManagedString. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.printString(i.toString()); // "0,1,0,1,0,0,0,0,0,0\n..." + * @endcode + */ + ManagedString toString(); + + /** + * Crops the image to the given dimensions. + * + * @param startx the location to start the crop in the x-axis + * + * @param starty the location to start the crop in the y-axis + * + * @param width the width of the desired cropped region + * + * @param height the height of the desired cropped region + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.crop(0,0,2,2).toString() // "0,1\n1,1\n" + * @endcode + */ + MicroBitImage crop(int startx, int starty, int finx, int finy); + + /** + * Check if image is read-only (i.e., residing in flash). + */ + bool isReadOnly(); + + /** + * Create a copy of the image bitmap. Used particularly, when isReadOnly() is true. + * + * @return an instance of MicroBitImage which can be modified independently of the current instance + */ + MicroBitImage clone(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/PacketBuffer.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,269 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_PACKET_BUFFER_H +#define MICROBIT_PACKET_BUFFER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "RefCounted.h" + +struct PacketData : RefCounted +{ + uint16_t rssi; // The radio signal strength this packet was received. + uint8_t length; // The length of the payload in bytes + uint8_t payload[0]; // User / higher layer protocol data +}; + +/** + * Class definition for a PacketBuffer. + * A PacketBuffer holds a series of bytes that can be sent or received from the MicroBitRadio channel. + * + * @note This is a mutable, managed type. + */ +class PacketBuffer +{ + PacketData *ptr; // Pointer to payload data + + public: + + /** + * Provide a pointer to a memory location containing the packet data. + * + * @return The contents of this packet, as an array of bytes. + */ + uint8_t *getBytes(); + + /** + * Default Constructor. + * Creates an empty Packet Buffer. + * + * @code + * PacketBuffer p(); + * @endcode + */ + PacketBuffer(); + + /** + * Constructor. + * Creates a new PacketBuffer of the given size. + * + * @param length The length of the buffer to create. + * + * @code + * PacketBuffer p(16); // Creates a PacketBuffer 16 bytes long. + * @endcode + */ + PacketBuffer(int length); + + /** + * Constructor. + * Creates an empty Packet Buffer of the given size, + * and fills it with the data provided. + * + * @param data The data with which to fill the buffer. + * + * @param length The length of the buffer to create. + * + * @param rssi The radio signal strength at the time this packet was recieved. Defaults to 0. + * + * @code + * uint8_t buf = {13,5,2}; + * PacketBuffer p(buf, 3); // Creates a PacketBuffer 3 bytes long. + * @endcode + */ + PacketBuffer(uint8_t *data, int length, int rssi = 0); + + /** + * Copy Constructor. + * Add ourselves as a reference to an existing PacketBuffer. + * + * @param buffer The PacketBuffer to reference. + * + * @code + * PacketBuffer p(); + * PacketBuffer p2(p); // Refers to the same packet as p. + * @endcode + */ + PacketBuffer(const PacketBuffer &buffer); + + /** + * Internal constructor-initialiser. + * + * @param data The data with which to fill the buffer. + * + * @param length The length of the buffer to create. + * + * @param rssi The radio signal strength at the time this packet was recieved. + */ + void init(uint8_t *data, int length, int rssi); + + /** + * Destructor. + * + * Removes buffer resources held by the instance. + */ + ~PacketBuffer(); + + /** + * Copy assign operation. + * + * Called when one PacketBuffer is assigned the value of another using the '=' operator. + * + * Decrements our reference count and free up the buffer as necessary. + * + * Then, update our buffer to refer to that of the supplied PacketBuffer, + * and increase its reference count. + * + * @param p The PacketBuffer to reference. + * + * @code + * uint8_t buf = {13,5,2}; + * PacketBuffer p1(16); + * PacketBuffer p2(buf, 3); + * + * p1 = p2; + * @endcode + */ + PacketBuffer& operator = (const PacketBuffer& p); + + /** + * Array access operation (read). + * + * Called when a PacketBuffer is dereferenced with a [] operation. + * + * Transparently map this through to the underlying payload for elegance of programming. + * + * @code + * PacketBuffer p1(16); + * uint8_t data = p1[0]; + * @endcode + */ + uint8_t operator [] (int i) const; + + /** + * Array access operation (modify). + * + * Called when a PacketBuffer is dereferenced with a [] operation. + * + * Transparently map this through to the underlying payload for elegance of programming. + * + * @code + * PacketBuffer p1(16); + * p1[0] = 42; + * @endcode + */ + uint8_t& operator [] (int i); + + /** + * Equality operation. + * + * Called when one PacketBuffer is tested to be equal to another using the '==' operator. + * + * @param p The PacketBuffer to test ourselves against. + * + * @return true if this PacketBuffer is identical to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * uint8_t buf = {13,5,2}; + * PacketBuffer p1(); + * PacketBuffer p2(); + * + * if(p1 == p2) // will be true + * display.scroll("same!"); + * @endcode + */ + bool operator== (const PacketBuffer& p); + + /** + * Sets the byte at the given index to value provided. + * + * @param position The index of the byte to change. + * + * @param value The new value of the byte (0-255). + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * PacketBuffer p1(16); + * p1.setByte(0,255); // Sets the first byte in the buffer to the value 255. + * @endcode + */ + int setByte(int position, uint8_t value); + + /** + * Determines the value of the given byte in the packet. + * + * @param position The index of the byte to read. + * + * @return The value of the byte at the given position, or MICROBIT_INVALID_PARAMETER. + * + * @code + * PacketBuffer p1(16); + * p1.setByte(0,255); // Sets the first byte in the buffer to the value 255. + * p1.getByte(0); // Returns 255. + * @endcode + */ + int getByte(int position); + + /** + * Gets number of bytes in this buffer + * + * @return The size of the buffer in bytes. + * + * @code + * PacketBuffer p1(16); + * p1.length(); // Returns 16. + * @endcode + */ + int length(); + + /** + * Retrieves the received signal strength of this packet. + * + * @return The signal strength of the radio when this packet was received, in -dbM. + * + * @code + * PacketBuffer p1(16); + * p1.getRSSI(); // Returns the received signal strength. + * @endcode + */ + int getRSSI(); + + /** + * Sets the received signal strength of this packet. + * + * @code + * PacketBuffer p1(16); + * p1.setRSSI(37); + * @endcode + */ + void setRSSI(uint8_t rssi); + + static PacketBuffer EmptyPacket; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/types/RefCounted.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,72 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef REF_COUNTED_H +#define REF_COUNTED_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitDevice.h" + +/** + * Base class for payload for ref-counted objects. Used by ManagedString and MicroBitImage. + * There is no constructor, as this struct is typically malloc()ed. + */ +struct RefCounted +{ +public: + + /** + * The high 15 bits hold the number of outstanding references. The lowest bit is always 1 + * to make sure it doesn't look like vtable. + * Should never be even or one (object should be deleted then). + * When it's set to 0xffff, it means the object sits in flash and should not be counted. + */ + uint16_t refCount; + + /** + * Increment reference count. + */ + void incr(); + + /** + * Decrement reference count. + */ + void decr(); + + /** + * Initializes for one outstanding reference. + */ + void init(); + + /** + * Checks if the object resides in flash memory. + * + * @return true if the object resides in flash memory, false otherwise. + */ + bool isReadOnly(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/module.json Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,28 @@ +{ + "name": "microbit-dal", + "version": "2.0.0-rc1", + "license": "MIT", + "description": "The runtime library for the BBC micro:bit, developed by Lancaster University", + "keywords": [ + "mbed-classic", + "microbit", + "runtime", + "library", + "lancaster", + "University" + ], + "author": "Joe Finney <j.finney@lancaster.ac.uk (mailto:j.finney@lancaster.ac.uk) >", + "homepage": "https://github.com/lancaster-university/microbit-dal/", + "dependencies": { + "mbed-classic": "lancaster-university/mbed-classic#microbit_hfclk+mb2", + "ble": "lancaster-university/BLE_API#v2.5.0+mb3", + "ble-nrf51822": "lancaster-university/nrf51822#v2.5.0+mb5", + "nrf51-sdk": "lancaster-university/nrf51-sdk#v2.2.0+mb3" + }, + "extraIncludes": [ + "inc/core", + "inc/types", + "inc/drivers", + "inc/bluetooth" + ] +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/CMakeLists.txt Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,104 @@ +# This file is no longer auto-generated to make the repository builds with GCC +# and ARMCC no matter what. + +cmake_minimum_required(VERSION 2.8.12) + +enable_language(ASM) + +set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES + "core/MemberFunctionCallback.cpp" + "core/MicroBitCompat.cpp" + "core/MicroBitDevice.cpp" + "core/MicroBitFiber.cpp" + "core/MicroBitFont.cpp" + "core/MicroBitHeapAllocator.cpp" + "core/MicroBitListener.cpp" + "core/MicroBitSystemTimer.cpp" + + "types/ManagedString.cpp" + "types/Matrix4.cpp" + "types/MicroBitEvent.cpp" + "types/MicroBitImage.cpp" + "types/PacketBuffer.cpp" + "types/RefCounted.cpp" + + "drivers/DynamicPwm.cpp" + "drivers/MicroBitAccelerometer.cpp" + "drivers/MicroBitButton.cpp" + "drivers/MicroBitCompass.cpp" + "drivers/MicroBitCompassCalibrator.cpp" + "drivers/MicroBitDisplay.cpp" + "drivers/MicroBitI2C.cpp" + "drivers/MicroBitIO.cpp" + "drivers/MicroBitLightSensor.cpp" + "drivers/MicroBitMessageBus.cpp" + "drivers/MicroBitMultiButton.cpp" + "drivers/MicroBitPin.cpp" + "drivers/MicroBitRadio.cpp" + "drivers/MicroBitRadioDatagram.cpp" + "drivers/MicroBitRadioEvent.cpp" + "drivers/MicroBitSerial.cpp" + "drivers/MicroBitStorage.cpp" + "drivers/MicroBitThermometer.cpp" + + "bluetooth/MicroBitAccelerometerService.cpp" + "bluetooth/MicroBitBLEManager.cpp" + "bluetooth/MicroBitButtonService.cpp" + "bluetooth/MicroBitDFUService.cpp" + "bluetooth/MicroBitEventService.cpp" + "bluetooth/MicroBitIOPinService.cpp" + "bluetooth/MicroBitLEDService.cpp" + "bluetooth/MicroBitMagnetometerService.cpp" + "bluetooth/MicroBitTemperatureService.cpp" + "bluetooth/MicroBitUARTService.cpp" +) + +execute_process(WORKING_DIRECTORY "../../yotta_modules/${PROJECT_NAME}" COMMAND "git" "log" "--pretty=format:%h" "-n" "1" OUTPUT_VARIABLE git_hash) +execute_process(WORKING_DIRECTORY "../../yotta_modules/${PROJECT_NAME}" COMMAND "git" "rev-parse" "--abbrev-ref" "HEAD" OUTPUT_VARIABLE git_branch OUTPUT_STRIP_TRAILING_WHITESPACE) + +if ("${git_branch}" STREQUAL "master") + set(MICROBIT_DAL_VERSION_STRING "${YOTTA_MICROBIT_DAL_VERSION_STRING}") +else() + set(MICROBIT_DAL_VERSION_STRING "${YOTTA_MICROBIT_DAL_VERSION_STRING}-${git_branch}-g${git_hash}") +endif() + +set(MICROBIT_DAL_VERSION_FLAGS "-DMICROBIT_DAL_VERSION=\\\"${MICROBIT_DAL_VERSION_STRING}\\\"") + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MICROBIT_DAL_VERSION_FLAGS}") + +if (YOTTA_CFG_MICROBIT_CONFIGFILE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${YOTTA_FORCE_INCLUDE_FLAG} \"${YOTTA_CFG_MICROBIT_CONFIGFILE}\"") +endif () + +if(CMAKE_COMPILER_IS_GNUCC) + file(REMOVE "asm/CortexContextSwitch.s") + configure_file("asm/CortexContextSwitch.s.gcc" "asm/CortexContextSwitch.s" COPYONLY) +else() + file(REMOVE "asm/CortexContextSwitch.s") + configure_file("asm/CortexContextSwitch.s.armcc" "asm/CortexContextSwitch.s" COPYONLY) +endif() + +set(YOTTA_AUTO_MICROBIT-DAL_S_FILES + "asm/CortexContextSwitch.s" +) + +add_library(microbit-dal + ${YOTTA_AUTO_MICROBIT-DAL_CPP_FILES} + ${YOTTA_AUTO_MICROBIT-DAL_S_FILES} +) + +yotta_postprocess_target(LIBRARY microbit-dal) + +target_link_libraries(microbit-dal + mbed-classic + ble + ble-nrf51822 +) + +if(CMAKE_COMPILER_IS_GNUCC) + 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()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/asm/CortexContextSwitch.s.armcc Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,293 @@ +; The MIT License (MIT) + +; Copyright (c) 2016 British Broadcasting Corporation. +; This software is provided by Lancaster University by arrangement with the BBC. + +; Permission is hereby granted, free of charge, to any person obtaining a +; copy of this software and associated documentation files (the "Software"), +; to deal in the Software without restriction, including without limitation +; the rights to use, copy, modify, merge, publish, distribute, sublicense, +; and/or sell copies of the Software, and to permit persons to whom the +; Software is furnished to do so, subject to the following conditions: + +; The above copyright notice and this permission notice shall be included in +; all copies or substantial portions of the Software. + +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +; DEALINGS IN THE SOFTWARE. + + AREA asm_func, CODE, READONLY + +; Export our context switching subroutine as a C function for use in mbed + EXPORT swap_context + EXPORT save_context + EXPORT save_register_context + EXPORT restore_register_context + + ALIGN + +; R0 Contains a pointer to the TCB of the fibre being scheduled out. +; R1 Contains a pointer to the TCB of the fibre being scheduled in. +; R2 Contains a pointer to the base of the stack of the fibre being scheduled out. +; R3 Contains a pointer to the base of the stack of the fibre being scheduled in. + +swap_context + + ; Write our core registers into the TCB + ; First, store the general registers + + ; Skip this is we're given a NULL parameter for the TCB + CMP R0, #0 + BEQ store_context_complete + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + ; Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + ; Now the Stack and Link Register. + ; As this context is only intended for use with a fiber scheduler, + ; we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + +store_context_complete + ; Finally, Copy the stack. We do this to reduce RAM footprint, as stack is usually very small at the point + ; of scheduling, but we need a lot of capacity for interrupt handling and other functions. + + ; Skip this is we're given a NULL parameter for the stack. + CMP R2, #0 + BEQ store_stack_complete + + LDR R4, [R0,#60] ; Load R4 with the fiber's defined stack_base. +store_stack + SUBS R4, #4 + SUBS R2, #4 + + LDR R5, [R4] + STR R5, [R2] + + CMP R4, R6 + BNE store_stack + +store_stack_complete + + ; + ; Now page in the new context. + ; Update all registers except the PC. We can also safely ignore the STATUS register, as we're just a fiber scheduler. + ; + LDR R4, [R1, #56] + MOV LR, R4 + LDR R6, [R1, #52] + MOV SP, R6 + + ; Copy the stack in. + ; n.b. we do this after setting the SP to make comparisons easier. + + ; Skip this is we're given a NULL parameter for the stack. + CMP R3, #0 + BEQ restore_stack_complete + + LDR R4, [R1,#60] ; Load R4 with the fiber's defined stack_base. + +restore_stack + SUBS R4, #4 + SUBS R3, #4 + + LDR R5, [R3] + STR R5, [R4] + + CMP R4, R6 + BNE restore_stack + +restore_stack_complete + LDR R4, [R1, #48] + MOV R12, R4 + LDR R4, [R1, #44] + MOV R11, R4 + LDR R4, [R1, #40] + MOV R10, R4 + LDR R4, [R1, #36] + MOV R9, R4 + LDR R4, [R1, #32] + MOV R8, R4 + + LDR R7, [R1, #28] + LDR R6, [R1, #24] + LDR R5, [R1, #20] + LDR R4, [R1, #16] + LDR R3, [R1, #12] + LDR R2, [R1, #8] + LDR R0, [R1, #0] + LDR R1, [R1, #4] + + ; Return to caller (scheduler). + BX LR + + +; R0 Contains a pointer to the TCB of the fibre to snapshot +; R1 Contains a pointer to the base of the stack of the fibre being snapshotted + +save_context + + ; Write our core registers into the TCB + ; First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + ; Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + ; Now the Stack and Link Register. + ; As this context is only intended for use with a fiber scheduler, + ; we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + ; Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point + ; of sceduling, but we need a lot of capacity for interrupt handling and other functions. + + LDR R4, [R0,#60] ; Load R4 with the fiber's defined stack_base. + +store_stack1 + SUBS R4, #4 + SUBS R1, #4 + + LDR R5, [R4] + STR R5, [R1] + + CMP R4, R6 + BNE store_stack1 + + ; Restore scratch registers. + + LDR R7, [R0, #28] + LDR R6, [R0, #24] + LDR R5, [R0, #20] + LDR R4, [R0, #16] + + ; Return to caller (scheduler). + BX LR + + +; R0 Contains a pointer to the TCB of the fiber to snapshot +save_register_context + + ; Write our core registers into the TCB + ; First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + ; Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + ; Now the Stack Pointer and Link Register. + ; As this context is only intended for use with a fiber scheduler, + ; we don't need the PC. + MOV R4, SP + STR R4, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + ; Restore scratch registers. + LDR R4, [R0, #16] + + ; Return to caller (scheduler). + BX LR + + +restore_register_context + + ; + ; Now page in the new context. + ; Update all registers except the PC. We can also safely ignore the STATUS register, as we're just a fiber scheduler. + ; + LDR R4, [R0, #56] + MOV LR, R4 + LDR R4, [R0, #52] + MOV SP, R4 + + ; High registers... + LDR R4, [R0, #48] + MOV R12, R4 + LDR R4, [R0, #44] + MOV R11, R4 + LDR R4, [R0, #40] + MOV R10, R4 + LDR R4, [R0, #36] + MOV R9, R4 + LDR R4, [R0, #32] + MOV R8, R4 + + ; Low registers... + LDR R7, [R0, #28] + LDR R6, [R0, #24] + LDR R5, [R0, #20] + LDR R4, [R0, #16] + LDR R3, [R0, #12] + LDR R2, [R0, #8] + LDR R0, [R0, #0] + LDR R1, [R0, #4] + + ; Return to caller (normally the scheduler). + BX LR + + ALIGN + END
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/asm/CortexContextSwitch.s.gcc Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,292 @@ +@ The MIT License (MIT) + +@ Copyright (c) 2016 British Broadcasting Corporation. +@ This software is provided by Lancaster University by arrangement with the BBC. + +@ Permission is hereby granted, free of charge, to any person obtaining a +@ copy of this software and associated documentation files (the "Software"), +@ to deal in the Software without restriction, including without limitation +@ the rights to use, copy, modify, merge, publish, distribute, sublicense, +@ and/or sell copies of the Software, and to permit persons to whom the +@ Software is furnished to do so, subject to the following conditions: + +@ The above copyright notice and this permission notice shall be included in +@ all copies or substantial portions of the Software. + +@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +@ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +@ DEALINGS IN THE SOFTWARE. + + .syntax unified + .cpu cortex-m0 + .thumb + .text + .align 2 + +@ Export our context switching subroutine as a C function for use in mbed + .global swap_context + .global save_context + .global save_register_context + .global restore_register_context + +@ R0 Contains a pointer to the TCB of the fibre being scheduled out. +@ R1 Contains a pointer to the TCB of the fibre being scheduled in. +@ R2 Contains a pointer to the base of the stack of the fibre being scheduled out. +@ R3 Contains a pointer to the base of the stack of the fibre being scheduled in. + +swap_context: + + @ Write our core registers into the TCB + @ First, store the general registers + + @ Skip this is we're given a NULL parameter for the TCB + CMP R0, #0 + BEQ store_context_complete + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + @ Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + @ Now the Stack and Link Register. + @ As this context is only intended for use with a fiber scheduler, + @ we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + +store_context_complete: + @ Finally, Copy the stack. We do this to reduce RAM footprint, as stack is usually very small at the point + @ of scheduling, but we need a lot of capacity for interrupt handling and other functions. + + @ Skip this is we're given a NULL parameter for the stack. + CMP R2, #0 + BEQ store_stack_complete + + LDR R4, [R0,#60] @ Load R4 with the fiber's defined stack_base. +store_stack: + SUBS R4, #4 + SUBS R2, #4 + + LDR R5, [R4] + STR R5, [R2] + + CMP R4, R6 + BNE store_stack + +store_stack_complete: + + @ + @ Now page in the new context. + @ Update all registers except the PC. We can also safely ignore the STATUS register, as we're just a fiber scheduler. + @ + LDR R4, [R1, #56] + MOV LR, R4 + LDR R6, [R1, #52] + MOV SP, R6 + + @ Copy the stack in. + @ n.b. we do this after setting the SP to make comparisons easier. + + @ Skip this is we're given a NULL parameter for the stack. + CMP R3, #0 + BEQ restore_stack_complete + + LDR R4, [R1,#60] @ Load R4 with the fiber's defined stack_base. + +restore_stack: + SUBS R4, #4 + SUBS R3, #4 + + LDR R5, [R3] + STR R5, [R4] + + CMP R4, R6 + BNE restore_stack + +restore_stack_complete: + LDR R4, [R1, #48] + MOV R12, R4 + LDR R4, [R1, #44] + MOV R11, R4 + LDR R4, [R1, #40] + MOV R10, R4 + LDR R4, [R1, #36] + MOV R9, R4 + LDR R4, [R1, #32] + MOV R8, R4 + + LDR R7, [R1, #28] + LDR R6, [R1, #24] + LDR R5, [R1, #20] + LDR R4, [R1, #16] + LDR R3, [R1, #12] + LDR R2, [R1, #8] + LDR R0, [R1, #0] + LDR R1, [R1, #4] + + @ Return to caller (scheduler). + BX LR + + +@ R0 Contains a pointer to the TCB of the fibre to snapshot +@ R1 Contains a pointer to the base of the stack of the fibre being snapshotted + +save_context: + + @ Write our core registers into the TCB + @ First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + @ Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + @ Now the Stack and Link Register. + @ As this context is only intended for use with a fiber scheduler, + @ we don't need the PC. + MOV R6, SP + STR R6, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + @ Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point + @ of sceduling, but we need a lot of capacity for interrupt handling and other functions. + + LDR R4, [R0,#60] @ Load R4 with the fiber's defined stack_base. + +store_stack1: + SUBS R4, #4 + SUBS R1, #4 + + LDR R5, [R4] + STR R5, [R1] + + CMP R4, R6 + BNE store_stack1 + + @ Restore scratch registers. + + LDR R7, [R0, #28] + LDR R6, [R0, #24] + LDR R5, [R0, #20] + LDR R4, [R0, #16] + + @ Return to caller (scheduler). + BX LR + + +@ R0 Contains a pointer to the TCB of the fiber to snapshot +save_register_context: + + @ Write our core registers into the TCB + @ First, store the general registers + + STR R0, [R0,#0] + STR R1, [R0,#4] + STR R2, [R0,#8] + STR R3, [R0,#12] + STR R4, [R0,#16] + STR R5, [R0,#20] + STR R6, [R0,#24] + STR R7, [R0,#28] + + @ Now the high general purpose registers + MOV R4, R8 + STR R4, [R0,#32] + MOV R4, R9 + STR R4, [R0,#36] + MOV R4, R10 + STR R4, [R0,#40] + MOV R4, R11 + STR R4, [R0,#44] + MOV R4, R12 + STR R4, [R0,#48] + + @ Now the Stack Pointer and Link Register. + @ As this context is only intended for use with a fiber scheduler, + @ we don't need the PC. + MOV R4, SP + STR R4, [R0,#52] + MOV R4, LR + STR R4, [R0,#56] + + @ Restore scratch registers. + LDR R4, [R0, #16] + + @ Return to caller (scheduler). + BX LR + + +restore_register_context: + + @ + @ Now page in the new context. + @ Update all registers except the PC. We can also safely ignore the STATUS register, as we're just a fiber scheduler. + @ + LDR R4, [R0, #56] + MOV LR, R4 + LDR R4, [R0, #52] + MOV SP, R4 + + @ High registers... + LDR R4, [R0, #48] + MOV R12, R4 + LDR R4, [R0, #44] + MOV R11, R4 + LDR R4, [R0, #40] + MOV R10, R4 + LDR R4, [R0, #36] + MOV R9, R4 + LDR R4, [R0, #32] + MOV R8, R4 + + @ Low registers... + LDR R7, [R0, #28] + LDR R6, [R0, #24] + LDR R5, [R0, #20] + LDR R4, [R0, #16] + LDR R3, [R0, #12] + LDR R2, [R0, #8] + LDR R0, [R0, #0] + LDR R1, [R0, #4] + + @ Return to caller (normally the scheduler). + BX LR
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitAccelerometerService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,121 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the custom MicroBit Accelerometer Service. + * Provides a BLE service to remotely read the state of the accelerometer, and configure its behaviour. + */ +#include "MicroBitConfig.h" +#include "ble/UUID.h" + +#include "MicroBitAccelerometerService.h" + +/** + * Constructor. + * Create a representation of the AccelerometerService + * @param _ble The instance of a BLE device that we're running on. + * @param _accelerometer An instance of MicroBitAccelerometer. + */ +MicroBitAccelerometerService::MicroBitAccelerometerService(BLEDevice &_ble, MicroBitAccelerometer &_accelerometer) : + ble(_ble), accelerometer(_accelerometer) +{ + // Create the data structures that represent each of our characteristics in Soft Device. + GattCharacteristic accelerometerDataCharacteristic(MicroBitAccelerometerServiceDataUUID, (uint8_t *)accelerometerDataCharacteristicBuffer, 0, + sizeof(accelerometerDataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + GattCharacteristic accelerometerPeriodCharacteristic(MicroBitAccelerometerServicePeriodUUID, (uint8_t *)&accelerometerPeriodCharacteristicBuffer, 0, + sizeof(accelerometerPeriodCharacteristicBuffer), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + // Initialise our characteristic values. + accelerometerDataCharacteristicBuffer[0] = 0; + accelerometerDataCharacteristicBuffer[1] = 0; + accelerometerDataCharacteristicBuffer[2] = 0; + accelerometerPeriodCharacteristicBuffer = accelerometer.getPeriod(); + + // Set default security requirements + accelerometerDataCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + accelerometerPeriodCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {&accelerometerDataCharacteristic, &accelerometerPeriodCharacteristic}; + GattService service(MicroBitAccelerometerServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + accelerometerDataCharacteristicHandle = accelerometerDataCharacteristic.getValueHandle(); + accelerometerPeriodCharacteristicHandle = accelerometerPeriodCharacteristic.getValueHandle(); + + ble.gattServer().write(accelerometerDataCharacteristicHandle,(uint8_t *)accelerometerDataCharacteristicBuffer, sizeof(accelerometerDataCharacteristicBuffer)); + ble.gattServer().write(accelerometerPeriodCharacteristicHandle, (const uint8_t *)&accelerometerPeriodCharacteristicBuffer, sizeof(accelerometerPeriodCharacteristicBuffer)); + + ble.onDataWritten(this, &MicroBitAccelerometerService::onDataWritten); + + if (EventModel::defaultEventBus) + EventModel::defaultEventBus->listen(MICROBIT_ID_ACCELEROMETER, MICROBIT_ACCELEROMETER_EVT_DATA_UPDATE, this, &MicroBitAccelerometerService::accelerometerUpdate, MESSAGE_BUS_LISTENER_IMMEDIATE); +} + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitAccelerometerService::onDataWritten(const GattWriteCallbackParams *params) +{ + if (params->handle == accelerometerPeriodCharacteristicHandle && params->len >= sizeof(accelerometerPeriodCharacteristicBuffer)) + { + accelerometerPeriodCharacteristicBuffer = *((uint16_t *)params->data); + accelerometer.setPeriod(accelerometerPeriodCharacteristicBuffer); + + // The accelerometer will choose the nearest period to that requested that it can support + // Read back the ACTUAL period it is using, and report this back. + accelerometerPeriodCharacteristicBuffer = accelerometer.getPeriod(); + ble.gattServer().write(accelerometerPeriodCharacteristicHandle, (const uint8_t *)&accelerometerPeriodCharacteristicBuffer, sizeof(accelerometerPeriodCharacteristicBuffer)); + } +} + +/** + * Accelerometer update callback + */ +void MicroBitAccelerometerService::accelerometerUpdate(MicroBitEvent) +{ + if (ble.getGapState().connected) + { + accelerometerDataCharacteristicBuffer[0] = accelerometer.getX(); + accelerometerDataCharacteristicBuffer[1] = accelerometer.getY(); + accelerometerDataCharacteristicBuffer[2] = accelerometer.getZ(); + + ble.gattServer().notify(accelerometerDataCharacteristicHandle,(uint8_t *)accelerometerDataCharacteristicBuffer, sizeof(accelerometerDataCharacteristicBuffer)); + } +} + +const uint8_t MicroBitAccelerometerServiceUUID[] = { + 0xe9,0x5d,0x07,0x53,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitAccelerometerServiceDataUUID[] = { + 0xe9,0x5d,0xca,0x4b,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitAccelerometerServicePeriodUUID[] = { + 0xe9,0x5d,0xfb,0x24,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitBLEManager.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,602 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitBLEManager.h" +#include "MicroBitStorage.h" +#include "MicroBitFiber.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" + +extern "C" +{ +#include "device_manager.h" +uint32_t btle_set_gatt_table_size(uint32_t size); +} + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + +#define MICROBIT_PAIRING_FADE_SPEED 4 + +const char* MICROBIT_BLE_MANUFACTURER = NULL; +const char* MICROBIT_BLE_MODEL = "BBC micro:bit"; +const char* MICROBIT_BLE_HARDWARE_VERSION = NULL; +const char* MICROBIT_BLE_FIRMWARE_VERSION = MICROBIT_DAL_VERSION; +const char* MICROBIT_BLE_SOFTWARE_VERSION = NULL; +const int8_t MICROBIT_BLE_POWER_LEVEL[] = {-30, -20, -16, -12, -8, -4, 0, 4}; + +/* + * 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; // Singleton reference to the BLE manager. many mbed BLE API callbacks still do not support member funcions yet. :-( +static uint8_t deviceID = 255; // Unique ID for the peer that has connected to us. +static Gap::Handle_t pairingHandle = 0; // The connection handle used during a pairing process. Used to ensure that connections are dropped elegantly. + +static void storeSystemAttributes(Gap::Handle_t handle) +{ + if(manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS) + { + ManagedString key("bleSysAttrs"); + + KeyValuePair* bleSysAttrs = manager->storage->get(key); + + BLESysAttribute attrib; + BLESysAttributeStore attribStore; + + uint16_t len = sizeof(attrib.sys_attr); + + sd_ble_gatts_sys_attr_get(handle, attrib.sys_attr, &len, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS); + + //copy our stored sysAttrs + if(bleSysAttrs != NULL) + { + memcpy(&attribStore, bleSysAttrs->value, sizeof(BLESysAttributeStore)); + delete bleSysAttrs; + } + + //check if we need to update + if(memcmp(attribStore.sys_attrs[deviceID].sys_attr, attrib.sys_attr, len) != 0) + { + attribStore.sys_attrs[deviceID] = attrib; + manager->storage->put(key, (uint8_t *)&attribStore); + } + } +} + +/** + * Callback when a BLE GATT disconnect occurs. + */ +static void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *reason) +{ + storeSystemAttributes(reason->handle); + + if (manager) + manager->advertise(); +} + +/** + * Callback when a BLE SYS_ATTR_MISSING. + */ +static void bleSysAttrMissingCallback(const GattSysAttrMissingCallbackParams *params) +{ + int complete = 0; + deviceID = 255; + + dm_handle_t dm_handle = {0,0,0,0}; + + int ret = dm_handle_get(params->connHandle, &dm_handle); + + if (ret == 0) + deviceID = dm_handle.device_id; + + if(manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS) + { + ManagedString key("bleSysAttrs"); + + KeyValuePair* bleSysAttrs = manager->storage->get(key); + + BLESysAttributeStore attribStore; + BLESysAttribute attrib; + + if(bleSysAttrs != NULL) + { + //restore our sysAttrStore + memcpy(&attribStore, bleSysAttrs->value, sizeof(BLESysAttributeStore)); + delete bleSysAttrs; + + attrib = attribStore.sys_attrs[deviceID]; + + ret = sd_ble_gatts_sys_attr_set(params->connHandle, attrib.sys_attr, sizeof(attrib.sys_attr), BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS); + + complete = 1; + + if(ret == 0) + ret = sd_ble_gatts_service_changed(params->connHandle, 0x000c, 0xffff); + } + } + + if (!complete) + sd_ble_gatts_sys_attr_set(params->connHandle, NULL, 0, 0); + +} + +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 */ + + dm_handle_t dm_handle = {0,0,0,0}; + int ret = dm_handle_get(handle, &dm_handle); + + if (ret == 0) + deviceID = dm_handle.device_id; + + if (manager) + { + pairingHandle = handle; + manager->pairingComplete(status == SecurityManager::SEC_STATUS_SUCCESS); + } +} + +/** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * + * @param _storage an instance of MicroBitStorage used to persist sys attribute information. (This is required for compatability with iOS). + * + * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). + * Hence, the init() member function should be used to initialise the BLE stack. + */ +MicroBitBLEManager::MicroBitBLEManager(MicroBitStorage& _storage) : + storage(&_storage) +{ + manager = this; + this->ble = NULL; + this->pairingStatus = 0; +} + +/** + * Constructor. + * + * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack. + * + * @note The BLE stack *cannot* be brought up in a static context (the software simply hangs or corrupts itself). + * Hence, the init() member function should be used to initialise the BLE stack. + */ +MicroBitBLEManager::MicroBitBLEManager() : + storage(NULL) +{ + manager = this; + this->ble = NULL; + this->pairingStatus = 0; +} + +/** + * When called, the micro:bit will begin advertising for a predefined period, + * MICROBIT_BLE_ADVERTISING_TIMEOUT seconds to bonded devices. + */ +void MicroBitBLEManager::advertise() +{ + if(ble) + ble->gap().startAdvertising(); +} + +/** + * Post constructor initialisation method as the BLE stack cannot be brought + * up in a static context. + * + * @param deviceName The name used when advertising + * @param serialNumber The serial number exposed by the device information service + * @param messageBus An instance of an EventModel, used during pairing. + * @param enableBonding If true, the security manager enabled bonding. + * + * @code + * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true); + * @endcode + */ +void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding) +{ + ManagedString BLEName("BBC micro:bit"); + this->deviceName = deviceName; + +#if !(CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)) + ManagedString namePrefix(" ["); + ManagedString namePostfix("]"); + BLEName = BLEName + namePrefix + deviceName + namePostfix; +#endif + + // Start the BLE stack. +#if CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD) + btle_set_gatt_table_size(MICROBIT_SD_GATT_TABLE_SIZE); +#endif + + ble = new BLEDevice(); + ble->init(); + + // automatically restart advertising after a device disconnects. + ble->gap().onDisconnection(bleDisconnectionCallback); + ble->gattServer().onSysAttrMissing(bleSysAttrMissingCallback); + + // Configure the stack to hold onto the CPU during critical timing events. + // mbed-classic performs __disable_irq() calls in its timers that 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->gap().setAddress(BLEProtocol::AddressType::RANDOM_PRIVATE_RESOLVABLE, {0}); +#endif + + // Setup our security requirements. + ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback); + ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback); + ble->securityManager().init(enableBonding, (SecurityManager::MICROBIT_BLE_SECURITY_LEVEL == SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM), SecurityManager::IO_CAPS_DISPLAY_ONLY); + + if (enableBonding) + { + // If we're in pairing mode, review the size of the bond table. + int bonds = getBondCount(); + + // TODO: It would be much better to implement some sort of LRU/NFU policy here, + // but this isn't currently supported in mbed, so we'd need to layer break... + + // If we're full, empty the bond table. + if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS) + ble->securityManager().purgeAllBondingState(); + } + +#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) + // Configure a whitelist to filter all connection requetss from unbonded devices. + // Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks. + BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS]; + Gap::Whitelist_t whitelist; + whitelist.addresses = bondedAddresses; + whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS; + + ble->securityManager().getAddressesFromBondTable(whitelist); + + ble->gap().setWhitelist(whitelist); + ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST); + ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS); +#endif + + // Configure the radio at our default power level + setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER); + + // Bring up core BLE services. + new MicroBitDFUService(*ble); + 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); + new MicroBitEventService(*ble, messageBus); + + + // 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. +#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED); +#else + ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); +#endif + + ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length()); + ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); + ble->setAdvertisingInterval(200); + +#if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0) + ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT); +#endif + + // If we have whitelisting enabled, then prevent only enable advertising of we have any binded devices... + // This is to further protect kids' privacy. If no-one initiates BLE, then the device is unreachable. + // If whiltelisting is disabled, then we always advertise. +#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) + if (whitelist.size > 0) +#endif + ble->startAdvertising(); +} + +/** + * Change the output power level of the transmitter to the given value. + * + * @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range. + * + * @code + * // maximum transmission power. + * bleManager.setTransmitPower(7); + * @endcode + */ +int MicroBitBLEManager::setTransmitPower(int power) +{ + if (power < 0 || power >= MICROBIT_BLE_POWER_LEVELS) + return MICROBIT_INVALID_PARAMETER; + + if (ble->gap().setTxPower(MICROBIT_BLE_POWER_LEVEL[power]) != NRF_SUCCESS) + return MICROBIT_NOT_SUPPORTED; + + return MICROBIT_OK; +} + +/** + * Determines the number of devices currently bonded with this micro:bit. + * @return The number of active bonds. + */ +int MicroBitBLEManager::getBondCount() +{ + BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS]; + Gap::Whitelist_t whitelist; + whitelist.addresses = bondedAddresses; + whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS; + ble->securityManager().getAddressesFromBondTable(whitelist); + + return whitelist.bonds; +} + +/** + * A request to pair has been received from a BLE device. + * If we're in pairing mode, display the passkey to the user. + * Also, purge the bonding table if it has reached capacity. + * + * @note for internal use only. + */ +void MicroBitBLEManager::pairingRequested(ManagedString passKey) +{ + // Update our mode to display the passkey. + this->passKey = passKey; + this->pairingStatus = MICROBIT_BLE_PAIR_REQUEST; +} + +/** + * A pairing request has been sucessfully completed. + * If we're in pairing mode, display a success or failure message. + * + * @note for internal use only. + */ +void MicroBitBLEManager::pairingComplete(bool success) +{ + this->pairingStatus = MICROBIT_BLE_PAIR_COMPLETE; + + if(success) + { + this->pairingStatus |= MICROBIT_BLE_PAIR_SUCCESSFUL; + fiber_add_idle_component(this); + } +} + +/** + * Periodic callback in thread context. + * We use this here purely to safely issue a disconnect operation after a pairing operation is complete. + */ +void MicroBitBLEManager::idleTick() +{ + if (ble) + ble->disconnect(pairingHandle, Gap::REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF); + + fiber_remove_idle_component(this); +} + +/** + * 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 An instance of MicroBitDisplay used when displaying pairing information. + * @param authorizationButton The button to use to authorise a pairing request. + * + * @code + * // initiate pairing mode + * bleManager.pairingMode(uBit.display, uBit.buttonA); + * @endcode + */ +void MicroBitBLEManager::pairingMode(MicroBitDisplay& display, MicroBitButton& authorisationButton) +{ + 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; + + ble->gap().stopAdvertising(); + + // Clear the whitelist (if we have one), so that we're discoverable by all BLE devices. +#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST) + BLEProtocol::Address_t addresses[MICROBIT_BLE_MAXIMUM_BONDS]; + Gap::Whitelist_t whitelist; + whitelist.addresses = addresses; + whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS; + whitelist.size = 0; + ble->gap().setWhitelist(whitelist); + ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_IGNORE_WHITELIST); +#endif + + // 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->gap().setAdvertisingTimeout(0); + ble->gap().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 (authorisationButton.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<passKey.length(); i++) + { + display.image.print(passKey.charAt(i),0,0); + fiber_sleep(800); + display.clear(); + fiber_sleep(200); + + if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) + break; + } + + fiber_sleep(1000); + } + + if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE) + { + if (pairingStatus & MICROBIT_BLE_PAIR_SUCCESSFUL) + { + MicroBitImage tick("0,0,0,0,0\n0,0,0,0,255\n0,0,0,255,0\n255,0,255,0,0\n0,255,0,0,0\n"); + display.print(tick,0,0,0); + fiber_sleep(15000); + timeInPairingMode = MICROBIT_BLE_PAIRING_TIMEOUT * 30; + + /* + * Disabled, as the API to return the number of active bonds is not reliable at present... + * + display.clear(); + ManagedString c(getBondCount()); + ManagedString c2("/"); + ManagedString c3(MICROBIT_BLE_MAXIMUM_BONDS); + ManagedString c4("USED"); + + display.scroll(c+c2+c3+c4); + * + * + */ + } + else + { + MicroBitImage cross("255,0,0,0,255\n0,255,0,255,0\n0,0,255,0,0\n0,255,0,255,0\n255,0,0,0,255\n"); + display.print(cross,0,0,0); + } + } + + fiber_sleep(100); + timeInPairingMode++; + + if (timeInPairingMode >= MICROBIT_BLE_PAIRING_TIMEOUT * 30) + microbit_reset(); + } +} + +/** + * Displays the device's ID code as a histogram on the provided MicroBitDisplay instance. + * + * @param display The display instance used for displaying the histogram. + */ +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<MICROBIT_DFU_HISTOGRAM_WIDTH;i++) + { + h = (n % d) / ld; + + n -= h; + d *= MICROBIT_DFU_HISTOGRAM_HEIGHT; + ld *= MICROBIT_DFU_HISTOGRAM_HEIGHT; + + for (int j=0; j<h+1; j++) + display.image.setPixelValue(MICROBIT_DFU_HISTOGRAM_WIDTH-i-1, MICROBIT_DFU_HISTOGRAM_HEIGHT-j-1, 255); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitButtonService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,143 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the custom MicroBit Button Service. + * Provides a BLE service to remotely read the state of each button, and configure its behaviour. + */ +#include "MicroBitConfig.h" +#include "ble/UUID.h" + +#include "MicroBitButtonService.h" +#include "MicroBitButton.h" + +/** + * Constructor. + * Create a representation of the ButtonService + * @param _ble The instance of a BLE device that we're running on. + */ +MicroBitButtonService::MicroBitButtonService(BLEDevice &_ble) : + ble(_ble) +{ + // Create the data structures that represent each of our characteristics in Soft Device. + GattCharacteristic buttonADataCharacteristic(MicroBitButtonAServiceDataUUID, (uint8_t *)&buttonADataCharacteristicBuffer, 0, + sizeof(buttonADataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + GattCharacteristic buttonBDataCharacteristic(MicroBitButtonBServiceDataUUID, (uint8_t *)&buttonADataCharacteristicBuffer, 0, + sizeof(buttonADataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + + // Initialise our characteristic values. + buttonADataCharacteristicBuffer = 0; + buttonBDataCharacteristicBuffer = 0; + + // Set default security requirements + buttonADataCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + buttonBDataCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {&buttonADataCharacteristic, &buttonBDataCharacteristic}; + GattService service(MicroBitButtonServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + buttonADataCharacteristicHandle = buttonADataCharacteristic.getValueHandle(); + buttonBDataCharacteristicHandle = buttonBDataCharacteristic.getValueHandle(); + + ble.gattServer().write(buttonADataCharacteristicHandle,(uint8_t *)&buttonADataCharacteristicBuffer, sizeof(buttonADataCharacteristicBuffer)); + ble.gattServer().write(buttonBDataCharacteristicHandle,(uint8_t *)&buttonBDataCharacteristicBuffer, sizeof(buttonBDataCharacteristicBuffer)); + + if (EventModel::defaultEventBus) + { + EventModel::defaultEventBus->listen(MICROBIT_ID_BUTTON_A, MICROBIT_EVT_ANY, this, &MicroBitButtonService::buttonAUpdate, MESSAGE_BUS_LISTENER_IMMEDIATE); + EventModel::defaultEventBus->listen(MICROBIT_ID_BUTTON_B, MICROBIT_EVT_ANY, this, &MicroBitButtonService::buttonBUpdate, MESSAGE_BUS_LISTENER_IMMEDIATE); + } +} + + +/** + * Button B update callback + */ +void MicroBitButtonService::buttonAUpdate(MicroBitEvent e) +{ + if (ble.getGapState().connected) + { + if (e.value == MICROBIT_BUTTON_EVT_UP) + { + buttonADataCharacteristicBuffer = 0; + ble.gattServer().notify(buttonADataCharacteristicHandle,(uint8_t *)&buttonADataCharacteristicBuffer, sizeof(buttonADataCharacteristicBuffer)); + } + + if (e.value == MICROBIT_BUTTON_EVT_DOWN) + { + buttonADataCharacteristicBuffer = 1; + ble.gattServer().notify(buttonADataCharacteristicHandle,(uint8_t *)&buttonADataCharacteristicBuffer, sizeof(buttonADataCharacteristicBuffer)); + } + + if (e.value == MICROBIT_BUTTON_EVT_HOLD) + { + buttonADataCharacteristicBuffer = 2; + ble.gattServer().notify(buttonADataCharacteristicHandle,(uint8_t *)&buttonADataCharacteristicBuffer, sizeof(buttonADataCharacteristicBuffer)); + } + } +} + +/** + * Button A update callback + */ +void MicroBitButtonService::buttonBUpdate(MicroBitEvent e) +{ + if (ble.getGapState().connected) + { + if (e.value == MICROBIT_BUTTON_EVT_UP) + { + buttonBDataCharacteristicBuffer = 0; + ble.gattServer().notify(buttonBDataCharacteristicHandle,(uint8_t *)&buttonBDataCharacteristicBuffer, sizeof(buttonBDataCharacteristicBuffer)); + } + + if (e.value == MICROBIT_BUTTON_EVT_DOWN) + { + buttonBDataCharacteristicBuffer = 1; + ble.gattServer().notify(buttonBDataCharacteristicHandle,(uint8_t *)&buttonBDataCharacteristicBuffer, sizeof(buttonBDataCharacteristicBuffer)); + } + + if (e.value == MICROBIT_BUTTON_EVT_HOLD) + { + buttonBDataCharacteristicBuffer = 2; + ble.gattServer().notify(buttonBDataCharacteristicHandle,(uint8_t *)&buttonBDataCharacteristicBuffer, sizeof(buttonBDataCharacteristicBuffer)); + } + } +} + +const uint8_t MicroBitButtonServiceUUID[] = { + 0xe9,0x5d,0x98,0x82,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitButtonAServiceDataUUID[] = { + 0xe9,0x5d,0xda,0x90,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +} +; +const uint8_t MicroBitButtonBServiceDataUUID[] = { + 0xe9,0x5d,0xda,0x91,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitDFUService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,137 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a MicroBit Device Firmware Update loader. + * + * This is actually just a frontend to a memory resident nordic DFU loader. + * + * We rely on the BLE standard pairing processes to provide encryption and authentication. + * We assume any device that is paied with the micro:bit is authorized to reprogram the device. + * + */ +#include "MicroBitConfig.h" +#include "MicroBitDFUService.h" +#include "ble/UUID.h" +#include "MicroBitConfig.h" + +#if !defined(__arm) +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +/* + * 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... + */ +extern "C" { +#include "dfu_app_handler.h" +} + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + + +/** + * Constructor. + * Initialise the Device Firmware Update service. + * @param _ble The instance of a BLE device that we're running on. + */ +MicroBitDFUService::MicroBitDFUService(BLEDevice &_ble) : + ble(_ble) +{ + // Opcodes can be issued here to control the MicroBitDFU Service, as defined above. + GattCharacteristic microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte, 0, sizeof(uint8_t), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + controlByte = 0x00; + + // Set default security requirements + microBitDFUServiceControlCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {µBitDFUServiceControlCharacteristic}; + GattService service(MicroBitDFUServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + microBitDFUServiceControlCharacteristicHandle = microBitDFUServiceControlCharacteristic.getValueHandle(); + + ble.gattServer().write(microBitDFUServiceControlCharacteristicHandle, &controlByte, sizeof(uint8_t)); + ble.gattServer().onDataWritten(this, &MicroBitDFUService::onDataWritten); +} + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params) +{ + if (params->handle == microBitDFUServiceControlCharacteristicHandle) + { + if(params->len > 0 && params->data[0] == MICROBIT_DFU_OPCODE_START_DFU) + { + // TODO: Raise a SYSTEM event here. + //uBit.display.stopAnimation(); + //uBit.display.clear(); + +#if CONFIG_ENABLED(MICROBIT_DBG) + printf(" ACTIVATING BOOTLOADER.\n"); +#endif + + // Perform an explicit disconnection to assist our peer to reconnect to the DFU service + ble.disconnect(Gap::REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF); + + wait_ms(1000); + + // 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); + } + } +} + + +/** + * 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 +}; + +const uint8_t MicroBitDFUServiceControlCharacteristicUUID[] = { + 0xe9,0x5d,0x93,0xb1,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitEventService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,188 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a MicroBit BLE Event Service. + * Provides a BLE gateway onto an Event Model. + */ + +#include "MicroBitConfig.h" +#include "MicroBitEventService.h" +#include "ble/UUID.h" +#include "ExternalEvents.h" +#include "MicroBitFiber.h" + +/** + * Constructor. + * Create a representation of the EventService + * @param _ble The instance of a BLE device that we're running on. + * @param _messageBus An instance of an EventModel which events will be mirrored from. + */ +MicroBitEventService::MicroBitEventService(BLEDevice &_ble, EventModel &_messageBus) : + ble(_ble),messageBus(_messageBus) +{ + GattCharacteristic microBitEventCharacteristic(MicroBitEventServiceMicroBitEventCharacteristicUUID, (uint8_t *)µBitEventBuffer, 0, sizeof(EventServiceEvent), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + GattCharacteristic clientEventCharacteristic(MicroBitEventServiceClientEventCharacteristicUUID, (uint8_t *)&clientEventBuffer, 0, sizeof(EventServiceEvent), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + GattCharacteristic clientRequirementsCharacteristic(MicroBitEventServiceClientRequirementsCharacteristicUUID, (uint8_t *)&clientRequirementsBuffer, 0, sizeof(EventServiceEvent), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + microBitRequirementsCharacteristic = new GattCharacteristic(MicroBitEventServiceMicroBitRequirementsCharacteristicUUID, (uint8_t *)µBitRequirementsBuffer, 0, sizeof(EventServiceEvent), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + microBitRequirementsCharacteristic->setReadAuthorizationCallback(this, &MicroBitEventService::onRequirementsRead); + + clientEventBuffer.type = 0x00; + clientEventBuffer.reason = 0x00; + + microBitEventBuffer = microBitRequirementsBuffer = clientRequirementsBuffer = clientEventBuffer; + + messageBusListenerOffset = 0; + + // Set default security requirements + microBitEventCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + clientEventCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + clientRequirementsCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + microBitRequirementsCharacteristic->requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {µBitEventCharacteristic, &clientEventCharacteristic, &clientRequirementsCharacteristic, microBitRequirementsCharacteristic}; + GattService service(MicroBitEventServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + microBitEventCharacteristicHandle = microBitEventCharacteristic.getValueHandle(); + clientEventCharacteristicHandle = clientEventCharacteristic.getValueHandle(); + clientRequirementsCharacteristicHandle = clientRequirementsCharacteristic.getValueHandle(); + + ble.onDataWritten(this, &MicroBitEventService::onDataWritten); + + fiber_add_idle_component(this); +} + + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitEventService::onDataWritten(const GattWriteCallbackParams *params) +{ + int len = params->len; + EventServiceEvent *e = (EventServiceEvent *)params->data; + + if (params->handle == clientEventCharacteristicHandle) { + + // Read and fire all events... + while (len >= 4) + { + MicroBitEvent evt(e->type, e->reason); + len-=4; + e++; + } + return; + } + + if (params->handle == clientRequirementsCharacteristicHandle) { + // Read and register for all the events given... + while (len >= 4) + { + messageBus.listen(e->type, e->reason, this, &MicroBitEventService::onMicroBitEvent, MESSAGE_BUS_LISTENER_IMMEDIATE); + + len-=4; + e++; + } + return; + } +} + +/** + * Callback. Invoked when any events are sent on the microBit message bus. + */ +void MicroBitEventService::onMicroBitEvent(MicroBitEvent evt) +{ + EventServiceEvent *e = µBitEventBuffer; + + if (ble.getGapState().connected) { + e->type = evt.source; + e->reason = evt.value; + + ble.gattServer().notify(microBitEventCharacteristicHandle, (const uint8_t *)e, sizeof(EventServiceEvent)); + } +} + +/** + * Periodic callback from MicroBit scheduler. + * If we're no longer connected, remove any registered Message Bus listeners. + */ +void MicroBitEventService::idleTick() +{ + if (!ble.getGapState().connected && messageBusListenerOffset >0) { + messageBusListenerOffset = 0; + messageBus.ignore(MICROBIT_ID_ANY, MICROBIT_EVT_ANY, this, &MicroBitEventService::onMicroBitEvent); + } +} + +/** + * Read callback on microBitRequirements characteristic. + * + * Used to iterate through the events that the code on this micro:bit is interested in. + */ +void MicroBitEventService::onRequirementsRead(GattReadAuthCallbackParams *params) +{ + if (params->handle == microBitRequirementsCharacteristic->getValueHandle()) + { + // Walk through the lsit of message bus listeners. + // We send one at a time, and our client can keep reading from this characterisitic until we return an emtpy value. + MicroBitListener *l = messageBus.elementAt(messageBusListenerOffset++); + + if (l != NULL) + { + microBitRequirementsBuffer.type = l->id; + microBitRequirementsBuffer.reason = l->value; + ble.gattServer().write(microBitRequirementsCharacteristic->getValueHandle(), (uint8_t *)µBitRequirementsBuffer, sizeof(EventServiceEvent)); + } else { + ble.gattServer().write(microBitRequirementsCharacteristic->getValueHandle(), (uint8_t *)µBitRequirementsBuffer, 0); + } + } +} + +const uint8_t MicroBitEventServiceUUID[] = { + 0xe9,0x5d,0x93,0xaf,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitEventServiceMicroBitEventCharacteristicUUID[] = { + 0xe9,0x5d,0x97,0x75,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitEventServiceClientEventCharacteristicUUID[] = { + 0xe9,0x5d,0x54,0x04,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitEventServiceMicroBitRequirementsCharacteristicUUID[] = { + 0xe9,0x5d,0xb8,0x4c,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitEventServiceClientRequirementsCharacteristicUUID[] = { + 0xe9,0x5d,0x23,0xc4,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitIOPinService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,330 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the custom MicroBit IOPin Service. + * Provides a BLE service to remotely read the state of the I/O Pin, and configure its behaviour. + */ +#include "MicroBitConfig.h" +#include "ble/UUID.h" + +#include "MicroBitIOPinService.h" +#include "MicroBitFiber.h" + +/** + * Constructor. + * Create a representation of the IOPinService + * @param _ble The instance of a BLE device that we're running on. + * @param _io An instance of MicroBitIO that this service will use to perform + * I/O operations. + */ +MicroBitIOPinService::MicroBitIOPinService(BLEDevice &_ble, MicroBitIO &_io) : + ble(_ble), io(_io) +{ + // Create the AD characteristic, that defines whether each pin is treated as analogue or digital + GattCharacteristic ioPinServiceADCharacteristic(MicroBitIOPinServiceADConfigurationUUID, (uint8_t *)&ioPinServiceADCharacteristicBuffer, 0, sizeof(ioPinServiceADCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + // Create the IO characteristic, that defines whether each pin is treated as input or output + GattCharacteristic ioPinServiceIOCharacteristic(MicroBitIOPinServiceIOConfigurationUUID, (uint8_t *)&ioPinServiceIOCharacteristicBuffer, 0, sizeof(ioPinServiceIOCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + // Create the Data characteristic, that allows the actual read and write operations. + ioPinServiceDataCharacteristic = new GattCharacteristic(MicroBitIOPinServiceDataUUID, (uint8_t *)ioPinServiceDataCharacteristicBuffer, 0, sizeof(ioPinServiceDataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + ioPinServiceDataCharacteristic->setReadAuthorizationCallback(this, &MicroBitIOPinService::onDataRead); + + ioPinServiceADCharacteristicBuffer = 0; + ioPinServiceIOCharacteristicBuffer = 0; + memset(ioPinServiceIOData, 0, sizeof(ioPinServiceIOData)); + + // Set default security requirements + ioPinServiceADCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + ioPinServiceIOCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + ioPinServiceDataCharacteristic->requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {&ioPinServiceADCharacteristic, &ioPinServiceIOCharacteristic, ioPinServiceDataCharacteristic}; + GattService service(MicroBitIOPinServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + ioPinServiceADCharacteristicHandle = ioPinServiceADCharacteristic.getValueHandle(); + ioPinServiceIOCharacteristicHandle = ioPinServiceIOCharacteristic.getValueHandle(); + + ble.gattServer().write(ioPinServiceADCharacteristicHandle, (const uint8_t *)&ioPinServiceADCharacteristicBuffer, sizeof(ioPinServiceADCharacteristicBuffer)); + ble.gattServer().write(ioPinServiceIOCharacteristicHandle, (const uint8_t *)&ioPinServiceIOCharacteristicBuffer, sizeof(ioPinServiceIOCharacteristicBuffer)); + + ble.onDataWritten(this, &MicroBitIOPinService::onDataWritten); + fiber_add_idle_component(this); +} + +/** + * Determines if the given pin was configured as a digital pin by the BLE ADPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as digital, 0 otherwise + */ +int MicroBitIOPinService::isDigital(int i) +{ + return ((ioPinServiceADCharacteristicBuffer & (1 << i)) == 0); +} + +/** + * Determines if the given pin was configured as an analog pin by the BLE ADPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as analog, 0 otherwise + */ +int MicroBitIOPinService::isAnalog(int i) +{ + return ((ioPinServiceADCharacteristicBuffer & (1 << i)) != 0); +} + +/** + * Determines if the given pin was configured as an input by the BLE IOPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as an input, 0 otherwise + */ +int MicroBitIOPinService::isInput(int i) +{ + return ((ioPinServiceIOCharacteristicBuffer & (1 << i)) != 0); +} + +/** + * Determines if the given pin was configured as output by the BLE IOPinConfigurationCharacterisitic. + * + * @param i the enumeration of the pin to test + * @return 1 if this pin is configured as an output, 0 otherwise + */ +int MicroBitIOPinService::isOutput(int i) +{ + return ((ioPinServiceIOCharacteristicBuffer & (1 << i)) == 0); +} + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitIOPinService::onDataWritten(const GattWriteCallbackParams *params) +{ + // Check for writes to the IO configuration characteristic + if (params->handle == ioPinServiceIOCharacteristicHandle && params->len >= sizeof(ioPinServiceIOCharacteristicBuffer)) + { + uint32_t *value = (uint32_t *)params->data; + + // Our IO configuration may be changing... read the new value, and push it back into the BLE stack. + ioPinServiceIOCharacteristicBuffer = *value; + ble.gattServer().write(ioPinServiceIOCharacteristicHandle, (const uint8_t *)&ioPinServiceIOCharacteristicBuffer, sizeof(ioPinServiceIOCharacteristicBuffer)); + + // Also, drop any selected pins into input mode, so we can pick up changes later + for (int i=0; i < MICROBIT_IO_PIN_SERVICE_PINCOUNT; i++) + { + if(isDigital(i) && isInput(i)) + io.pin[i].getDigitalValue(); + //MicroBitIOPins[i]->getDigitalValue(); + + if(isAnalog(i) && isInput(i)) + io.pin[i].getAnalogValue(); + //MicroBitIOPins[i]->getAnalogValue(); + } + } + + // Check for writes to the IO configuration characteristic + if (params->handle == ioPinServiceADCharacteristicHandle && params->len >= sizeof(ioPinServiceADCharacteristicBuffer)) + { + uint32_t *value = (uint32_t *)params->data; + + // Our IO configuration may be changing... read the new value, and push it back into the BLE stack. + ioPinServiceADCharacteristicBuffer = *value; + ble.gattServer().write(ioPinServiceADCharacteristicHandle, (const uint8_t *)&ioPinServiceADCharacteristicBuffer, sizeof(ioPinServiceADCharacteristicBuffer)); + + // Also, drop any selected pins into input mode, so we can pick up changes later + for (int i=0; i < MICROBIT_IO_PIN_SERVICE_PINCOUNT; i++) + { + if(isDigital(i) && isInput(i)) + io.pin[i].getDigitalValue(); + //MicroBitIOPins[i]->getDigitalValue(); + + if(isAnalog(i) && isInput(i)) + io.pin[i].getAnalogValue(); + //MicroBitIOPins[i]->getAnalogValue(); + } + } + + if (params->handle == ioPinServiceDataCharacteristic->getValueHandle()) + { + // We have some pin data to change... + uint16_t len = params->len; + IOData *data = (IOData *)params->data; + + // There may be multiple write operaitons... take each in turn and update the pin values + while (len >= sizeof(IOData)) + { + if (isOutput(data->pin)) + { + if (isDigital(data->pin)) + io.pin[data->pin].setDigitalValue(data->value); + //MicroBitIOPins[data->pin]->setDigitalValue(data->value); + else + io.pin[data->pin].setAnalogValue(data->value*4); + //MicroBitIOPins[data->pin]->setAnalogValue(data->value*4); + } + + data++; + len -= sizeof(IOData); + } + } +} + +/** + * Callback. invoked when the BLE data characteristic is read. + * + * Reads all the pins marked as inputs, and updates the data stored in the characteristic. + */ +void MicroBitIOPinService::onDataRead(GattReadAuthCallbackParams *params) +{ + if (params->handle == ioPinServiceDataCharacteristic->getValueHandle()) + { + + // Scan through all pins that our BLE client may be listening for. If any have changed value, update the BLE characterisitc, and NOTIFY our client. + int pairs = 0; + + for (int i=0; i < MICROBIT_IO_PIN_SERVICE_PINCOUNT; i++) + { + if (isInput(i)) + { + uint8_t value; + + if (isDigital(i)) + value = io.pin[i].getDigitalValue(); + //value = MicroBitIOPins[i]->getDigitalValue(); + else + value = io.pin[i].getAnalogValue(); + //value = MicroBitIOPins[i]->getAnalogValue(); + + ioPinServiceIOData[i] = value; + ioPinServiceDataCharacteristicBuffer[pairs].pin = i; + ioPinServiceDataCharacteristicBuffer[pairs].value = value; + + pairs++; + + if (pairs >= MICROBIT_IO_PIN_SERVICE_DATA_SIZE) + break; + } + } + + // If there's any data, issue a BLE notification. + if (pairs > 0) + ble.gattServer().notify(ioPinServiceDataCharacteristic->getValueHandle(), (uint8_t *)ioPinServiceDataCharacteristicBuffer, pairs * sizeof(IOData)); + } +} + + +/** + * Periodic callback from MicroBit scheduler. + * + * Check if any of the pins we're watching need updating. Notify any connected + * device with any changes. + */ +void MicroBitIOPinService::idleTick() +{ + // If we're not we're connected, then there's nothing to do... + if (!ble.getGapState().connected) + return; + + // Scan through all pins that our BLE client may be listening for. If any have changed value, update the BLE characterisitc, and NOTIFY our client. + int pairs = 0; + + for (int i=0; i < MICROBIT_IO_PIN_SERVICE_PINCOUNT; i++) + { + if (isInput(i)) + { + uint8_t value; + + if (isDigital(i)) + value = io.pin[i].getDigitalValue(); + //value = MicroBitIOPins[i]->getDigitalValue(); + else + value = io.pin[i].getAnalogValue(); + //value = MicroBitIOPins[i]->getAnalogValue(); + + // If the data has changed, send an update. + if (value != ioPinServiceIOData[i]) + { + ioPinServiceIOData[i] = value; + + ioPinServiceDataCharacteristicBuffer[pairs].pin = i; + ioPinServiceDataCharacteristicBuffer[pairs].value = value; + + pairs++; + + if (pairs >= MICROBIT_IO_PIN_SERVICE_DATA_SIZE) + break; + } + } + } + + // If there were any changes, issue a BLE notification. + if (pairs > 0) + ble.gattServer().notify(ioPinServiceDataCharacteristic->getValueHandle(), (uint8_t *)ioPinServiceDataCharacteristicBuffer, pairs * sizeof(IOData)); +} + +const uint8_t MicroBitIOPinServiceUUID[] = { + 0xe9,0x5d,0x12,0x7b,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitIOPinServiceIOConfigurationUUID[] = { + 0xe9,0x5d,0xb9,0xfe,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitIOPinServiceADConfigurationUUID[] = { + 0xe9,0x5d,0x58,0x99,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitIOPinServiceDataUUID[] = { + 0xe9,0x5d,0x8d,0x00,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +/* +MicroBitPin * const MicroBitIOPins[] = { + &uBit.io.P0, + &uBit.io.P1, + &uBit.io.P2, + &uBit.io.P3, + &uBit.io.P4, + &uBit.io.P5, + &uBit.io.P6, + &uBit.io.P7, + &uBit.io.P8, + &uBit.io.P9, + &uBit.io.P10, + &uBit.io.P11, + &uBit.io.P12, + &uBit.io.P13, + &uBit.io.P14, + &uBit.io.P15, + &uBit.io.P16, + &uBit.io.P19, + &uBit.io.P20 +}; +*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitLEDService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,150 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the custom MicroBit LED Service. + * Provides a BLE service to remotely read and write the state of the LED display. + */ +#include "MicroBitConfig.h" +#include "ble/UUID.h" + +#include "MicroBitLEDService.h" + +/** + * Constructor. + * Create a representation of the LEDService + * @param _ble The instance of a BLE device that we're running on. + * @param _display An instance of MicroBitDisplay to interface with. + */ +MicroBitLEDService::MicroBitLEDService(BLEDevice &_ble, MicroBitDisplay &_display) : + ble(_ble), display(_display), + matrixCharacteristic(MicroBitLEDServiceMatrixUUID, (uint8_t *)&matrixCharacteristicBuffer, 0, sizeof(matrixCharacteristicBuffer), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ) +{ + // Create the data structures that represent each of our characteristics in Soft Device. + GattCharacteristic textCharacteristic(MicroBitLEDServiceTextUUID, (uint8_t *)textCharacteristicBuffer, 0, MICROBIT_BLE_MAXIMUM_SCROLLTEXT, + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + GattCharacteristic scrollingSpeedCharacteristic(MicroBitLEDServiceScrollingSpeedUUID, (uint8_t *)&scrollingSpeedCharacteristicBuffer, 0, + sizeof(scrollingSpeedCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ); + + // Initialise our characteristic values. + memclr(matrixCharacteristicBuffer, sizeof(matrixCharacteristicBuffer)); + textCharacteristicBuffer[0] = 0; + scrollingSpeedCharacteristicBuffer = MICROBIT_DEFAULT_SCROLL_SPEED; + + matrixCharacteristic.setReadAuthorizationCallback(this, &MicroBitLEDService::onDataRead); + + // Set default security requirements + matrixCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + textCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + scrollingSpeedCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {&matrixCharacteristic, &textCharacteristic, &scrollingSpeedCharacteristic}; + GattService service(MicroBitLEDServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + matrixCharacteristicHandle = matrixCharacteristic.getValueHandle(); + textCharacteristicHandle = textCharacteristic.getValueHandle(); + scrollingSpeedCharacteristicHandle = scrollingSpeedCharacteristic.getValueHandle(); + + ble.gattServer().write(scrollingSpeedCharacteristicHandle, (const uint8_t *)&scrollingSpeedCharacteristicBuffer, sizeof(scrollingSpeedCharacteristicBuffer)); + ble.gattServer().write(matrixCharacteristicHandle, (const uint8_t *)&matrixCharacteristicBuffer, sizeof(matrixCharacteristicBuffer)); + + ble.onDataWritten(this, &MicroBitLEDService::onDataWritten); +} + + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitLEDService::onDataWritten(const GattWriteCallbackParams *params) +{ + uint8_t *data = (uint8_t *)params->data; + + if (params->handle == matrixCharacteristicHandle && params->len > 0 && params->len < 6) + { + for (int y=0; y<params->len; y++) + for (int x=0; x<5; x++) + display.image.setPixelValue(x, y, (data[y] & (0x01 << (4-x))) ? 255 : 0); + } + + else if (params->handle == textCharacteristicHandle) + { + // Create a ManagedString representation from the UTF8 data. + // We do this explicitly to control the length (in case the string is not NULL terminated!) + ManagedString s((char *)params->data, params->len); + + // Start the string scrolling and we're done. + display.scrollAsync(s, (int) scrollingSpeedCharacteristicBuffer); + } + + else if (params->handle == scrollingSpeedCharacteristicHandle && params->len >= sizeof(scrollingSpeedCharacteristicBuffer)) + { + // Read the speed requested, and store it locally. + // We use this as the speed for all scroll operations subsquently initiated from BLE. + scrollingSpeedCharacteristicBuffer = *((uint16_t *)params->data); + } +} + +/** + * Callback. Invoked when any of our attributes are read via BLE. + */ +void MicroBitLEDService::onDataRead(GattReadAuthCallbackParams *params) +{ + if (params->handle == matrixCharacteristicHandle) + { + for (int y=0; y<5; y++) + { + matrixCharacteristicBuffer[y] = 0; + + for (int x=0; x<5; x++) + { + if (display.image.getPixelValue(x, y)) + matrixCharacteristicBuffer[y] |= 0x01 << (4-x); + } + } + + ble.gattServer().write(matrixCharacteristicHandle, (const uint8_t *)&matrixCharacteristicBuffer, sizeof(matrixCharacteristicBuffer)); + } +} + + +const uint8_t MicroBitLEDServiceUUID[] = { + 0xe9,0x5d,0xd9,0x1d,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitLEDServiceMatrixUUID[] = { + 0xe9,0x5d,0x7b,0x77,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitLEDServiceTextUUID[] = { + 0xe9,0x5d,0x93,0xee,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitLEDServiceScrollingSpeedUUID[] = { + 0xe9,0x5d,0x0d,0x2d,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitMagnetometerService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,156 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the MicroBit BLE Magnetometer Service. + * Provides access to live magnetometer data via BLE, and provides basic configuration options. + */ +#include "MicroBitConfig.h" +#include "ble/UUID.h" + +#include "MicroBitMagnetometerService.h" + +/** + * Constructor. + * Create a representation of the MagnetometerService. + * @param _ble The instance of a BLE device that we're running on. + * @param _compass An instance of MicroBitCompass to use as our Magnetometer source. + */ +MicroBitMagnetometerService::MicroBitMagnetometerService(BLEDevice &_ble, MicroBitCompass &_compass) : + ble(_ble), compass(_compass) +{ + // Create the data structures that represent each of our characteristics in Soft Device. + GattCharacteristic magnetometerDataCharacteristic(MicroBitMagnetometerServiceDataUUID, (uint8_t *)magnetometerDataCharacteristicBuffer, 0, + sizeof(magnetometerDataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + GattCharacteristic magnetometerBearingCharacteristic(MicroBitMagnetometerServiceBearingUUID, (uint8_t *)&magnetometerBearingCharacteristicBuffer, 0, + sizeof(magnetometerBearingCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + GattCharacteristic magnetometerPeriodCharacteristic(MicroBitMagnetometerServicePeriodUUID, (uint8_t *)&magnetometerPeriodCharacteristicBuffer, 0, + sizeof(magnetometerPeriodCharacteristicBuffer), + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + // Initialise our characteristic values. + magnetometerDataCharacteristicBuffer[0] = 0; + magnetometerDataCharacteristicBuffer[1] = 0; + magnetometerDataCharacteristicBuffer[2] = 0; + magnetometerBearingCharacteristicBuffer = 0; + magnetometerPeriodCharacteristicBuffer = compass.getPeriod(); + + // Set default security requirements + magnetometerDataCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + magnetometerBearingCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + magnetometerPeriodCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {&magnetometerDataCharacteristic, &magnetometerBearingCharacteristic, &magnetometerPeriodCharacteristic}; + GattService service(MicroBitMagnetometerServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + magnetometerDataCharacteristicHandle = magnetometerDataCharacteristic.getValueHandle(); + magnetometerBearingCharacteristicHandle = magnetometerBearingCharacteristic.getValueHandle(); + magnetometerPeriodCharacteristicHandle = magnetometerPeriodCharacteristic.getValueHandle(); + + ble.gattServer().notify(magnetometerDataCharacteristicHandle,(uint8_t *)magnetometerDataCharacteristicBuffer, sizeof(magnetometerDataCharacteristicBuffer)); + ble.gattServer().notify(magnetometerBearingCharacteristicHandle,(uint8_t *)&magnetometerBearingCharacteristicBuffer, sizeof(magnetometerDataCharacteristicBuffer)); + ble.gattServer().write(magnetometerPeriodCharacteristicHandle, (const uint8_t *)&magnetometerPeriodCharacteristicBuffer, sizeof(magnetometerPeriodCharacteristicBuffer)); + + ble.onDataWritten(this, &MicroBitMagnetometerService::onDataWritten); + if (EventModel::defaultEventBus) + { + EventModel::defaultEventBus->listen(MICROBIT_ID_COMPASS, MICROBIT_COMPASS_EVT_DATA_UPDATE, this, &MicroBitMagnetometerService::magnetometerUpdate, MESSAGE_BUS_LISTENER_IMMEDIATE); + EventModel::defaultEventBus->listen(MICROBIT_ID_COMPASS, MICROBIT_COMPASS_EVT_CONFIG_NEEDED, this, &MicroBitMagnetometerService::samplePeriodUpdateNeeded); + } +} + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitMagnetometerService::onDataWritten(const GattWriteCallbackParams *params) +{ + if (params->handle == magnetometerPeriodCharacteristicHandle && params->len >= sizeof(magnetometerPeriodCharacteristicBuffer)) + { + magnetometerPeriodCharacteristicBuffer = *((uint16_t *)params->data); + MicroBitEvent evt(MICROBIT_ID_COMPASS, MICROBIT_COMPASS_EVT_CONFIG_NEEDED); + } +} + +/** + * Magnetometer update callback + */ +void MicroBitMagnetometerService::magnetometerUpdate(MicroBitEvent) +{ + if (ble.getGapState().connected) + { + magnetometerDataCharacteristicBuffer[0] = compass.getX(); + magnetometerDataCharacteristicBuffer[1] = compass.getY(); + magnetometerDataCharacteristicBuffer[2] = compass.getZ(); + magnetometerPeriodCharacteristicBuffer = compass.getPeriod(); + + ble.gattServer().write(magnetometerPeriodCharacteristicHandle, (const uint8_t *)&magnetometerPeriodCharacteristicBuffer, sizeof(magnetometerPeriodCharacteristicBuffer)); + ble.gattServer().notify(magnetometerDataCharacteristicHandle,(uint8_t *)magnetometerDataCharacteristicBuffer, sizeof(magnetometerDataCharacteristicBuffer)); + + if (compass.isCalibrated()) + { + magnetometerBearingCharacteristicBuffer = (uint16_t) compass.heading(); + ble.gattServer().notify(magnetometerBearingCharacteristicHandle,(uint8_t *)&magnetometerBearingCharacteristicBuffer, sizeof(magnetometerBearingCharacteristicBuffer)); + } + } +} + +/** + * Sample Period Change Needed callback. + * Reconfiguring the magnetometer can to a REALLY long time (sometimes even seconds to complete) + * So we do this in the background when necessary, through this event handler. + */ +void MicroBitMagnetometerService::samplePeriodUpdateNeeded(MicroBitEvent) +{ + // Reconfigure the compass. This might take a while... + compass.setPeriod(magnetometerPeriodCharacteristicBuffer); + + // The compass will choose the nearest sample period to that we've specified. + // Read the ACTUAL sample period back. + magnetometerPeriodCharacteristicBuffer = compass.getPeriod(); + + // Ensure this is reflected in our BLE connection. + ble.gattServer().write(magnetometerPeriodCharacteristicHandle, (const uint8_t *)&magnetometerPeriodCharacteristicBuffer, sizeof(magnetometerPeriodCharacteristicBuffer)); + +} + +const uint8_t MicroBitMagnetometerServiceUUID[] = { + 0xe9,0x5d,0xf2,0xd8,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitMagnetometerServiceDataUUID[] = { + 0xe9,0x5d,0xfb,0x11,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitMagnetometerServicePeriodUUID[] = { + 0xe9,0x5d,0x38,0x6c,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitMagnetometerServiceBearingUUID[] = { + 0xe9,0x5d,0x97,0x15,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitTemperatureService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,115 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the custom MicroBit Temperature Service. + * Provides a BLE service to remotely read the silicon temperature of the nRF51822. + */ +#include "MicroBitConfig.h" +#include "ble/UUID.h" + +#include "MicroBitTemperatureService.h" + +/** + * Constructor. + * Create a representation of the TemperatureService + * @param _ble The instance of a BLE device that we're running on. + * @param _thermometer An instance of MicroBitThermometer to use as our temperature source. + */ +MicroBitTemperatureService::MicroBitTemperatureService(BLEDevice &_ble, MicroBitThermometer &_thermometer) : + ble(_ble), thermometer(_thermometer) +{ + // Create the data structures that represent each of our characteristics in Soft Device. + GattCharacteristic temperatureDataCharacteristic(MicroBitTemperatureServiceDataUUID, (uint8_t *)&temperatureDataCharacteristicBuffer, 0, + sizeof(temperatureDataCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); + + GattCharacteristic temperaturePeriodCharacteristic(MicroBitTemperatureServicePeriodUUID, (uint8_t *)&temperaturePeriodCharacteristicBuffer, 0, + sizeof(temperaturePeriodCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + + // Initialise our characteristic values. + temperatureDataCharacteristicBuffer = 0; + temperaturePeriodCharacteristicBuffer = thermometer.getPeriod(); + + // Set default security requirements + temperatureDataCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + temperaturePeriodCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL); + + GattCharacteristic *characteristics[] = {&temperatureDataCharacteristic, &temperaturePeriodCharacteristic}; + GattService service(MicroBitTemperatureServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)); + + ble.addService(service); + + temperatureDataCharacteristicHandle = temperatureDataCharacteristic.getValueHandle(); + temperaturePeriodCharacteristicHandle = temperaturePeriodCharacteristic.getValueHandle(); + + ble.gattServer().write(temperatureDataCharacteristicHandle,(uint8_t *)&temperatureDataCharacteristicBuffer, sizeof(temperatureDataCharacteristicBuffer)); + ble.gattServer().write(temperaturePeriodCharacteristicHandle,(uint8_t *)&temperaturePeriodCharacteristicBuffer, sizeof(temperaturePeriodCharacteristicBuffer)); + + ble.onDataWritten(this, &MicroBitTemperatureService::onDataWritten); + if (EventModel::defaultEventBus) + EventModel::defaultEventBus->listen(MICROBIT_ID_THERMOMETER, MICROBIT_THERMOMETER_EVT_UPDATE, this, &MicroBitTemperatureService::temperatureUpdate, MESSAGE_BUS_LISTENER_IMMEDIATE); +} + +/** + * Temperature update callback + */ +void MicroBitTemperatureService::temperatureUpdate(MicroBitEvent) +{ + if (ble.getGapState().connected) + { + temperatureDataCharacteristicBuffer = thermometer.getTemperature(); + ble.gattServer().notify(temperatureDataCharacteristicHandle,(uint8_t *)&temperatureDataCharacteristicBuffer, sizeof(temperatureDataCharacteristicBuffer)); + } +} + +/** + * Callback. Invoked when any of our attributes are written via BLE. + */ +void MicroBitTemperatureService::onDataWritten(const GattWriteCallbackParams *params) +{ + if (params->handle == temperaturePeriodCharacteristicHandle && params->len >= sizeof(temperaturePeriodCharacteristicBuffer)) + { + temperaturePeriodCharacteristicBuffer = *((uint16_t *)params->data); + thermometer.setPeriod(temperaturePeriodCharacteristicBuffer); + + // The accelerometer will choose the nearest period to that requested that it can support + // Read back the ACTUAL period it is using, and report this back. + temperaturePeriodCharacteristicBuffer = thermometer.getPeriod(); + ble.gattServer().write(temperaturePeriodCharacteristicHandle, (const uint8_t *)&temperaturePeriodCharacteristicBuffer, sizeof(temperaturePeriodCharacteristicBuffer)); + } +} + + +const uint8_t MicroBitTemperatureServiceUUID[] = { + 0xe9,0x5d,0x61,0x00,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitTemperatureServiceDataUUID[] = { + 0xe9,0x5d,0x92,0x50,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +}; + +const uint8_t MicroBitTemperatureServicePeriodUUID[] = { + 0xe9,0x5d,0x1b,0x25,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/bluetooth/MicroBitUARTService.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,539 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the custom MicroBit UART Service. + * Provides a BLE service that acts as a UART port, enabling the reception and transmission + * of an arbitrary number of bytes. + */ + +#include "ble/UUID.h" + +#include "ExternalEvents.h" +#include "MicroBitUARTService.h" +#include "MicroBitFiber.h" +#include "ErrorNo.h" +#include "NotifyEvents.h" + +static uint8_t txBufferHead = 0; +static uint8_t txBufferTail = 0; + +static GattCharacteristic* rxCharacteristic = NULL; + +/** + * A callback function for whenever a Bluetooth device consumes our RX Buffer + */ +void on_confirmation_received_callback(uint16_t handle) +{ +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG->printf("RECEIVED!! %d \r\n",handle); +#endif + if(handle == rxCharacteristic->getValueAttribute().getHandle()) + { + txBufferTail = txBufferHead; + MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_UART_S_EVT_TX_EMPTY); + } +} + +/** + * Constructor for the UARTService. + * @param _ble an instance of BLEDevice + * @param rxBufferSize the size of the rxBuffer + * @param txBufferSize the size of the txBuffer + * + * @note defaults to 20 + */ +MicroBitUARTService::MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize, uint8_t txBufferSize) : ble(_ble) +{ + + txBuffer = (uint8_t *)malloc(txBufferSize); + rxBuffer = (uint8_t *)malloc(rxBufferSize); + + rxBufferHead = 0; + rxBufferTail = 0; + this->rxBufferSize = rxBufferSize; + + txBufferHead = 0; + txBufferTail = 0; + this->txBufferSize = txBufferSize; + + GattCharacteristic txCharacteristic(UARTServiceTXCharacteristicUUID, rxBuffer, 1, rxBufferSize, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE); + + rxCharacteristic = new GattCharacteristic(UARTServiceRXCharacteristicUUID, txBuffer, 1, txBufferSize, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE); + + GattCharacteristic *charTable[] = {&txCharacteristic, rxCharacteristic}; + + GattService uartService(UARTServiceUUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); + + _ble.addService(uartService); + + this->txCharacteristicHandle = txCharacteristic.getValueAttribute().getHandle(); + + _ble.gattServer().onDataWritten(this, &MicroBitUARTService::onDataWritten); + _ble.gattServer().onConfirmationReceived(on_confirmation_received_callback); +} + +/** + * A callback function for whenever a Bluetooth device writes to our TX characteristic. + */ +void MicroBitUARTService::onDataWritten(const GattWriteCallbackParams *params) { + if (params->handle == this->txCharacteristicHandle) + { + uint16_t bytesWritten = params->len; + + for(int byteIterator = 0; byteIterator < bytesWritten; byteIterator++) + { + int newHead = (rxBufferHead + 1) % rxBufferSize; + + if(newHead != rxBufferTail) + { + char c = params->data[byteIterator]; + + int delimeterOffset = 0; + int delimLength = this->delimeters.length(); + + //iterate through our delimeters (if any) to see if there is a match + while(delimeterOffset < delimLength) + { + //fire an event if there is to block any waiting fibers + if(this->delimeters.charAt(delimeterOffset) == c) + MicroBitEvent(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_DELIM_MATCH); + + delimeterOffset++; + } + + rxBuffer[rxBufferHead] = c; + + rxBufferHead = newHead; + + if(rxBufferHead == rxBuffHeadMatch) + { + rxBuffHeadMatch = -1; + MicroBitEvent(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_HEAD_MATCH); + } + } + else + MicroBitEvent(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_RX_FULL); + } + } +} + +/** + * An internal method that copies values from a circular buffer to a linear buffer. + * + * @param circularBuff a pointer to the source circular buffer + * @param circularBuffSize the size of the circular buffer + * @param linearBuff a pointer to the destination linear buffer + * @param tailPosition the tail position in the circular buffer you want to copy from + * @param headPosition the head position in the circular buffer you want to copy to + * + * @note this method assumes that the linear buffer has the appropriate amount of + * memory to contain the copy operation + */ +void MicroBitUARTService::circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8_t *linearBuff, uint16_t tailPosition, uint16_t headPosition) +{ + int toBuffIndex = 0; + + while(tailPosition != headPosition) + { + linearBuff[toBuffIndex++] = circularBuff[tailPosition]; + + tailPosition = (tailPosition + 1) % circularBuffSize; + } +} + +/** + * Retreives a single character from our RxBuffer. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read a single character, and return immediately + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, a character or MICROBIT_NO_DATA + */ +int MicroBitUARTService::getc(MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + if(mode == ASYNC) + { + if(!isReadable()) + return MICROBIT_NO_DATA; + } + + if(mode == SYNC_SLEEP) + { + if(!isReadable()) + eventAfter(1, mode); + } + + char c = rxBuffer[rxBufferTail]; + + rxBufferTail = (rxBufferTail + 1) % rxBufferSize; + + return c; +} + +/** + * places a single character into our transmission buffer, + * + * @param c the character to transmit + * + * @return the number of characters written (0, or 1). + */ +int MicroBitUARTService::putc(char c) +{ + return (send((uint8_t *)&c, 1) == 1) ? 1 : EOF; +} + +/** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param buf a buffer containing length number of bytes. + * @param length the size of the buffer. + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ +int MicroBitUARTService::send(const uint8_t *buf, int length) +{ + if(length < 1) + return MICROBIT_INVALID_PARAMETER; + + int bytesWritten = 0; + + if (ble.getGapState().connected) { + + for(int bufferIterator = 0; bufferIterator < length; bufferIterator++) + { + int nextHead = (txBufferHead + 1) % txBufferSize; + + if(nextHead != txBufferTail) + { + txBuffer[txBufferHead] = buf[bufferIterator]; + + txBufferHead = nextHead; + + bytesWritten++; + } + } + + int size = txBufferedSize(); + +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG->printf("tx size: %d", size); +#endif + + uint8_t temp[size] = { 0 }; + + circularCopy(txBuffer, txBufferSize, temp, txBufferTail, txBufferHead); + +#if CONFIG_ENABLED(MICROBIT_DBG) + for(int i = 0; i < size; i++) + SERIAL_DEBUG->printf("%c",temp[i]); +#endif + + ble.gattServer().write(rxCharacteristic->getValueAttribute().getHandle(), temp, size); + } + +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG->printf("written: %d \r\n",bytesWritten); +#endif + + return bytesWritten; +} + +/** + * Copies characters into the buffer used for Transmitting to the central device. + * + * @param s the string to transmit + * + * @return the number of characters copied into the buffer + * + * @note no modes for sending are available at the moment, due to interrupt overhead. + */ +int MicroBitUARTService::send(ManagedString s) +{ + return send((uint8_t *)s.toCharArray(), s.length()); +} + +/** + * Reads a number of characters from the rxBuffer and fills user given buffer. + * + * @param buf a pointer to a buffer of len bytes. + * @param len the size of the user allocated buffer + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return the number of characters digested + */ +int MicroBitUARTService::read(uint8_t *buf, int len, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + int i = 0; + + if(mode == ASYNC) + { + int c; + + while((c = getc(mode)) > 0 && i < len) + { + buf[i] = c; + i++; + } + } + + if(mode == SYNC_SLEEP) + { + if(len > rxBufferedSize()) + eventAfter(len - rxBufferedSize(), mode); + + while(i < len) + { + buf[i] = (char)getc(mode); + i++; + } + } + + return i; +} + +/** + * Reads a number of characters from the rxBuffer and returns them as a ManagedString + * + * @param len the number of characters to read. + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt to read all available characters, and return immediately + * until the buffer limit is reached + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all determine whether the given number of characters + * are available in our buffer, if not, it will set an event and sleep + * until the number of characters are avaialable. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ +ManagedString MicroBitUARTService::read(int len, MicroBitSerialMode mode) +{ + uint8_t buf[len + 1] = { 0 }; + + int ret = read(buf, len, mode); + + if(ret < 1) + return ManagedString(); + + return ManagedString((const char *)buf); +} + +/** + * Reads characters until a character matches one of the given delimeters + * + * @param delimeters the number of characters to match against + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will attempt read the immediate buffer, and look for a match. + * If there isn't, an empty ManagedString will be returned. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will first of all consider the characters in the immediate buffer, + * if a match is not found, it will block on an event, fired when a + * character is matched. + * + * @return an empty ManagedString on error, or a ManagedString containing characters + */ +ManagedString MicroBitUARTService::readUntil(ManagedString delimeters, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + int localTail = rxBufferTail; + int preservedTail = rxBufferTail; + + int foundIndex = -1; + + //ASYNC mode just iterates through our stored characters checking for any matches. + while(localTail != rxBufferHead && foundIndex == -1) + { + //we use localTail to prevent modification of the actual tail. + char c = rxBuffer[localTail]; + + for(int delimeterIterator = 0; delimeterIterator < delimeters.length(); delimeterIterator++) + if(delimeters.charAt(delimeterIterator) == c) + foundIndex = localTail; + + localTail = (localTail + 1) % rxBufferSize; + } + + //if our mode is SYNC_SLEEP, we set up an event to be fired when we see a + //matching character. + if(mode == SYNC_SLEEP && foundIndex == -1) + { + eventOn(delimeters, mode); + + foundIndex = rxBufferHead - 1; + + this->delimeters = ManagedString(); + } + + if(foundIndex >= 0) + { + //calculate our local buffer size + int localBuffSize = (preservedTail > foundIndex) ? (rxBufferSize - preservedTail) + foundIndex : foundIndex - preservedTail; + + uint8_t localBuff[localBuffSize + 1] = { 0 }; + + circularCopy(rxBuffer, rxBufferSize, localBuff, preservedTail, foundIndex); + + //plus one for the character we listened for... + rxBufferTail = (rxBufferTail + localBuffSize + 1) % rxBufferSize; + + return ManagedString((char *)localBuff, localBuffSize); + } + + return ManagedString(); +} + +/** + * Configures an event to be fired on a match with one of the delimeters. + * + * @param delimeters the characters to match received characters against e.g. ManagedString("\r\n") + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + * + * @note delimeters are matched on a per byte basis. + */ +int MicroBitUARTService::eventOn(ManagedString delimeters, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + //configure our head match... + this->delimeters = delimeters; + + //block! + if(mode == SYNC_SLEEP) + fiber_wait_for_event(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_DELIM_MATCH); + + return MICROBIT_OK; +} + +/** + * Configures an event to be fired after "len" characters. + * + * @param len the number of characters to wait before triggering the event + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + */ +int MicroBitUARTService::eventAfter(int len, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + //configure our head match... + this->rxBuffHeadMatch = (rxBufferHead + len) % rxBufferSize; + + //block! + if(mode == SYNC_SLEEP) + fiber_wait_for_event(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_HEAD_MATCH); + + return MICROBIT_OK; +} + +/** + * Determines if we have space in our rxBuff. + * + * @return 1 if we have space, 0 if we do not. + * + * @note the reason we do not wrap the super's readable() method is so that we + * don't interfere with communities that use manual calls to uBit.serial.readable() + */ +int MicroBitUARTService::isReadable() +{ + return (rxBufferTail != rxBufferHead) ? 1 : 0; +} + +/** + * @return The currently buffered number of bytes in our rxBuff. + */ +int MicroBitUARTService::rxBufferedSize() +{ + if(rxBufferTail > rxBufferHead) + return (rxBufferSize - rxBufferTail) + rxBufferHead; + + return rxBufferHead - rxBufferTail; +} + +/** + * @return The currently buffered number of bytes in our txBuff. + */ +int MicroBitUARTService::txBufferedSize() +{ + if(txBufferTail > txBufferHead) + return (txBufferSize - txBufferTail) + txBufferHead; + + return txBufferHead - txBufferTail; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MemberFunctionCallback.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,58 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a MemberFunctionCallback. + * + * C++ member functions (also known as methods) have a more complex + * representation than normal C functions. This class allows a reference to + * a C++ member function to be stored then called at a later date. + * + * This class is used extensively by the MicroBitMessageBus to deliver + * events to C++ methods. + */ + +#include "MicroBitConfig.h" +#include "MemberFunctionCallback.h" + +/** + * Calls the method reference held by this MemberFunctionCallback. + * + * @param e The event to deliver to the method + */ +void MemberFunctionCallback::fire(MicroBitEvent e) +{ + invoke(object, method, e); +} + +/** + * A comparison of two MemberFunctionCallback objects. + * + * @return true if the given MemberFunctionCallback is equivalent to this one, false otherwise. + */ +bool MemberFunctionCallback::operator==(const MemberFunctionCallback &mfc) +{ + return (object == mfc.object && (memcmp(method,mfc.method,sizeof(method))==0)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitCompat.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,101 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * This file contains functions used to maintain compatability and portability. + * It also contains constants that are used elsewhere in the DAL. + */ +#include "MicroBitConfig.h" +#include "MicroBitCompat.h" +#include "ErrorNo.h" + + +/** + * Performs an in buffer reverse of a given char array. + * + * @param s the string to reverse. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int string_reverse(char *s) +{ + //sanity check... + if(s == NULL) + return MICROBIT_INVALID_PARAMETER; + + char *j; + int c; + + j = s + strlen(s) - 1; + + while(s < j) + { + c = *s; + *s++ = *j; + *j-- = c; + } + + return MICROBIT_OK; +} + +/** + * Converts a given integer into a string representation. + * + * @param n The number to convert. + * + * @param s A pointer to the buffer where the resulting string will be stored. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int itoa(int n, char *s) +{ + int i = 0; + int positive = (n >= 0); + + if (s == NULL) + return MICROBIT_INVALID_PARAMETER; + + // Record the sign of the number, + // Ensure our working value is positive. + if (positive) + n = -n; + + // Calculate each character, starting with the LSB. + do { + s[i++] = abs(n % 10) + '0'; + } while (abs(n /= 10) > 0); + + // Add a negative sign as needed + if (!positive) + s[i++] = '-'; + + // Terminate the string. + s[i] = '\0'; + + // Flip the order. + string_reverse(s); + + return MICROBIT_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitDevice.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,375 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Compatibility / portability funcitons and constants for the MicroBit DAL. + */ +#include "MicroBitConfig.h" +#include "MicroBitButton.h" +#include "MicroBitDevice.h" +#include "MicroBitFont.h" +#include "mbed.h" +#include "ErrorNo.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 "nrf_soc.h" +#include "nrf_sdm.h" + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + +static char friendly_name[MICROBIT_NAME_LENGTH+1]; +static const uint8_t panicFace[5] = {0x1B, 0x1B,0x0,0x0E,0x11}; +static int panic_timeout = 0; +static uint32_t random_value = 0; + +/** + * Determines if a BLE stack is currently running. + * + * @return true is a bluetooth stack is operational, false otherwise. + */ +bool ble_running() +{ + uint8_t t; + sd_softdevice_is_enabled(&t); + return t==1; +} + +/** + * Derived a unique, consistent serial number of this device from internal data. + * + * @return the serial number of this device. + */ +uint32_t microbit_serial_number() +{ + return NRF_FICR->DEVICEID[1]; +} + +/** + * Derive the friendly name for this device, based on its serial number. + * + * @return the serial number of this device. + */ +char* microbit_friendly_name() +{ + const uint8_t codebook[MICROBIT_NAME_LENGTH][MICROBIT_NAME_CODE_LETTERS] = + { + {'z', 'v', 'g', 'p', 't'}, + {'u', 'o', 'i', 'e', 'a'}, + {'z', 'v', 'g', 'p', 't'}, + {'u', 'o', 'i', 'e', 'a'}, + {'z', 'v', 'g', 'p', 't'} + }; + + // We count right to left, so create a pointer to the end of the buffer. + char *name = friendly_name; + name += MICROBIT_NAME_LENGTH; + + // Terminate the string. + *name = 0; + + // Derive our name from the nrf51822's unique ID. + uint32_t n = microbit_serial_number(); + int ld = 1; + int d = MICROBIT_NAME_CODE_LETTERS; + int h; + + for (int i=0; i<MICROBIT_NAME_LENGTH; i++) + { + h = (n % d) / ld; + n -= h; + d *= MICROBIT_NAME_CODE_LETTERS; + ld *= MICROBIT_NAME_CODE_LETTERS; + *--name = codebook[i][h]; + } + + return friendly_name; +} + +/** + * Perform a hard reset of the micro:bit. + */ +void +microbit_reset() +{ + NVIC_SystemReset(); +} + +/** + * Determine the version of microbit-dal currently running. + * @return a pointer to a character buffer containing a representation of the semantic version number. + */ +const char * +microbit_dal_version() +{ + return MICROBIT_DAL_VERSION; +} + +/** + * Defines the length of time that the device will remain in a error state before resetting. + * + * @param iteration The number of times the error code will be displayed before resetting. Set to zero to remain in error state forever. + * + * @code + * microbit_panic_timeout(4); + * @endcode + */ +void microbit_panic_timeout(int iterations) +{ + panic_timeout = iterations; +} + +/** + * Disables all interrupts and user processing. + * Displays "=(" and an accompanying status code on the default display. + * @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255. + * + * @code + * microbit_panic(20); + * @endcode + */ +void microbit_panic(int statusCode) +{ + DigitalIn resetButton(MICROBIT_PIN_BUTTON_RESET); + resetButton.mode(PullUp); + + uint32_t row_mask = 0; + uint32_t col_mask = 0; + uint32_t row_reset = 0x01 << microbitMatrixMap.rowStart; + uint32_t row_data = row_reset; + uint8_t count = panic_timeout ? panic_timeout : 1; + uint8_t strobeRow = 0; + + row_mask = 0; + for (int i = microbitMatrixMap.rowStart; i < microbitMatrixMap.rowStart + microbitMatrixMap.rows; i++) + row_mask |= 0x01 << i; + + for (int i = microbitMatrixMap.columnStart; i < microbitMatrixMap.columnStart + microbitMatrixMap.columns; i++) + col_mask |= 0x01 << i; + + PortOut LEDMatrix(Port0, row_mask | col_mask); + + if(statusCode < 0 || statusCode > 255) + statusCode = 0; + + __disable_irq(); //stop ALL interrupts + + + //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_PANIC_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(count) + { + //iterate through our chars :) + for(int characterCount = 0; characterCount < MICROBIT_PANIC_ERROR_CHARS; characterCount++) + { + int outerCount = 0; + + //display the current character + while(outerCount < 500) + { + uint32_t col_data = 0; + + int i = 0; + + //if we have hit the row limit - reset both the bit mask and the row variable + if(strobeRow == microbitMatrixMap.rows) + { + strobeRow = 0; + row_data = row_reset; + } + + // Calculate the bitpattern to write. + for (i = 0; i < microbitMatrixMap.columns; i++) + { + int index = (i * microbitMatrixMap.rows) + strobeRow; + + int bitMsk = 0x10 >> microbitMatrixMap.map[index].x; //chars are right aligned but read left to right + int y = microbitMatrixMap.map[index].y; + + if(chars[characterCount][y] & bitMsk) + col_data |= (1 << i); + } + + col_data = ~col_data << microbitMatrixMap.columnStart & col_mask; + + LEDMatrix = col_data | row_data; + + //burn cycles + i = 1000; + while(i>0) + { + // Check if the reset button has been pressed. Interrupts are disabled, so the normal method can't be relied upon... + if (resetButton == 0) + microbit_reset(); + + i--; + } + + //update the bit mask and row count + row_data <<= 1; + strobeRow++; + outerCount++; + } + } + + if (panic_timeout) + count--; + } + + microbit_reset(); +} + +/** + * Generate a random number in the given range. + * We use a simple Galois LFSR random number generator here, + * as a Galois LFSR is sufficient for our applications, and much more lightweight + * than the hardware random number generator built int the processor, which takes + * a long time and uses a lot of energy. + * + * KIDS: You shouldn't use this is the real world to generte cryptographic keys though... + * have a think why not. :-) + * + * @param max the upper range to generate a number for. This number cannot be negative. + * + * @return A random, natural number between 0 and the max-1. Or MICROBIT_INVALID_VALUE if max is <= 0. + * + * @code + * microbit_random(200); //a number between 0 and 199 + * @endcode + */ +int microbit_random(int max) +{ + uint32_t m, result; + + if(max <= 0) + return MICROBIT_INVALID_PARAMETER; + + // 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 = random_value; + + rnd = ((((rnd >> 31) + ^ (rnd >> 6) + ^ (rnd >> 4) + ^ (rnd >> 2) + ^ (rnd >> 1) + ^ rnd) + & 0x0000001) + << 31 ) + | (rnd >> 1); + + random_value = rnd; + + result = ((result << 1) | (rnd & 0x00000001)); + } while(m >>= 1); + } while (result > (uint32_t)max); + + return result; +} + +/** + * Seed the random number generator (RNG). + * + * This function uses the NRF51822's in built cryptographic random number generator to seed a Galois LFSR. + * We do this as the hardware RNG is relatively high power, and is locked out by the BLE stack internally, + * with a less than optimal application interface. A Galois LFSR is sufficient for our + * applications, and much more lightweight. + */ +void microbit_seed_random() +{ + random_value = 0; + + if(ble_running()) + { + // If Bluetooth is enabled, we need to go through the Nordic software to safely do this. + uint32_t result = sd_rand_application_vector_get((uint8_t*)&random_value, sizeof(random_value)); + + // If we couldn't get the random bytes then at least make the seed non-zero. + if (result != NRF_SUCCESS) + random_value = 0xBBC5EED; + } + else + { + // Othwerwise we can access the hardware RNG directly. + + // 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); + + random_value = (random_value << 8) | ((int) NRF_RNG->VALUE); + } + + // Disable the generator to save power. + NRF_RNG->TASKS_STOP = 1; + } +} + +/** + * Seed the pseudo random number generator (RNG) using the given 32-bit value. + * This function does not use the NRF51822's in built cryptographic random number generator. + * + * @param seed The value to use as a seed. + */ +void microbit_seed_random(uint32_t seed) +{ + random_value = seed; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitFiber.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,964 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Functionality definitions for the MicroBit Fiber scheduler. + * + * This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes: + * + * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). + * 2) To provide ISR decoupling for EventModel events generated in an ISR context. + */ +#include "MicroBitConfig.h" +#include "MicroBitFiber.h" +#include "MicroBitSystemTimer.h" + +/* + * Statically allocated values used to create and destroy Fibers. + * required to be defined here to allow persistence during context switches. + */ +Fiber *currentFiber = NULL; // The context in which the current fiber is executing. +static Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing. +static Fiber *idleFiber = NULL; // the idle task - performs a power efficient sleep, and system maintenance tasks. + +/* + * Scheduler state. + */ +static Fiber *runQueue = NULL; // The list of runnable fibers. +static Fiber *sleepQueue = NULL; // The list of blocked fibers waiting on a fiber_sleep() operation. +static Fiber *waitQueue = NULL; // The list of blocked fibers waiting on an event. +static Fiber *fiberPool = NULL; // Pool of unused fibers, just waiting for a job to do. + +/* + * Scheduler wide flags + */ +static uint8_t fiber_flags = 0; + + +/* + * Fibers may perform wait/notify semantics on events. If set, these operations will be permitted on this EventModel. + */ +static EventModel *messageBus = NULL; + +// Array of components which are iterated during idle thread execution, isIdleCallbackNeeded is polled during a systemTick. +static MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS]; + +/** + * Utility function to add the currenty running fiber to the given queue. + * + * Perform a simple add at the head, to avoid complexity, + * + * Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of + * brute force searching. + * + * @param f The fiber to add to the queue + * + * @param queue The run queue to add the fiber to. + */ +void queue_fiber(Fiber *f, Fiber **queue) +{ + __disable_irq(); + + // Record which queue this fiber is on. + f->queue = queue; + + // Add the fiber to the tail of the queue. Although this involves scanning the + // list, it results in fairer scheduling. + if (*queue == NULL) + { + f->next = NULL; + f->prev = NULL; + *queue = f; + } + else + { + // Scan to the end of the queue. + // We don't maintain a tail pointer to save RAM (queues are nrmally very short). + Fiber *last = *queue; + + while (last->next != NULL) + last = last->next; + + last->next = f; + f->prev = last; + f->next = NULL; + } + + __enable_irq(); +} + +/** + * Utility function to the given fiber from whichever queue it is currently stored on. + * + * @param f the fiber to remove. + */ +void dequeue_fiber(Fiber *f) +{ + // If this fiber is already dequeued, nothing the there's nothing to do. + if (f->queue == NULL) + return; + + // Remove this fiber fromm whichever queue it is on. + __disable_irq(); + + if (f->prev != NULL) + f->prev->next = f->next; + else + *(f->queue) = f->next; + + if(f->next) + f->next->prev = f->prev; + + f->next = NULL; + f->prev = NULL; + f->queue = NULL; + + __enable_irq(); + +} + +/** + * Allocates a fiber from the fiber pool if availiable. Otherwise, allocates a new one from the heap. + */ +Fiber *getFiberContext() +{ + Fiber *f; + + __disable_irq(); + + if (fiberPool != NULL) + { + f = fiberPool; + dequeue_fiber(f); + // dequeue_fiber() exits with irqs enabled, so no need to do this again! + } + else + { + __enable_irq(); + + f = new Fiber(); + + if (f == NULL) + return NULL; + + f->stack_bottom = 0; + f->stack_top = 0; + } + + // Ensure this fiber is in suitable state for reuse. + f->flags = 0; + f->tcb.stack_base = CORTEX_M0_STACK_BASE; + + return f; +} + + +/** + * Initialises the Fiber scheduler. + * Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread. + * + * This function must be called once only from the main thread, and before any other Fiber operation. + * + * @param _messageBus An event model, used to direct the priorities of the scheduler. + */ +void scheduler_init(EventModel &_messageBus) +{ + // If we're already initialised, then nothing to do. + if (fiber_scheduler_running()) + return; + + // Store a reference to the messageBus provided. + // This parameter will be NULL if we're being run without a message bus. + messageBus = &_messageBus; + + // Create a new fiber context + currentFiber = getFiberContext(); + + // Add ourselves to the run queue. + queue_fiber(currentFiber, &runQueue); + + // Create the IDLE fiber. + // Configure the fiber to directly enter the idle task. + idleFiber = getFiberContext(); + idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; + idleFiber->tcb.LR = (uint32_t) &idle_task; + + if (messageBus) + { + // Register to receive events in the NOTIFY channel - this is used to implement wait-notify semantics + messageBus->listen(MICROBIT_ID_NOTIFY, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + messageBus->listen(MICROBIT_ID_NOTIFY_ONE, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + } + + // register a period callback to drive the scheduler and any other registered components. + new MicroBitSystemTimerCallback(scheduler_tick); + + fiber_flags |= MICROBIT_SCHEDULER_RUNNING; +} + +/** + * Determines if the fiber scheduler is operational. + * + * @return 1 if the fber scheduler is running, 0 otherwise. + */ +int fiber_scheduler_running() +{ + if (fiber_flags & MICROBIT_SCHEDULER_RUNNING) + return 1; + + return 0; +} + +/** + * The timer callback, called from interrupt context once every SYSTEM_TICK_PERIOD_MS milliseconds. + * This function checks to determine if any fibers blocked on the sleep queue need to be woken up + * and made runnable. + */ +void scheduler_tick() +{ + Fiber *f = sleepQueue; + Fiber *t; + + // Check the sleep queue, and wake up any fibers as necessary. + while (f != NULL) + { + t = f->next; + + if (system_timer_current_time() >= f->context) + { + // Wakey wakey! + dequeue_fiber(f); + queue_fiber(f,&runQueue); + } + + f = t; + } +} + +/** + * Event callback. Called from an instance of MicroBitMessageBus whenever an event is raised. + * + * This function checks to determine if any fibers blocked on the wait queue need to be woken up + * and made runnable due to the event. + * + * @param evt the event that has just been raised on an instance of MicroBitMessageBus. + */ +void scheduler_event(MicroBitEvent evt) +{ + Fiber *f = waitQueue; + Fiber *t; + int notifyOneComplete = 0; + + // This should never happen. + // It is however, safe to simply ignore any events provided, as if no messageBus if recorded, + // no fibers are permitted to block on events. + if (messageBus == NULL) + return; + + // Check the wait queue, and wake up any fibers as necessary. + while (f != NULL) + { + t = f->next; + + // extract the event data this fiber is blocked on. + uint16_t id = f->context & 0xFFFF; + uint16_t value = (f->context & 0xFFFF0000) >> 16; + + // Special case for the NOTIFY_ONE channel... + if ((evt.source == MICROBIT_ID_NOTIFY_ONE && id == MICROBIT_ID_NOTIFY) && (value == MICROBIT_EVT_ANY || value == evt.value)) + { + if (!notifyOneComplete) + { + // Wakey wakey! + dequeue_fiber(f); + queue_fiber(f,&runQueue); + notifyOneComplete = 1; + } + } + + // Normal case. + else if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value)) + { + // Wakey wakey! + dequeue_fiber(f); + queue_fiber(f,&runQueue); + } + + f = t; + } + + // Unregister this event, as we've woken up all the fibers with this match. + if (evt.source != MICROBIT_ID_NOTIFY && evt.source != MICROBIT_ID_NOTIFY_ONE) + messageBus->ignore(evt.source, evt.value, scheduler_event); +} + + +/** + * Blocks the calling thread for the given period of time. + * The calling thread will be immediateley descheduled, and placed onto a + * wait queue until the requested amount of time has elapsed. + * + * @param t The period of time to sleep, in milliseconds. + * + * @note the fiber will not be be made runnable until after the elapsed time, but there + * are no guarantees precisely when the fiber will next be scheduled. + */ +void fiber_sleep(unsigned long t) +{ + Fiber *f = currentFiber; + + // If the scheduler is not running, then simply perform a spin wait and exit. + if (!fiber_scheduler_running()) + { + wait_ms(t); + return; + } + + // Sleep is a blocking call, so if we're in a fork on block context, + // it's time to spawn a new fiber... + if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) + { + // Allocate a new fiber. This will come from the fiber pool if availiable, + // else a new one will be allocated on the heap. + forkedFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + // keep running in the context of the current thread as a best effort. + if (forkedFiber != NULL) + f = forkedFiber; + } + + // Calculate and store the time we want to wake up. + f->context = system_timer_current_time() + t; + + // Remove fiber from the run queue + dequeue_fiber(f); + + // Add fiber to the sleep queue. We maintain strict ordering here to reduce lookup times. + queue_fiber(f, &sleepQueue); + + // Finally, enter the scheduler. + schedule(); +} + +/** + * Blocks the calling thread until the specified event is raised. + * The calling thread will be immediateley descheduled, and placed onto a + * wait queue until the requested event is received. + * + * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) + * + * @param value The value of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) + * + * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. + * + * @code + * fiber_wait_for_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * @endcode + * + * @note the fiber will not be be made runnable until after the event is raised, but there + * are no guarantees precisely when the fiber will next be scheduled. + */ +int fiber_wait_for_event(uint16_t id, uint16_t value) +{ + int ret = fiber_wake_on_event(id, value); + + if(ret == MICROBIT_OK) + schedule(); + + return ret; +} + +/** + * Configures the fiber context for the current fiber to block on an event ID + * and value, but does not deschedule the fiber. + * + * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) + * + * @param value The value of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) + * + * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. + * + * @code + * fiber_wake_on_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * + * //perform some time critical operation. + * + * //deschedule the current fiber manually, waiting for the previously configured event. + * schedule(); + * @endcode + */ +int fiber_wake_on_event(uint16_t id, uint16_t value) +{ + Fiber *f = currentFiber; + + if (messageBus == NULL || !fiber_scheduler_running()) + return MICROBIT_NOT_SUPPORTED; + + // Sleep is a blocking call, so if we'r ein a fork on block context, + // it's time to spawn a new fiber... + if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) + { + // Allocate a TCB from the new fiber. This will come from the tread pool if availiable, + // else a new one will be allocated on the heap. + forkedFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + // keep running in the context of the current thread as a best effort. + if (forkedFiber != NULL) + f = forkedFiber; + } + + // Encode the event data in the context field. It's handy having a 32 bit core. :-) + f->context = value << 16 | id; + + // Remove ourselve from the run queue + dequeue_fiber(f); + + // Add ourselves to the sleep queue. We maintain strict ordering here to reduce lookup times. + queue_fiber(f, &waitQueue); + + // Register to receive this event, so we can wake up the fiber when it happens. + // Special case for teh notify channel, as we always stay registered for that. + if (id != MICROBIT_ID_NOTIFY && id != MICROBIT_ID_NOTIFY_ONE) + messageBus->listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + + return MICROBIT_OK; +} + +/** + * Executes the given function asynchronously if necessary. + * + * Fibers are often used to run event handlers, however many of these event handlers are very simple functions + * that complete very quickly, bringing unecessary RAM overhead. + * + * This function takes a snapshot of the current processor context, then attempts to optimistically call the given function directly. + * We only create an additional fiber if that function performs a block operation. + * + * @param entry_fn The function to execute. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int invoke(void (*entry_fn)(void)) +{ + // Validate our parameters. + if (entry_fn == NULL) + return MICROBIT_INVALID_PARAMETER; + + if (!fiber_scheduler_running()) + return MICROBIT_NOT_SUPPORTED; + + if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) + { + // If we attempt a fork on block whilst already in fork n block context, + // simply launch a fiber to deal with the request and we're done. + create_fiber(entry_fn); + return MICROBIT_OK; + } + + // Snapshot current context, but also update the Link Register to + // refer to our calling function. + save_register_context(¤tFiber->tcb); + + // If we're here, there are two possibilities: + // 1) We're about to attempt to execute the user code + // 2) We've already tried to execute the code, it blocked, and we've backtracked. + + // If we're returning from the user function and we forked another fiber then cleanup and exit. + if (currentFiber->flags & MICROBIT_FIBER_FLAG_PARENT) + { + currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; + currentFiber->flags &= ~MICROBIT_FIBER_FLAG_PARENT; + return MICROBIT_OK; + } + + // Otherwise, we're here for the first time. Enter FORK ON BLOCK mode, and + // execute the function directly. If the code tries to block, we detect this and + // spawn a thread to deal with it. + currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; + entry_fn(); + currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; + + // If this is is an exiting fiber that for spawned to handle a blocking call, recycle it. + // The fiber will then re-enter the scheduler, so no need for further cleanup. + if (currentFiber->flags & MICROBIT_FIBER_FLAG_CHILD) + release_fiber(); + + return MICROBIT_OK; +} + +/** + * Executes the given function asynchronously if necessary, and offers the ability to provide a parameter. + * + * Fibers are often used to run event handlers, however many of these event handlers are very simple functions + * that complete very quickly, bringing unecessary RAM. overhead + * + * This function takes a snapshot of the current fiber context, then attempt to optimistically call the given function directly. + * We only create an additional fiber if that function performs a block operation. + * + * @param entry_fn The function to execute. + * + * @param param an untyped parameter passed into the entry_fn and completion_fn. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int invoke(void (*entry_fn)(void *), void *param) +{ + // Validate our parameters. + if (entry_fn == NULL) + return MICROBIT_INVALID_PARAMETER; + + if (!fiber_scheduler_running()) + return MICROBIT_NOT_SUPPORTED; + + if (currentFiber->flags & (MICROBIT_FIBER_FLAG_FOB | MICROBIT_FIBER_FLAG_PARENT | MICROBIT_FIBER_FLAG_CHILD)) + { + // If we attempt a fork on block whilst already in a fork on block context, + // simply launch a fiber to deal with the request and we're done. + create_fiber(entry_fn, param); + return MICROBIT_OK; + } + + // Snapshot current context, but also update the Link Register to + // refer to our calling function. + save_register_context(¤tFiber->tcb); + + // If we're here, there are two possibilities: + // 1) We're about to attempt to execute the user code + // 2) We've already tried to execute the code, it blocked, and we've backtracked. + + // If we're returning from the user function and we forked another fiber then cleanup and exit. + if (currentFiber->flags & MICROBIT_FIBER_FLAG_PARENT) + { + currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; + currentFiber->flags &= ~MICROBIT_FIBER_FLAG_PARENT; + return MICROBIT_OK; + } + + // Otherwise, we're here for the first time. Enter FORK ON BLOCK mode, and + // execute the function directly. If the code tries to block, we detect this and + // spawn a thread to deal with it. + currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; + entry_fn(param); + currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; + + // If this is is an exiting fiber that for spawned to handle a blocking call, recycle it. + // The fiber will then re-enter the scheduler, so no need for further cleanup. + if (currentFiber->flags & MICROBIT_FIBER_FLAG_CHILD) + release_fiber(param); + + return MICROBIT_OK; +} + +/** + * Launches a fiber. + * + * @param ep the entry point for the fiber. + * + * @param cp the completion routine after ep has finished execution + */ +void launch_new_fiber(void (*ep)(void), void (*cp)(void)) +{ + // Execute the thread's entrypoint + ep(); + + // Execute the thread's completion routine; + cp(); + + // If we get here, then the completion routine didn't recycle the fiber... so do it anyway. :-) + release_fiber(); +} + +/** + * Launches a fiber with a parameter + * + * @param ep the entry point for the fiber. + * + * @param cp the completion routine after ep has finished execution + * + * @param pm the parameter to provide to ep and cp. + */ +void launch_new_fiber_param(void (*ep)(void *), void (*cp)(void *), void *pm) +{ + // Execute the thread's entrypoint. + ep(pm); + + // Execute the thread's completion routine. + cp(pm); + + // If we get here, then the completion routine didn't recycle the fiber... so do it anyway. :-) + release_fiber(pm); +} + +Fiber *__create_fiber(uint32_t ep, uint32_t cp, uint32_t pm, int parameterised) +{ + // Validate our parameters. + if (ep == 0 || cp == 0) + return NULL; + + // Allocate a TCB from the new fiber. This will come from the fiber pool if availiable, + // else a new one will be allocated on the heap. + Fiber *newFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + if (newFiber == NULL) + return NULL; + + newFiber->tcb.R0 = (uint32_t) ep; + newFiber->tcb.R1 = (uint32_t) cp; + newFiber->tcb.R2 = (uint32_t) pm; + + // Set the stack and assign the link register to refer to the appropriate entry point wrapper. + newFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; + newFiber->tcb.LR = parameterised ? (uint32_t) &launch_new_fiber_param : (uint32_t) &launch_new_fiber; + + // Add new fiber to the run queue. + queue_fiber(newFiber, &runQueue); + + return newFiber; +} + +/** + * Creates a new Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * + * @param completion_fn The function called when the thread completes execution of entry_fn. + * Defaults to release_fiber. + * + * @return The new Fiber, or NULL if the operation could not be completed. + */ +Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) +{ + if (!fiber_scheduler_running()) + return NULL; + + return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, 0, 0); +} + + +/** + * Creates a new parameterised Fiber, and launches it. + * + * @param entry_fn The function the new Fiber will begin execution in. + * + * @param param an untyped parameter passed into the entry_fn and completion_fn. + * + * @param completion_fn The function called when the thread completes execution of entry_fn. + * Defaults to release_fiber. + * + * @return The new Fiber, or NULL if the operation could not be completed. + */ +Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *)) +{ + if (!fiber_scheduler_running()) + return NULL; + + return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, (uint32_t) param, 1); +} + +/** + * Exit point for all fibers. + * + * Any fiber reaching the end of its entry function will return here for recycling. + */ +void release_fiber(void *) +{ + if (!fiber_scheduler_running()) + return; + + release_fiber(); +} + +/** + * Exit point for all fibers. + * + * Any fiber reaching the end of its entry function will return here for recycling. + */ +void release_fiber(void) +{ + if (!fiber_scheduler_running()) + return; + + // Remove ourselves form the runqueue. + dequeue_fiber(currentFiber); + + // Add ourselves to the list of free fibers + queue_fiber(currentFiber, &fiberPool); + + // Find something else to do! + schedule(); +} + +/** + * Resizes the stack allocation of the current fiber if necessary to hold the system stack. + * + * If the stack allocation is large enough to hold the current system stack, then this function does nothing. + * Otherwise, the the current allocation of the fiber is freed, and a larger block is allocated. + * + * @param f The fiber context to verify. + * + * @return The stack depth of the given fiber. + */ +void verify_stack_size(Fiber *f) +{ + // Ensure the stack buffer is large enough to hold the stack Reallocate if necessary. + uint32_t stackDepth; + uint32_t bufferSize; + + // Calculate the stack depth. + stackDepth = f->tcb.stack_base - ((uint32_t) __get_MSP()); + + // Calculate the size of our allocated stack buffer + bufferSize = f->stack_top - f->stack_bottom; + + // If we're too small, increase our buffer size. + if (bufferSize < stackDepth) + { + // To ease heap churn, we choose the next largest multple of 32 bytes. + bufferSize = (stackDepth + 32) & 0xffffffe0; + + // Release the old memory + if (f->stack_bottom != 0) + free((void *)f->stack_bottom); + + // Allocate a new one of the appropriate size. + f->stack_bottom = (uint32_t) malloc(bufferSize); + + // Recalculate where the top of the stack is and we're done. + f->stack_top = f->stack_bottom + bufferSize; + } +} + +/** + * Determines if any fibers are waiting to be scheduled. + * + * @return The number of fibers currently on the run queue + */ +int scheduler_runqueue_empty() +{ + return (runQueue == NULL); +} + +/** + * Calls the Fiber scheduler. + * The calling Fiber will likely be blocked, and control given to another waiting fiber. + * Call this function to yield control of the processor when you have nothing more to do. + */ +void schedule() +{ + if (!fiber_scheduler_running()) + return; + + // First, take a reference to the currently running fiber; + Fiber *oldFiber = currentFiber; + + // First, see if we're in Fork on Block context. If so, we simply want to store the full context + // of the currently running thread in a newly created fiber, and restore the context of the + // currently running fiber, back to the point where it entered FOB. + + if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) + { + // Record that the fibers have a parent/child relationship + currentFiber->flags |= MICROBIT_FIBER_FLAG_PARENT; + forkedFiber->flags |= MICROBIT_FIBER_FLAG_CHILD; + + // Define the stack base of the forked fiber to be align with the entry point of the parent fiber + forkedFiber->tcb.stack_base = currentFiber->tcb.SP; + + // Ensure the stack allocation of the new fiber is large enough + verify_stack_size(forkedFiber); + + // Store the full context of this fiber. + save_context(&forkedFiber->tcb, forkedFiber->stack_top); + + // We may now be either the newly created thread, or the one that created it. + // if the MICROBIT_FIBER_FLAG_PARENT flag is still set, we're the old thread, so + // restore the current fiber to its stored context and we're done. + if (currentFiber->flags & MICROBIT_FIBER_FLAG_PARENT) + restore_register_context(¤tFiber->tcb); + + // If we're the new thread, we must have been unblocked by the scheduler, so simply return + // and continue processing. + return; + } + + // We're in a normal scheduling context, so perform a round robin algorithm across runnable fibers. + // OK - if we've nothing to do, then run the IDLE task (power saving sleep) + if (runQueue == NULL) + currentFiber = idleFiber; + + else if (currentFiber->queue == &runQueue) + // If the current fiber is on the run queue, round robin. + currentFiber = currentFiber->next == NULL ? runQueue : currentFiber->next; + + else + // Otherwise, just pick the head of the run queue. + currentFiber = runQueue; + + if (currentFiber == idleFiber && oldFiber->flags & MICROBIT_FIBER_FLAG_DO_NOT_PAGE) + { + // Run the idle task right here using the old fiber's stack. + // Keep idling while the runqueue is empty, or there is data to process. + + // Run in the context of the original fiber, to preserve state of flags... + // as we are running on top of this fiber's stack. + currentFiber = oldFiber; + + do + { + idle(); + } + while (runQueue == NULL); + + // Switch to a non-idle fiber. + // If this fiber is the same as the old one then there'll be no switching at all. + currentFiber = runQueue; + } + + // Swap to the context of the chosen fiber, and we're done. + // Don't bother with the overhead of switching if there's only one fiber on the runqueue! + if (currentFiber != oldFiber) + { + // Special case for the idle task, as we don't maintain a stack context (just to save memory). + if (currentFiber == idleFiber) + { + idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; + idleFiber->tcb.LR = (uint32_t) &idle_task; + } + + if (oldFiber == idleFiber) + { + // Just swap in the new fiber, and discard changes to stack and register context. + swap_context(NULL, ¤tFiber->tcb, 0, currentFiber->stack_top); + } + else + { + // Ensure the stack allocation of the fiber being scheduled out is large enough + verify_stack_size(oldFiber); + + // Schedule in the new fiber. + swap_context(&oldFiber->tcb, ¤tFiber->tcb, oldFiber->stack_top, currentFiber->stack_top); + } + } +} + +/** + * Adds a component to the array of idle thread components, which are processed + * when the run queue is empty. + * + * The system timer will poll isIdleCallbackNeeded on each component to determine + * if the scheduler should schedule the idle_task imminently. + * + * @param component The component to add to the array. + * + * @return MICROBIT_OK on success or MICROBIT_NO_RESOURCES if the fiber components array is full. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * // heap allocated - otherwise it will be paged out! + * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); + * + * fiber_add_idle_component(accelerometer); + * @endcode + */ +int fiber_add_idle_component(MicroBitComponent *component) +{ + int i = 0; + + while(idleThreadComponents[i] != NULL && i < MICROBIT_IDLE_COMPONENTS) + i++; + + if(i == MICROBIT_IDLE_COMPONENTS) + return MICROBIT_NO_RESOURCES; + + idleThreadComponents[i] = component; + + return MICROBIT_OK; +} + +/** + * Remove a component from the array of idle thread components + * + * @param component The component to remove from the idle component array. + * + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previously added. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * // heap allocated - otherwise it will be paged out! + * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); + * + * fiber_add_idle_component(accelerometer); + * + * fiber_remove_idle_component(accelerometer); + * @endcode + */ +int fiber_remove_idle_component(MicroBitComponent *component) +{ + int i = 0; + + while(idleThreadComponents[i] != component && i < MICROBIT_IDLE_COMPONENTS) + i++; + + if(i == MICROBIT_IDLE_COMPONENTS) + return MICROBIT_INVALID_PARAMETER; + + idleThreadComponents[i] = NULL; + + return MICROBIT_OK; +} + +/** + * Set of tasks to perform when idle. + * Service any background tasks that are required, and attempt a power efficient sleep. + */ +void idle() +{ + // Service background tasks + for(int i = 0; i < MICROBIT_IDLE_COMPONENTS; i++) + if(idleThreadComponents[i] != NULL) + idleThreadComponents[i]->idleTick(); + + // If the above did create any useful work, enter power efficient sleep. + if(scheduler_runqueue_empty()) + __WFE(); +} + +/** + * The idle task, which is called when the runtime has no fibers that require execution. + * + * This function typically calls idle(). + */ +void idle_task() +{ + while(1) + { + idle(); + schedule(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitFont.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,99 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a MicrobitFont + * This class represents a font that can be used by the display to render text. + * + * A MicroBitFont is 5x5. + * Each Row is represented by a byte in the array. + * + * Row Format: + * ================================================================ + * | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | + * ================================================================ + * | N/A | N/A | N/A | Col 1 | Col 2 | Col 3 | Col 4 | Col 5 | + * | 0x80 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01 | + * + * Example: { 0x08, 0x08, 0x08, 0x0, 0x08 } + * + * The above will produce an exclaimation mark on the second column in form the left. + * + * We could compress further, but the complexity of decode would likely outweigh the gains. + */ + +#include "MicroBitConfig.h" +#include "MicroBitFont.h" + +const unsigned char pendolino3[475] = { +0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x4, 0x8c, 0x84, 0x84, 0x8e, 0x1c, 0x82, 0x4c, 0x90, 0x1e, 0x1e, 0xc2, 0x44, 0x92, 0x4c, 0x6, 0xca, 0x52, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0x2, 0x44, 0x8e, 0xd1, 0x2e, 0x1f, 0xe2, 0x44, 0x88, 0x10, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2e, 0xc4, 0x88, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xc, 0x92, 0x5e, 0xd2, 0x52, 0x1c, 0x92, 0x5c, 0x92, 0x5c, 0xe, 0xd0, 0x10, 0x10, 0xe, 0x1c, 0x92, 0x52, 0x52, 0x5c, 0x1e, 0xd0, 0x1c, 0x90, 0x1e, 0x1e, 0xd0, 0x1c, 0x90, 0x10, 0xe, 0xd0, 0x13, 0x71, 0x2e, 0x12, 0x52, 0x5e, 0xd2, 0x52, 0x1c, 0x88, 0x8, 0x8, 0x1c, 0x1f, 0xe2, 0x42, 0x52, 0x4c, 0x12, 0x54, 0x98, 0x14, 0x92, 0x10, 0x10, 0x10, 0x10, 0x1e, 0x11, 0x3b, 0x75, 0xb1, 0x31, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x1c, 0x92, 0x5c, 0x90, 0x10, 0xc, 0x92, 0x52, 0x4c, 0x86, 0x1c, 0x92, 0x5c, 0x92, 0x51, 0xe, 0xd0, 0xc, 0x82, 0x5c, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x12, 0x52, 0x52, 0x52, 0x4c, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x31, 0x35, 0xbb, 0x71, 0x12, 0x52, 0x4c, 0x92, 0x52, 0x11, 0x2a, 0x44, 0x84, 0x84, 0x1e, 0xc4, 0x88, 0x10, 0x1e, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0x0, 0xe, 0xd2, 0x52, 0x4f, 0x10, 0x10, 0x1c, 0x92, 0x5c, 0x0, 0xe, 0xd0, 0x10, 0xe, 0x2, 0x42, 0x4e, 0xd2, 0x4e, 0xc, 0x92, 0x5c, 0x90, 0xe, 0x6, 0xc8, 0x1c, 0x88, 0x8, 0xe, 0xd2, 0x4e, 0xc2, 0x4c, 0x10, 0x10, 0x1c, 0x92, 0x52, 0x8, 0x0, 0x8, 0x8, 0x8, 0x2, 0x40, 0x2, 0x42, 0x4c, 0x10, 0x14, 0x98, 0x14, 0x92, 0x8, 0x8, 0x8, 0x8, 0x6, 0x0, 0x1b, 0x75, 0xb1, 0x31, 0x0, 0x1c, 0x92, 0x52, 0x52, 0x0, 0xc, 0x92, 0x52, 0x4c, 0x0, 0x1c, 0x92, 0x5c, 0x90, 0x0, 0xe, 0xd2, 0x4e, 0xc2, 0x0, 0xe, 0xd0, 0x10, 0x10, 0x0, 0x6, 0xc8, 0x4, 0x98, 0x8, 0x8, 0xe, 0xc8, 0x7, 0x0, 0x12, 0x52, 0x52, 0x4f, 0x0, 0x11, 0x31, 0x2a, 0x44, 0x0, 0x11, 0x31, 0x35, 0xbb, 0x0, 0x12, 0x4c, 0x8c, 0x92, 0x0, 0x11, 0x2a, 0x44, 0x98, 0x0, 0x1e, 0xc4, 0x88, 0x1e, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60}; + + +const unsigned char* MicroBitFont::defaultFont = pendolino3; +MicroBitFont MicroBitFont::systemFont = MicroBitFont(defaultFont, MICROBIT_FONT_ASCII_END); + +/** + * Constructor. + * + * Sets the font represented by this font object. + * + * @param font A pointer to the beginning of the new font. + * + * @param asciiEnd the char value at which this font finishes. + */ +MicroBitFont::MicroBitFont(const unsigned char* characters, int asciiEnd) +{ + this->characters = characters; + this->asciiEnd = asciiEnd; +} + +/** + * Default Constructor. + * + * Configures the default font for the display to use. + */ +MicroBitFont::MicroBitFont() +{ + this->characters = defaultFont; + this->asciiEnd = MICROBIT_FONT_ASCII_END; +} + +/** + * Modifies the current system font to the given instance of MicroBitFont. + * + * @param font the new font that will be used to render characters on the display. + */ +void MicroBitFont::setSystemFont(MicroBitFont font) +{ + MicroBitFont::systemFont = font; +} + +/** + * Retreives the font object used for rendering characters on the display. + */ +MicroBitFont MicroBitFont::getSystemFont() +{ + return MicroBitFont::systemFont; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitHeapAllocator.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,405 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * A simple 32 bit block based memory allocator. This allows one or more memory segments to + * be designated as heap storage, and is designed to run in a static memory area or inside the standard C + * heap for use by the micro:bit runtime. This is required for several reasons: + * + * 1) It reduces memory fragmentation due to the high churn sometime placed on the heap + * by ManagedTypes, fibers and user code. Underlying heap implentations are often have very simplistic + * allocation pilicies and suffer from fragmentation in prolonged use - which can cause programs to + * stop working after a period of time. The algorithm implemented here is simple, but highly tolerant to + * large amounts of churn. + * + * 2) It allows us to reuse the 8K of SRAM set aside for SoftDevice as additional heap storage + * when BLE is not in use. + * + * 3) It gives a simple example of how memory allocation works! :-) + * + * P.S. This is a very simple allocator, therefore not without its weaknesses. Why don't you consider + * what these are, and consider the tradeoffs against simplicity... + * + * @note The need for this should be reviewed in the future, if a different memory allocator is + * made availiable in the mbed platform. + * + * TODO: Consider caching recently freed blocks to improve allocation time. + */ + +#include "MicroBitConfig.h" +#include "MicroBitHeapAllocator.h" +#include "MicroBitDevice.h" +#include "ErrorNo.h" + +struct HeapDefinition +{ + uint32_t *heap_start; // Physical address of the start of this heap. + uint32_t *heap_end; // Physical address of the end of this heap. +}; + +// A list of all active heap regions, and their dimensions in memory. +HeapDefinition heap[MICROBIT_MAXIMUM_HEAPS] = { }; +uint8_t heap_count = 0; + +#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) +// Diplays a usage summary about a given heap... +void microbit_heap_print(HeapDefinition &heap) +{ + uint32_t blockSize; + uint32_t *block; + int totalFreeBlock = 0; + int totalUsedBlock = 0; + int cols = 0; + + if (heap.heap_start == NULL) + { + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("--- HEAP NOT INITIALISED ---\n"); + return; + } + + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("heap_start : %p\n", heap.heap_start); + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("heap_end : %p\n", heap.heap_end); + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("heap_size : %d\n", (int)heap.heap_end - (int)heap.heap_start); + + // Disable IRQ temporarily to ensure no race conditions! + __disable_irq(); + + block = heap.heap_start; + while (block < heap.heap_end) + { + blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE; + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("[%c:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4); + if (cols++ == 20) + { + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("\n"); + cols = 0; + } + + if (*block & MICROBIT_HEAP_BLOCK_FREE) + totalFreeBlock += blockSize; + else + totalUsedBlock += blockSize; + + block += blockSize; + } + + // Enable Interrupts + __enable_irq(); + + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("\n"); + + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("mb_total_free : %d\n", totalFreeBlock*4); + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("mb_total_used : %d\n", totalUsedBlock*4); +} + + +// Diagnostics function. Displays a usage summary about all initialised heaps. +void microbit_heap_print() +{ + for (int i=0; i < heap_count; i++) + { + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("\nHEAP %d: \n", i); + microbit_heap_print(heap[i]); + } +} +#endif + +void microbit_initialise_heap(HeapDefinition &heap) +{ + // Simply mark the entire heap as free. + *heap.heap_start = ((uint32_t) heap.heap_end - (uint32_t) heap.heap_start) / MICROBIT_HEAP_BLOCK_SIZE; + *heap.heap_start |= MICROBIT_HEAP_BLOCK_FREE; +} + +/** + * Create and initialise a given memory region as for heap storage. + * After this is called, any future calls to malloc, new, free or delete may use the new heap. + * The heap allocator will attempt to allocate memory from heaps in the order that they are created. + * i.e. memory will be allocated from first heap created until it is full, then the second heap, and so on. + * + * @param start The start address of memory to use as a heap region. + * + * @param end The end address of memory to use as a heap region. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the heap could not be allocated. + * + * @note Only code that #includes MicroBitHeapAllocator.h will use this heap. This includes all micro:bit runtime + * code, and user code targetting the runtime. External code can choose to include this file, or + * simply use the standard heap. + */ +int microbit_create_heap(uint32_t start, uint32_t end) +{ + // Ensure we don't exceed the maximum number of heap segments. + if (heap_count == MICROBIT_MAXIMUM_HEAPS) + return MICROBIT_NO_RESOURCES; + + // Sanity check. Ensure range is valid, large enough and word aligned. + if (end <= start || end - start < MICROBIT_HEAP_BLOCK_SIZE*2 || end % 4 != 0 || start % 4 != 0) + return MICROBIT_INVALID_PARAMETER; + + // Disable IRQ temporarily to ensure no race conditions! + __disable_irq(); + + // Record the dimensions of this new heap + heap[heap_count].heap_start = (uint32_t *)start; + heap[heap_count].heap_end = (uint32_t *)end; + + // Initialise the heap as being completely empty and available for use. + microbit_initialise_heap(heap[heap_count]); + heap_count++; + + // Enable Interrupts + __enable_irq(); + +#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) + microbit_heap_print(); +#endif + + return MICROBIT_OK; +} + +/** + * Create and initialise a heap region within the current the heap region specified + * by the linker script. + * + * If the requested amount is not available, then the amount requested will be reduced + * automatically to fit the space available. + * + * @param ratio The proportion of the underlying heap to allocate. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the heap could not be allocated. + */ +int microbit_create_nested_heap(float ratio) +{ + uint32_t length; + void *p; + + if (ratio <= 0.0 || ratio > 1.0) + return MICROBIT_INVALID_PARAMETER; + + // Snapshot something at the top of the main heap. + p = native_malloc(sizeof(uint32_t)); + + // Estimate the size left in our heap, taking care to ensure it lands on a word boundary. + length = (uint32_t) (((float)(MICROBIT_HEAP_END - (uint32_t)p)) * ratio); + length &= 0xFFFFFFFC; + + // Release our reference pointer. + native_free(p); + p = NULL; + + // Allocate memory for our heap. + // We iteratively reduce the size of memory are allocate until it fits within available space. + while (p == NULL) + { + p = native_malloc(length); + if (p == NULL) + { + length -= 32; + if (length <= 0) + return MICROBIT_NO_RESOURCES; + } + } + + uint32_t start = (uint32_t) p; + microbit_create_heap(start, start + length); + + return MICROBIT_OK; +} + +/** + * Attempt to allocate a given amount of memory from any of our configured heap areas. + * + * @param size The amount of memory, in bytes, to allocate. + * + * @return A pointer to the allocated memory, or NULL if insufficient memory is available. + */ +void *microbit_malloc(size_t size, HeapDefinition &heap) +{ + uint32_t blockSize = 0; + uint32_t blocksNeeded = size % MICROBIT_HEAP_BLOCK_SIZE == 0 ? size / MICROBIT_HEAP_BLOCK_SIZE : size / MICROBIT_HEAP_BLOCK_SIZE + 1; + uint32_t *block; + uint32_t *next; + + if (size <= 0) + return NULL; + + // Account for the index block; + blocksNeeded++; + + // Disable IRQ temporarily to ensure no race conditions! + __disable_irq(); + + // We implement a first fit algorithm with cache to handle rapid churn... + // We also defragment free blocks as we search, to optimise this and future searches. + block = heap.heap_start; + while (block < heap.heap_end) + { + // If the block is used, then keep looking. + if(!(*block & MICROBIT_HEAP_BLOCK_FREE)) + { + block += *block; + continue; + } + + blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE; + + // We have a free block. Let's see if the subsequent ones are too. If so, we can merge... + next = block + blockSize; + + while (*next & MICROBIT_HEAP_BLOCK_FREE) + { + if (next >= heap.heap_end) + break; + + // We can merge! + blockSize += (*next & ~MICROBIT_HEAP_BLOCK_FREE); + *block = blockSize | MICROBIT_HEAP_BLOCK_FREE; + + next = block + blockSize; + } + + // We have a free block. Let's see if it's big enough. + // If so, we have a winner. + if (blockSize >= blocksNeeded) + break; + + // Otherwise, keep looking... + block += blockSize; + } + + // We're full! + if (block >= heap.heap_end) + { + __enable_irq(); + return NULL; + } + + // If we're at the end of memory or have very near match then mark the whole segment as in use. + if (blockSize <= blocksNeeded+1 || block+blocksNeeded+1 >= heap.heap_end) + { + // Just mark the whole block as used. + *block &= ~MICROBIT_HEAP_BLOCK_FREE; + } + else + { + // We need to split the block. + uint32_t *splitBlock = block + blocksNeeded; + *splitBlock = blockSize - blocksNeeded; + *splitBlock |= MICROBIT_HEAP_BLOCK_FREE; + + *block = blocksNeeded; + } + + // Enable Interrupts + __enable_irq(); + + return block+1; +} + +/** + * Release a given area of memory from the heap. + * + * @param mem The memory area to release. + */ +void *microbit_malloc(size_t size) +{ + void *p; + + // Assign the memory from the first heap created that has space. + for (int i=0; i < heap_count; i++) + { + p = microbit_malloc(size, heap[i]); + if (p != NULL) + { +#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("microbit_malloc: ALLOCATED: %d [%p]\n", size, p); +#endif + return p; + } + } + + // If we reach here, then either we have no memory available, or our heap spaces + // haven't been initialised. Either way, we try the native allocator. + + p = native_malloc(size); + if (p != NULL) + { +#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) + // Keep everything trasparent if we've not been initialised yet + if (heap_count > 0) + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p); +#endif + return p; + } + + // We're totally out of options (and memory!). +#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) + // Keep everything transparent if we've not been initialised yet + if (heap_count > 0) + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("microbit_malloc: OUT OF MEMORY [%d]\n", size); +#endif + +#if CONFIG_ENABLED(MICROBIT_PANIC_HEAP_FULL) + microbit_panic(MICROBIT_OOM); +#endif + + return NULL; +} + +/** + * Release a given area of memory from the heap. + * + * @param mem The memory area to release. + */ +void microbit_free(void *mem) +{ + uint32_t *memory = (uint32_t *)mem; + uint32_t *cb = memory-1; + +#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) + if (heap_count > 0) + if(SERIAL_DEBUG) SERIAL_DEBUG->printf("microbit_free: %p\n", mem); +#endif + // Sanity check. + if (memory == NULL) + return; + + // If this memory was created from a heap registered with us, free it. + for (int i=0; i < heap_count; i++) + { + if(memory > heap[i].heap_start && memory < heap[i].heap_end) + { + // The memory block given is part of this heap, so we can simply + // flag that this memory area is now free, and we're done. + *cb |= MICROBIT_HEAP_BLOCK_FREE; + return; + } + } + + // If we reach here, then the memory is not part of any registered heap. + // Forward it to the native heap allocator, and let nature take its course... + native_free(mem); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitListener.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,122 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * This structure defines a MicroBitListener used to invoke functions, or member + * functions if an instance of EventModel receives an event whose id and value + * match this MicroBitListener's id and value. + */ +#include "MicroBitConfig.h" +#include "MicroBitListener.h" + +/** + * Constructor. + * + * Create a new Message Bus Listener. + * + * @param id The ID of the component you want to listen to. + * + * @param value The event value you would like to listen to from that component + * + * @param handler A function pointer to call when the event is detected. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + */ +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent), uint16_t flags) +{ + this->id = id; + this->value = value; + this->cb = handler; + this->cb_arg = NULL; + this->flags = flags; + this->next = NULL; + this->evt_queue = NULL; +} + +/** + * Constructor. + * + * Create a new Message Bus Listener, this constructor accepts an additional + * parameter "arg", which is passed to the handler. + * + * @param id The ID of the component you want to listen to. + * + * @param value The event value you would like to listen to from that component + * + * @param handler A function pointer to call when the event is detected. + * + * @param arg A pointer to some data that will be given to the handler. + * + * @param flags User specified, implementation specific flags, that allow behaviour of this events listener + * to be tuned. + */ +MicroBitListener::MicroBitListener(uint16_t id, uint16_t value, void (*handler)(MicroBitEvent, void *), void* arg, uint16_t flags) +{ + this->id = id; + this->value = value; + this->cb_param = handler; + this->cb_arg = arg; + this->flags = flags | MESSAGE_BUS_LISTENER_PARAMETERISED; + this->next = NULL; + this->evt_queue = NULL; +} + +/** + * Destructor. Ensures all resources used by this listener are freed. + */ +MicroBitListener::~MicroBitListener() +{ + if(this->flags & MESSAGE_BUS_LISTENER_METHOD) + delete cb_method; +} + +/** + * Queues and event up to be processed. + * + * @param e The event to queue + */ +void MicroBitListener::queue(MicroBitEvent e) +{ + int queueDepth; + + MicroBitEventQueueItem *p = evt_queue; + + if (evt_queue == NULL) + evt_queue = new MicroBitEventQueueItem(e); + else + { + queueDepth = 1; + + while (p->next != NULL) + { + p = p->next; + queueDepth++; + } + + if (queueDepth < MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH) + p->next = new MicroBitEventQueueItem(e); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/core/MicroBitSystemTimer.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,181 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Definitions for the MicroBit system timer. + * + * This module provides: + * + * 1) a concept of global system time since power up + * 2) a simple periodic multiplexing API for the underlying mbed implementation. + * + * The latter is useful to avoid costs associated with multiple mbed Ticker instances + * in microbit-dal components, as each incurs a significant additional RAM overhead (circa 80 bytes). + */ +#include "MicroBitConfig.h" +#include "MicroBitSystemTimer.h" +#include "ErrorNo.h" + +/* + * Time since power on. Measured in milliseconds. + * When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-) + */ +static unsigned long ticks = 0; +static unsigned int tick_period = 0; + +// Array of components which are iterated during a system tick +static MicroBitComponent* systemTickComponents[MICROBIT_SYSTEM_COMPONENTS]; + +// Periodic callback interrupt +static Ticker *timer = NULL; + + +/** + * Initialises the system wide timer. + * + * This must be called before any components register to receive periodic periodic callbacks. + * + * @param timer_period The initial period between interrupts, in millseconds. + * + * @return MICROBIT_OK on success. + */ +int system_timer_init(int period) +{ + if (timer == NULL) + timer = new Ticker(); + + return system_timer_set_period(period); +} + +/** + * Reconfigures the system wide timer to the given period in milliseconds. + * + * @param period the new period of the timer in milliseconds + * + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if period < 1 + */ +int system_timer_set_period(int period) +{ + if (period < 1) + return MICROBIT_INVALID_PARAMETER; + + // If a timer is already running, ensure it is disabled before reconfiguring. + if (tick_period) + timer->detach(); + + // register a period callback to drive the scheduler and any other registered components. + tick_period = period; + timer->attach_us(system_timer_tick, period * 1000); + + return MICROBIT_OK; +} + +/** + * Accessor to obtain the current tick period in milliseconds + * + * @return the current tick period in milliseconds + */ +int system_timer_get_period() +{ + return tick_period; +} + +/** + * Determines the time since the device was powered on. + * + * @return the current time since power on in milliseconds + */ +unsigned long system_timer_current_time() +{ + return ticks; +} + +/** + * Timer callback. Called from interrupt context, once per period. + * + * Simply checks to determine if any fibers blocked on the sleep queue need to be woken up + * and made runnable. + */ +void system_timer_tick() +{ + // increment our real-time counter. + ticks += system_timer_get_period(); + + // Update any components registered for a callback + for(int i = 0; i < MICROBIT_SYSTEM_COMPONENTS; i++) + if(systemTickComponents[i] != NULL) + systemTickComponents[i]->systemTick(); +} + +/** + * Add a component to the array of system components. This component will then receive + * periodic callbacks, once every tick period. + * + * @param component The component to add. + * + * @return MICROBIT_OK on success. MICROBIT_NO_RESOURCES is returned if the component array is full. + * + * @note The callback will be in interrupt context. + */ +int system_timer_add_component(MicroBitComponent *component) +{ + int i = 0; + + // If we haven't been initialized, bring up the timer with the default period. + if (timer == NULL) + system_timer_init(SYSTEM_TICK_PERIOD_MS); + + while(systemTickComponents[i] != NULL && i < MICROBIT_SYSTEM_COMPONENTS) + i++; + + if(i == MICROBIT_SYSTEM_COMPONENTS) + return MICROBIT_NO_RESOURCES; + + systemTickComponents[i] = component; + return MICROBIT_OK; +} + +/** + * Remove a component from the array of system components. This component will no longer receive + * periodic callbacks. + * + * @param component The component to remove. + * + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previously added. + */ +int system_timer_remove_component(MicroBitComponent *component) +{ + int i = 0; + + while(systemTickComponents[i] != component && i < MICROBIT_SYSTEM_COMPONENTS) + i++; + + if(i == MICROBIT_SYSTEM_COMPONENTS) + return MICROBIT_INVALID_PARAMETER; + + systemTickComponents[i] = NULL; + + return MICROBIT_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/DynamicPwm.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,331 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for DynamicPwm. + * + * This class addresses a few issues found in the underlying libraries. + * This provides the ability for a neat, clean swap between PWM channels. + */ + +#include "MicroBitConfig.h" +#include "DynamicPwm.h" +#include "MicroBitPin.h" +#include "ErrorNo.h" + +DynamicPwm* DynamicPwm::pwms[NO_PWMS] = { NULL }; + +uint8_t DynamicPwm::lastUsed = NO_PWMS+1; //set it to out of range i.e. 4 so we know it hasn't been used yet. + +uint16_t DynamicPwm::sharedPeriod = 0; //set the shared period to an unknown state + +/** + * Reassigns an already operational PWM channel to the given pin. + * + * @param pin The desired pin to begin a PWM wave. + * + * @param oldPin The pin to stop running a PWM wave. + * + * @param channel_number The GPIOTE channel being used to drive this PWM channel + * + * TODO: Merge into mbed, at a later date. + */ +void gpiote_reinit(PinName pin, PinName oldPin, uint8_t channel_number) +{ + // Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output. + NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) + | (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) + | (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) + | (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) + | (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos); + + NRF_GPIO->OUTCLR = (1 << oldPin); + NRF_GPIO->OUTCLR = (1 << pin); + + /* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly. + If it does not, the channel output inheritance sets the proper level. */ + + NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) | + ((uint32_t)pin << GPIOTE_CONFIG_PSEL_Pos) | + ((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) | + ((uint32_t)GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos); // ((uint32_t)GPIOTE_CONFIG_OUTINIT_High << + // GPIOTE_CONFIG_OUTINIT_Pos);// + + /* Three NOPs are required to make sure configuration is written before setting tasks or getting events */ + __NOP(); + __NOP(); + __NOP(); + + NRF_TIMER2->CC[channel_number] = 0; +} + +/** + * An internal constructor used when allocating a new DynamicPwm instance. + * + * @param pin the name of the pin for the pwm to target + * + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + */ +DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence) : PwmOut(pin) +{ + this->flags = persistence; +} + +/** + * Redirects the pwm channel to point at a different pin. + * + * @param pin the desired pin to output a PWM wave. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->redirect(p0); // pwm is now produced on p0 + * @endcode + */ +void DynamicPwm::redirect(PinName pin) +{ + gpiote_reinit(pin, _pwm.pin, (uint8_t)_pwm.pwm); + this->_pwm.pin = pin; +} + +/** + * Creates a new DynamicPwm instance, or reuses an existing instance that + * has a persistence level of PWM_PERSISTENCE_TRANSIENT. + * + * @param pin the name of the pin for the pwm to target + * + * @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.) + * or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.) + * + * @return a pointer to the first available free pwm channel - or the first one that can be reallocated. If + * no channels are available, NULL is returned. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * @endcode + */ +DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence) +{ + //try to find a blank spot first + for(int i = 0; i < NO_PWMS; i++) + { + if(pwms[i] == NULL) + { + lastUsed = i; + pwms[i] = new DynamicPwm(pin, persistence); + return pwms[i]; + } + } + + //no blank spot.. try to find a transient PWM + int channelIterator = (lastUsed + 1 > NO_PWMS - 1) ? 0 : lastUsed + 1; + + while(channelIterator != lastUsed) + { + if(pwms[channelIterator]->flags & PWM_PERSISTENCE_TRANSIENT) + { + 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; +} + +/** + * Frees this DynamicPwm instance for reuse. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(); + * pwm->release(); + * @endcode + */ +void DynamicPwm::release() +{ + //free the pwm instance. + NRF_GPIOTE->CONFIG[(uint8_t) _pwm.pwm] = 0; + pwmout_free(&_pwm); + this->flags = PWM_PERSISTENCE_TRANSIENT; + + //set the pointer to this object to null... + for(int i =0; i < NO_PWMS; i++) + if(pwms[i] == this) + { + delete pwms[i]; + pwms[i] = NULL; + } +} + +/** + * 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 + * + * @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 PinName associated with this DynamicPwm instance. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * + * // returns the PinName n. + * pwm->getPinName(); + * @endcode + * + * @note This should be used to check that the DynamicPwm instance has not + * been reallocated for use in another part of a program. + */ +PinName DynamicPwm::getPinName() +{ + return _pwm.pin; +} + +/** + * Retreives the last value that has been written to this DynamicPwm instance. + * in the range 0 - 1023 inclusive. + * + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->write(0.5); + * + * // will return 512. + * 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 in microseconds. + * + * 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 in milliseconds. + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * pwm->setPeriodUs(20000); + * + * // will return 20000 + * pwm->getPeriod(); + * @endcode + */ +int DynamicPwm::getPeriod() +{ + return getPeriodUs() / 1000; +} + +/** + * Sets the period used by the WHOLE PWM module. + * + * @param period the desired period in microseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if period is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * + * // period now is 20ms + * pwm->setPeriodUs(20000); + * @endcode + * + * @note Any changes to the period will AFFECT ALL CHANNELS. + */ +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 milliseconds. + * + * @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if period is out of range + * + * Example: + * @code + * DynamicPwm* pwm = DynamicPwm::allocate(PinName n); + * + * // period now is 20ms + * pwm->setPeriod(20); + * @endcode + */ +int DynamicPwm::setPeriod(int period) +{ + return setPeriodUs(period * 1000); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitAccelerometer.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,710 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * 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 "MicroBitConfig.h" +#include "MicroBitAccelerometer.h" +#include "ErrorNo.h" +#include "MicroBitConfig.h" +#include "MicroBitEvent.h" +#include "MicroBitCompat.h" +#include "MicroBitFiber.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. + */ +int MicroBitAccelerometer::configure() +{ + const MMA8653SampleRangeConfig *actualSampleRange; + const MMA8653SampleRateConfig *actualSampleRate; + int result; + + // First find the nearest sample rate to that specified. + actualSampleRate = &MMA8653SampleRate[MMA8653_SAMPLE_RATES-1]; + for (int i=MMA8653_SAMPLE_RATES-1; i>=0; i--) + { + if(MMA8653SampleRate[i].sample_period < this->samplePeriod * 1000) + break; + + actualSampleRate = &MMA8653SampleRate[i]; + } + + // Now find the nearest sample range to that specified. + actualSampleRange = &MMA8653SampleRange[MMA8653_SAMPLE_RANGES-1]; + for (int i=MMA8653_SAMPLE_RANGES-1; i>=0; i--) + { + if(MMA8653SampleRange[i].sample_range < this->sampleRange) + break; + + actualSampleRange = &MMA8653SampleRange[i]; + } + + // OK, we have the correct data. Update our local state. + this->samplePeriod = actualSampleRate->sample_period / 1000; + this->sampleRange = actualSampleRange->sample_range; + + // 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) + 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) + return MICROBIT_I2C_ERROR; + + // Enable the INT1 interrupt pin. + result = writeCommand(MMA8653_CTRL_REG4, 0x01); + 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) + return MICROBIT_I2C_ERROR; + + // Configure for the selected g range. + result = writeCommand(MMA8653_XYZ_DATA_CFG, actualSampleRange->xyz_data_cfg); + 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) + 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. + */ +int MicroBitAccelerometer::writeCommand(uint8_t reg, uint8_t value) +{ + uint8_t command[2]; + command[0] = reg; + command[1] = value; + + return i2c.write(address, (const char *)command, 2); +} + +/** + * Issues a read command, copying data 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; + + if (buffer == NULL || length <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + result = i2c.write(address, (const char *)®, 1, true); + if (result !=0) + return MICROBIT_I2C_ERROR; + + result = i2c.read(address, (char *)buffer, length); + if (result !=0) + return MICROBIT_I2C_ERROR; + + return MICROBIT_OK; +} + +/** + * Constructor. + * Create a software abstraction of an accelerometer. + * + * @param _i2c an instance of MicroBitI2C used to communicate with the onboard accelerometer. + * + * @param address the default I2C address of the accelerometer. Defaults to: MMA8653_DEFAULT_ADDR. + * + * @param id the unique EventModel id of this component. Defaults to: MICROBIT_ID_ACCELEROMETER + * + * @code + * MicroBitI2C i2c = MicroBitI2C(I2C_SDA0, I2C_SCL0); + * + * MicroBitAccelerometer accelerometer = MicroBitAccelerometer(i2c); + * @endcode + */ +MicroBitAccelerometer::MicroBitAccelerometer(MicroBitI2C& _i2c, uint16_t address, uint16_t id) : sample(), int1(MICROBIT_PIN_ACCEL_DATA_READY), i2c(_i2c) +{ + // 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) + status |= MICROBIT_COMPONENT_RUNNING; +} + +/** + * Attempts to read the 8 bit ID from the accelerometer, this can be used for + * validation purposes. + * + * @return the 8 bit ID returned by the accelerometer, or MICROBIT_I2C_ERROR if the request fails. + * + * @code + * accelerometer.whoAmI(); + * @endcode + */ +int MicroBitAccelerometer::whoAmI() +{ + uint8_t data; + int result; + + result = readCommand(MMA8653_WHOAMI, &data, 1); + if (result !=0) + return MICROBIT_I2C_ERROR; + + return (int)data; +} + +/** + * Reads the acceleration data from the accelerometer, and stores it in our buffer. + * This only happens if the accelerometer indicates that it has new data via int1. + * + * On first use, this member function will attempt to add this component to the + * list of fiber components in order to constantly update the values stored + * by this object. + * + * This technique is called lazy instantiation, and it means that we do not + * obtain the overhead from non-chalantly adding this component to fiber components. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if the read request fails. + */ +int MicroBitAccelerometer::updateSample() +{ + if(!(status & MICROBIT_ACCEL_ADDED_TO_IDLE)) + { + fiber_add_idle_component(this); + status |= MICROBIT_ACCEL_ADDED_TO_IDLE; + } + + // Poll interrupt line from accelerometer. + // n.b. Default is Active LO. Interrupt is cleared in data read. + if(!int1) + { + int8_t data[6]; + int result; + + result = readCommand(MMA8653_OUT_X_MSB, (uint8_t *)data, 6); + if (result !=0) + return MICROBIT_I2C_ERROR; + + // read MSB values... + 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; + +#if CONFIG_ENABLED(USE_ACCEL_LSB) + // Add in LSB values. + sample.x += (data[1] / 64); + sample.y += (data[3] / 64); + sample.z += (data[5] / 64); +#endif + + // Scale into millig (approx!) + sample.x *= this->sampleRange; + 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; +}; + +/** + * A service function. + * It 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() +{ + updateSample(); + + // 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 a 'best guess' posture of the device based on instantaneous data. + * + * This makes no use of historic data, and forms this input to the filter implemented in updateGesture(). + * + * @return A 'best guess' of the current posture of the device, based on instanataneous 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; +} + +/** + * Updates the basic gesture recognizer. This performs instantaneous pose recognition, and also some low pass filtering to promote + * stability. + */ +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). + * + * @param period the requested time between samples, in milliseconds. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the request fails. + * + * @code + * // sample rate is now 20 ms. + * accelerometer.setPeriod(20); + * @endcode + * + * @note The requested rate may not be possible on the hardware. In this case, the + * nearest lower rate is chosen. + */ +int MicroBitAccelerometer::setPeriod(int period) +{ + this->samplePeriod = period; + return this->configure(); +} + +/** + * Reads the currently configured sample rate of the accelerometer. + * + * @return The time between samples, in milliseconds. + */ +int MicroBitAccelerometer::getPeriod() +{ + return (int)samplePeriod; +} + +/** + * Attempts to set the sample range of the accelerometer to the specified value (in g). + * + * @param range The requested sample range of samples, in g. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR is the request fails. + * + * @code + * // the sample range of the accelerometer is now 8G. + * accelerometer.setRange(8); + * @endcode + * + * @note The requested range may not be possible on the hardware. In this case, the + * nearest lower range is chosen. + */ +int MicroBitAccelerometer::setRange(int range) +{ + this->sampleRange = range; + return this->configure(); +} + +/** + * Reads the currently configured sample range of the accelerometer. + * + * @return The sample range, in g. + */ +int MicroBitAccelerometer::getRange() +{ + return (int)sampleRange; +} + +/** + * Reads the value of the X axis from the latest update retrieved from the accelerometer. + * + * @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. + * + * @code + * accelerometer.getX(); + * @endcode + */ +int MicroBitAccelerometer::getX(MicroBitCoordinateSystem system) +{ + updateSample(); + + switch (system) + { + case SIMPLE_CARTESIAN: + return -sample.x; + + case NORTH_EAST_DOWN: + return sample.y; + + case RAW: + default: + return sample.x; + } +} + +/** + * Reads the value of the Y axis from the latest update retrieved from the accelerometer. + * + * @return The force measured in the Y axis, in milli-g. + * + * @code + * accelerometer.getY(); + * @endcode + */ +int MicroBitAccelerometer::getY(MicroBitCoordinateSystem system) +{ + updateSample(); + + switch (system) + { + case SIMPLE_CARTESIAN: + return -sample.y; + + case NORTH_EAST_DOWN: + return -sample.x; + + case RAW: + default: + return sample.y; + } +} + +/** + * Reads the value of the Z axis from the latest update retrieved from the accelerometer. + * + * @return The force measured in the Z axis, in milli-g. + * + * @code + * accelerometer.getZ(); + * @endcode + */ +int MicroBitAccelerometer::getZ(MicroBitCoordinateSystem system) +{ + updateSample(); + + switch (system) + { + case NORTH_EAST_DOWN: + return -sample.z; + + case SIMPLE_CARTESIAN: + case RAW: + default: + return sample.z; + } +} + +/** + * Provides a rotation compensated pitch of the device, based on the latest update retrieved from the accelerometer. + * + * @return The pitch of the device, in degrees. + * + * @code + * accelerometer.getPitch(); + * @endcode + */ +int MicroBitAccelerometer::getPitch() +{ + return (int) ((360*getPitchRadians()) / (2*PI)); +} + +/** + * Provides a rotation compensated pitch of the device, based on the latest update retrieved from the accelerometer. + * + * @return The pitch of the device, in radians. + * + * @code + * accelerometer.getPitchRadians(); + * @endcode + */ +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 retrieved from the accelerometer. + * + * @return The roll of the device, in degrees. + * + * @code + * accelerometer.getRoll(); + * @endcode + */ +int MicroBitAccelerometer::getRoll() +{ + return (int) ((360*getRollRadians()) / (2*PI)); +} + +/** + * Provides a rotation compensated roll of the device, based on the latest update retrieved from the accelerometer. + * + * @return The roll of the device, in radians. + * + * @code + * accelerometer.getRollRadians(); + * @endcode + */ +float MicroBitAccelerometer::getRollRadians() +{ + if (!(status & MICROBIT_ACCEL_PITCH_ROLL_VALID)) + recalculatePitchRoll(); + + return roll; +} + +/** + * Recalculate roll and pitch values for the current sample. + * + * @note 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; +} + +/** + * Retrieves the last recorded gesture. + * + * @return The last gesture that was detected. + * + * Example: + * @code + * MicroBitDisplay display; + * + * if (accelerometer.getGesture() == SHAKE) + * display.scroll("SHAKE!"); + * @endcode + */ +BasicGesture MicroBitAccelerometer::getGesture() +{ + return lastGesture; +} + +/** + * A periodic callback invoked by the fiber scheduler idle thread. + * + * Internally calls updateSample(). + */ +void MicroBitAccelerometer::idleTick() +{ + updateSample(); +} + +/** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + * + * We check if any data is ready for reading by checking the interrupt flag on the accelerometer. + */ +int MicroBitAccelerometer::isIdleCallbackNeeded() +{ + return !int1; +} + +/** + * Destructor for MicroBitAccelerometer, where we deregister from the array of fiber components. + */ +MicroBitAccelerometer::~MicroBitAccelerometer() +{ + fiber_remove_idle_component(this); +} + +const MMA8653SampleRangeConfig MMA8653SampleRange[MMA8653_SAMPLE_RANGES] = { + {2, 0}, + {4, 1}, + {8, 2} +}; + +const MMA8653SampleRateConfig MMA8653SampleRate[MMA8653_SAMPLE_RATES] = { + {1250, 0x00}, + {2500, 0x08}, + {5000, 0x10}, + {10000, 0x18}, + {20000, 0x20}, + {80000, 0x28}, + {160000, 0x30}, + {640000, 0x38} +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitButton.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,162 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitButton.h" +#include "MicroBitSystemTimer.h" + +/** + * Constructor. + * + * Create a software representation of a button. + * + * @param name the physical pin on the processor that should be used as input. + * + * @param id the ID of the new MicroBitButton object. + * + * @param eventConfiguration Configures the events that will be generated by this MicroBitButton instance. + * Defaults to MICROBIT_BUTTON_ALL_EVENTS. + * + * @param mode the configuration of internal pullups/pulldowns, as defined in the mbed PinMode class. PullNone by default. + * + * @code + * buttonA(MICROBIT_PIN_BUTTON_A, MICROBIT_ID_BUTTON_A); + * @endcode + */ +MicroBitButton::MicroBitButton(PinName name, uint16_t id, MicroBitButtonEventConfiguration eventConfiguration, PinMode mode) : pin(name, mode) +{ + this->id = id; + this->name = name; + this->eventConfiguration = eventConfiguration; + this->downStartTime = 0; + this->sigma = 0; + system_timer_add_component(this); +} + +/** + * Changes the event configuration used by this button to the given MicroBitButtonEventConfiguration. + * + * All subsequent events generated by this button will then be informed by this configuraiton. + * + * @param config The new configuration for this button. Legal values are MICROBIT_BUTTON_ALL_EVENTS or MICROBIT_BUTTON_SIMPLE_EVENTS. + * + * Example: + * @code + * // Configure a button to generate all possible events. + * buttonA.setEventConfiguration(MICROBIT_BUTTON_ALL_EVENTS); + * + * // Configure a button to suppress MICROBIT_BUTTON_EVT_CLICK and MICROBIT_BUTTON_EVT_LONG_CLICK events. + * buttonA.setEventConfiguration(MICROBIT_BUTTON_SIMPLE_EVENTS); + * @endcode + */ +void MicroBitButton::setEventConfiguration(MicroBitButtonEventConfiguration config) +{ + this->eventConfiguration = config; +} + +/** + * periodic callback from MicroBit system timer. + * + * Check for state change for this button, and fires various events on a state change. + */ +void MicroBitButton::systemTick() +{ + // + // If the pin is pulled low (touched), increment our culumative counter. + // otherwise, decrement it. We're essentially building a lazy follower here. + // This makes the output debounced for buttons, and desensitizes touch sensors + // (particularly in environments where there is mains noise!) + // + if(!pin) + { + if (sigma < MICROBIT_BUTTON_SIGMA_MAX) + sigma++; + } + else + { + if (sigma > MICROBIT_BUTTON_SIGMA_MIN) + sigma--; + } + + // Check to see if we have off->on state change. + if(sigma > MICROBIT_BUTTON_SIGMA_THRESH_HI && !(status & MICROBIT_BUTTON_STATE)) + { + // Record we have a state change, and raise an event. + status |= MICROBIT_BUTTON_STATE; + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN); + + //Record the time the button was pressed. + downStartTime = system_timer_current_time(); + } + + // 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((system_timer_current_time() - downStartTime) >= MICROBIT_BUTTON_LONG_CLICK_TIME) + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_LONG_CLICK); + else + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); + } + } + + //if button is pressed and the hold triggered event state is not triggered AND we are greater than the button debounce value + if((status & MICROBIT_BUTTON_STATE) && !(status & MICROBIT_BUTTON_STATE_HOLD_TRIGGERED) && (system_timer_current_time() - downStartTime) >= MICROBIT_BUTTON_HOLD_TIME) + { + //set the hold triggered event flag + status |= MICROBIT_BUTTON_STATE_HOLD_TRIGGERED; + + //fire hold event + MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_HOLD); + } +} + +/** + * Tests if this Button is currently pressed. + * + * @code + * if(buttonA.isPressed()) + * display.scroll("Pressed!"); + * @endcode + * + * @return 1 if this button is pressed, 0 otherwise. + */ +int MicroBitButton::isPressed() +{ + return status & MICROBIT_BUTTON_STATE ? 1 : 0; +} + +/** + * Destructor for MicroBitButton, where we deregister this instance from the array of fiber components. + */ +MicroBitButton::~MicroBitButton() +{ + system_timer_remove_component(this); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitCompass.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,778 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBit Compass. + * + * Represents an implementation of the Freescale MAG3110 I2C Magnetmometer. + * Also includes basic caching, calibration and on demand activation. + */ +#include "MicroBitConfig.h" +#include "MicroBitCompass.h" +#include "MicroBitFiber.h" +#include "ErrorNo.h" + +/** + * An initialisation member function used by the many constructors of MicroBitCompass. + * + * @param id the unique identifier for this compass instance. + * + * @param address the base address of the magnetometer on the i2c bus. + */ +void MicroBitCompass::init(uint16_t id, uint16_t address) +{ + this->id = id; + this->address = address; + + // Select 10Hz update rate, with oversampling, and enable the device. + this->samplePeriod = 100; + this->configure(); + + // Assume that we have no calibration information. + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; + + if(this->storage != NULL) + { + KeyValuePair *calibrationData = storage->get(ManagedString("compassCal")); + + if(calibrationData != NULL) + { + CompassSample storedSample = CompassSample(); + + memcpy(&storedSample, calibrationData->value, sizeof(CompassSample)); + + setCalibration(storedSample); + + delete calibrationData; + } + } + + // Indicate that we're up and running. + status |= MICROBIT_COMPONENT_RUNNING; +} + +/** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param _accelerometer an instance of the accelerometer, used for tilt compensation. + * + * @param _storage an instance of MicroBitStorage, used to persist calibration data across resets. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitAccelerometer accelerometer(i2c); + * + * MicroBitStorage storage; + * + * MicroBitCompass compass(i2c, accelerometer, storage); + * @endcode + */ +MicroBitCompass::MicroBitCompass(MicroBitI2C& _i2c, MicroBitAccelerometer& _accelerometer, MicroBitStorage& _storage, uint16_t address, uint16_t id) : + average(), + sample(), + int1(MICROBIT_PIN_COMPASS_DATA_READY), + i2c(_i2c), + accelerometer(&_accelerometer), + storage(&_storage) +{ + init(id, address); +} + +/** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param _accelerometer an instance of the accelerometer, used for tilt compensation. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitAccelerometer accelerometer(i2c); + * + * MicroBitCompass compass(i2c, accelerometer, storage); + * @endcode + */ +MicroBitCompass::MicroBitCompass(MicroBitI2C& _i2c, MicroBitAccelerometer& _accelerometer, uint16_t address, uint16_t id) : + average(), + sample(), + int1(MICROBIT_PIN_COMPASS_DATA_READY), + i2c(_i2c), + accelerometer(&_accelerometer), + storage(NULL) +{ + init(id, address); +} + +/** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param _storage an instance of MicroBitStorage, used to persist calibration data across resets. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitStorage storage; + * + * MicroBitCompass compass(i2c, storage); + * @endcode + */ +MicroBitCompass::MicroBitCompass(MicroBitI2C& _i2c, MicroBitStorage& _storage, uint16_t address, uint16_t id) : + average(), + sample(), + int1(MICROBIT_PIN_COMPASS_DATA_READY), + i2c(_i2c), + accelerometer(NULL), + storage(&_storage) +{ + init(id, address); +} + +/** + * Constructor. + * Create a software representation of an e-compass. + * + * @param _i2c an instance of i2c, which the compass is accessible from. + * + * @param address the default address for the compass register on the i2c bus. Defaults to MAG3110_DEFAULT_ADDR. + * + * @param id the ID of the new MicroBitCompass object. Defaults to MAG3110_DEFAULT_ADDR. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * MicroBitCompass compass(i2c); + * @endcode + */ +MicroBitCompass::MicroBitCompass(MicroBitI2C& _i2c, uint16_t address, uint16_t id) : + average(), + sample(), + int1(MICROBIT_PIN_COMPASS_DATA_READY), + i2c(_i2c), + accelerometer(NULL), + storage(NULL) +{ + init(id, address); +} + +/** + * 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 MicroBitCompass::writeCommand(uint8_t reg, uint8_t value) +{ + uint8_t command[2]; + command[0] = reg; + command[1] = value; + + return i2c.write(address, (const char *)command, 2); +} + +/** + * Issues a read command, copying data 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 MicroBitCompass::readCommand(uint8_t reg, uint8_t* buffer, int length) +{ + int result; + + if (buffer == NULL || length <= 0) + return MICROBIT_INVALID_PARAMETER; + + result = i2c.write(address, (const char *)®, 1, true); + if (result !=0) + return MICROBIT_I2C_ERROR; + + result = i2c.read(address, (char *)buffer, length); + if (result !=0) + return MICROBIT_I2C_ERROR; + + return MICROBIT_OK; +} + + +/** + * Issues a read of a given address, and returns the value. + * + * Blocks the calling thread until complete. + * + * @param reg The address of the 16 bit register to access. + * + * @return The register value, interpreted as a 16 but signed value, or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. + */ +int MicroBitCompass::read16(uint8_t reg) +{ + uint8_t cmd[2]; + int result; + + cmd[0] = reg; + result = i2c.write(address, (const char *)cmd, 1); + if (result !=0) + return MICROBIT_I2C_ERROR; + + cmd[0] = 0x00; + cmd[1] = 0x00; + + result = i2c.read(address, (char *)cmd, 2); + if (result !=0) + return MICROBIT_I2C_ERROR; + + return (int16_t) ((cmd[1] | (cmd[0] << 8))); //concatenate the MSB and LSB +} + +/** + * Issues a read of a given address, and returns the value. + * + * Blocks the calling thread until complete. + * + * @param reg The address of the 16 bit register to access. + * + * @return The register value, interpreted as a 8 bit unsigned value, or MICROBIT_I2C_ERROR if the magnetometer could not be accessed. + */ +int MicroBitCompass::read8(uint8_t reg) +{ + uint8_t data; + int result; + + data = 0; + result = readCommand(reg, (uint8_t*) &data, 1); + if (result != MICROBIT_OK) + return MICROBIT_I2C_ERROR; + + return data; +} + +/** + * Calculates a tilt compensated bearing of the device, using the accelerometer. + */ +int MicroBitCompass::tiltCompensatedBearing() +{ + // Precompute the tilt compensation parameters to improve readability. + float phi = accelerometer->getRollRadians(); + float theta = 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); + + float bearing = (360*atan2(z*sinPhi - y*cosPhi, x*cosTheta + y*sinTheta*sinPhi + z*sinTheta*cosPhi)) / (2*PI); + + if (bearing < 0) + bearing += 360.0; + + return (int) bearing; +} + +/** + * Calculates a non-tilt compensated bearing of the device. + */ +int MicroBitCompass::basicBearing() +{ + updateSample(); + + 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); +} + +/** + * 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. + * + * @code + * compass.heading(); + * @endcode + */ +int MicroBitCompass::heading() +{ + if(status & MICROBIT_COMPASS_STATUS_CALIBRATING) + return MICROBIT_CALIBRATION_IN_PROGRESS; + + if(!(status & MICROBIT_COMPASS_STATUS_CALIBRATED)) + calibrate(); + + if(accelerometer != NULL) + return tiltCompensatedBearing(); + + return basicBearing(); +} + +/** + * Updates the local sample, only if the compass indicates that + * data is stale. + * + * @note Can be used to trigger manual updates, if the device is running without a scheduler. + * Also called internally by all get[X,Y,Z]() member functions. + */ +int MicroBitCompass::updateSample() +{ + /** + * Adds the compass to idle, if it hasn't been added already. + * This is an optimisation so that the compass is only added on first 'use'. + */ + if(!(status & MICROBIT_COMPASS_STATUS_ADDED_TO_IDLE)) + { + fiber_add_idle_component(this); + status |= MICROBIT_COMPASS_STATUS_ADDED_TO_IDLE; + } + + // Poll interrupt line from compass (Active HI). + // Interrupt is cleared on data read of MAG_OUT_X_MSB. + if(int1) + { + 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)); + + // Indicate that a new sample is available + MicroBitEvent e(id, MICROBIT_COMPASS_EVT_DATA_UPDATE); + } + + return MICROBIT_OK; +} + +/** + * Periodic callback from MicroBit idle thread. + * + * Calls updateSample(). + */ +void MicroBitCompass::idleTick() +{ + updateSample(); +} + +/** + * Reads the value of the X axis from the latest update retrieved from the magnetometer. + * + * @param system The coordinate system to use. By default, a simple cartesian system is provided. + * + * @return The magnetic force measured in the X axis, in nano teslas. + * + * @code + * compass.getX(); + * @endcode + */ +int MicroBitCompass::getX(MicroBitCoordinateSystem system) +{ + updateSample(); + + 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 value of the Y axis from the latest update retrieved from the magnetometer. + * + * @param system The coordinate system to use. By default, a simple cartesian system is provided. + * + * @return The magnetic force measured in the Y axis, in nano teslas. + * + * @code + * compass.getY(); + * @endcode + */ +int MicroBitCompass::getY(MicroBitCoordinateSystem system) +{ + updateSample(); + + 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 value of the Z axis from the latest update retrieved from the magnetometer. + * + * @param system The coordinate system to use. By default, a simple cartesian system is provided. + * + * @return The magnetic force measured in the Z axis, in nano teslas. + * + * @code + * compass.getZ(); + * @endcode + */ +int MicroBitCompass::getZ(MicroBitCoordinateSystem system) +{ + updateSample(); + + 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 magnetometer. + * + * @return The magnetic force measured across all axis, in nano teslas. + * + * @code + * compass.getFieldStrength(); + * @endcode + */ +int MicroBitCompass::getFieldStrength() +{ + double x = getX(); + double y = getY(); + double z = getZ(); + + return (int) sqrt(x*x + y*y + z*z); +} + +/** + * Configures the compass for the 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 or MICROBIT_I2C_ERROR if the magnetometer could not be configured. + */ +int MicroBitCompass::configure() +{ + const MAG3110SampleRateConfig *actualSampleRate; + int result; + + // First, take the device offline, so it can be configured. + result = writeCommand(MAG_CTRL_REG1, 0x00); + if (result != MICROBIT_OK) + return MICROBIT_I2C_ERROR; + + // Wait for the part to enter standby mode... + while(1) + { + // Read the status of the part... + // If we can't communicate with it over I2C, pass on the error. + result = this->read8(MAG_SYSMOD); + if (result == MICROBIT_I2C_ERROR) + return MICROBIT_I2C_ERROR; + + // if the part in in standby, we're good to carry on. + if((result & 0x03) == 0) + break; + + // Perform a power efficient sleep... + fiber_sleep(100); + } + + // Find the nearest sample rate to that specified. + actualSampleRate = &MAG3110SampleRate[MAG3110_SAMPLE_RATES-1]; + for (int i=MAG3110_SAMPLE_RATES-1; i>=0; i--) + { + if(MAG3110SampleRate[i].sample_period < this->samplePeriod * 1000) + break; + + actualSampleRate = &MAG3110SampleRate[i]; + } + + // OK, we have the correct data. Update our local state. + this->samplePeriod = actualSampleRate->sample_period / 1000; + + // Enable automatic reset after each sample; + result = writeCommand(MAG_CTRL_REG2, 0xA0); + 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) + return MICROBIT_I2C_ERROR; + + return MICROBIT_OK; +} + +/** + * Attempts to set the sample rate of the compass to the specified value (in ms). + * + * @param period the requested time between samples, in milliseconds. + * + * @return MICROBIT_OK or MICROBIT_I2C_ERROR if the magnetometer could not be updated. + * + * @code + * // sample rate is now 20 ms. + * compass.setPeriod(20); + * @endcode + * + * @note The requested rate may not be possible on the hardware. In this case, the + * nearest lower rate is chosen. + */ +int MicroBitCompass::setPeriod(int period) +{ + this->samplePeriod = period; + return this->configure(); +} + +/** + * Reads the currently configured sample rate of the compass. + * + * @return The time between samples, in milliseconds. + */ +int MicroBitCompass::getPeriod() +{ + return (int)samplePeriod; +} + +/** + * Attempts to read the 8 bit ID from the magnetometer, this can be used for + * validation purposes. + * + * @return the 8 bit ID returned by the magnetometer, or MICROBIT_I2C_ERROR if the request fails. + * + * @code + * compass.whoAmI(); + * @endcode + */ +int MicroBitCompass::whoAmI() +{ + uint8_t data; + int result; + + result = readCommand(MAG_WHOAMI, &data, 1); + if (result != MICROBIT_OK) + return MICROBIT_I2C_ERROR; + + return (int)data; +} + +/** + * Reads the current die temperature of the compass. + * + * @return the temperature in degrees celsius, or MICROBIT_I2C_ERROR if the temperature reading could not be retreived + * from the accelerometer. + */ +int MicroBitCompass::readTemperature() +{ + int8_t temperature; + int result; + + result = readCommand(MAG_DIE_TEMP, (uint8_t *)&temperature, 1); + if (result != MICROBIT_OK) + return MICROBIT_I2C_ERROR; + + 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. + * + * 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 successfully. + * + * @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; + + updateSample(); + + // 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; +} + +/** + * Configure the compass to use the calibration data that is supplied to this call. + * + * 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 calibration A CompassSample containing the offsets for the x, y and z axis. + */ +void MicroBitCompass::setCalibration(CompassSample calibration) +{ + if(this->storage != NULL) + this->storage->put(ManagedString("compassCal"), (uint8_t *)&calibration); + + average = calibration; + status |= MICROBIT_COMPASS_STATUS_CALIBRATED; +} + +/** + * Provides the calibration data currently in use by the compass. + * + * More specifically, the x, y and z zero offsets of the compass. + * + * @return calibration A CompassSample containing the offsets for the x, y and z axis. + */ +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; +} + +/** + * Returns 0 or 1. 1 indicates that the compass is calibrating, zero means the compass is not currently calibrating. + */ +int MicroBitCompass::isCalibrating() +{ + return status & MICROBIT_COMPASS_STATUS_CALIBRATING; +} + +/** + * Clears the calibration held in persistent storage, and sets the calibrated flag to zero. + */ +void MicroBitCompass::clearCalibration() +{ + status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED; +} + +/** + * Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read. + */ +int MicroBitCompass::isIdleCallbackNeeded() +{ + // The MAG3110 raises an interrupt line when data is ready, which we sample here. + // The interrupt line is active HI, so simply return the state of the pin. + return int1; +} + +/** + * Destructor for MicroBitCompass, where we deregister this instance from the array of fiber components. + */ +MicroBitCompass::~MicroBitCompass() +{ + fiber_remove_idle_component(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 +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitCompassCalibrator.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,188 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitCompassCalibrator.h" +#include "EventModel.h" +#include "Matrix4.h" + +/** + * Constructor. + * + * Create an object capable of calibrating the compass. + * + * The algorithm uses an accelerometer to ensure that a broad range of sample data has been gathered + * from the compass module, then performs a least mean squares optimisation of the + * results to determine the calibration data for the compass. + * + * The LED matrix display is used to provide feedback to the user on the gestures required. + * + * @param compass The compass instance to calibrate. + * + * @param accelerometer The accelerometer to gather contextual data from. + * + * @param display The LED matrix to display user feedback on. + */ +MicroBitCompassCalibrator::MicroBitCompassCalibrator(MicroBitCompass& _compass, MicroBitAccelerometer& _accelerometer, MicroBitDisplay& _display) : compass(_compass), accelerometer(_accelerometer), display(_display) +{ + if (EventModel::defaultEventBus) + EventModel::defaultEventBus->listen(MICROBIT_ID_COMPASS, MICROBIT_COMPASS_EVT_CALIBRATE, this, &MicroBitCompassCalibrator::calibrate, MESSAGE_BUS_LISTENER_IMMEDIATE); +} + +/** + * 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 MicroBitCompassCalibrator::calibrate(MicroBitEvent) +{ + struct Point + { + uint8_t x; + uint8_t y; + uint8_t on; + }; + + const int PERIMETER_POINTS = 12; + const int PIXEL1_THRESHOLD = 200; + const int PIXEL2_THRESHOLD = 800; + + wait_ms(100); + + 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++) + wait_ms(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 = accelerometer.getX(); + int y = accelerometer.getY(); + + // Wait a little whie for the button state to stabilise (one scheduler tick). + wait_ms(10); + + // 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; i<PERIMETER_POINTS; i++) + if (perimeter[i].on) + img.setPixelValue(perimeter[i].x, perimeter[i].y, 255); + + // Update the pixel at the users position. + img.setPixelValue(cursor.x, cursor.y, 255); + + // Update the buffer to the screen. + display.image.paste(img,0,0,0); + + // test if we need to update the state at the users position. + for (int i=0; i<PERIMETER_POINTS; i++) + { + if (cursor.x == perimeter[i].x && cursor.y == perimeter[i].y && !perimeter[i].on) + { + // Record the sample data for later processing... + X.set(samples, 0, compass.getX(RAW)); + X.set(samples, 1, compass.getY(RAW)); + X.set(samples, 2, compass.getZ(RAW)); + X.set(samples, 3, 1); + + // Record that this pixel has been visited. + perimeter[i].on = 1; + samples++; + } + } + + wait_ms(100); + } + + // We have enough sample data to make a fairly accurate calibration. + // We use a Least Mean Squares approximation, as detailed in Freescale application note AN2426. + + // Firstly, calculate the square of each sample. + Matrix4 Y(X.height(), 1); + for (int i = 0; i < X.height(); i++) + { + float v = X.get(i, 0)*X.get(i, 0) + X.get(i, 1)*X.get(i, 1) + X.get(i, 2)*X.get(i, 2); + Y.set(i, 0, v); + } + + // Now perform a Least Squares Approximation. + Matrix4 Alpha = X.multiplyT(X).invert(); + Matrix4 Gamma = X.multiplyT(Y); + Matrix4 Beta = Alpha.multiply(Gamma); + + // The result contains the approximate zero point of each axis, but doubled. + // Halve each sample, and record this as the compass calibration data. + CompassSample cal ((int)(Beta.get(0,0) / 2), (int)(Beta.get(1,0) / 2), (int)(Beta.get(2,0) / 2)); + compass.setCalibration(cal); + + // Show a smiley to indicate that we're done, and continue on with the user program. + display.clear(); + display.printAsync(smiley, 0, 0, 0, 1500); + wait_ms(1000); + display.clear(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitDisplay.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,1218 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBitDisplay. + * + * A MicroBitDisplay represents the LED matrix array on the micro:bit. + */ +#include "MicroBitConfig.h" +#include "MicroBitDisplay.h" +#include "MicroBitSystemTimer.h" +#include "MicroBitFiber.h" +#include "ErrorNo.h" +#include "NotifyEvents.h" + +const int greyScaleTimings[MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH] = {1, 23, 70, 163, 351, 726, 1476, 2976}; + +/** + * Constructor. + * + * Create a software representation the micro:bit's 5x5 LED matrix. + * The display is initially blank. + * + * @param id The id the display should use when sending events on the MessageBus. Defaults to MICROBIT_ID_DISPLAY. + * + * @param map The mapping information that relates pin inputs/outputs to physical screen coordinates. + * Defaults to microbitMatrixMap, defined in MicroBitMatrixMaps.h. + * + * @code + * MicroBitDisplay display; + * @endcode + */ +MicroBitDisplay::MicroBitDisplay(uint16_t id, const MatrixMap &map) : + matrixMap(map), + image(map.width*2,map.height) +{ + uint32_t row_mask; + + this->id = id; + this->width = map.width; + this->height = map.height; + this->rotation = MICROBIT_DISPLAY_ROTATION_0; + + row_mask = 0; + col_mask = 0; + strobeRow = 0; + row_mask = 0; + + for (int i = matrixMap.rowStart; i < matrixMap.rowStart + matrixMap.rows; i++) + row_mask |= 0x01 << i; + + for (int i = matrixMap.columnStart; i < matrixMap.columnStart + matrixMap.columns; i++) + col_mask |= 0x01 << i; + + LEDMatrix = new PortOut(Port0, row_mask | col_mask); + + this->greyscaleBitMsk = 0x01; + this->timingCount = 0; + this->setBrightness(MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS); + this->mode = DISPLAY_MODE_BLACK_AND_WHITE; + this->animationMode = ANIMATION_MODE_NONE; + this->lightSensor = NULL; + + system_timer_add_component(this); + + status |= MICROBIT_COMPONENT_RUNNING; +} + +/** + * Internal frame update method, used to strobe the display. + * + * TODO: Write a more efficient, complementary variation of this method for the case where + * MICROBIT_DISPLAY_ROW_COUNT > MICROBIT_DISPLAY_COLUMN_COUNT. + */ +void MicroBitDisplay::systemTick() +{ + if(!(status & MICROBIT_COMPONENT_RUNNING)) + return; + + if(mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + renderWithLightSense(); + return; + } + + // Move on to the next row. + strobeRow++; + + //reset the row counts and bit mask when we have hit the max. + if(strobeRow == matrixMap.rows) + strobeRow = 0; + + if(mode == DISPLAY_MODE_BLACK_AND_WHITE) + render(); + + if(mode == DISPLAY_MODE_GREYSCALE) + { + greyscaleBitMsk = 0x01; + timingCount = 0; + renderGreyscale(); + } + + // Update text and image animations if we need to. + this->animationUpdate(); +} + +void MicroBitDisplay::renderFinish() +{ + *LEDMatrix = 0; +} + +void MicroBitDisplay::render() +{ + // Simple optimisation. + // If display is at zero brightness, there's nothing to do. + if(brightness == 0) + return; + + // Calculate the bitpattern to write. + uint32_t row_data = 0x01 << (microbitMatrixMap.rowStart + strobeRow); + uint32_t col_data = 0; + + for (int i = 0; i < matrixMap.columns; i++) + { + int index = (i * matrixMap.rows) + strobeRow; + + int x = matrixMap.map[index].x; + int y = matrixMap.map[index].y; + int t = x; + + if(rotation == MICROBIT_DISPLAY_ROTATION_90) + { + x = width - 1 - y; + y = t; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_180) + { + x = width - 1 - x; + y = height - 1 - y; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_270) + { + x = y; + y = height - 1 - t; + } + + if(image.getBitmap()[y*(width*2)+x]) + col_data |= (1 << i); + } + + // Invert column bits (as we're sinking not sourcing power), and mask off any unused bits. + col_data = ~col_data << matrixMap.columnStart & col_mask; + + // Write the new bit pattern + *LEDMatrix = col_data | row_data; + + //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_us(this, &MicroBitDisplay::renderFinish, (((brightness * 950) / (MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * system_timer_get_period())); + + //this will take around 23us to execute + if(brightness <= MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) + renderFinish(); +} + +void MicroBitDisplay::renderWithLightSense() +{ + //reset the row counts and bit mask when we have hit the max. + if(strobeRow == matrixMap.rows + 1) + { + MicroBitEvent(id, MICROBIT_DISPLAY_EVT_LIGHT_SENSE); + strobeRow = 0; + } + else + { + render(); + this->animationUpdate(); + + // Move on to the next row. + strobeRow++; + } + +} + +void MicroBitDisplay::renderGreyscale() +{ + uint32_t row_data = 0x01 << (microbitMatrixMap.rowStart + strobeRow); + uint32_t col_data = 0; + + // Calculate the bitpattern to write. + for (int i = 0; i < matrixMap.columns; i++) + { + int index = (i * matrixMap.rows) + strobeRow; + + int x = matrixMap.map[index].x; + int y = matrixMap.map[index].y; + int t = x; + + if(rotation == MICROBIT_DISPLAY_ROTATION_90) + { + x = width - 1 - y; + y = t; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_180) + { + x = width - 1 - x; + y = height - 1 - y; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_270) + { + x = y; + y = height - 1 - t; + } + + if(min(image.getBitmap()[y * (width * 2) + x],brightness) & greyscaleBitMsk) + col_data |= (1 << i); + } + + // Invert column bits (as we're sinking not sourcing power), and mask off any unused bits. + col_data = ~col_data << matrixMap.columnStart & col_mask; + + // Write the new bit pattern + *LEDMatrix = col_data | row_data; + + if(timingCount > MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH-1) + return; + + greyscaleBitMsk <<= 1; + + if(timingCount < 3) + { + wait_us(greyScaleTimings[timingCount++]); + renderGreyscale(); + return; + } + renderTimer.attach_us(this,&MicroBitDisplay::renderGreyscale, greyScaleTimings[timingCount++]); +} + +/** + * Periodic callback, that we use to perform any animations we have running. + */ +void +MicroBitDisplay::animationUpdate() +{ + // If there's no ongoing animation, then nothing to do. + if (animationMode == ANIMATION_MODE_NONE) + return; + + animationTick += system_timer_get_period(); + + 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; + this->sendAnimationCompleteEvent(); + } + } +} + +/** + * Broadcasts an event onto the defult EventModel indicating that the + * current animation has completed. + */ +void MicroBitDisplay::sendAnimationCompleteEvent() +{ + // Signal that we've completed an animation. + MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + + // Wake up a fiber that was blocked on the animation (if any). + MicroBitEvent(MICROBIT_ID_NOTIFY_ONE, MICROBIT_DISPLAY_EVT_FREE); +} + +/** + * Internal scrollText update method. + * Shift the screen image by one pixel to the left. If necessary, paste in the next char. + */ +void MicroBitDisplay::updateScrollText() +{ + image.shiftLeft(1); + scrollingPosition++; + + if (scrollingPosition == width + MICROBIT_DISPLAY_SPACING) + { + scrollingPosition = 0; + + image.print(scrollingChar < scrollingText.length() ? scrollingText.charAt(scrollingChar) : ' ',width,0); + + if (scrollingChar > scrollingText.length()) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + return; + } + scrollingChar++; + } +} + +/** + * Internal printText update method. + * Paste the next character in the string. + */ +void MicroBitDisplay::updatePrintText() +{ + image.print(printingChar < printingText.length() ? printingText.charAt(printingChar) : ' ',0,0); + + if (printingChar > printingText.length()) + { + animationMode = ANIMATION_MODE_NONE; + + this->sendAnimationCompleteEvent(); + return; + } + + printingChar++; +} + +/** + * Internal scrollImage update method. + * Paste the stored bitmap at the appropriate point. + */ +void MicroBitDisplay::updateScrollImage() +{ + image.clear(); + + if (((image.paste(scrollingImage, scrollingImagePosition, 0, 0) == 0) && scrollingImageRendered) || scrollingImageStride == 0) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + + return; + } + + scrollingImagePosition += scrollingImageStride; + scrollingImageRendered = true; +} + +/** + * Internal animateImage update method. + * Paste the stored bitmap at the appropriate point and stop on the last frame. + */ +void MicroBitDisplay::updateAnimateImage() +{ + //wait until we have rendered the last position to give a continuous animation. + if (scrollingImagePosition <= -scrollingImage.getWidth() + (MICROBIT_DISPLAY_WIDTH + scrollingImageStride) && scrollingImageRendered) + { + animationMode = ANIMATION_MODE_NONE; + this->clear(); + this->sendAnimationCompleteEvent(); + return; + } + + if(scrollingImagePosition > 0) + image.shiftLeft(-scrollingImageStride); + + image.paste(scrollingImage, scrollingImagePosition, 0, 0); + + if(scrollingImageStride == 0) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + } + + scrollingImageRendered = true; + + scrollingImagePosition += scrollingImageStride; +} + +/** + * Resets the current given animation. + */ +void MicroBitDisplay::stopAnimation() +{ + // Reset any ongoing animation. + if (animationMode != ANIMATION_MODE_NONE) + { + animationMode = ANIMATION_MODE_NONE; + + // Indicate that we've completed an animation. + MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + + // Wake up aall fibers that may blocked on the animation (if any). + MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); + } + + // Clear the display and setup the animation timers. + this->image.clear(); +} + +/** + * Blocks the current fiber until the display is available (i.e. does not effect is being displayed). + * Animations are queued until their time to display. + */ +void MicroBitDisplay::waitForFreeDisplay() +{ + // If there's an ongoing animation, wait for our turn to display. + if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + fiber_wait_for_event(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); +} + +/** + * Blocks the current fiber until the current animation has finished. + * If the scheduler is not running, this call will essentially perform a spinning wait. + */ +void MicroBitDisplay::fiberWait() +{ + if (fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE) == MICROBIT_NOT_SUPPORTED) + while(animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + __WFE(); +} + +/** + * 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, + * or until the Displays next use. + * + * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync('p'); + * display.printAsync('p',100); + * @endcode + */ +int MicroBitDisplay::printCharAsync(char c, int delay) +{ + //sanitise this value + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + image.print(c, 0, 0); + + if (delay > 0) + { + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_PRINT_CHARACTER; + } + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Prints the given ManagedString to the display, one character at a time. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Must be > 0. + * Defaults to: MICROBIT_DEFAULT_PRINT_SPEED. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync("abc123",400); + * @endcode + */ +int MicroBitDisplay::printAsync(ManagedString s, int delay) +{ + if (s.length() == 1) + return printCharAsync(s.charAt(0)); + + //sanitise this value + if (delay <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + printingChar = 0; + printingText = s; + animationDelay = delay; + animationTick = 0; + + animationMode = ANIMATION_MODE_PRINT_TEXT; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Prints the given image to the display, if the display is not in use. + * Returns immediately, and executes the animation asynchronously. + * + * @param i The image to display. + * + * @param x The horizontal position on the screen to display the image. Defaults to 0. + * + * @param y The vertical position on the screen to display the image. Defaults to 0. + * + * @param alpha Treats the brightness level '0' as transparent. Defaults to 0. + * + * @param delay The time to delay between characters, in milliseconds. Defaults to 0. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.print(i,400); + * @endcode + */ +int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int delay) +{ + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + image.paste(i, x, y, alpha); + + if(delay > 0) + { + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_PRINT_CHARACTER; + } + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Prints the given character to the display. + * + * @param c The character to display. + * + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever, + * or until the Displays next use. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync('p'); + * display.printAsync('p',100); + * @endcode + */ +int MicroBitDisplay::printChar(char c, int delay) +{ + if (delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printCharAsync(c, delay); + + if (delay > 0) + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Prints the given string to the display, one character at a time. + * + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_PRINT_SPEED. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.print("abc123",400); + * @endcode + */ +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(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + if (s.length() == 1) + { + return printCharAsync(s.charAt(0)); + } + else + { + this->printAsync(s, delay); + fiberWait(); + } + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Prints the given image to the display. + * Blocks the calling thread until all the image has been displayed. + * + * @param i The image to display. + * + * @param x The horizontal position on the screen to display the image. Defaults to 0. + * + * @param y The vertical position on the screen to display the image. Defaults to 0. + * + * @param alpha Treats the brightness level '0' as transparent. Defaults to 0. + * + * @param delay The time to display the image for, or zero to show the image forever. Defaults to 0. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.print(i,400); + * @endcode + */ +int MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay) +{ + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printAsync(i, x, y, alpha, delay); + + if (delay > 0) + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given string to the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.scrollAsync("abc123",100); + * @endcode + */ +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) + { + scrollingPosition = width-1; + scrollingChar = 0; + scrollingText = s; + + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_SCROLL_TEXT; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given image across the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * + * @param image The image to display. + * + * @param delay The time between updates, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @param stride The number of pixels to shift by in each update. Defaults to MICROBIT_DEFAULT_SCROLL_STRIDE. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.scrollAsync(i,100,1); + * @endcode + */ +int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) +{ + //sanitise the delay 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) + { + scrollingImagePosition = stride < 0 ? width : -image.getWidth(); + scrollingImageStride = stride; + scrollingImage = image; + scrollingImageRendered = false; + + animationDelay = stride == 0 ? 0 : delay; + animationTick = 0; + animationMode = ANIMATION_MODE_SCROLL_IMAGE; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given string across the display, from right to left. + * Blocks the calling thread until all text has been displayed. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.scroll("abc123",100); + * @endcode + */ +int MicroBitDisplay::scroll(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(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->scrollAsync(s, delay); + + // Wait for completion. + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given image across the display, from right to left. + * Blocks the calling thread until all the text has been displayed. + * + * @param image The image to display. + * + * @param delay The time between updates, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @param stride The number of pixels to shift by in each update. Defaults to MICROBIT_DEFAULT_SCROLL_STRIDE. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.scroll(i,100,1); + * @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(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->scrollAsync(image, delay, stride); + + // Wait for completion. + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Returns immediately. + * + * @param image The image to display. + * + * @param delay The time to delay between each update of the display, in milliseconds. + * + * @param stride The number of pixels to shift by in each update. + * + * @param startingPosition the starting position on the display for the animation + * to begin at. Defaults to MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * display.animateAsync(i,100,5); + * @endcode + */ +int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, int startingPosition) +{ + //sanitise the delay value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, we can display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + // Assume right to left functionality, to align with scrollString() + stride = -stride; + + //calculate starting position which is offset by the stride + scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition; + scrollingImageStride = stride; + scrollingImage = image; + scrollingImageRendered = false; + + animationDelay = stride == 0 ? 0 : delay; + animationTick = delay-1; + animationMode = ANIMATION_MODE_ANIMATE_IMAGE; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Blocks the calling thread until the animation is complete. + * + * + * @param delay The time to delay between each update of the display, in milliseconds. + * + * @param stride The number of pixels to shift by in each update. + * + * @param startingPosition the starting position on the display for the animation + * to begin at. Defaults to MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * display.animate(i,100,5); + * @endcode + */ +int MicroBitDisplay::animate(MicroBitImage image, int delay, int stride, int startingPosition) +{ + //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(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->animateAsync(image, delay, stride, startingPosition); + + // Wait for completion. + //TODO: Put this in when we merge tight-validation + //if (delay > 0) + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + + +/** + * Configures the brightness of the display. + * + * @param b The brightness to set the brightness to, in the range 0 - 255. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER + * + * @code + * display.setBrightness(255); //max brightness + * @endcode + */ +int MicroBitDisplay::setBrightness(int b) +{ + //sanitise the brightness level + if(b < 0 || b > 255) + return MICROBIT_INVALID_PARAMETER; + + this->brightness = b; + + return MICROBIT_OK; +} + +/** + * Configures the mode of the display. + * + * @param mode The mode to swap the display into. One of: DISPLAY_MODE_GREYSCALE, + * DISPLAY_MODE_BLACK_AND_WHITE, DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE + * + * @code + * display.setDisplayMode(DISPLAY_MODE_GREYSCALE); //per pixel brightness + * @endcode + */ +void MicroBitDisplay::setDisplayMode(DisplayMode mode) +{ + if(mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + //to reduce the artifacts on the display - increase the tick + if(system_timer_get_period() != MICROBIT_LIGHT_SENSOR_TICK_PERIOD) + system_timer_set_period(MICROBIT_LIGHT_SENSOR_TICK_PERIOD); + } + + if(this->mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE && mode != DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + delete this->lightSensor; + + this->lightSensor = NULL; + } + + this->mode = mode; +} + +/** + * Retrieves the mode of the display. + * + * @return the current mode of the display + */ +int MicroBitDisplay::getDisplayMode() +{ + return this->mode; +} + +/** + * Fetches the current brightness of this display. + * + * @return the brightness of this display, in the range 0..255. + * + * @code + * display.getBrightness(); //the current brightness + * @endcode + */ +int MicroBitDisplay::getBrightness() +{ + return this->brightness; +} + +/** + * Rotates the display to the given position. + * + * Axis aligned values only. + * + * @code + * display.rotateTo(MICROBIT_DISPLAY_ROTATION_180); //rotates 180 degrees from original orientation + * @endcode + */ +void MicroBitDisplay::rotateTo(DisplayRotation rotation) +{ + this->rotation = rotation; +} + +/** + * Enables or disables the display entirely, and releases the pins for other uses. + * + * @param enableDisplay true to enabled the display, or false to disable it. + */ +void MicroBitDisplay::setEnable(bool enableDisplay) +{ + // If we're already in the correct state, then there's nothing to do. + if(((status & MICROBIT_COMPONENT_RUNNING) && enableDisplay) || (!(status & MICROBIT_COMPONENT_RUNNING) && !enableDisplay)) + return; + + uint32_t rmask = 0; + uint32_t cmask = 0; + + for (int i = matrixMap.rowStart; i < matrixMap.rowStart + matrixMap.rows; i++) + rmask |= 0x01 << i; + + for (int i = matrixMap.columnStart; i < matrixMap.columnStart + matrixMap.columns; i++) + cmask |= 0x01 << i; + + if (enableDisplay) + { + PortOut p(Port0, rmask | cmask); + status |= MICROBIT_COMPONENT_RUNNING; + } + else + { + PortIn p(Port0, rmask | cmask); + p.mode(PullNone); + status &= ~MICROBIT_COMPONENT_RUNNING; + } +} + +/** + * Enables the display, should only be called if the display is disabled. + * + * @code + * display.enable(); //Enables the display mechanics + * @endcode + * + * @note Only enables the display if the display is currently disabled. + */ +void MicroBitDisplay::enable() +{ + setEnable(true); +} + +/** + * Disables the display, which releases control of the GPIO pins used by the display, + * which are exposed on the edge connector. + * + * @code + * display.disable(); //disables the display + * @endcode + * + * @note Only disables the display if the display is currently enabled. + */ +void MicroBitDisplay::disable() +{ + setEnable(false); +} + +/** + * Clears the display of any remaining pixels. + * + * `display.image.clear()` can also be used! + * + * @code + * display.clear(); //clears the display + * @endcode + */ +void MicroBitDisplay::clear() +{ + image.clear(); +} + +/** + * Updates the font that will be used for display operations. + * + * @param font the new font that will be used to render characters. + * + * @note DEPRECATED! Please use MicroBitFont::setSystemFont() instead. + */ +void MicroBitDisplay::setFont(MicroBitFont font) +{ + MicroBitFont::setSystemFont(font); +} + +/** + * Retrieves the font object used for rendering characters on the display. + * + * @note DEPRECATED! Please use MicroBitFont::getSystemFont() instead. + */ +MicroBitFont MicroBitDisplay::getFont() +{ + return MicroBitFont::getSystemFont(); +} + +/** + * Captures the bitmap currently being rendered on the display. + * + * @return a MicroBitImage containing the captured data. + */ +MicroBitImage MicroBitDisplay::screenShot() +{ + return image.crop(0,0,MICROBIT_DISPLAY_WIDTH,MICROBIT_DISPLAY_HEIGHT); +} + +/** + * Gives a representative figure of the light level in the current environment + * where are micro:bit is situated. + * + * Internally, it constructs an instance of a MicroBitLightSensor if not already configured + * and sets the display mode to DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE. + * + * This also changes the tickPeriod to MICROBIT_LIGHT_SENSOR_TICK_SPEED so + * that the display does not suffer from artifacts. + * + * @return an indicative light level in the range 0 - 255. + * + * @note this will return 0 on the first call to this method, a light reading + * will be available after the display has activated the light sensor for the + * first time. + */ +int MicroBitDisplay::readLightLevel() +{ + if(mode != DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + setDisplayMode(DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE); + this->lightSensor = new MicroBitLightSensor(matrixMap); + } + + return this->lightSensor->read(); +} + +/** + * Destructor for MicroBitDisplay, where we deregister this instance from the array of system components. + */ +MicroBitDisplay::~MicroBitDisplay() +{ + system_timer_remove_component(this); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitI2C.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,134 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitI2C.h" +#include "ErrorNo.h" +#include "twi_master.h" +#include "nrf_delay.h" + +/** + * Constructor. + * + * Create an instance of MicroBitI2C for I2C communication. + * + * @param sda the Pin to be used for SDA + * + * @param scl the Pin to be used for SCL + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * @endcode + * + * @note This class presents a wrapped mbed call to capture failed I2C operations caused by a known silicon bug in the nrf51822. + * Attempts to automatically reset and restart the I2C hardware if this case is detected. + * + * For reference see PAN56 in: + * + * https://www.nordicsemi.com/eng/nordic/Products/nRF51822/PAN-nRF51822/24634 + * + * v2.0 through to v2.4 + */ +MicroBitI2C::MicroBitI2C(PinName sda, PinName scl) : I2C(sda,scl) +{ + this->retries = 0; +} + +/** + * Performs a complete read transaction. The bottom bit of the address is forced to 1 to indicate a read. + * + * @param address 8-bit I2C slave address [ addr | 1 ] + * + * @param data A pointer to a byte buffer used for storing retrieved data. + * + * @param length Number of bytes to read. + * + * @param repeated if true, stop is not sent at the end. Defaults to false. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if an unresolved read failure is detected. + */ +int MicroBitI2C::read(int address, char *data, int length, bool repeated) +{ + int result = I2C::read(address,data,length,repeated); + + //0 indicates a success, presume failure + while(result != 0 && retries < MICROBIT_I2C_MAX_RETRIES) + { + _i2c.i2c->EVENTS_ERROR = 0; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos; + _i2c.i2c->POWER = 0; + nrf_delay_us(5); + _i2c.i2c->POWER = 1; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos; + twi_master_init_and_clear(); + result = I2C::read(address,data,length,repeated); + retries++; + } + + if(result != 0) + return MICROBIT_I2C_ERROR; + + retries = 0; + return MICROBIT_OK; +} + +/** + * Performs a complete write transaction. The bottom bit of the address is forced to 0 to indicate a write. + * + * @param address 8-bit I2C slave address [ addr | 0 ] + * + * @param data A pointer to a byte buffer containing the data to write. + * + * @param length Number of bytes to write + * + * @param repeated if true, stop is not sent at the end. Defaults to false. + * + * @return MICROBIT_OK on success, MICROBIT_I2C_ERROR if an unresolved write failure is detected. + */ +int MicroBitI2C::write(int address, const char *data, int length, bool repeated) +{ + int result = I2C::write(address,data,length,repeated); + + //0 indicates a success, presume failure + while(result != 0 && retries < MICROBIT_I2C_MAX_RETRIES) + { + _i2c.i2c->EVENTS_ERROR = 0; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos; + _i2c.i2c->POWER = 0; + nrf_delay_us(5); + _i2c.i2c->POWER = 1; + _i2c.i2c->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos; + + twi_master_init_and_clear(); + result = I2C::write(address,data,length,repeated); + retries++; + } + + if(result != 0) + return MICROBIT_I2C_ERROR; + + retries = 0; + return MICROBIT_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitIO.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,70 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBit IO. + * + * Represents a collection of all I/O pins on the edge connector. + */ + +#include "MicroBitConfig.h" +#include "MicroBitIO.h" + +/** + * Constructor. + * + * Create a representation of all given I/O pins on the edge connector + * + * Accepts a sequence of unique ID's used to distinguish events raised + * by MicroBitPin instances on the default EventModel. + */ +MicroBitIO::MicroBitIO(int ID_P0, int ID_P1, int ID_P2, + int ID_P3, int ID_P4, int ID_P5, + int ID_P6, int ID_P7, int ID_P8, + int ID_P9, int ID_P10,int ID_P11, + int ID_P12,int ID_P13,int ID_P14, + int ID_P15,int ID_P16,int ID_P19, + int ID_P20) : + P0 (ID_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL), //P0 is the left most pad (ANALOG/DIGITAL/TOUCH) + P1 (ID_P1, MICROBIT_PIN_P1, PIN_CAPABILITY_ALL), //P1 is the middle pad (ANALOG/DIGITAL/TOUCH) + P2 (ID_P2, MICROBIT_PIN_P2, PIN_CAPABILITY_ALL), //P2 is the right most pad (ANALOG/DIGITAL/TOUCH) + P3 (ID_P3, MICROBIT_PIN_P3, PIN_CAPABILITY_AD), //COL1 (ANALOG/DIGITAL) + P4 (ID_P4, MICROBIT_PIN_P4, PIN_CAPABILITY_AD), //COL2 (ANALOG/DIGITAL) + P5 (ID_P5, MICROBIT_PIN_P5, PIN_CAPABILITY_DIGITAL), //BTN_A + P6 (ID_P6, MICROBIT_PIN_P6, PIN_CAPABILITY_DIGITAL), //ROW2 + P7 (ID_P7, MICROBIT_PIN_P7, PIN_CAPABILITY_DIGITAL), //ROW1 + P8 (ID_P8, MICROBIT_PIN_P8, PIN_CAPABILITY_DIGITAL), //PIN 18 + P9 (ID_P9, MICROBIT_PIN_P9, PIN_CAPABILITY_DIGITAL), //ROW3 + P10(ID_P10,MICROBIT_PIN_P10,PIN_CAPABILITY_AD), //COL3 (ANALOG/DIGITAL) + P11(ID_P11,MICROBIT_PIN_P11,PIN_CAPABILITY_DIGITAL), //BTN_B + P12(ID_P12,MICROBIT_PIN_P12,PIN_CAPABILITY_DIGITAL), //PIN 20 + P13(ID_P13,MICROBIT_PIN_P13,PIN_CAPABILITY_DIGITAL), //SCK + P14(ID_P14,MICROBIT_PIN_P14,PIN_CAPABILITY_DIGITAL), //MISO + P15(ID_P15,MICROBIT_PIN_P15,PIN_CAPABILITY_DIGITAL), //MOSI + P16(ID_P16,MICROBIT_PIN_P16,PIN_CAPABILITY_DIGITAL), //PIN 16 + P19(ID_P19,MICROBIT_PIN_P19,PIN_CAPABILITY_DIGITAL), //SCL + P20(ID_P20,MICROBIT_PIN_P20,PIN_CAPABILITY_DIGITAL) //SDA +{ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitLightSensor.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,177 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBitLightSensor. + * + * This is an object that interleaves light sensing with MicroBitDisplay. + */ + +#include "MicroBitConfig.h" +#include "MicroBitLightSensor.h" +#include "MicroBitDisplay.h" + +/** + * After the startSensing method has been called, this method will be called + * MICROBIT_LIGHT_SENSOR_AN_SET_TIME after. + * + * It will then read from the currently selected channel using the AnalogIn + * that was configured in the startSensing method. + */ +void MicroBitLightSensor::analogReady() +{ + this->results[chan] = this->sensePin->read_u16(); + + analogDisable(); + + DigitalOut((PinName)(matrixMap.columnStart + chan)).write(1); + + chan++; + + chan = chan % MICROBIT_LIGHT_SENSOR_CHAN_NUM; +} + +/** + * Forcibly disables the AnalogIn, otherwise it will remain in possession + * of the GPIO channel it is using, meaning that the display will not be + * able to use a channel (COL). + * + * This is required as per PAN 3, details of which can be found here: + * + * https://www.nordicsemi.com/eng/nordic/download_resource/24634/5/88440387 + */ +void MicroBitLightSensor::analogDisable() +{ + NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Disabled; + + NRF_ADC->CONFIG = (ADC_CONFIG_RES_8bit << ADC_CONFIG_RES_Pos) | + (ADC_CONFIG_INPSEL_SupplyTwoThirdsPrescaling << ADC_CONFIG_INPSEL_Pos) | + (ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos) | + (ADC_CONFIG_PSEL_Disabled << ADC_CONFIG_PSEL_Pos) | + (ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos); +} + +/** + * Constructor. + * + * Create a representation of the light sensor. + * + * @param map The mapping information that relates pin inputs/outputs to physical screen coordinates. + * Defaults to microbitMatrixMap, defined in MicroBitMatrixMaps.h. + */ +MicroBitLightSensor::MicroBitLightSensor(const MatrixMap &map) : + analogTrigger(), + matrixMap(map) +{ + this->chan = 0; + + if (EventModel::defaultEventBus) + EventModel::defaultEventBus->listen(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_LIGHT_SENSE, this, &MicroBitLightSensor::startSensing, MESSAGE_BUS_LISTENER_IMMEDIATE); + + this->sensePin = NULL; +} + +/** + * This method returns a summed average of the three sections of the display. + * + * A section is defined as: + * ___________________ + * | 1 | | 2 | | 3 | + * |___|___|___|___|___| + * | | | | | | + * |___|___|___|___|___| + * | 2 | | 3 | | 1 | + * |___|___|___|___|___| + * | | | | | | + * |___|___|___|___|___| + * | 3 | | 1 | | 2 | + * |___|___|___|___|___| + * + * Where each number represents a different section on the 5 x 5 matrix display. + * + * @return returns a value in the range 0 - 255 where 0 is dark, and 255 + * is very bright + */ +int MicroBitLightSensor::read() +{ + int sum = 0; + + for(int i = 0; i < MICROBIT_LIGHT_SENSOR_CHAN_NUM; i++) + sum += results[i]; + + int average = sum / MICROBIT_LIGHT_SENSOR_CHAN_NUM; + + average = min(average, MICROBIT_LIGHT_SENSOR_MAX_VALUE); + + average = max(average, MICROBIT_LIGHT_SENSOR_MIN_VALUE); + + int inverted = (MICROBIT_LIGHT_SENSOR_MAX_VALUE - average) + MICROBIT_LIGHT_SENSOR_MIN_VALUE; + + int a = 0; + + int b = 255; + + int normalised = a + ((((inverted - MICROBIT_LIGHT_SENSOR_MIN_VALUE)) * (b - a))/ (MICROBIT_LIGHT_SENSOR_MAX_VALUE - MICROBIT_LIGHT_SENSOR_MIN_VALUE)); + + return normalised; +} + +/** + * The method that is invoked by sending MICROBIT_DISPLAY_EVT_LIGHT_SENSE + * using the id MICROBIT_ID_DISPLAY. + * + * @note this can be manually driven by calling this member function, with + * a MicroBitEvent using the CREATE_ONLY option of the MicroBitEvent + * constructor. + */ +void MicroBitLightSensor::startSensing(MicroBitEvent) +{ + for(int rowCount = 0; rowCount < matrixMap.rows; rowCount++) + DigitalOut((PinName)(matrixMap.rowStart + rowCount)).write(0); + + PinName currentPin = (PinName)(matrixMap.columnStart + chan); + + DigitalOut(currentPin).write(1); + + DigitalIn(currentPin, PullNone).~DigitalIn(); + + if(this->sensePin != NULL) + delete this->sensePin; + + this->sensePin = new AnalogIn(currentPin); + + analogTrigger.attach_us(this, &MicroBitLightSensor::analogReady, MICROBIT_LIGHT_SENSOR_AN_SET_TIME); +} + +/** + * A destructor for MicroBitLightSensor. + * + * The destructor removes the listener, used by MicroBitLightSensor from the default EventModel. + */ +MicroBitLightSensor::~MicroBitLightSensor() +{ + if (EventModel::defaultEventBus) + EventModel::defaultEventBus->ignore(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_LIGHT_SENSE, this, &MicroBitLightSensor::startSensing); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitMessageBus.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,552 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the MicroBitMessageBus. + * + * The MicroBitMessageBus is the common mechanism to deliver asynchronous events on the + * MicroBit platform. It serves a number of purposes: + * + * 1) It provides an eventing abstraction that is independent of the underlying substrate. + * + * 2) It provides a mechanism to decouple user code from trusted system code + * i.e. the basis of a message passing nano kernel. + * + * 3) It allows a common high level eventing abstraction across a range of hardware types.e.g. buttons, BLE... + * + * 4) It provides a mechanim for extensibility - new devices added via I/O pins can have OO based + * drivers and communicate via the message bus with minima impact on user level languages. + * + * 5) It allows for the possiblility of event / data aggregation, which in turn can save energy. + * + * It has the following design principles: + * + * 1) Maintain a low RAM footprint where possible + * + * 2) Make few assumptions about the underlying platform, but allow optimizations where possible. + */ +#include "MicroBitConfig.h" +#include "MicroBitMessageBus.h" +#include "MicroBitFiber.h" +#include "ErrorNo.h" + +/** + * Default constructor. + * + * Adds itself as a fiber component, and also configures itself to be the + * default EventModel if defaultEventBus is NULL. + */ +MicroBitMessageBus::MicroBitMessageBus() +{ + this->listeners = NULL; + this->evt_queue_head = NULL; + this->evt_queue_tail = NULL; + this->queueLength = 0; + + fiber_add_idle_component(this); + + if(EventModel::defaultEventBus == NULL) + EventModel::defaultEventBus = this; +} + +/** + * Invokes a callback on a given MicroBitListener + * + * Internal wrapper function, used to enable + * parameterised callbacks through the fiber scheduler. + */ +void async_callback(void *param) +{ + MicroBitListener *listener = (MicroBitListener *)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. + // + + if (listener->flags & MESSAGE_BUS_LISTENER_BUSY) + { + // Drop this event, if that's how we've been configured. + if (listener->flags & MESSAGE_BUS_LISTENER_DROP_IF_BUSY) + return; + + // Queue this event up for later, if that's how we've been configured. + if (listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) + { + listener->queue(listener->evt); + return; + } + } + + // 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... + listener->flags |= MESSAGE_BUS_LISTENER_BUSY; + + while (1) + { + // Firstly, check for a method callback into an object. + if (listener->flags & MESSAGE_BUS_LISTENER_METHOD) + listener->cb_method->fire(listener->evt); + + // Now a parameterised C function + else if (listener->flags & MESSAGE_BUS_LISTENER_PARAMETERISED) + listener->cb_param(listener->evt, listener->cb_arg); + + // We must have a plain C function + else + listener->cb(listener->evt); + + // If there are more events to process, dequeue the next one and process it. + if ((listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) && listener->evt_queue) + { + MicroBitEventQueueItem *item = listener->evt_queue; + + listener->evt = item->evt; + listener->evt_queue = listener->evt_queue->next; + delete item; + + // We spin the scheduler here, to preven any particular event handler from continuously holding onto resources. + schedule(); + } + else + break; + } + + // The fiber of exiting... clear our state. + listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY; +} + +/** + * 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. + */ +void MicroBitMessageBus::queueEvent(MicroBitEvent &evt) +{ + int processingComplete; + + MicroBitEventQueueItem *prev = evt_queue_tail; + + // Now process all handler regsitered as URGENT. + // These pre-empt the queue, and are useful for fast, high priority services. + processingComplete = this->process(evt, true); + + // If we've already processed all event handlers, we're all done. + // No need to queue the event. + if (processingComplete) + return; + + // If we need to queue, but there is no space, then there's nothg we can do. + if (queueLength >= MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH) + return; + + // Otherwise, we need to queue this event for later processing... + // We queue this event at the tail of the queue at the point where we entered queueEvent() + // This is important as the processing above *may* have generated further events, and + // we want to maintain ordering of events. + MicroBitEventQueueItem *item = new MicroBitEventQueueItem(evt); + + // The queue was empty when we entered this function, so queue our event at the start of the queue. + __disable_irq(); + + if (prev == NULL) + { + item->next = evt_queue_head; + evt_queue_head = item; + } + else + { + item->next = prev->next; + prev->next = item; + } + + if (item->next == NULL) + evt_queue_tail = item; + + queueLength++; + + __enable_irq(); +} + +/** + * Extract the next event from the front of the event queue (if present). + * + * @return a pointer to the MicroBitEventQueueItem that is at the head of the list. + */ +MicroBitEventQueueItem* MicroBitMessageBus::dequeueEvent() +{ + MicroBitEventQueueItem *item = NULL; + + __disable_irq(); + + if (evt_queue_head != NULL) + { + item = evt_queue_head; + evt_queue_head = item->next; + + if (evt_queue_head == NULL) + evt_queue_tail = NULL; + + queueLength--; + } + + __enable_irq(); + + + return item; +} + +/** + * Cleanup any MicroBitListeners marked for deletion from the list. + * + * @return The number of listeners removed from the list. + */ +int MicroBitMessageBus::deleteMarkedListeners() +{ + MicroBitListener *l, *p; + int removed = 0; + + l = listeners; + p = NULL; + + // Walk this list of event handlers. Delete any that match the given listener. + while (l != NULL) + { + if (l->flags & MESSAGE_BUS_LISTENER_DELETING && !l->flags & MESSAGE_BUS_LISTENER_BUSY) + { + if (p == NULL) + listeners = l->next; + else + p->next = l->next; + + // delete the listener. + MicroBitListener *t = l; + l = l->next; + + delete t; + removed++; + + continue; + } + + p = l; + l = l->next; + } + + return removed; +} + +/** + * Periodic callback from MicroBit. + * + * 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 + this->deleteMarkedListeners(); + + MicroBitEventQueueItem *item = this->dequeueEvent(); + + // Whilst there are events to process and we have no useful other work to do, pull them off the queue and process them. + while (item) + { + // send the event to all standard event listeners. + this->process(item->evt); + + // Free the queue item. + delete item; + + // 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. + if(!scheduler_runqueue_empty()) + break; + + // Pull the next event to process, if there is one. + item = this->dequeueEvent(); + } +} + +/** + * Indicates whether or not we have any background work to do. + * + * @return 1 if there are any events waitingto be processed, 0 otherwise. + */ +int MicroBitMessageBus::isIdleCallbackNeeded() +{ + return !(evt_queue_head == NULL); +} + +/** + * Queues the given event to be sent to all registered recipients. + * + * @param evt The event to send. + * + * @code + * MicroBitMessageBus bus; + * + * // Creates and sends the MicroBitEvent using bus. + * MicrobitEvent evt(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * + * // Creates the MicrobitEvent, but delays the sending of that event. + * MicrobitEvent evt1(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, CREATE_ONLY); + * + * bus.send(evt1); + * + * // This has the same effect! + * evt1.fire() + * @endcode + */ +int MicroBitMessageBus::send(MicroBitEvent evt) +{ + // We simply queue processing of the event until we're scheduled in normal thread context. + // We do this to avoid the possibility of executing event handler code in IRQ context, which may bring + // hidden race conditions to kids code. Queuing all events ensures causal ordering (total ordering in fact). + this->queueEvent(evt); + return MICROBIT_OK; +} + +/** + * Internal function, used to deliver the given event to all relevant recipients. + * Normally, this is called once an event has been removed from the event queue. + * + * @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. Defaults to false. + * + * @return 1 if all matching listeners were processed, 0 if further processing is required. + * + * @note It is recommended that all external code uses the send() function instead of this function, + * or the constructors provided by MicrobitEvent. + */ +int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) +{ + MicroBitListener *l; + int complete = 1; + bool listenerUrgent; + + l = listeners; + while (l != NULL) + { + if((l->id == evt.source || l->id == MICROBIT_ID_ANY) && (l->value == evt.value || l->value == MICROBIT_EVT_ANY)) + { + listenerUrgent = (l->flags & MESSAGE_BUS_LISTENER_IMMEDIATE) == MESSAGE_BUS_LISTENER_IMMEDIATE; + if(listenerUrgent == urgent && !(l->flags & MESSAGE_BUS_LISTENER_DELETING)) + { + l->evt = evt; + + // 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 + // of creating a fiber needlessly. (cool huh?) + if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING || !fiber_scheduler_running()) + async_callback(l); + else + invoke(async_callback, l); + } + else + { + complete = 0; + } + } + + l = l->next; + } + + return complete; +} + +/** + * Add the given MicroBitListener to the list of event handlers, unconditionally. + * + * @param listener The MicroBitListener to add. + * + * @return MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise. + */ +int MicroBitMessageBus::add(MicroBitListener *newListener) +{ + MicroBitListener *l, *p; + int methodCallback; + + //handler can't be NULL! + if (newListener == NULL) + return MICROBIT_INVALID_PARAMETER; + + l = listeners; + + // Firstly, we treat a listener as an idempotent operation. Ensure we don't already have this handler + // registered in a that will already capture these events. If we do, silently ignore. + + // We always check the ID, VALUE and CB_METHOD fields. + // If we have a callback to a method, check the cb_method class. Otherwise, the cb function point is sufficient. + while (l != NULL) + { + methodCallback = (newListener->flags & MESSAGE_BUS_LISTENER_METHOD) && (l->flags & MESSAGE_BUS_LISTENER_METHOD); + + if (l->id == newListener->id && l->value == newListener->value && (methodCallback ? *l->cb_method == *newListener->cb_method : l->cb == newListener->cb)) + { + // We have a perfect match for this event listener already registered. + // If it's marked for deletion, we simply resurrect the listener, and we're done. + // Either way, we return an error code, as the *new* listener should be released... + if(l->flags & MESSAGE_BUS_LISTENER_DELETING) + l->flags &= ~MESSAGE_BUS_LISTENER_DELETING; + + return MICROBIT_NOT_SUPPORTED; + } + + l = l->next; + } + + // We have a valid, new event handler. Add it to the list. + // if listeners is null - we can automatically add this listener to the list at the beginning... + if (listeners == NULL) + { + listeners = newListener; + MicroBitEvent(MICROBIT_ID_MESSAGE_BUS_LISTENER, newListener->id); + + return MICROBIT_OK; + } + + // 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... + + p = listeners; + l = listeners; + + while (l != NULL && l->id < newListener->id) + { + p = l; + l = l->next; + } + + while (l != NULL && l->id == newListener->id && l->value < newListener->value) + { + p = l; + l = l->next; + } + + //add at front of list + if (p == listeners && (newListener->id < p->id || (p->id == newListener->id && p->value > newListener->value))) + { + newListener->next = p; + + //this new listener is now the front! + listeners = newListener; + } + + //add after p + else + { + newListener->next = p->next; + p->next = newListener; + } + + MicroBitEvent(MICROBIT_ID_MESSAGE_BUS_LISTENER, newListener->id); + return MICROBIT_OK; +} + +/** + * Remove the given MicroBitListener from the list of event handlers. + * + * @param listener The MicroBitListener to remove. + * + * @return MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise. + */ +int MicroBitMessageBus::remove(MicroBitListener *listener) +{ + MicroBitListener *l; + int removed = 0; + + //handler can't be NULL! + if (listener == NULL) + return MICROBIT_INVALID_PARAMETER; + + l = listeners; + + // Walk this list of event handlers. Delete any that match the given listener. + while (l != NULL) + { + 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)) || + ((!(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)) + { + // Found a match. mark this to be removed from the list. + l->flags |= MESSAGE_BUS_LISTENER_DELETING; + removed++; + } + } + } + + l = l->next; + } + + if (removed > 0) + return MICROBIT_OK; + else + return MICROBIT_INVALID_PARAMETER; +} + +/** + * Returns the microBitListener with the given position in our list. + * + * @param n The position in the list to return. + * + * @return the MicroBitListener at postion n in the list, or NULL if the position is invalid. + */ +MicroBitListener* MicroBitMessageBus::elementAt(int n) +{ + MicroBitListener *l = listeners; + + while (n > 0) + { + if (l == NULL) + return NULL; + + n--; + l = l->next; + } + + return l; +} + +/** + * Destructor for MicroBitMessageBus, where we deregister this instance from the array of fiber components. + */ +MicroBitMessageBus::~MicroBitMessageBus() +{ + fiber_remove_idle_component(this); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitMultiButton.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,298 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBitMultiButton. + * + * Represents a virtual button, capable of reacting to simultaneous presses of two + * other buttons. + */ +#include "MicroBitConfig.h" +#include "MicroBitMultiButton.h" + +/** + * Constructor. + * + * Create a representation of a virtual button, that generates events based upon the combination + * of two given buttons. + * + * @param button1 the unique ID of the first button to watch. + * + * @param button2 the unique ID of the second button to watch. + * + * @param id the unique EventModel id of this MicroBitMultiButton instance. + * + * @code + * multiButton(MICROBIT_ID_BUTTON_A, MICROBIT_ID_BUTTON_B, MICROBIT_ID_BUTTON_AB); + * @endcode + */ +MicroBitMultiButton::MicroBitMultiButton(uint16_t button1, uint16_t button2, uint16_t id) +{ + this->id = id; + this->button1 = button1; + this->button2 = button2; + this->eventConfiguration = MICROBIT_BUTTON_SIMPLE_EVENTS; + + if (EventModel::defaultEventBus) + { + EventModel::defaultEventBus->listen(button1, MICROBIT_EVT_ANY, this, &MicroBitMultiButton::onButtonEvent, MESSAGE_BUS_LISTENER_IMMEDIATE); + EventModel::defaultEventBus->listen(button2, MICROBIT_EVT_ANY, this, &MicroBitMultiButton::onButtonEvent, MESSAGE_BUS_LISTENER_IMMEDIATE); + } +} + +/** + * Retrieves the button id for the alternate button id given. + * + * @param b the id of the button whose state we would like to retrieve. + * + * @return the other sub button id. + */ +uint16_t MicroBitMultiButton::otherSubButton(uint16_t b) +{ + return (b == button1 ? button2 : button1); +} + +/** + * Determines if the given button id is marked as pressed. + * + * @param button the id of the button whose state we would like to retrieve. + * + * @return 1 if pressed, 0 if not. + */ +int MicroBitMultiButton::isSubButtonPressed(uint16_t button) +{ + if (button == button1) + return status & MICROBIT_MULTI_BUTTON_STATE_1; + + if (button == button2) + return status & MICROBIT_MULTI_BUTTON_STATE_2; + + return 0; +} + +/** + * Determines if the given button id is marked as held. + * + * @param button the id of the button whose state we would like to retrieve. + * + * @return 1 if held, 0 if not. + */ +int MicroBitMultiButton::isSubButtonHeld(uint16_t button) +{ + if (button == button1) + return status & MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1; + + if (button == button2) + return status & MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2; + + return 0; +} + +/** + * Determines if the given button id is marked as supressed. + * + * @param button the id of the button whose state we would like to retrieve. + * + * @return 1 if supressed, 0 if not. + */ +int MicroBitMultiButton::isSubButtonSupressed(uint16_t button) +{ + if (button == button1) + return status & MICROBIT_MULTI_BUTTON_SUPRESSED_1; + + if (button == button2) + return status & MICROBIT_MULTI_BUTTON_SUPRESSED_2; + + return 0; +} + +/** + * Configures the button pressed state for the given button id. + * + * @param button the id of the button whose state requires updating. + * + * @param value the value to set for this buttons state. (Transformed into a logical 0 or 1). + */ +void MicroBitMultiButton::setButtonState(uint16_t button, int value) +{ + if (button == button1) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_STATE_1; + else + status &= ~MICROBIT_MULTI_BUTTON_STATE_1; + } + + if (button == button2) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_STATE_2; + else + status &= ~MICROBIT_MULTI_BUTTON_STATE_2; + } +} + +/** + * Configures the button held state for the given button id. + * + * @param button the id of the button whose state requires updating. + * + * @param value the value to set for this buttons state. (Transformed into a logical 0 or 1). + */ +void MicroBitMultiButton::setHoldState(uint16_t button, int value) +{ + if (button == button1) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1; + else + status &= ~MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_1; + } + + if (button == button2) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2; + else + status &= ~MICROBIT_MULTI_BUTTON_HOLD_TRIGGERED_2; + } +} + +/** + * Configures the button suppressed state for the given button id. + * + * @param button the id of the button whose state requires updating. + * + * @param value the value to set for this buttons state. (Transformed into a logical 0 or 1). + */ +void MicroBitMultiButton::setSupressedState(uint16_t button, int value) +{ + if (button == button1) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_SUPRESSED_1; + else + status &= ~MICROBIT_MULTI_BUTTON_SUPRESSED_1; + } + + if (button == button2) + { + if (value) + status |= MICROBIT_MULTI_BUTTON_SUPRESSED_2; + else + status &= ~MICROBIT_MULTI_BUTTON_SUPRESSED_2; + } +} + +/** + * Changes the event configuration of this button to the given MicroBitButtonEventConfiguration. + * All subsequent events generated by this button will then be informed by this configuraiton. + * + * @param config The new configuration for this button. Legal values are MICROBIT_BUTTON_ALL_EVENTS or MICROBIT_BUTTON_SIMPLE_EVENTS. + * + * @code + * // Configure a button to generate all possible events. + * buttonAB.setEventConfiguration(MICROBIT_BUTTON_ALL_EVENTS); + * + * // Configure a button to suppress MICROBIT_BUTTON_EVT_CLICK and MICROBIT_BUTTON_EVT_LONG_CLICK events. + * buttonAB.setEventConfiguration(MICROBIT_BUTTON_SIMPLE_EVENTS); + * @endcode + */ +void MicroBitMultiButton::setEventConfiguration(MicroBitButtonEventConfiguration config) +{ + this->eventConfiguration = config; +} + +/** + * A member function that is invoked when any event is detected from the two + * button IDs this MicrobitMultiButton instance was constructed with. + * + * @param evt the event received from the default EventModel. + */ +void MicroBitMultiButton::onButtonEvent(MicroBitEvent evt) +{ + int button = evt.source; + int otherButton = otherSubButton(button); + + switch(evt.value) + { + case MICROBIT_BUTTON_EVT_DOWN: + setButtonState(button, 1); + if(isSubButtonPressed(otherButton)) + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_DOWN); + + break; + + case MICROBIT_BUTTON_EVT_HOLD: + setHoldState(button, 1); + if(isSubButtonHeld(otherButton)) + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_HOLD); + + break; + + case MICROBIT_BUTTON_EVT_UP: + if(isSubButtonPressed(otherButton)) + { + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_UP); + + if (isSubButtonHeld(button) && isSubButtonHeld(otherButton)) + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_LONG_CLICK); + else + MicroBitEvent e(id, MICROBIT_BUTTON_EVT_CLICK); + + setSupressedState(otherButton, 1); + } + else if (!isSubButtonSupressed(button) && eventConfiguration == MICROBIT_BUTTON_ALL_EVENTS) + { + if (isSubButtonHeld(button)) + MicroBitEvent e(button, MICROBIT_BUTTON_EVT_LONG_CLICK); + else + MicroBitEvent e(button, MICROBIT_BUTTON_EVT_CLICK); + } + + setButtonState(button, 0); + setHoldState(button, 0); + setSupressedState(button, 0); + + break; + + } +} + + +/** + * Tests if this MicroBitMultiButton instance is virtually pressed. + * + * @return 1 if both physical buttons are pressed simultaneously. + * + * @code + * if(buttonAB.isPressed()) + * display.scroll("Pressed!"); + * @endcode + */ +int MicroBitMultiButton::isPressed() +{ + return ((status & MICROBIT_MULTI_BUTTON_STATE_1) && (status & MICROBIT_MULTI_BUTTON_STATE_2)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitPin.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,432 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBitPin. + * + * Commonly represents an I/O pin on the edge connector. + */ +#include "MicroBitConfig.h" +#include "MicroBitPin.h" +#include "MicroBitButton.h" +#include "DynamicPwm.h" +#include "ErrorNo.h" + +/** + * Constructor. + * Create a MicroBitPin instance, generally used to represent a pin on the edge connector. + * + * @param id the unique EventModel id of this component. + * + * @param name the mbed PinName for this MicroBitPin instance. + * + * @param capability the capabilities this MicroBitPin instance should have. + * (PIN_CAPABILITY_DIGITAL, PIN_CAPABILITY_ANALOG, PIN_CAPABILITY_TOUCH, PIN_CAPABILITY_AD, PIN_CAPABILITY_ALL) + * + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); + * @endcode + */ +MicroBitPin::MicroBitPin(int id, PinName name, PinCapability capability) +{ + //set mandatory attributes + this->id = id; + this->name = name; + this->capability = capability; + + // Power up in a disconnected, low power state. + // If we're unused, this is how it will stay... + this->status = 0x00; + this->pin = NULL; + +} + +/** + * Disconnect any attached mBed IO from this pin. + * + * Used only when pin changes mode (i.e. Input/Output/Analog/Digital) + */ +void MicroBitPin::disconnect() +{ + // This is a bit ugly, but rarely used code. + // It would be much better to use some polymorphism here, but the mBed I/O classes aren't arranged in an inheritance hierarchy... yet. :-) + if (status & IO_STATUS_DIGITAL_IN) + delete ((DigitalIn *)pin); + + if (status & IO_STATUS_DIGITAL_OUT) + delete ((DigitalOut *)pin); + + if (status & IO_STATUS_ANALOG_IN){ + NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Disabled; // forcibly disable the ADC - BUG in mbed.... + delete ((AnalogIn *)pin); + } + + if (status & IO_STATUS_ANALOG_OUT) + { + if(((DynamicPwm *)pin)->getPinName() == name) + ((DynamicPwm *)pin)->release(); + } + + if (status & IO_STATUS_TOUCH_IN) + delete ((MicroBitButton *)pin); + + this->pin = NULL; + this->status = status & IO_STATUS_EVENTBUS_ENABLED; //retain event bus status +} + +/** + * Configures this IO pin as a digital output (if necessary) and sets the pin to 'value'. + * + * @param value 0 (LO) or 1 (HI) + * + * @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. + * + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.setDigitalValue(1); // P0 is now HI + * @endcode + */ +int MicroBitPin::setDigitalValue(int value) +{ + // Check if this pin has a digital mode... + if(!(PIN_CAPABILITY_DIGITAL & capability)) + return MICROBIT_NOT_SUPPORTED; + + // 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(); + pin = new DigitalOut(name); + status |= IO_STATUS_DIGITAL_OUT; + } + + // Write the value. + ((DigitalOut *)pin)->write(value); + + return MICROBIT_OK; +} + +/** + * 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. + * + * @code + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH); + * P0.getDigitalValue(); // P0 is either 0 or 1; + * @endcode + */ +int MicroBitPin::getDigitalValue() +{ + //check if this pin has a digital mode... + if(!(PIN_CAPABILITY_DIGITAL & capability)) + return MICROBIT_NOT_SUPPORTED; + + // Move into a Digital input state if necessary. + if (!(status & IO_STATUS_DIGITAL_IN)){ + 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 + * + * @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::setAnalogValue(int value) +{ + //check if this pin has an analogue mode... + if(!(PIN_CAPABILITY_ANALOG & capability)) + return MICROBIT_NOT_SUPPORTED; + + //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); + + //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 necessary) and configures the period to be 20ms, + * with a duty cycle between 500 us and 2500 us. + * + * A value of 180 sets the duty cycle to be 2500us, and a value of 0 sets the duty cycle to be 500us 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. the 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 samples the Pin for its analog value. + * + * @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. + * + * @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 + */ +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(); + pin = new AnalogIn(name); + status |= IO_STATUS_ANALOG_IN; + } + + //perform a read! + return ((AnalogIn *)pin)->read_u16(); +} + +/** + * Determines if this IO pin is currently configured as an input. + * + * @return 1 if pin is an analog or digital input, 0 otherwise. + */ +int MicroBitPin::isInput() +{ + return (status & (IO_STATUS_DIGITAL_IN | IO_STATUS_ANALOG_IN)) == 0 ? 0 : 1; +} + +/** + * Determines if this IO pin is currently configured as an output. + * + * @return 1 if pin is an analog or digital output, 0 otherwise. + */ +int MicroBitPin::isOutput() +{ + return (status & (IO_STATUS_DIGITAL_OUT | IO_STATUS_ANALOG_OUT)) == 0 ? 0 : 1; +} + +/** + * Determines if this IO pin is currently configured for digital use. + * + * @return 1 if pin is digital, 0 otherwise. + */ +int MicroBitPin::isDigital() +{ + return (status & (IO_STATUS_DIGITAL_IN | IO_STATUS_DIGITAL_OUT)) == 0 ? 0 : 1; +} + +/** + * Determines if this IO pin is currently configured for analog use. + * + * @return 1 if pin is analog, 0 otherwise. + */ +int MicroBitPin::isAnalog() +{ + return (status & (IO_STATUS_ANALOG_IN | IO_STATUS_ANALOG_OUT)) == 0 ? 0 : 1; +} + +/** + * Configures this IO pin as a "makey makey" style touch sensor (if necessary) + * and tests its current debounced state. + * + * Users can also subscribe to MicroBitButton events generated from this pin. + * + * @return 1 if pin is touched, 0 if not, or MICROBIT_NOT_SUPPORTED if this pin does not support touch capability. + * + * @code + * MicroBitMessageBus bus; + * + * MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL); + * if(P0.isTouched()) + * { + * //do something! + * } + * + * // subscribe to events generated by this pin! + * bus.listen(MICROBIT_ID_IO_P0, MICROBIT_BUTTON_EVT_CLICK, someFunction); + * @endcode + */ +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(name, id); + 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 + * given pin is not configured as an analog output. + */ +int MicroBitPin::setAnalogPeriodUs(int period) +{ + if (!(status & IO_STATUS_ANALOG_OUT)) + return MICROBIT_NOT_SUPPORTED; + + 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 milliseconds. + * + * @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); +} + +/** + * Obtains the PWM period of the analog output in microseconds. + * + * @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(); +} + +/** + * Obtains the PWM period of the analog output in milliseconds. + * + * @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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitRadio.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,512 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitRadio.h" +#include "MicroBitComponent.h" +#include "EventModel.h" +#include "MicroBitDevice.h" +#include "ErrorNo.h" +#include "MicroBitFiber.h" +#include "MicroBitBLEManager.h" + +/** + * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module. + * + * The nrf51822 RADIO module supports a number of proprietary modes of operation oher than the typical BLE usage. + * This class uses one of these modes to enable simple, point to multipoint communication directly between micro:bits. + * + * TODO: The protocols implemented here do not currently perform any significant form of energy management, + * which means that they will consume far more energy than their BLE equivalent. Later versions of the protocol + * should look to address this through energy efficient broadcast techbiques / sleep scheduling. In particular, the GLOSSY + * approach to efficient rebroadcast and network synchronisation would likely provide an effective future step. + * + * TODO: Meshing should also be considered - again a GLOSSY approach may be effective here, and highly complementary to + * the master/slave arachitecture of BLE. + * + * TODO: This implementation may only operated whilst the BLE stack is disabled. The nrf51822 provides a timeslot API to allow + * BLE to cohabit with other protocols. Future work to allow this colocation would be benefical, and would also allow for the + * creation of wireless BLE bridges. + * + * NOTE: This API does not contain any form of encryption, authentication or authorisation. Its purpose is solely for use as a + * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place. + * For serious applications, BLE should be considered a substantially more secure alternative. + */ + +MicroBitRadio* MicroBitRadio::instance = NULL; + +extern "C" void RADIO_IRQHandler(void) +{ + // Move on to the next buffer, if possible. + MicroBitRadio::instance->queueRxBuf(); + NRF_RADIO->PACKETPTR = (uint32_t) MicroBitRadio::instance->getRxBuf(); + + if(NRF_RADIO->EVENTS_READY) + { + NRF_RADIO->EVENTS_READY = 0; + + // Start listening and wait for the END event + NRF_RADIO->TASKS_START = 1; + } + + if(NRF_RADIO->EVENTS_END) + { + NRF_RADIO->EVENTS_END = 0; + + if(NRF_RADIO->CRCSTATUS == 1) + { + uint8_t sample = NRF_RADIO->RSSISAMPLE; + + MicroBitRadio::instance->setRSSI(sample); + } + + // Start listening and wait for the END event + NRF_RADIO->TASKS_START = 1; + } +} + +/** + * Constructor. + * + * Initialise the MicroBitRadio. + * + * @note This class is demand activated, as a result most resources are only + * committed if send/recv or event registrations calls are made. + */ +MicroBitRadio::MicroBitRadio(uint16_t id) : datagram(*this), event (*this) +{ + this->id = id; + this->status = 0; + this->group = 0; + this->queueDepth = 0; + this->rssi = 0; + this->rxQueue = NULL; + this->rxBuf = NULL; + + instance = this; +} + +/** + * Change the output power level of the transmitter to the given value. + * + * @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range. + */ +int MicroBitRadio::setTransmitPower(int power) +{ + if (power < 0 || power >= MICROBIT_BLE_POWER_LEVELS) + return MICROBIT_INVALID_PARAMETER; + + NRF_RADIO->TXPOWER = (uint32_t)MICROBIT_BLE_POWER_LEVEL[power]; + + return MICROBIT_OK; +} + +/** + * Change the transmission and reception band of the radio to the given channel + * + * @param band a frequency band in the range 0 - 100. Each step is 1MHz wide, based at 2400MHz. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range, + * or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ +int MicroBitRadio::setFrequencyBand(int band) +{ + if (ble_running()) + return MICROBIT_NOT_SUPPORTED; + + if (band < 0 || band > 100) + return MICROBIT_INVALID_PARAMETER; + + NRF_RADIO->FREQUENCY = (uint32_t)band; + + return MICROBIT_OK; +} + +/** + * Retrieve a pointer to the currently allocated receive buffer. This is the area of memory + * actively being used by the radio hardware to store incoming data. + * + * @return a pointer to the current receive buffer. + */ +FrameBuffer* MicroBitRadio::getRxBuf() +{ + return rxBuf; +} + +/** + * Attempt to queue a buffer received by the radio hardware, if sufficient space is available. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if a replacement receiver buffer + * could not be allocated (either by policy or memory exhaustion). + */ +int MicroBitRadio::queueRxBuf() +{ + if (rxBuf == NULL) + return MICROBIT_INVALID_PARAMETER; + + if (queueDepth >= MICROBIT_RADIO_MAXIMUM_RX_BUFFERS) + return MICROBIT_NO_RESOURCES; + + // Store the received RSSI value in the frame + rxBuf->rssi = getRSSI(); + + // Ensure that a replacement buffer is available before queuing. + FrameBuffer *newRxBuf = new FrameBuffer(); + + if (newRxBuf == NULL) + return MICROBIT_NO_RESOURCES; + + // We add to the tail of the queue to preserve causal ordering. + rxBuf->next = NULL; + + if (rxQueue == NULL) + { + rxQueue = rxBuf; + } + else + { + FrameBuffer *p = rxQueue; + while (p->next != NULL) + p = p->next; + + p->next = rxBuf; + } + + // Increase our received packet count + queueDepth++; + + // Allocate a new buffer for the receiver hardware to use. the old on will be passed on to higher layer protocols/apps. + rxBuf = newRxBuf; + + return MICROBIT_OK; +} + +/** + * Sets the RSSI for the most recent packet. + * + * @param rssi the new rssi value. + * + * @note should only be called from RADIO_IRQHandler... + */ +int MicroBitRadio::setRSSI(uint8_t rssi) +{ + if (!(status & MICROBIT_RADIO_STATUS_INITIALISED)) + return MICROBIT_NOT_SUPPORTED; + + this->rssi = rssi; + + return MICROBIT_OK; +} + +/** + * Retrieves the current RSSI for the most recent packet. + * + * @return the most recent RSSI value or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ +int MicroBitRadio::getRSSI() +{ + if (!(status & MICROBIT_RADIO_STATUS_INITIALISED)) + return MICROBIT_NOT_SUPPORTED; + + return this->rssi; +} + +/** + * Initialises the radio for use as a multipoint sender/receiver + * + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ +int MicroBitRadio::enable() +{ + // If the device is already initialised, then there's nothing to do. + if (status & MICROBIT_RADIO_STATUS_INITIALISED) + return MICROBIT_OK; + + // Only attempt to enable this radio mode if BLE is disabled. + if (ble_running()) + return MICROBIT_NOT_SUPPORTED; + + // If this is the first time we've been enable, allocate out receive buffers. + if (rxBuf == NULL) + rxBuf = new FrameBuffer(); + + if (rxBuf == NULL) + return MICROBIT_NO_RESOURCES; + + // Enable the High Frequency clock on the processor. This is a pre-requisite for + // the RADIO module. Without this clock, no communication is possible. + NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; + NRF_CLOCK->TASKS_HFCLKSTART = 1; + while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0); + + // Bring up the nrf51822 RADIO module in Nordic's proprietary 1MBps packet radio mode. + setTransmitPower(MICROBIT_RADIO_DEFAULT_TX_POWER); + setFrequencyBand(MICROBIT_RADIO_DEFAULT_FREQUENCY); + + // Configure for 1Mbps throughput. + // This may sound excessive, but running a high data rates reduces the chances of collisions... + NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_1Mbit; + + // Configure the addresses we use for this protocol. We run ANONYMOUSLY at the core. + // A 40 bit addresses is used. The first 32 bits match the ASCII character code for "uBit". + // Statistically, this provides assurance to avoid other similar 2.4GHz protocols that may be in the vicinity. + // We also map the assigned 8-bit GROUP id into the PREFIX field. This allows the RADIO hardware to perform + // address matching for us, and only generate an interrupt when a packet matching our group is received. + NRF_RADIO->BASE0 = MICROBIT_RADIO_BASE_ADDRESS; + + // Join the default group. This will configure the remaining byte in the RADIO hardware module. + setGroup(MICROBIT_RADIO_DEFAULT_GROUP); + + // The RADIO hardware module supports the use of multiple addresses, but as we're running anonymously, we only need one. + // Configure the RADIO module to use the default address (address 0) for both send and receive operations. + NRF_RADIO->TXADDRESS = 0; + NRF_RADIO->RXADDRESSES = 1; + + // Packet layout configuration. The nrf51822 has a highly capable and flexible RADIO module that, in addition to transmission + // and reception of data, also contains a LENGTH field, two optional additional 1 byte fields (S0 and S1) and a CRC calculation. + // Configure the packet format for a simple 8 bit length field and no additional fields. + NRF_RADIO->PCNF0 = 0x00000008; + NRF_RADIO->PCNF1 = 0x02040000 | MICROBIT_RADIO_MAX_PACKET_SIZE; + + // Most communication channels contain some form of checksum - a mathematical calculation taken based on all the data + // in a packet, that is also sent as part of the packet. When received, this calculation can be repeated, and the results + // from the sender and receiver compared. If they are different, then some corruption of the data ahas happened in transit, + // and we know we can't trust it. The nrf51822 RADIO uses a CRC for this - a very effective checksum calculation. + // + // Enable automatic 16bit CRC generation and checking, and configure how the CRC is calculated. + NRF_RADIO->CRCCNF = RADIO_CRCCNF_LEN_Two; + NRF_RADIO->CRCINIT = 0xFFFF; + NRF_RADIO->CRCPOLY = 0x11021; + + // Set the start random value of the data whitening algorithm. This can be any non zero number. + NRF_RADIO->DATAWHITEIV = 0x18; + + // Set up the RADIO module to read and write from our internal buffer. + NRF_RADIO->PACKETPTR = (uint32_t)rxBuf; + + // Configure the hardware to issue an interrupt whenever a task is complete (e.g. send/receive). + NRF_RADIO->INTENSET = 0x00000008; + NVIC_ClearPendingIRQ(RADIO_IRQn); + NVIC_EnableIRQ(RADIO_IRQn); + + NRF_RADIO->SHORTS |= RADIO_SHORTS_ADDRESS_RSSISTART_Msk; + + // Start listening for the next packet + NRF_RADIO->EVENTS_READY = 0; + NRF_RADIO->TASKS_RXEN = 1; + while(NRF_RADIO->EVENTS_READY == 0); + + NRF_RADIO->EVENTS_END = 0; + NRF_RADIO->TASKS_START = 1; + + // register ourselves for a callback event, in order to empty the receive queue. + fiber_add_idle_component(this); + + // Done. Record that our RADIO is configured. + status |= MICROBIT_RADIO_STATUS_INITIALISED; + + return MICROBIT_OK; +} + +/** + * Disables the radio for use as a multipoint sender/receiver. + * + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ +int MicroBitRadio::disable() +{ + // Only attempt to enable.disable the radio if the protocol is alreayd running. + if (ble_running()) + return MICROBIT_NOT_SUPPORTED; + + if (!(status & MICROBIT_RADIO_STATUS_INITIALISED)) + return MICROBIT_OK; + + // Disable interrupts and STOP any ongoing packet reception. + NVIC_DisableIRQ(RADIO_IRQn); + + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + while(NRF_RADIO->EVENTS_DISABLED == 0); + + // deregister ourselves from the callback event used to empty the receive queue. + fiber_remove_idle_component(this); + + return MICROBIT_OK; +} + +/** + * Sets the radio to listen to packets sent with the given group id. + * + * @param group The group to join. A micro:bit can only listen to one group ID at any time. + * + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ +int MicroBitRadio::setGroup(uint8_t group) +{ + if (ble_running()) + return MICROBIT_NOT_SUPPORTED; + + // Record our group id locally + this->group = group; + + // Also append it to the address of this device, to allow the RADIO module to filter for us. + NRF_RADIO->PREFIX0 = (uint32_t)group; + + return MICROBIT_OK; +} + +/** + * A background, low priority callback that is triggered whenever the processor is idle. + * Here, we empty our queue of received packets, and pass them onto higher level protocol handlers. + */ +void MicroBitRadio::idleTick() +{ + // Walk the list of packets and process each one. + while(rxQueue) + { + FrameBuffer *p = rxQueue; + + switch (p->protocol) + { + case MICROBIT_RADIO_PROTOCOL_DATAGRAM: + datagram.packetReceived(); + break; + + case MICROBIT_RADIO_PROTOCOL_EVENTBUS: + event.packetReceived(); + break; + + default: + MicroBitEvent(MICROBIT_ID_RADIO_DATA_READY, p->protocol); + } + + // If the packet was processed, it will have been recv'd, and taken from the queue. + // If this was a packet for an unknown protocol, it will still be there, so simply free it. + if (p == rxQueue) + { + recv(); + delete p; + } + } +} + +/** + * Determines the number of packets ready to be processed. + * + * @return The number of packets in the receive buffer. + */ +int MicroBitRadio::dataReady() +{ + return queueDepth; +} + +/** + * Retrieves the next packet from the receive buffer. + * If a data packet is available, then it will be returned immediately to + * the caller. This call will also dequeue the buffer. + * + * @return The buffer containing the the packet. If no data is available, NULL is returned. + * + * @note Once recv() has been called, it is the callers resposibility to + * delete the buffer when appropriate. + */ +FrameBuffer* MicroBitRadio::recv() +{ + FrameBuffer *p = rxQueue; + + if (p) + { + rxQueue = rxQueue->next; + queueDepth--; + } + + return p; +} + +/** + * Transmits the given buffer onto the broadcast radio. + * The call will wait until the transmission of the packet has completed before returning. + * + * @param data The packet contents to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the BLE stack is running. + */ +int MicroBitRadio::send(FrameBuffer *buffer) +{ + if (ble_running()) + return MICROBIT_NOT_SUPPORTED; + + if (buffer == NULL) + return MICROBIT_INVALID_PARAMETER; + + if (buffer->length > MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE - 1) + return MICROBIT_INVALID_PARAMETER; + + // Firstly, disable the Radio interrupt. We want to wait until the trasmission completes. + NVIC_DisableIRQ(RADIO_IRQn); + + // Turn off the transceiver. + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + while(NRF_RADIO->EVENTS_DISABLED == 0); + + // Configure the radio to send the buffer provided. + NRF_RADIO->PACKETPTR = (uint32_t) buffer; + + // Turn on the transmitter, and wait for it to signal that it's ready to use. + NRF_RADIO->EVENTS_READY = 0; + NRF_RADIO->TASKS_TXEN = 1; + while (NRF_RADIO->EVENTS_READY == 0); + + // Start transmission and wait for end of packet. + NRF_RADIO->TASKS_START = 1; + NRF_RADIO->EVENTS_END = 0; + while(NRF_RADIO->EVENTS_END == 0); + + // Return the radio to using the default receive buffer + NRF_RADIO->PACKETPTR = (uint32_t) rxBuf; + + // Turn off the transmitter. + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + while(NRF_RADIO->EVENTS_DISABLED == 0); + + // Start listening for the next packet + NRF_RADIO->EVENTS_READY = 0; + NRF_RADIO->TASKS_RXEN = 1; + while(NRF_RADIO->EVENTS_READY == 0); + + NRF_RADIO->EVENTS_END = 0; + NRF_RADIO->TASKS_START = 1; + + // Re-enable the Radio interrupt. + NVIC_ClearPendingIRQ(RADIO_IRQn); + NVIC_EnableIRQ(RADIO_IRQn); + + return MICROBIT_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitRadioDatagram.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,203 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitRadio.h" + +/** + * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module. + * + * This class provides the ability to broadcast simple text or binary messages to other micro:bits in the vicinity + * It is envisaged that this would provide the basis for children to experiment with building their own, simple, + * custom protocols. + * + * @note This API does not contain any form of encryption, authentication or authorisation. Its purpose is solely for use as a + * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place. + * For serious applications, BLE should be considered a substantially more secure alternative. + */ + +/** +* Constructor. +* +* Creates an instance of a MicroBitRadioDatagram which offers the ability +* to broadcast simple text or binary messages to other micro:bits in the vicinity +* +* @param r The underlying radio module used to send and receive data. +*/ +MicroBitRadioDatagram::MicroBitRadioDatagram(MicroBitRadio &r) : radio(r) +{ + this->rxQueue = NULL; +} + +/** + * Retrieves packet payload data into the given buffer. + * + * If a data packet is already available, then it will be returned immediately to the caller. + * If no data is available then MICROBIT_INVALID_PARAMETER is returned. + * + * @param buf A pointer to a valid memory location where the received data is to be stored + * + * @param len The maximum amount of data that can safely be stored in 'buf' + * + * @return The length of the data stored, or MICROBIT_INVALID_PARAMETER if no data is available, or the memory regions provided are invalid. + */ +int MicroBitRadioDatagram::recv(uint8_t *buf, int len) +{ + if (buf == NULL || rxQueue == NULL || len < 0) + return MICROBIT_INVALID_PARAMETER; + + // Take the first buffer from the queue. + FrameBuffer *p = rxQueue; + rxQueue = rxQueue->next; + + int l = min(len, p->length - (MICROBIT_RADIO_HEADER_SIZE - 1)); + + // Fill in the buffer provided, if possible. + memcpy(buf, p->payload, l); + + delete p; + return l; +} + +/** + * Retreives packet payload data into the given buffer. + * + * If a data packet is already available, then it will be returned immediately to the caller + * in the form of a PacketBuffer. + * + * @return the data received, or an empty PacketBuffer if no data is available. + */ +PacketBuffer MicroBitRadioDatagram::recv() +{ + if (rxQueue == NULL) + return PacketBuffer::EmptyPacket; + + FrameBuffer *p = rxQueue; + rxQueue = rxQueue->next; + + PacketBuffer packet(p->payload, p->length - (MICROBIT_RADIO_HEADER_SIZE - 1), p->rssi); + + delete p; + return packet; +} + +/** + * Transmits the given buffer onto the broadcast radio. + * + * This is a synchronous call that will wait until the transmission of the packet + * has completed before returning. + * + * @param buffer The packet contents to transmit. + * + * @param len The number of bytes to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, + * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. + */ +int MicroBitRadioDatagram::send(uint8_t *buffer, int len) +{ + if (buffer == NULL || len < 0 || len > MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE - 1) + return MICROBIT_INVALID_PARAMETER; + + FrameBuffer buf; + + buf.length = len + MICROBIT_RADIO_HEADER_SIZE - 1; + buf.version = 1; + buf.group = 0; + buf.protocol = MICROBIT_RADIO_PROTOCOL_DATAGRAM; + memcpy(buf.payload, buffer, len); + + return radio.send(&buf); +} + +/** + * Transmits the given string onto the broadcast radio. + * + * This is a synchronous call that will wait until the transmission of the packet + * has completed before returning. + * + * @param data The packet contents to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, + * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. + */ +int MicroBitRadioDatagram::send(PacketBuffer data) +{ + return send((uint8_t *)data.getBytes(), data.length()); +} + +/** + * Transmits the given string onto the broadcast radio. + * + * This is a synchronous call that will wait until the transmission of the packet + * has completed before returning. + * + * @param data The packet contents to transmit. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the buffer is invalid, + * or the number of bytes to transmit is greater than `MICROBIT_RADIO_MAX_PACKET_SIZE + MICROBIT_RADIO_HEADER_SIZE`. + */ +int MicroBitRadioDatagram::send(ManagedString data) +{ + return send((uint8_t *)data.toCharArray(), data.length()); +} + +/** + * Protocol handler callback. This is called when the radio receives a packet marked as a datagram. + * + * This function process this packet, and queues it for user reception. + */ +void MicroBitRadioDatagram::packetReceived() +{ + FrameBuffer *packet = radio.recv(); + int queueDepth = 0; + + // We add to the tail of the queue to preserve causal ordering. + packet->next = NULL; + + if (rxQueue == NULL) + { + rxQueue = packet; + } + else + { + FrameBuffer *p = rxQueue; + while (p->next != NULL) + { + p = p->next; + queueDepth++; + } + + if (queueDepth >= MICROBIT_RADIO_MAXIMUM_RX_BUFFERS) + { + delete packet; + return; + } + + p->next = packet; + } + + MicroBitEvent(MICROBIT_ID_RADIO, MICROBIT_RADIO_EVT_DATAGRAM); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitRadioEvent.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,175 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitRadio.h" + +/** + * Provides a simple broadcast radio abstraction, built upon the raw nrf51822 RADIO module. + * + * This class provides the ability to extend the micro:bit's default EventModel to other micro:bits in the vicinity, + * in a very similar way to the MicroBitEventService for BLE interfaces. + * + * It is envisaged that this would provide the basis for children to experiment with building their own, simple, + * custom asynchronous events and actions. + * + * @note This API does not contain any form of encryption, authentication or authorisation. Its purpose is solely for use as a + * teaching aid to demonstrate how simple communications operates, and to provide a sandpit through which learning can take place. + * For serious applications, BLE should be considered a substantially more secure alternative. + */ + +/** + * Constructor. + * + * Creates an instance of MicroBitRadioEvent which offers the ability to extend + * the micro:bit's default EventModel to other micro:bits in the vicinity. + * + * @param r The underlying radio module used to send and receive data. + */ +MicroBitRadioEvent::MicroBitRadioEvent(MicroBitRadio &r) : radio(r) +{ + this->suppressForwarding = false; +} + +/** + * Associates the given event with the radio channel. + * + * Once registered, all events matching the given registration sent to this micro:bit's + * default EventModel will be automatically retransmitted on the radio. + * + * @param id The id of the event to register. + * + * @param value the value of the event to register. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if no default EventModel is available. + * + * @note The wildcards MICROBIT_ID_ANY and MICROBIT_EVT_ANY can also be in place of the + * id and value fields. + */ +int MicroBitRadioEvent::listen(uint16_t id, uint16_t value) +{ + if (EventModel::defaultEventBus) + return listen(id, value, *EventModel::defaultEventBus); + + return MICROBIT_NO_RESOURCES; +} + +/** + * Associates the given event with the radio channel. + * + * Once registered, all events matching the given registration sent to the given + * EventModel will be automatically retransmitted on the radio. + * + * @param id The id of the events to register. + * + * @param value the value of the event to register. + * + * @param eventBus The EventModel to listen for events on. + * + * @return MICROBIT_OK on success. + * + * @note The wildcards MICROBIT_ID_ANY and MICROBIT_EVT_ANY can also be in place of the + * id and value fields. + */ +int MicroBitRadioEvent::listen(uint16_t id, uint16_t value, EventModel &eventBus) +{ + return eventBus.listen(id, value, this, &MicroBitRadioEvent::eventReceived, MESSAGE_BUS_LISTENER_IMMEDIATE); +} + +/** + * Disassociates the given event with the radio channel. + * + * @param id The id of the events to deregister. + * + * @param value The value of the event to deregister. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the default message bus does not exist. + * + * @note MICROBIT_EVT_ANY can be used to deregister all event values matching the given id. + */ +int MicroBitRadioEvent::ignore(uint16_t id, uint16_t value) +{ + if (EventModel::defaultEventBus) + return ignore(id, value, *EventModel::defaultEventBus); + + return MICROBIT_INVALID_PARAMETER; +} + +/** + * Disassociates the given events with the radio channel. + * + * @param id The id of the events to deregister. + * + * @param value The value of the event to deregister. + * + * @param eventBus The EventModel to deregister on. + * + * @return MICROBIT_OK on success. + * + * @note MICROBIT_EVT_ANY can be used to deregister all event values matching the given id. + */ +int MicroBitRadioEvent::ignore(uint16_t id, uint16_t value, EventModel &eventBus) +{ + return eventBus.ignore(id, value, this, &MicroBitRadioEvent::eventReceived); +} + + +/** + * Protocol handler callback. This is called when the radio receives a packet marked as using the event protocol. + * + * This function process this packet, and fires the event contained inside onto the default EventModel. + */ +void MicroBitRadioEvent::packetReceived() +{ + FrameBuffer *p = radio.recv(); + MicroBitEvent *e = (MicroBitEvent *) p->payload; + + suppressForwarding = true; + e->fire(); + suppressForwarding = false; + + delete p; +} + +/** + * Event handler callback. This is called whenever an event is received matching one of those registered through + * the registerEvent() method described above. Upon receiving such an event, it is wrapped into + * a radio packet and transmitted to any other micro:bits in the same group. + */ +void MicroBitRadioEvent::eventReceived(MicroBitEvent e) +{ + if(suppressForwarding) + return; + + FrameBuffer buf; + + buf.length = sizeof(MicroBitEvent) + MICROBIT_RADIO_HEADER_SIZE - 1; + buf.version = 1; + buf.group = 0; + buf.protocol = MICROBIT_RADIO_PROTOCOL_EVENTBUS; + memcpy(buf.payload, (const uint8_t *)&e, sizeof(MicroBitEvent)); + + radio.send(&buf); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitSerial.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,1099 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "mbed.h" +#include "MicroBitSerial.h" +#include "ErrorNo.h" +#include "MicroBitComponent.h" +#include "MicroBitFiber.h" +#include "NotifyEvents.h" + +uint8_t MicroBitSerial::status = 0; + +int MicroBitSerial::baudrate = 0; + +/** + * Constructor. + * Create an instance of MicroBitSerial + * + * @param tx the Pin to be used for transmission + * + * @param rx the Pin to be used for receiving data + * + * @param rxBufferSize the size of the buffer to be used for receiving bytes + * + * @param txBufferSize the size of the buffer to be used for transmitting bytes + * + * @code + * MicroBitSerial serial(USBTX, USBRX); + * @endcode + * @note the default baud rate is 115200. More API details can be found: + * -https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/api/SerialBase.h + * -https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/api/RawSerial.h + * + * Buffers aren't allocated until the first send or receive respectively. + */ +MicroBitSerial::MicroBitSerial(PinName tx, PinName rx, uint8_t rxBufferSize, uint8_t txBufferSize) : RawSerial(tx,rx), delimeters() +{ + this->rxBuffSize = rxBufferSize; + this->txBuffSize = txBufferSize; + + this->rxBuff = NULL; + this->txBuff = NULL; + + this->rxBuffHead = 0; + this->rxBuffTail = 0; + + this->txBuffHead = 0; + this->txBuffTail = 0; + + this->rxBuffHeadMatch = -1; + + this->baud(MICROBIT_SERIAL_DEFAULT_BAUD_RATE); + +#if CONFIG_ENABLED(MICROBIT_DBG) + SERIAL_DEBUG = this; +#endif + +} + +/** + * An internal interrupt callback for MicroBitSerial configured for when a + * character is received. + * + * Each time a character is received fill our circular buffer! + */ +void MicroBitSerial::dataReceived() +{ + if(!(status & MICROBIT_SERIAL_RX_BUFF_INIT)) + return; + + //get the received character + char c = getc(); + + int delimeterOffset = 0; + int delimLength = this->delimeters.length(); + + //iterate through our delimeters (if any) to see if there is a match + while(delimeterOffset < delimLength) + { + //fire an event if there is to block any waiting fibers + if(this->delimeters.charAt(delimeterOffset) == c) + MicroBitEvent(MICROBIT_ID_SERIAL, MICROBIT_SERIAL_EVT_DELIM_MATCH); + + delimeterOffset++; + } + + uint16_t newHead = (rxBuffHead + 1) % rxBuffSize; + + //look ahead to our newHead value to see if we are about to collide with the tail + if(newHead != rxBuffTail) + { + //if we are not, store the character, and update our actual head. + this->rxBuff[rxBuffHead] = c; + rxBuffHead = newHead; + + //if we have any fibers waiting for a specific number of characters, unblock them + if(rxBuffHeadMatch >= 0) + if(rxBuffHead == rxBuffHeadMatch) + { + rxBuffHeadMatch = -1; + MicroBitEvent(MICROBIT_ID_SERIAL, MICROBIT_SERIAL_EVT_HEAD_MATCH); + } + } + else + //otherwise, our buffer is full, send an event to the user... + MicroBitEvent(MICROBIT_ID_SERIAL, MICROBIT_SERIAL_EVT_RX_FULL); +} + +/** + * An internal interrupt callback for MicroBitSerial. + * + * Each time the Serial module's buffer is empty, write a character if we have + * characters to write. + */ +void MicroBitSerial::dataWritten() +{ + if(txBuffTail == txBuffHead || !(status & MICROBIT_SERIAL_TX_BUFF_INIT)) + return; + + //send our current char + putc(txBuff[txBuffTail]); + + uint16_t nextTail = (txBuffTail + 1) % txBuffSize; + + //unblock any waiting fibers that are waiting for transmission to finish. + if(nextTail == txBuffHead) + { + MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_SERIAL_EVT_TX_EMPTY); + detach(Serial::IrqType::TxIrq); + } + + //update our tail! + txBuffTail = nextTail; +} + +/** + * An internal method to configure an interrupt on tx buffer and also + * a best effort copy operation to move bytes from a user buffer to our txBuff + * + * @param string a pointer to the first character of the users' buffer. + * + * @param len the length of the string, and ultimately the maximum number of bytes + * that will be copied dependent on the state of txBuff + * + * @return the number of bytes copied into the buffer. + */ +int MicroBitSerial::setTxInterrupt(uint8_t *string, int len) +{ + int copiedBytes = 0; + + for(copiedBytes = 0; copiedBytes < len; copiedBytes++) + { + uint16_t nextHead = (txBuffHead + 1) % txBuffSize; + if(nextHead != txBuffTail) + { + this->txBuff[txBuffHead] = string[copiedBytes]; + txBuffHead = nextHead; + } + else + break; + } + + fiber_wake_on_event(MICROBIT_ID_NOTIFY, MICROBIT_SERIAL_EVT_TX_EMPTY); + + //set the TX interrupt + attach(this, &MicroBitSerial::dataWritten, Serial::IrqType::TxIrq); + + return copiedBytes; +} + +/** + * Locks the mutex so that others can't use this serial instance for reception + */ +void MicroBitSerial::lockRx() +{ + status |= MICROBIT_SERIAL_RX_IN_USE; +} + +/** + * Locks the mutex so that others can't use this serial instance for transmission + */ +void MicroBitSerial::lockTx() +{ + status |= MICROBIT_SERIAL_TX_IN_USE; +} + +/** + * Unlocks the mutex so that others can use this serial instance for reception + */ +void MicroBitSerial::unlockRx() +{ + status &= ~MICROBIT_SERIAL_RX_IN_USE; +} + +/** + * Unlocks the mutex so that others can use this serial instance for transmission + */ +void MicroBitSerial::unlockTx() +{ + status &= ~MICROBIT_SERIAL_TX_IN_USE; +} + +/** + * We do not want to always have our buffers initialised, especially if users to not + * use them. We only bring them up on demand. + */ +int MicroBitSerial::initialiseRx() +{ + if((status & MICROBIT_SERIAL_RX_BUFF_INIT)) + { + //ensure that we receive no interrupts after freeing our buffer + detach(Serial::IrqType::RxIrq); + free(this->rxBuff); + } + + status &= ~MICROBIT_SERIAL_RX_BUFF_INIT; + + if((this->rxBuff = (uint8_t *)malloc(rxBuffSize)) == NULL) + return MICROBIT_NO_RESOURCES; + + this->rxBuffHead = 0; + this->rxBuffTail = 0; + + //set the receive interrupt + status |= MICROBIT_SERIAL_RX_BUFF_INIT; + attach(this, &MicroBitSerial::dataReceived, Serial::IrqType::RxIrq); + + return MICROBIT_OK; +} + +/** + * We do not want to always have our buffers initialised, especially if users to not + * use them. We only bring them up on demand. + */ +int MicroBitSerial::initialiseTx() +{ + if((status & MICROBIT_SERIAL_TX_BUFF_INIT)) + { + //ensure that we receive no interrupts after freeing our buffer + detach(Serial::IrqType::TxIrq); + free(this->txBuff); + } + + status &= ~MICROBIT_SERIAL_TX_BUFF_INIT; + + if((this->txBuff = (uint8_t *)malloc(txBuffSize)) == NULL) + return MICROBIT_NO_RESOURCES; + + this->txBuffHead = 0; + this->txBuffTail = 0; + + status |= MICROBIT_SERIAL_TX_BUFF_INIT; + + return MICROBIT_OK; +} + +/** + * An internal method that either spin waits if mode is set to SYNC_SPINWAIT + * or puts the fiber to sleep if the mode is set to SYNC_SLEEP + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP + */ +void MicroBitSerial::send(MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + while(txBufferedSize() > 0); + + if(mode == SYNC_SLEEP) + fiber_sleep(0); +} + +/** + * Reads a single character from the rxBuff + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - A character is read from the rxBuff if available, if there + * are no characters to be read, a value of zero is returned immediately. + * + * SYNC_SPINWAIT - A character is read from the rxBuff if available, if there + * are no characters to be read, this method will spin + * (lock up the processor) until a character is available. + * + * SYNC_SLEEP - A character is read from the rxBuff if available, if there + * are no characters to be read, the calling fiber sleeps + * until there is a character available. + * + * Defaults to SYNC_SLEEP. + * + * @return a character from the circular buffer, or MICROBIT_NO_DATA is there + * are no characters in the buffer. + */ +int MicroBitSerial::getChar(MicroBitSerialMode mode) +{ + if(mode == ASYNC) + { + if(!isReadable()) + return MICROBIT_NO_DATA; + } + + if(mode == SYNC_SPINWAIT) + while(!isReadable()); + + if(mode == SYNC_SLEEP) + { + if(!isReadable()) + eventAfter(1, mode); + } + + char c = rxBuff[rxBuffTail]; + + rxBuffTail = (rxBuffTail + 1) % rxBuffSize; + + return c; +} + +/** + * An internal method that copies values from a circular buffer to a linear buffer. + * + * @param circularBuff a pointer to the source circular buffer + * + * @param circularBuffSize the size of the circular buffer + * + * @param linearBuff a pointer to the destination linear buffer + * + * @param tailPosition the tail position in the circular buffer you want to copy from + * + * @param headPosition the head position in the circular buffer you want to copy to + * + * @note this method assumes that the linear buffer has the appropriate amount of + * memory to contain the copy operation + */ +void MicroBitSerial::circularCopy(uint8_t *circularBuff, uint8_t circularBuffSize, uint8_t *linearBuff, uint16_t tailPosition, uint16_t headPosition) +{ + int toBuffIndex = 0; + + while(tailPosition != headPosition) + { + linearBuff[toBuffIndex++] = circularBuff[tailPosition]; + + tailPosition = (tailPosition + 1) % circularBuffSize; + } +} + +/** + * Sends a single character over the serial line. + * + * @param c the character to send + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - the character is copied into the txBuff and returns immediately. + * + * SYNC_SPINWAIT - the character is copied into the txBuff and this method + * will spin (lock up the processor) until the character has + * been sent. + * + * SYNC_SLEEP - the character is copied into the txBuff and the fiber sleeps + * until the character has been sent. This allows other fibers + * to continue execution. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of bytes written, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the serial instance for transmission. + */ +int MicroBitSerial::sendChar(char c, MicroBitSerialMode mode) +{ + if(txInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockTx(); + + //lazy initialisation of our tx buffer + if(!(status & MICROBIT_SERIAL_TX_BUFF_INIT)) + { + int result = initialiseTx(); + + if(result != MICROBIT_OK) + return result; + } + + uint8_t toTransmit[2] = { c, '\0'}; + + int bytesWritten = setTxInterrupt(toTransmit, 1); + + send(mode); + + unlockTx(); + + return bytesWritten; +} + +/** + * Sends a ManagedString over the serial line. + * + * @param s the string to send + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - bytes are copied into the txBuff and returns immediately. + * + * SYNC_SPINWAIT - bytes are copied into the txBuff and this method + * will spin (lock up the processor) until all bytes + * have been sent. + * + * SYNC_SLEEP - bytes are copied into the txBuff and the fiber sleeps + * until all bytes have been sent. This allows other fibers + * to continue execution. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of bytes written, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the serial instance for transmission. + */ +int MicroBitSerial::send(ManagedString s, MicroBitSerialMode mode) +{ + return send((uint8_t *)s.toCharArray(), s.length(), mode); +} + +/** + * Sends a buffer of known length over the serial line. + * + * @param buffer a pointer to the first character of the buffer + * + * @param len the number of bytes that are safely available to read. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - bytes are copied into the txBuff and returns immediately. + * + * SYNC_SPINWAIT - bytes are copied into the txBuff and this method + * will spin (lock up the processor) until all bytes + * have been sent. + * + * SYNC_SLEEP - bytes are copied into the txBuff and the fiber sleeps + * until all bytes have been sent. This allows other fibers + * to continue execution. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of bytes written, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the serial instance for transmission. + */ +int MicroBitSerial::send(uint8_t *buffer, int bufferLen, MicroBitSerialMode mode) +{ + if(txInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockTx(); + + //lazy initialisation of our tx buffer + if(!(status & MICROBIT_SERIAL_TX_BUFF_INIT)) + { + int result = initialiseTx(); + + if(result != MICROBIT_OK) + return result; + } + + int bytesWritten = setTxInterrupt(buffer, bufferLen); + + send(mode); + + unlockTx(); + + return bytesWritten; +} + +/** + * Reads a single character from the rxBuff + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - A character is read from the rxBuff if available, if there + * are no characters to be read, a value of MICROBIT_NO_DATA is returned immediately. + * + * SYNC_SPINWAIT - A character is read from the rxBuff if available, if there + * are no characters to be read, this method will spin + * (lock up the processor) until a character is available. + * + * SYNC_SLEEP - A character is read from the rxBuff if available, if there + * are no characters to be read, the calling fiber sleeps + * until there is a character available. + * + * Defaults to SYNC_SLEEP. + * + * @return a character, MICROBIT_SERIAL_IN_USE if another fiber is using the serial instance for reception, + * MICROBIT_NO_RESOURCES if buffer allocation did not complete successfully, or MICROBIT_NO_DATA if + * the rx buffer is empty and the mode given is ASYNC. + */ +int MicroBitSerial::read(MicroBitSerialMode mode) +{ + if(rxInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockRx(); + + //lazy initialisation of our buffers + if(!(status & MICROBIT_SERIAL_RX_BUFF_INIT)) + { + int result = initialiseRx(); + + if(result != MICROBIT_OK) + return result; + } + + char c = (char)getChar(mode); + + unlockRx(); + + return c; +} + +/** + * Reads multiple characters from the rxBuff and returns them as a ManagedString + * + * @param size the number of characters to read. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - If the desired number of characters are available, this will return + * a ManagedString with the expected size. Otherwise, it will read however + * many characters there are available. + * + * SYNC_SPINWAIT - If the desired number of characters are available, this will return + * a ManagedString with the expected size. Otherwise, this method will spin + * (lock up the processor) until the desired number of characters have been read. + * + * SYNC_SLEEP - If the desired number of characters are available, this will return + * a ManagedString with the expected size. Otherwise, the calling fiber sleeps + * until the desired number of characters have been read. + * + * Defaults to SYNC_SLEEP. + * + * @return A ManagedString, or an empty ManagedString if an error was encountered during the read. + */ +ManagedString MicroBitSerial::read(int size, MicroBitSerialMode mode) +{ + uint8_t buff[size + 1] = { 0 }; + + int returnedSize = read((uint8_t *)buff, size, mode); + + if(returnedSize <= 0) + return ManagedString(); + + return ManagedString((char *)buff, returnedSize); +} + +/** + * Reads multiple characters from the rxBuff and fills a user buffer. + * + * @param buffer a pointer to a user allocated buffer. + * + * @param bufferLen the amount of data that can be safely stored + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - If the desired number of characters are available, this will fill + * the given buffer. Otherwise, it will fill the buffer with however + * many characters there are available. + * + * SYNC_SPINWAIT - If the desired number of characters are available, this will fill + * the given buffer. Otherwise, this method will spin (lock up the processor) + * and fill the buffer until the desired number of characters have been read. + * + * SYNC_SLEEP - If the desired number of characters are available, this will fill + * the given buffer. Otherwise, the calling fiber sleeps + * until the desired number of characters have been read. + * + * Defaults to SYNC_SLEEP. + * + * @return the number of characters read, or MICROBIT_SERIAL_IN_USE if another fiber + * is using the instance for receiving. + */ +int MicroBitSerial::read(uint8_t *buffer, int bufferLen, MicroBitSerialMode mode) +{ + if(rxInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockRx(); + + //lazy initialisation of our rx buffer + if(!(status & MICROBIT_SERIAL_RX_BUFF_INIT)) + { + int result = initialiseRx(); + + if(result != MICROBIT_OK) + return result; + } + + int bufferIndex = 0; + + int temp = 0; + + if(mode == ASYNC) + { + while((temp = getChar(mode)) != MICROBIT_NO_DATA && bufferIndex < bufferLen) + { + buffer[bufferIndex] = (char)temp; + bufferIndex++; + } + } + + if(mode == SYNC_SPINWAIT) + { + while(bufferIndex < bufferLen) + { + buffer[bufferIndex] = (char)getChar(mode); + bufferIndex++; + } + } + + if(mode == SYNC_SLEEP) + { + if(bufferLen > rxBufferedSize()) + eventAfter(bufferLen - rxBufferedSize(), mode); + + while(bufferIndex < bufferLen) + { + buffer[bufferIndex] = (char)getChar(mode); + bufferIndex++; + } + } + + unlockRx(); + + return bufferIndex; +} + + +/** + * Reads until one of the delimeters matches a character in the rxBuff + * + * @param delimeters a ManagedString containing a sequence of delimeter characters e.g. ManagedString("\r\n") + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - If one of the delimeters matches a character already in the rxBuff + * this method will return a ManagedString up to the delimeter. + * Otherwise, it will return an Empty ManagedString. + * + * SYNC_SPINWAIT - If one of the delimeters matches a character already in the rxBuff + * this method will return a ManagedString up to the delimeter. + * Otherwise, this method will spin (lock up the processor) until a + * received character matches one of the delimeters. + * + * SYNC_SLEEP - If one of the delimeters matches a character already in the rxBuff + * this method will return a ManagedString up to the delimeter. + * Otherwise, the calling fiber sleeps until a character matching one + * of the delimeters is seen. + * + * Defaults to SYNC_SLEEP. + * + * @return A ManagedString containing the characters up to a delimeter, or an Empty ManagedString, + * if another fiber is currently using this instance for reception. + * + * @note delimeters are matched on a per byte basis. + */ +ManagedString MicroBitSerial::readUntil(ManagedString delimeters, MicroBitSerialMode mode) +{ + + if(rxInUse()) + return ManagedString(); + + //lazy initialisation of our rx buffer + if(!(status & MICROBIT_SERIAL_RX_BUFF_INIT)) + { + int result = initialiseRx(); + + if(result != MICROBIT_OK) + return result; + } + + lockRx(); + + int localTail = rxBuffTail; + int preservedTail = rxBuffTail; + + int foundIndex = -1; + + //ASYNC mode just iterates through our stored characters checking for any matches. + while(localTail != rxBuffHead && foundIndex == -1) + { + //we use localTail to prevent modification of the actual tail. + char c = rxBuff[localTail]; + + for(int delimeterIterator = 0; delimeterIterator < delimeters.length(); delimeterIterator++) + if(delimeters.charAt(delimeterIterator) == c) + foundIndex = localTail; + + localTail = (localTail + 1) % rxBuffSize; + } + + //if our mode is SYNC_SPINWAIT and we didn't see any matching characters in our buffer + //spin until we find a match! + if(mode == SYNC_SPINWAIT) + { + while(foundIndex == -1) + { + while(localTail == rxBuffHead); + + char c = rxBuff[localTail]; + + for(int delimeterIterator = 0; delimeterIterator < delimeters.length(); delimeterIterator++) + if(delimeters.charAt(delimeterIterator) == c) + foundIndex = localTail; + + localTail = (localTail + 1) % rxBuffSize; + } + } + + //if our mode is SYNC_SLEEP, we set up an event to be fired when we see a + //matching character. + if(mode == SYNC_SLEEP && foundIndex == -1) + { + eventOn(delimeters, mode); + + foundIndex = rxBuffHead - 1; + + this->delimeters = ManagedString(); + } + + if(foundIndex >= 0) + { + //calculate our local buffer size + int localBuffSize = (preservedTail > foundIndex) ? (rxBuffSize - preservedTail) + foundIndex : foundIndex - preservedTail; + + uint8_t localBuff[localBuffSize + 1] = { 0 }; + + circularCopy(rxBuff, rxBuffSize, localBuff, preservedTail, foundIndex); + + //plus one for the character we listened for... + rxBuffTail = (rxBuffTail + localBuffSize + 1) % rxBuffSize; + + unlockRx(); + + return ManagedString((char *)localBuff, localBuffSize); + } + + unlockRx(); + + return ManagedString(); +} + +/** + * A wrapper around the inherited method "baud" so we can trap the baud rate + * as it changes and restore it if redirect() is called. + * + * @param baudrate the new baudrate. See: + * - https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/serial_api.c + * for permitted baud rates. + * + * @return MICROBIT_INVALID_PARAMETER if baud rate is less than 0, otherwise MICROBIT_OK. + * + * @note the underlying implementation chooses the first allowable rate at or above that requested. + */ +void MicroBitSerial::baud(int baudrate) +{ + if(baudrate < 0) + return; + + this->baudrate = baudrate; + + RawSerial::baud(baudrate); +} + +/** + * A way of dynamically configuring the serial instance to use pins other than USBTX and USBRX. + * + * @param tx the new transmission pin. + * + * @param rx the new reception pin. + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently transmitting or receiving, otherwise MICROBIT_OK. + */ +int MicroBitSerial::redirect(PinName tx, PinName rx) +{ + if(txInUse() || rxInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockTx(); + lockRx(); + + if(txBufferedSize() > 0) + detach(Serial::IrqType::TxIrq); + + detach(Serial::IrqType::RxIrq); + + serial_free(&_serial); + serial_init(&_serial, tx, rx); + + attach(this, &MicroBitSerial::dataReceived, Serial::IrqType::RxIrq); + + if(txBufferedSize() > 0) + attach(this, &MicroBitSerial::dataWritten, Serial::IrqType::TxIrq); + + this->baud(this->baudrate); + + unlockRx(); + unlockTx(); + + return MICROBIT_OK; +} + +/** + * Configures an event to be fired after "len" characters. + * + * @param len the number of characters to wait before triggering the event. + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + */ +int MicroBitSerial::eventAfter(int len, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + //configure our head match... + this->rxBuffHeadMatch = (rxBuffHead + len) % rxBuffSize; + + //block! + if(mode == SYNC_SLEEP) + fiber_wait_for_event(MICROBIT_ID_SERIAL, MICROBIT_SERIAL_EVT_HEAD_MATCH); + + return MICROBIT_OK; +} + +/** + * Configures an event to be fired on a match with one of the delimeters. + * + * @param delimeters the characters to match received characters against e.g. ManagedString("\r\n") + * + * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode + * gives a different behaviour: + * + * ASYNC - Will configure the event and return immediately. + * + * SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER + * + * SYNC_SLEEP - Will configure the event and block the current fiber until the + * event is received. + * + * @return MICROBIT_INVALID_PARAMETER if the mode given is SYNC_SPINWAIT, otherwise MICROBIT_OK. + * + * @note delimeters are matched on a per byte basis. + */ +int MicroBitSerial::eventOn(ManagedString delimeters, MicroBitSerialMode mode) +{ + if(mode == SYNC_SPINWAIT) + return MICROBIT_INVALID_PARAMETER; + + //configure our head match... + this->delimeters = delimeters; + + //block! + if(mode == SYNC_SLEEP) + fiber_wait_for_event(MICROBIT_ID_SERIAL, MICROBIT_SERIAL_EVT_DELIM_MATCH); + + return MICROBIT_OK; +} + +/** + * Determines whether there is any data waiting in our Rx buffer. + * + * @return 1 if we have space, 0 if we do not. + * + * @note We do not wrap the super's readable() method as we don't want to + * interfere with communities that use manual calls to serial.readable(). + */ +int MicroBitSerial::isReadable() +{ + return (rxBuffTail != rxBuffHead) ? 1 : 0; +} + +/** + * Determines if we have space in our txBuff. + * + * @return 1 if we have space, 0 if we do not. + * + * @note We do not wrap the super's writeable() method as we don't want to + * interfere with communities that use manual calls to serial.writeable(). + */ +int MicroBitSerial::isWriteable() +{ + return (txBuffHead != (txBuffTail - 1)) ? 1 : 0; +} + +/** + * Reconfigures the size of our rxBuff + * + * @param size the new size for our rxBuff + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for reception, otherwise MICROBIT_OK. + */ +int MicroBitSerial::setRxBufferSize(uint8_t size) +{ + if(rxInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockRx(); + + this->rxBuffSize = size; + + int result = initialiseRx(); + + unlockRx(); + + return result; +} + +/** + * Reconfigures the size of our txBuff + * + * @param size the new size for our txBuff + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for transmission, otherwise MICROBIT_OK. + */ +int MicroBitSerial::setTxBufferSize(uint8_t size) +{ + if(txInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockTx(); + + this->txBuffSize = size; + + int result = initialiseTx(); + + unlockTx(); + + return result; +} + +/** + * The size of our rx buffer in bytes. + * + * @return the current size of rxBuff in bytes + */ +int MicroBitSerial::getRxBufferSize() +{ + return this->rxBuffSize; +} + +/** + * The size of our tx buffer in bytes. + * + * @return the current size of txBuff in bytes + */ +int MicroBitSerial::getTxBufferSize() +{ + return this->txBuffSize; +} + +/** + * Sets the tail to match the head of our circular buffer for reception, + * effectively clearing the reception buffer. + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for reception, otherwise MICROBIT_OK. + */ +int MicroBitSerial::clearRxBuffer() +{ + if(rxInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockRx(); + + rxBuffTail = rxBuffHead; + + unlockRx(); + + return MICROBIT_OK; +} + +/** + * Sets the tail to match the head of our circular buffer for transmission, + * effectively clearing the transmission buffer. + * + * @return MICROBIT_SERIAL_IN_USE if another fiber is currently using this instance + * for transmission, otherwise MICROBIT_OK. + */ +int MicroBitSerial::clearTxBuffer() +{ + if(txInUse()) + return MICROBIT_SERIAL_IN_USE; + + lockTx(); + + txBuffTail = txBuffHead; + + unlockTx(); + + return MICROBIT_OK; +} + +/** + * The number of bytes currently stored in our rx buffer waiting to be digested, + * by the user. + * + * @return The currently buffered number of bytes in our rxBuff. + */ +int MicroBitSerial::rxBufferedSize() +{ + if(rxBuffTail > rxBuffHead) + return (rxBuffSize - rxBuffTail) + rxBuffHead; + + return rxBuffHead - rxBuffTail; +} + +/** + * The number of bytes currently stored in our tx buffer waiting to be transmitted + * by the hardware. + * + * @return The currently buffered number of bytes in our txBuff. + */ +int MicroBitSerial::txBufferedSize() +{ + if(txBuffTail > txBuffHead) + return (txBuffSize - txBuffTail) + txBuffHead; + + return txBuffHead - txBuffTail; +} + +/** + * Determines if the serial bus is currently in use by another fiber for reception. + * + * @return The state of our mutex lock for reception. + * + * @note Only one fiber can call read at a time + */ +int MicroBitSerial::rxInUse() +{ + return (status & MICROBIT_SERIAL_RX_IN_USE); +} + +/** + * Determines if the serial bus is currently in use by another fiber for transmission. + * + * @return The state of our mutex lock for transmition. + * + * @note Only one fiber can call send at a time + */ +int MicroBitSerial::txInUse() +{ + return (status & MICROBIT_SERIAL_TX_IN_USE); +} + +/** + * Detaches a previously configured interrupt + * + * @param interruptType one of Serial::RxIrq or Serial::TxIrq + */ +void MicroBitSerial::detach(Serial::IrqType interruptType) +{ + //we detach by sending a bad value to attach, for some weird reason... + attach((MicroBitSerial *)NULL, &MicroBitSerial::dataReceived, interruptType); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitStorage.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,485 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for the MicroBitStorage class. + * This allows reading and writing of FLASH memory. + */ + +#include "MicroBitConfig.h" +#include "MicroBitStorage.h" +#include "MicroBitCompat.h" + +/** + * Default constructor. + * + * Creates an instance of MicroBitStorage which acts like a KeyValueStore + * that allows the retrieval, addition and deletion of KeyValuePairs. + */ +MicroBitStorage::MicroBitStorage() +{ + //initialise our magic block, if required. + size(); +} + +/** + * Writes the given number of bytes to the address specified. + * + * @param buffer the data to write. + * + * @param address the location in memory to write to. + * + * @param length the number of bytes to write. + * + * @note currently not implemented. + */ +int MicroBitStorage::writeBytes(uint8_t *buffer, uint32_t address, int length) +{ + (void) buffer; + (void) address; + (void) length; + + return MICROBIT_OK; +} + +/** + * Method for erasing a page in flash. + * + * @param page_address Address of the first word in the page to be erased. + */ +void MicroBitStorage::flashPageErase(uint32_t * page_address) +{ + // Turn on flash erase enable and wait until the NVMC is ready: + NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos); + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); + + // Erase page: + NRF_NVMC->ERASEPAGE = (uint32_t)page_address; + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); + + // Turn off flash erase enable and wait until the NVMC is ready: + NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos); + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); +} + +/** + * Function for copying words from one location to another. + * + * @param from the address to copy data from. + * + * @param to the address to copy the data to. + * + * @param sizeInWords the number of words to copy + */ +void MicroBitStorage::flashCopy(uint32_t* from, uint32_t* to, int sizeInWords) +{ + // Turn on flash write enable and wait until the NVMC is ready: + NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos); + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {}; + + for(int i = 0; i < sizeInWords; i++) + { + *(to + i) = *(from + i); + while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {}; + } + + // Turn off flash write enable and wait until the NVMC is ready: + NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos); + while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {}; +} + +/** + * Method for writing a word of data in flash with a value. + * + * @param address Address of the word to change. + * + * @param value Value to be written to flash. + */ +void MicroBitStorage::flashWordWrite(uint32_t * address, uint32_t value) +{ + // Turn on flash write enable and wait until the NVMC is ready: + NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos); + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); + + *address = value; + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); + + // Turn off flash write enable and wait until the NVMC is ready: + NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos); + + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); +} + +/** + * Function for populating the scratch page with a KeyValueStore. + * + * @param store the KeyValueStore struct to write to the scratch page. + */ +void MicroBitStorage::scratchKeyValueStore(KeyValueStore store) +{ + //calculate our various offsets + uint32_t *s = (uint32_t *) &store; + uint32_t pg_size = NRF_FICR->CODEPAGESIZE; + + uint32_t *scratchPointer = (uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)); + + //KeyValueStore is word aligned. + int wordsToWrite = sizeof(KeyValueStore) / 4; + + //write the given KeyValueStore + for (int i = 0; i < wordsToWrite; i++) + { + flashWordWrite(scratchPointer, *s); + scratchPointer++; + s++; + } +} + +/** + * Function for populating the scratch page with a KeyValuePair. + * + * @param pair the KeyValuePair struct to write to the scratch page. + * + * @param flashPointer the pointer in flash where this KeyValuePair resides. This pointer + * is used to determine the offset into the scratch page, where the KeyValuePair should + * be written. + */ +void MicroBitStorage::scratchKeyValuePair(KeyValuePair pair, uint32_t* flashPointer) +{ + //we can only write using words + uint32_t *p = (uint32_t *) &pair; + + //calculate our various offsets + uint32_t pg_size = NRF_FICR->CODEPAGESIZE; + uint32_t pg_num = NRF_FICR->CODESIZE - MICROBIT_STORAGE_STORE_PAGE_OFFSET; + + uint32_t *scratchPointer = (uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)); + uint32_t *flashBlockPointer = (uint32_t *)(pg_size * pg_num); + + uint32_t flashPointerOffset = flashPointer - flashBlockPointer; + + scratchPointer += flashPointerOffset; + + //KeyValuePair is word aligned... + int wordsToWrite = sizeof(KeyValuePair) / 4; + + //write + for (int i = 0; i < wordsToWrite; i++) + { + flashWordWrite(scratchPointer, *p); + scratchPointer++; + p++; + } +} + +/** + * Places a given key, and it's corresponding value into flash at the earliest + * available point. + * + * @param key the unique name that should be used as an identifier for the given data. + * The key is presumed to be null terminated. + * + * @param data a pointer to the beginning of the data to be persisted. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the storage page is full + */ +int MicroBitStorage::put(const char *key, uint8_t *data) +{ + KeyValuePair pair = KeyValuePair(); + + memcpy(pair.key, key, min(sizeof(pair.key), strlen(key))); + memcpy(pair.value, data, sizeof(pair.value)); + + //calculate our various offsets. + uint32_t pg_size = NRF_FICR->CODEPAGESIZE; + uint32_t *flashPointer = (uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_STORE_PAGE_OFFSET)); + uint32_t *flashBlockPointer = flashPointer; + uint32_t *scratchPointer = (uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)); + + uint32_t kvStoreSize = sizeof(KeyValueStore) / 4; + uint32_t kvPairSize = sizeof(KeyValuePair) / 4; + + int storeSize = size(); + + //our KeyValueStore struct is always at 0 + flashPointer += kvStoreSize; + + KeyValuePair storedPair = KeyValuePair(); + + int found = 0; + + //erase our scratch page + flashPageErase(scratchPointer); + + //iterate through key value pairs in flash, writing them to the scratch page. + for(int i = 0; i < storeSize; i++) + { + memcpy(&storedPair, flashPointer, sizeof(KeyValuePair)); + + //check if the keys match... + if(strcmp((char *)storedPair.key, (char *)pair.key) == 0) + { + found = 1; + //scratch our KeyValueStore struct so that it is preserved. + scratchKeyValueStore(KeyValueStore(MICROBIT_STORAGE_MAGIC, storeSize)); + scratchKeyValuePair(pair, flashPointer); + } + else + { + scratchKeyValuePair(storedPair, flashPointer); + } + + flashPointer += kvPairSize; + } + + if(!found) + { + //if we haven't got a match for the key, check we can add a new KeyValuePair + if(storeSize == (int)((pg_size - kvStoreSize) / MICROBIT_STORAGE_BLOCK_SIZE)) + return MICROBIT_NO_RESOURCES; + + storeSize += 1; + + //scratch our updated values. + scratchKeyValueStore(KeyValueStore(MICROBIT_STORAGE_MAGIC, storeSize)); + scratchKeyValuePair(pair, flashPointer); + } + + //erase our storage page + flashPageErase((uint32_t *)flashBlockPointer); + + //copy from scratch to storage. + flashCopy((uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)), flashBlockPointer, kvStoreSize + (storeSize * kvPairSize)); + + return MICROBIT_OK; +} + +/** + * Places a given key, and it's corresponding value into flash at the earliest + * available point. + * + * @param key the unique name that should be used as an identifier for the given data. + * + * @param data a pointer to the beginning of the data to be persisted. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the storage page is full + */ +int MicroBitStorage::put(ManagedString key, uint8_t* data) +{ + return put((char *)key.toCharArray(), data); +} + +/** + * Retreives a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return a pointer to a heap allocated KeyValuePair struct, this pointer will be + * NULL if the key was not found in storage. + * + * @note it is up to the user to free memory after use. + */ +KeyValuePair* MicroBitStorage::get(const char* key) +{ + //calculate our offsets for our storage page + uint32_t pg_size = NRF_FICR->CODEPAGESIZE; + uint32_t pg_num = NRF_FICR->CODESIZE - MICROBIT_STORAGE_STORE_PAGE_OFFSET; + + uint32_t *flashBlockPointer = (uint32_t *)(pg_size * pg_num); + + int storeSize = size(); + + //we haven't got anything stored, so return... + if(storeSize == 0) + return NULL; + + //our KeyValueStore struct is always at 0 + flashBlockPointer += sizeof(KeyValueStore) / 4; + + KeyValuePair *pair = new KeyValuePair(); + + int i; + + //iterate through flash until we have a match, or drop out. + for(i = 0; i < storeSize; i++) + { + memcpy(pair, flashBlockPointer, sizeof(KeyValuePair)); + + if(strcmp(key,(char *)pair->key) == 0) + break; + + flashBlockPointer += sizeof(KeyValuePair) / 4; + } + + //clean up + if(i == storeSize) + { + delete pair; + return NULL; + } + + return pair; +} + +/** + * Retreives a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return a pointer to a heap allocated KeyValuePair struct, this pointer will be + * NULL if the key was not found in storage. + * + * @note it is up to the user to free memory after use. + */ +KeyValuePair* MicroBitStorage::get(ManagedString key) +{ + return get((char *)key.toCharArray()); +} + +/** + * Removes a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_DATA if the given key + * was not found in flash. + */ +int MicroBitStorage::remove(const char* key) +{ + //calculate our various offsets + uint32_t pg_size = NRF_FICR->CODEPAGESIZE; + uint32_t *flashPointer = (uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_STORE_PAGE_OFFSET)); + uint32_t *flashBlockPointer = flashPointer; + uint32_t *scratchPointer = (uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)); + + uint32_t kvStoreSize = sizeof(KeyValueStore) / 4; + uint32_t kvPairSize = sizeof(KeyValuePair) / 4; + + int storeSize = size(); + + //if we have no data, we have nothing to do. + if(storeSize == 0) + return MICROBIT_NO_DATA; + + //our KeyValueStore struct is always at 0 + flashPointer += kvStoreSize; + scratchPointer += kvStoreSize; + + KeyValuePair storedPair = KeyValuePair(); + + int found = 0; + + //set up our scratch area + flashPageErase(scratchPointer); + + //iterate through our flash copy pairs to scratch, unless there is a key patch + for(int i = 0; i < storeSize; i++) + { + memcpy(&storedPair, flashPointer, sizeof(KeyValuePair)); + + //if we have a match, don't increment our scratchPointer + if(strcmp((char *)storedPair.key, (char *)key) == 0) + { + found = 1; + //write our new KeyValueStore data + scratchKeyValueStore(KeyValueStore(MICROBIT_STORAGE_MAGIC, storeSize - 1)); + } + else + { + //otherwise copy the KeyValuePair from our storage page. + flashCopy(flashPointer, scratchPointer, sizeof(KeyValuePair) / 4); + scratchPointer += sizeof(KeyValuePair) / 4; + } + + flashPointer += sizeof(KeyValuePair) / 4; + } + + //if we haven't got a match, write our old KeyValueStore struct + if(!found) + { + scratchKeyValueStore(KeyValueStore(MICROBIT_STORAGE_MAGIC, storeSize)); + return MICROBIT_NO_DATA; + } + + //copy scratch to our storage page + flashPageErase((uint32_t *)flashBlockPointer); + flashCopy((uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)), flashBlockPointer, kvStoreSize + (storeSize * kvPairSize)); + + return MICROBIT_OK; +} + +/** + * Removes a KeyValuePair identified by a given key. + * + * @param key the unique name used to identify a KeyValuePair in flash. + * + * @return MICROBIT_OK on success, or MICROBIT_NO_DATA if the given key + * was not found in flash. + */ +int MicroBitStorage::remove(ManagedString key) +{ + return remove((char *)key.toCharArray()); +} + +/** + * The size of the flash based KeyValueStore. + * + * @return the number of entries in the key value store + */ +int MicroBitStorage::size() +{ + uint32_t pg_size = NRF_FICR->CODEPAGESIZE; + uint32_t pg_num = NRF_FICR->CODESIZE - MICROBIT_STORAGE_STORE_PAGE_OFFSET; + + uint32_t *flashBlockPointer = (uint32_t *)(pg_size * pg_num); + + KeyValueStore store = KeyValueStore(); + + //read our data! + memcpy(&store, flashBlockPointer, sizeof(KeyValueStore)); + + //if we haven't used flash before, we need to configure it + if(store.magic != MICROBIT_STORAGE_MAGIC) + { + store.magic = MICROBIT_STORAGE_MAGIC; + store.size = 0; + + //erase the scratch page and write our new KeyValueStore + flashPageErase((uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET))); + scratchKeyValueStore(store); + + //erase flash, and copy the scratch page over + flashPageErase((uint32_t *)flashBlockPointer); + flashCopy((uint32_t *)(pg_size * (NRF_FICR->CODESIZE - MICROBIT_STORAGE_SCRATCH_PAGE_OFFSET)), flashBlockPointer, pg_size/4); + } + + return store.size; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitThermometer.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,278 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "MicroBitThermometer.h" +#include "MicroBitSystemTimer.h" +#include "MicroBitFiber.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 "nrf_soc.h" +#include "nrf_sdm.h" + +/* + * Return to our predefined compiler settings. + */ +#if !defined(__arm) +#pragma GCC diagnostic pop +#endif + +/** + * Constructor. + * Create new MicroBitThermometer that gives an indication of the current temperature. + * + * @param _storage an instance of MicroBitStorage used to persist temperature offset data + * + * @param id the unique EventModel id of this component. Defaults to MICROBIT_ID_THERMOMETER. + * + * @code + * MicroBitStorage storage; + * MicroBitThermometer thermometer(storage); + * @endcode + */ +MicroBitThermometer::MicroBitThermometer(MicroBitStorage& _storage, uint16_t id) : + storage(&_storage) +{ + this->id = id; + this->samplePeriod = MICROBIT_THERMOMETER_PERIOD; + this->sampleTime = 0; + this->offset = 0; + + KeyValuePair *tempCalibration = storage->get(ManagedString("tempCal")); + + if(tempCalibration != NULL) + { + memcpy(&offset, tempCalibration->value, sizeof(int16_t)); + delete tempCalibration; + } +} + +/** + * Constructor. + * Create new MicroBitThermometer that gives an indication of the current temperature. + * + * @param id the unique EventModel id of this component. Defaults to MICROBIT_ID_THERMOMETER. + * + * @code + * MicroBitThermometer thermometer; + * @endcode + */ +MicroBitThermometer::MicroBitThermometer(uint16_t id) : + storage(NULL) +{ + this->id = id; + this->samplePeriod = MICROBIT_THERMOMETER_PERIOD; + this->sampleTime = 0; + this->offset = 0; +} + +/** + * Gets the current temperature of the microbit. + * + * @return the current temperature, in degrees celsius. + * + * @code + * thermometer.getTemperature(); + * @endcode + */ +int MicroBitThermometer::getTemperature() +{ + updateSample(); + return temperature - offset; +} + + +/** + * Updates the temperature sample of this instance of MicroBitThermometer + * only if isSampleNeeded() indicates that an update is required. + * + * This call also will add the thermometer to fiber components to receive + * periodic callbacks. + * + * @return MICROBIT_OK on success. + */ +int MicroBitThermometer::updateSample() +{ + if(!(status & MICROBIT_THERMOMETER_ADDED_TO_IDLE)) + { + // If we're running under a fiber scheduer, register ourselves for a periodic callback to keep our data up to date. + // Otherwise, we do just do this on demand, when polled through our read() interface. + fiber_add_idle_component(this); + status |= MICROBIT_THERMOMETER_ADDED_TO_IDLE; + } + + // check if we need to update our sample... + if(isSampleNeeded()) + { + int32_t processorTemperature; + uint8_t sd_enabled; + + // For now, we just rely on the nrf senesor to be the most accurate. + // The compass module also has a temperature sensor, and has the lowest power consumption, so will run the cooler... + // ...however it isn't trimmed for accuracy during manufacture, so requires calibration. + + sd_softdevice_is_enabled(&sd_enabled); + + if (sd_enabled) + { + // If Bluetooth is enabled, we need to go through the Nordic software to safely do this + sd_temp_get(&processorTemperature); + } + else + { + // Othwerwise, we access the information directly... + uint32_t *TEMP = (uint32_t *)0x4000C508; + + NRF_TEMP->TASKS_START = 1; + + while (NRF_TEMP->EVENTS_DATARDY == 0); + + NRF_TEMP->EVENTS_DATARDY = 0; + + processorTemperature = *TEMP; + + NRF_TEMP->TASKS_STOP = 1; + } + + + // Record our reading... + temperature = processorTemperature / 4; + + // Schedule our next sample. + sampleTime = system_timer_current_time() + samplePeriod; + + // Send an event to indicate that we'e updated our temperature. + MicroBitEvent e(id, MICROBIT_THERMOMETER_EVT_UPDATE); + } + + return MICROBIT_OK; +}; + +/** + * Indicates if we'd like some processor time to sense the temperature. + * + * @returns 1 if we'd like some processor time, 0 otherwise. + */ +int MicroBitThermometer::isIdleCallbackNeeded() +{ + return isSampleNeeded(); +} + +/** + * Periodic callback from MicroBit idle thread. + */ +void MicroBitThermometer::idleTick() +{ + updateSample(); +} + +/** + * Determines if we're due to take another temperature reading + * + * @return 1 if we're due to take a temperature reading, 0 otherwise. + */ +int MicroBitThermometer::isSampleNeeded() +{ + return system_timer_current_time() >= sampleTime; +} + +/** + * Set the sample rate at which the temperatureis read (in ms). + * + * The default sample period is 1 second. + * + * @param period the requested time between samples, in milliseconds. + * + * @note the temperature is always read in the background, and is only updated + * when the processor is idle, or when the temperature is explicitly read. + */ +void MicroBitThermometer::setPeriod(int period) +{ + updateSample(); + samplePeriod = period; +} + +/** + * Reads the currently configured sample rate of the thermometer. + * + * @return The time between samples, in milliseconds. + */ +int MicroBitThermometer::getPeriod() +{ + return samplePeriod; +} + +/** + * Set the value that is used to offset the raw silicon temperature. + * + * @param offset the offset for the silicon temperature + * + * @return MICROBIT_OK on success + */ +int MicroBitThermometer::setOffset(int offset) +{ + if(this->storage != NULL) + this->storage->put(ManagedString("tempCal"), (uint8_t *)&offset); + + this->offset = offset; + + return MICROBIT_OK; +} + +/** + * Retreive the value that is used to offset the raw silicon temperature. + * + * @return the current offset. + */ +int MicroBitThermometer::getOffset() +{ + return offset; +} + +/** + * This member function fetches the raw silicon temperature, and calculates + * the value used to offset the raw silicon temperature based on a given temperature. + * + * @param calibrationTemp the temperature used to calculate the raw silicon temperature + * offset. + * + * @return MICROBIT_OK on success + */ +int MicroBitThermometer::setCalibration(int calibrationTemp) +{ + updateSample(); + return setOffset(temperature - calibrationTemp); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/types/ManagedString.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,489 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a ManagedString. + * + * Uses basic reference counting to implement a copy-assignable, immutable string. + * + * This maps closely to the constructs found in many high level application languages, + * such as Touch Develop. + * + * Written from first principles here, for several reasons: + * 1) std::shared_ptr is not yet availiable on the ARMCC compiler + * + * 2) to reduce memory footprint - we don't need many of the other features in the std library + * + * 3) it makes an interesting case study for anyone interested in seeing how it works! + * + * 4) we need explicit reference counting to inter-op with low-level application langauge runtimes. + * + * 5) the reference counting needs to also work for read-only, flash-resident strings + */ +#include <string.h> +#include <stdlib.h> + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "ManagedString.h" +#include "MicroBitCompat.h" + +static const char empty[] __attribute__ ((aligned (4))) = "\xff\xff\0\0\0"; + +/** + * Internal constructor helper. + * + * Configures this ManagedString to refer to the static EmptyString + */ +void ManagedString::initEmpty() +{ + ptr = (StringData*)(void*)empty; +} + +/** + * Internal constructor helper. + * + * Creates this ManagedString based on a given null terminated char array. + */ +void ManagedString::initString(const char *str) +{ + // Initialise this ManagedString as a new string, using the data provided. + // We assume the string is sane, and null terminated. + int len = strlen(str); + ptr = (StringData *) malloc(4+len+1); + ptr->init(); + ptr->len = len; + memcpy(ptr->data, str, len+1); +} + +/** + * Constructor. + * Create a managed string from a specially prepared string literal. + * + * @param ptr The literal - first two bytes should be 0xff, then the length in little endian, then the literal. The literal has to be 4-byte aligned. + * + * @code + * static const char hello[] __attribute__ ((aligned (4))) = "\xff\xff\x05\x00" "Hello"; + * ManagedString s((StringData*)(void*)hello); + * @endcode + */ +ManagedString::ManagedString(StringData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Get current ptr, do not decr() it, and set the current instance to empty string. + * + * This is to be used by specialized runtimes which pass StringData around. + */ +StringData* ManagedString::leakData() +{ + StringData *res = ptr; + initEmpty(); + return res; +} + +/** + * Constructor. + * + * Create a managed string from a given integer. + * + * @param value The integer from which to create the ManagedString. + * + * @code + * ManagedString s(20); + * @endcode + */ +ManagedString::ManagedString(const int value) +{ + char str[12]; + + itoa(value, str); + initString(str); +} + +/** + * Constructor. + * Create a managed string from a given char. + * + * @param value The character from which to create the ManagedString. + * + * @code + * ManagedString s('a'); + * @endcode + */ +ManagedString::ManagedString(const char value) +{ + char str[2] = {value, 0}; + initString(str); +} + + +/** + * Constructor. + * + * Create a managed string from a pointer to an 8-bit character buffer. + * + * The buffer is copied to ensure safe memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * + * @code + * ManagedString s("abcdefg"); + * @endcode + */ +ManagedString::ManagedString(const char *str) +{ + // Sanity check. Return EmptyString for anything distasteful + if (str == NULL || *str == 0) + { + initEmpty(); + return; + } + + initString(str); +} + +/** + * Private Constructor. + * + * Create a managed string based on a concat of two strings. + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str1 The first string on which to base the new ManagedString. + * + * @param str2 The second string on which to base the new ManagedString. + */ +ManagedString::ManagedString(const ManagedString &s1, const ManagedString &s2) +{ + // Calculate length of new string. + int len = s1.length() + s2.length(); + + // Create a new buffer for holding the new string data. + ptr = (StringData*) malloc(4+len+1); + ptr->init(); + ptr->len = len; + + // Enter the data, and terminate the string. + memcpy(ptr->data, s1.toCharArray(), s1.length()); + memcpy(ptr->data + s1.length(), s2.toCharArray(), s2.length()); + ptr->data[len] = 0; +} + + +/** + * Constructor. + * Create a ManagedString from a PacketBuffer. All bytes in the + * PacketBuffer are added to the ManagedString. + * + * @param buffer The PacktBuffer from which to create the ManagedString. + * + * @code + * ManagedString s = radio.datagram.recv(); + * @endcode + */ +ManagedString::ManagedString(PacketBuffer buffer) +{ + // Allocate a new buffer ( just in case the data is not NULL terminated). + ptr = (StringData*) malloc(4+buffer.length()+1); + ptr->init(); + + // Store the length of the new string + ptr->len = buffer.length(); + memcpy(ptr->data, buffer.getBytes(), buffer.length()); + ptr->data[buffer.length()] = 0; +} + +/** + * Constructor. + * Create a ManagedString from a pointer to an 8-bit character buffer of a given length. + * + * The buffer is copied to ensure sane memory management (the supplied + * character buffer may be declared on the stack for instance). + * + * @param str The character array on which to base the new ManagedString. + * + * @param length The length of the character array + * + * @code + * ManagedString s("abcdefg",7); + * @endcode + */ +ManagedString::ManagedString(const char *str, const int16_t length) +{ + // Sanity check. Return EmptyString for anything distasteful + if (str == NULL || *str == 0 || (uint16_t)length > strlen(str)) // XXX length should be unsigned on the interface + { + initEmpty(); + return; + } + + + // Allocate a new buffer, and create a NULL terminated string. + ptr = (StringData*) malloc(4+length+1); + ptr->init(); + // Store the length of the new string + ptr->len = length; + memcpy(ptr->data, str, length); + ptr->data[length] = 0; +} + +/** + * Copy constructor. + * Makes a new ManagedString identical to the one supplied. + * + * Shares the character buffer and reference count with the supplied ManagedString. + * + * @param s The ManagedString to copy. + * + * @code + * ManagedString s("abcdefg"); + * ManagedString p(s); + * @endcode + */ +ManagedString::ManagedString(const ManagedString &s) +{ + ptr = s.ptr; + ptr->incr(); +} + + +/** + * Default constructor. + * + * Create an empty ManagedString. + * + * @code + * ManagedString s(); + * @endcode + */ +ManagedString::ManagedString() +{ + initEmpty(); +} + +/** + * Destructor. + * + * Free this ManagedString, and decrement the reference count to the + * internal character buffer. + * + * If we're holding the last reference, also free the character buffer. + */ +ManagedString::~ManagedString() +{ + ptr->decr(); +} + +/** + * Copy assign operation. + * + * Called when one ManagedString is assigned the value of another. + * + * If the ManagedString being assigned is already refering to a character buffer, + * decrement the reference count and free up the buffer as necessary. + * + * Then, update our character buffer to refer to that of the supplied ManagedString, + * and increase its reference count. + * + * @param s The ManagedString to copy. + * + * @code + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * p = s // p now points to s, s' ref is incremented + * @endcode + */ +ManagedString& ManagedString::operator = (const ManagedString& s) +{ + if (this->ptr == s.ptr) + return *this; + + ptr->decr(); + ptr = s.ptr; + ptr->incr(); + + return *this; +} + +/** + * Equality operation. + * + * Called when one ManagedString is tested to be equal to another using the '==' operator. + * + * @param s The ManagedString to test ourselves against. + * + * @return true if this ManagedString is identical to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * ManagedString p("efgh"); + * + * if(p == s) + * display.scroll("We are the same!"); + * else + * display.scroll("We are different!"); //p is not equal to s - this will be called + * @endcode + */ +bool ManagedString::operator== (const ManagedString& s) +{ + return ((length() == s.length()) && (strcmp(toCharArray(),s.toCharArray())==0)); +} + +/** + * Inequality operation. + * + * Called when one ManagedString is tested to be less than another using the '<' operator. + * + * @param s The ManagedString to test ourselves against. + * + * @return true if this ManagedString is alphabetically less than to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(s < p) + * display.scroll("a is before b!"); //a is before b + * else + * display.scroll("b is before a!"); + * @endcode + */ +bool ManagedString::operator< (const ManagedString& s) +{ + return (strcmp(toCharArray(), s.toCharArray())<0); +} + +/** + * Inequality operation. + * + * Called when one ManagedString is tested to be greater than another using the '>' operator. + * + * @param s The ManagedString to test ourselves against. + * + * @return true if this ManagedString is alphabetically greater than to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * ManagedString s("a"); + * ManagedString p("b"); + * + * if(p>a) + * display.scroll("b is after a!"); //b is after a + * else + * display.scroll("a is after b!"); + * @endcode + */ +bool ManagedString::operator> (const ManagedString& s) +{ + return (strcmp(toCharArray(), s.toCharArray())>0); +} + +/** + * Extracts a ManagedString from this string, at the position provided. + * + * @param start The index of the first character to extract, indexed from zero. + * + * @param length The number of characters to extract from the start position + * + * @return a ManagedString representing the requested substring. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcdefg"); + * + * display.scroll(s.substring(0,2)) // displays "ab" + * @endcode + */ +ManagedString ManagedString::substring(int16_t start, int16_t length) +{ + // If the parameters are illegal, just return a reference to the empty string. + if (start >= this->length()) + return ManagedString(ManagedString::EmptyString); + + // Compute a safe copy length; + length = min(this->length()-start, length); + + // Build a ManagedString from this. + return ManagedString(toCharArray()+start, length); +} + +/** + * Concatenates this string with the one provided. + * + * @param s The ManagedString to concatenate. + * + * @return a new ManagedString representing the joined strings. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * ManagedString p("efgh") + * + * display.scroll(s + p) // scrolls "abcdefgh" + * @endcode + */ +ManagedString ManagedString::operator+ (ManagedString& s) +{ + // If the other string is empty, nothing to do! + if(s.length() == 0) + return *this; + + if (length() == 0) + return s; + + return ManagedString(*this, s); +} + + +/** + * Provides a character value at a given position in the string, indexed from zero. + * + * @param index The position of the character to return. + * + * @return the character at posisiton index, zero if index is invalid. + * + * @code + * MicroBitDisplay display; + * ManagedString s("abcd"); + * + * display.scroll(s.charAt(1)) // scrolls "b" + * @endcode + */ +char ManagedString::charAt(int16_t index) +{ + return (index >=0 && index < length()) ? ptr->data[index] : 0; +} + +/** + * Empty string constant literal + */ +ManagedString ManagedString::EmptyString((StringData*)(void*)empty);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/types/Matrix4.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,285 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "Matrix4.h" +#include "mbed.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. + * + * @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 float[size]; + else + data = NULL; +} + +/** + * Constructor. + * Create a matrix that is an identical copy of the given matrix. + * + * @param matrix The matrix to copy. + * + * @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 float[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. + * + * @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. + * + * @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. 0 is returned if the given index is out of range. + * + * @code + * float v = matrix.get(1,2); + * @endcode + */ +float 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. + * + * @code + * matrix.set(1,2,42.0); + * @endcode + */ +void Matrix4::set(int row, int col, float v) +{ + if (row < 0 || col < 0 || row >= rows || col >= cols) + return; + + data[width() * row + col] = v; +} + +/** + * Transposes this matrix. + * + * @return the resultant matrix. + * + * @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). + * + * @param matrix the matrix to multiply this matrix's values against. + * + * @param transpose Transpose the matrices before multiplication. Defaults to false. + * + * @return the resultant matrix. An empty matrix is returned if the operation canot be completed. + * + * @code + * Matrix result = matrixA.multiply(matrixB); + * @endcode + */ +Matrix4 Matrix4::multiply(Matrix4 &matrix, bool transpose) +{ + int w = transpose ? height() : width(); + int h = transpose ? width() : height(); + + if (w != matrix.height()) + return Matrix4(0, 0); + + Matrix4 result(h, matrix.width()); + + for (int r = 0; r < result.height(); r++) + { + for (int c = 0; c < result.width(); c++) + { + float v = 0.0; + + for (int i = 0; i < w; i++) + v += (transpose ? get(i, r) : 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. + * + * @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]; + + float 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. + * + * Frees any memory consumed by this Matrix4 instance. + */ +Matrix4::~Matrix4() +{ + if (data != NULL) + { + delete data; + data = NULL; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/types/MicroBitEvent.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,97 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a MicroBitEvent + * + * It represents a common event that is generated by the various components on the micro:bit. + */ +#include "MicroBitConfig.h" +#include "MicroBitEvent.h" +#include "MicroBitSystemTimer.h" +#include "EventModel.h" + +EventModel* EventModel::defaultEventBus = NULL; + +/** + * Constructor. + * + * @param src The id of the MicroBit Component that generated the event e.g. MICROBIT_ID_BUTTON_A. + * + * @param value A component specific code indicating the cause of the event. + * + * @param mode Optional definition of how the event should be processed after construction (if at all): + * CREATE_ONLY: MicroBitEvent is initialised, and no further processing takes place. + * CREATE_AND_FIRE: MicroBitEvent is initialised, and its event handlers are immediately fired (not suitable for use in interrupts!). + * + * @code + * // Create and launch an event using the default configuration + * MicrobitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK); + * + * // Create an event only, do not fire onto an EventModel. + * MicrobitEvent evt(id,MICROBIT_BUTTON_EVT_CLICK,CREATE_AND_FIRE); + * @endcode + */ +MicroBitEvent::MicroBitEvent(uint16_t source, uint16_t value, MicroBitEventLaunchMode mode) +{ + this->source = source; + this->value = value; + this->timestamp = system_timer_current_time(); + + if(mode != CREATE_ONLY) + this->fire(); +} + +/** + * Default constructor - initialises all values, and sets timestamp to the current time. + */ +MicroBitEvent::MicroBitEvent() +{ + this->source = 0; + this->value = 0; + this->timestamp = system_timer_current_time(); +} + +/** + * Fires this MicroBitEvent onto the Default EventModel, or a custom one! + */ +void MicroBitEvent::fire() +{ + if(EventModel::defaultEventBus) + EventModel::defaultEventBus->send(*this); +} + + +/** + * Constructor. + * Create a new MicroBitEventQueueItem. + * + * @param evt The event to be queued. + */ +MicroBitEventQueueItem::MicroBitEventQueueItem(MicroBitEvent evt) +{ + this->evt = evt; + this->next = NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/types/MicroBitImage.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,887 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for a MicroBitImage. + * + * An MicroBitImage is a simple bitmap representation of an image. + * n.b. This is a mutable, managed type. + */ + +#include "MicroBitConfig.h" +#include "MicroBitImage.h" +#include "MicroBitFont.h" +#include "MicroBitCompat.h" +#include "ManagedString.h" +#include "ErrorNo.h" + + +/** + * The null image. We actally create a small one byte buffer here, just to keep NULL pointers out of the equation. + */ +static const uint16_t empty[] __attribute__ ((aligned (4))) = { 0xffff, 1, 1, 0, }; +MicroBitImage MicroBitImage::EmptyImage((ImageData*)(void*)empty); + +/** + * Default Constructor. + * Creates a new reference to the empty MicroBitImage bitmap + * + * @code + * MicroBitImage i(); //an empty image instance + * @endcode + */ +MicroBitImage::MicroBitImage() +{ + // Create new reference to the EmptyImage and we're done. + init_empty(); +} + + +/** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param x the width of the image. + * + * @param y the height of the image. + * + * Bitmap buffer is linear, with 8 bits per pixel, row by row, + * top to bottom with no word alignment. Stride is therefore the image width in pixels. + * in where w and h are width and height respectively, the layout is therefore: + * + * |[0,0]...[w,o][1,0]...[w,1] ... [[w,h] + * + * A copy of the image is made in RAM, as images are mutable. + * + * TODO: Consider an immutable flavour, which might save us RAM for animation spritesheets... + * ...as these could be kept in FLASH. + */ +MicroBitImage::MicroBitImage(const int16_t x, const int16_t y) +{ + this->init(x,y,NULL); +} + +/** + * Copy Constructor. + * Add ourselves as a reference to an existing MicroBitImage. + * + * @param image The MicroBitImage to reference. + * + * @code + * MicroBitImage i("0,1,0,1,0\n"); + * MicroBitImage i2(i); //points to i + * @endcode + */ +MicroBitImage::MicroBitImage(const MicroBitImage &image) +{ + ptr = image.ptr; + ptr->incr(); +} + +/** + * Constructor. + * Create a blank bitmap representation of a given size. + * + * @param s A text based representation of the image given whitespace delimited numeric values. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * @endcode + */ +MicroBitImage::MicroBitImage(const char *s) +{ + int width = 0; + int height = 0; + int count = 0; + int digit = 0; + + char parseBuf[10]; + + const char *parseReadPtr; + char *parseWritePtr; + uint8_t *bitmapPtr; + + if (s == NULL) + { + init_empty(); + return; + } + + // First pass: Parse the string to determine the geometry of the image. + // We do this from first principles to avoid unecessary load of the strtok() libs etc. + parseReadPtr = s; + + while (*parseReadPtr) + { + if (isdigit(*parseReadPtr)) + { + // Ignore numbers. + digit = 1; + } + else if (*parseReadPtr =='\n') + { + if (digit) + { + count++; + digit = 0; + } + + height++; + + width = count > width ? count : width; + count = 0; + } + else + { + if (digit) + { + count++; + digit = 0; + } + } + + parseReadPtr++; + } + + this->init(width, height, NULL); + + // Second pass: collect the data. + parseReadPtr = s; + parseWritePtr = parseBuf; + bitmapPtr = this->getBitmap(); + + while (*parseReadPtr) + { + if (isdigit(*parseReadPtr)) + { + *parseWritePtr = *parseReadPtr; + parseWritePtr++; + } + else + { + *parseWritePtr = 0; + if (parseWritePtr > parseBuf) + { + *bitmapPtr = atoi(parseBuf); + bitmapPtr++; + parseWritePtr = parseBuf; + } + } + + parseReadPtr++; + } +} + +/** + * Constructor. + * Create an image from a specially prepared constant array, with no copying. Will call ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then width, 0, height, 0, and the bitmap. Width and height are 16 bit. The literal has to be 4-byte aligned. + * + * @code + * static const uint8_t heart[] __attribute__ ((aligned (4))) = { 0xff, 0xff, 10, 0, 5, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i((ImageData*)(void*)heart); + * @endcode + */ +MicroBitImage::MicroBitImage(ImageData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Get current ptr, do not decr() it, and set the current instance to empty image. + * + * This is to be used by specialized runtimes which pass ImageData around. + */ +ImageData *MicroBitImage::leakData() +{ + ImageData* res = ptr; + init_empty(); + return res; +} + + +/** + * Constructor. + * Create a bitmap representation of a given size, based on a given buffer. + * + * @param x the width of the image. + * + * @param y the height of the image. + * + * @param bitmap a 2D array representing the image. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * @endcode + */ +MicroBitImage::MicroBitImage(const int16_t x, const int16_t y, const uint8_t *bitmap) +{ + this->init(x,y,bitmap); +} + +/** + * Destructor. + * + * Removes buffer resources held by the instance. + */ +MicroBitImage::~MicroBitImage() +{ + ptr->decr(); +} + +/** + * Internal constructor which defaults to the EmptyImage instance variable + */ +void MicroBitImage::init_empty() +{ + ptr = (ImageData*)(void*)empty; +} + +/** + * Internal constructor which provides sanity checking and initialises class properties. + * + * @param x the width of the image + * + * @param y the height of the image + * + * @param bitmap an array of integers that make up an image. + */ +void MicroBitImage::init(const int16_t x, const int16_t y, const uint8_t *bitmap) +{ + //sanity check size of image - you cannot have a negative sizes + if(x < 0 || y < 0) + { + init_empty(); + return; + } + + + // Create a copy of the array + ptr = (ImageData*)malloc(sizeof(ImageData) + x * y); + ptr->init(); + ptr->width = x; + ptr->height = y; + + // create a linear buffer to represent the image. We could use a jagged/2D array here, but experimentation + // showed this had a negative effect on memory management (heap fragmentation etc). + + if (bitmap) + this->printImage(x,y,bitmap); + else + this->clear(); +} + +/** + * Copy assign operation. + * + * Called when one MicroBitImage is assigned the value of another using the '=' operator. + * + * Decrement our reference count and free up the buffer as necessary. + * + * Then, update our buffer to refer to that of the supplied MicroBitImage, + * and increase its reference count. + * + * @param s The MicroBitImage to reference. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * MicroBitImage i1(); + * i1 = i; // i1 now references i + * @endcode + */ +MicroBitImage& MicroBitImage::operator = (const MicroBitImage& i) +{ + if(ptr == i.ptr) + return *this; + + ptr->decr(); + ptr = i.ptr; + ptr->incr(); + + return *this; +} + +/** + * Equality operation. + * + * Called when one MicroBitImage is tested to be equal to another using the '==' operator. + * + * @param i The MicroBitImage to test ourselves against. + * + * @return true if this MicroBitImage is identical to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * MicroBitImage i(); + * MicroBitImage i1(); + * + * if(i == i1) //will be true + * display.scroll("true"); + * @endcode + */ +bool MicroBitImage::operator== (const MicroBitImage& i) +{ + if (ptr == i.ptr) + return true; + else + return (ptr->width == i.ptr->width && ptr->height == i.ptr->height && (memcmp(getBitmap(), i.ptr->data, getSize())==0)); +} + + +/** + * Resets all pixels in this image to 0. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.clear(); + * @endcode + */ +void MicroBitImage::clear() +{ + memclr(getBitmap(), getSize()); +} + +/** + * Sets the pixel at the given co-ordinates to a given value. + * + * @param x The co-ordinate of the pixel to change. + * + * @param y The co-ordinate of the pixel to change. + * + * @param value The new value of the pixel (the brightness level 0-255) + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.setPixelValue(0,0,255); + * @endcode + * + * @note all coordinates originate from the top left of an image. + */ +int MicroBitImage::setPixelValue(int16_t x , int16_t y, uint8_t value) +{ + //sanity check + if(x >= getWidth() || y >= getHeight() || x < 0 || y < 0) + return MICROBIT_INVALID_PARAMETER; + + this->getBitmap()[y*getWidth()+x] = value; + return MICROBIT_OK; +} + +/** + * Retreives the value of a given pixel. + * + * @param x The x co-ordinate of the pixel to read. Must be within the dimensions of the image. + * + * @param y The y co-ordinate of the pixel to read. Must be within the dimensions of the image. + * + * @return The value assigned to the given pixel location (the brightness level 0-255), or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicroBitImage i("0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n1,0,1,0,1\n0,1,0,1,0\n"); // 5x5 image + * i.getPixelValue(0,0); //should be 0; + * @endcode + */ +int MicroBitImage::getPixelValue(int16_t x , int16_t y) +{ + //sanity check + if(x >= getWidth() || y >= getHeight() || x < 0 || y < 0) + return MICROBIT_INVALID_PARAMETER; + + return this->getBitmap()[y*getWidth()+x]; +} + +/** + * Replaces the content of this image with that of a given 2D array representing + * the image. + * + * @param x the width of the image. Must be within the dimensions of the image. + * + * @param y the width of the image. Must be within the dimensions of the image. + * + * @param bitmap a 2D array representing the image. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(); + * i.printImage(0,0,heart); + * @endcode + * + * @note all coordinates originate from the top left of an image. + */ +int MicroBitImage::printImage(int16_t width, int16_t height, const uint8_t *bitmap) +{ + const uint8_t *pIn; + uint8_t *pOut; + int pixelsToCopyX, pixelsToCopyY; + + // Sanity check. + if (width <= 0 || width <= 0 || bitmap == NULL) + return MICROBIT_INVALID_PARAMETER; + + // Calcualte sane start pointer. + pixelsToCopyX = min(width,this->getWidth()); + pixelsToCopyY = min(height,this->getHeight()); + + pIn = bitmap; + pOut = this->getBitmap(); + + // Copy the image, stride by stride. + for (int i=0; i<pixelsToCopyY; i++) + { + memcpy(pOut, pIn, pixelsToCopyX); + pIn += width; + pOut += this->getWidth(); + } + + return MICROBIT_OK; +} + +/** + * Pastes a given bitmap at the given co-ordinates. + * + * Any pixels in the relvant area of this image are replaced. + * + * @param image The MicroBitImage to paste. + * + * @param x The leftmost X co-ordinate in this image where the given image should be pasted. Defaults to 0. + * + * @param y The uppermost Y co-ordinate in this image where the given image should be pasted. Defaults to 0. + * + * @param alpha set to 1 if transparency clear pixels in given image should be treated as transparent. Set to 0 otherwise. Defaults to 0. + * + * @return The number of pixels written. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); // a big heart + * i.paste(i, -5, 0); // a small heart + * @endcode + */ +int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8_t alpha) +{ + uint8_t *pIn, *pOut; + int cx, cy; + int pxWritten = 0; + + // Sanity check. + // We permit writes that overlap us, but ones that are clearly out of scope we can filter early. + if (x >= getWidth() || y >= getHeight() || x+image.getWidth() <= 0 || y+image.getHeight() <= 0) + return 0; + + //Calculate the number of byte we need to copy in each dimension. + cx = x < 0 ? min(image.getWidth() + x, getWidth()) : min(image.getWidth(), getWidth() - x); + cy = y < 0 ? min(image.getHeight() + y, getHeight()) : min(image.getHeight(), getHeight() - y); + + // Calculate sane start pointer. + pIn = image.ptr->data; + pIn += (x < 0) ? -x : 0; + pIn += (y < 0) ? -image.getWidth()*y : 0; + + pOut = getBitmap(); + pOut += (x > 0) ? x : 0; + pOut += (y > 0) ? getWidth()*y : 0; + + // Copy the image, stride by stride + // If we want primitive transparecy, we do this byte by byte. + // If we don't, use a more efficient block memory copy instead. Every little helps! + + if (alpha) + { + for (int i=0; i<cy; i++) + { + for (int j=0; j<cx; j++) + { + // Copy this byte if appropriate. + if (*(pIn+j) != 0){ + *(pOut+j) = *(pIn+j); + pxWritten++; + } + } + + pIn += image.getWidth(); + pOut += getWidth(); + } + } + else + { + for (int i=0; i<cy; i++) + { + memcpy(pOut, pIn, cx); + + pxWritten += cx; + pIn += image.getWidth(); + pOut += getWidth(); + } + } + + return pxWritten; +} + +/** + * Prints a character to the display at the given location + * + * @param c The character to display. + * + * @param x The x co-ordinate of on the image to place the top left of the character. Defaults to 0. + * + * @param y The y co-ordinate of on the image to place the top left of the character. Defaults to 0. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicroBitImage i(5,5); + * i.print('a'); + * @endcode + */ +int MicroBitImage::print(char c, int16_t x, int16_t y) +{ + unsigned char v; + int x1, y1; + + MicroBitFont font = MicroBitFont::getSystemFont(); + + // Sanity check. Silently ignore anything out of bounds. + if (x >= getWidth() || y >= getHeight() || c < MICROBIT_FONT_ASCII_START || c > font.asciiEnd) + return MICROBIT_INVALID_PARAMETER; + + // Paste. + int offset = (c-MICROBIT_FONT_ASCII_START) * 5; + + for (int row=0; row<MICROBIT_FONT_HEIGHT; row++) + { + v = (char)*(font.characters + offset); + + offset++; + + // Update our Y co-ord write position + y1 = y+row; + + for (int col = 0; col < MICROBIT_FONT_WIDTH; col++) + { + // Update our X co-ord write position + x1 = x+col; + + if (x1 < getWidth() && y1 < getHeight()) + this->getBitmap()[y1*getWidth()+x1] = (v & (0x10 >> col)) ? 255 : 0; + } + } + + return MICROBIT_OK; +} + + +/** + * Shifts the pixels in this Image a given number of pixels to the left. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); // a big heart + * i.shiftLeft(5); // a small heart + * @endcode + */ +int MicroBitImage::shiftLeft(int16_t n) +{ + uint8_t *p = getBitmap(); + int pixels = getWidth()-n; + + if (n <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + if(n >= getWidth()) + { + clear(); + return MICROBIT_OK; + } + + for (int y = 0; y < getHeight(); y++) + { + // Copy, and blank fill the rightmost column. + memcpy(p, p+n, pixels); + memclr(p+pixels, n); + p += getWidth(); + } + + return MICROBIT_OK; +} + +/** + * Shifts the pixels in this Image a given number of pixels to the right. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); // a big heart + * i.shiftLeft(5); // a small heart + * i.shiftRight(5); // a big heart + * @endcode + */ +int MicroBitImage::shiftRight(int16_t n) +{ + uint8_t *p = getBitmap(); + int pixels = getWidth()-n; + + if (n <= 0) + return MICROBIT_INVALID_PARAMETER; + + if(n >= getWidth()) + { + clear(); + return MICROBIT_OK; + } + + for (int y = 0; y < getHeight(); y++) + { + // Copy, and blank fill the leftmost column. + memmove(p+n, p, pixels); + memclr(p, n); + p += getWidth(); + } + + return MICROBIT_OK; +} + + +/** + * Shifts the pixels in this Image a given number of pixels to upward. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftUp(1); + * @endcode + */ +int MicroBitImage::shiftUp(int16_t n) +{ + uint8_t *pOut, *pIn; + + if (n <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + if(n >= getHeight()) + { + clear(); + return MICROBIT_OK; + } + + pOut = getBitmap(); + pIn = getBitmap()+getWidth()*n; + + for (int y = 0; y < getHeight(); y++) + { + // Copy, and blank fill the leftmost column. + if (y < getHeight()-n) + memcpy(pOut, pIn, getWidth()); + else + memclr(pOut, getWidth()); + + pIn += getWidth(); + pOut += getWidth(); + } + + return MICROBIT_OK; +} + + +/** + * Shifts the pixels in this Image a given number of pixels to downward. + * + * @param n The number of pixels to shift. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.shiftDown(1); + * @endcode + */ +int MicroBitImage::shiftDown(int16_t n) +{ + uint8_t *pOut, *pIn; + + if (n <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + if(n >= getHeight()) + { + clear(); + return MICROBIT_OK; + } + + pOut = getBitmap() + getWidth()*(getHeight()-1); + pIn = pOut - getWidth()*n; + + for (int y = 0; y < getHeight(); y++) + { + // Copy, and blank fill the leftmost column. + if (y < getHeight()-n) + memcpy(pOut, pIn, getWidth()); + else + memclr(pOut, getWidth()); + + pIn -= getWidth(); + pOut -= getWidth(); + } + + return MICROBIT_OK; +} + + +/** + * Converts the bitmap to a csv ManagedString. + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * uBit.serial.printString(i.toString()); // "0,1,0,1,0,0,0,0,0,0\n..." + * @endcode + */ +ManagedString MicroBitImage::toString() +{ + //width including commans and \n * height + int stringSize = getSize() * 2; + + //plus one for string terminator + char parseBuffer[stringSize + 1]; + + parseBuffer[stringSize] = '\0'; + + uint8_t *bitmapPtr = getBitmap(); + + int parseIndex = 0; + int widthCount = 0; + + while (parseIndex < stringSize) + { + if(*bitmapPtr) + parseBuffer[parseIndex] = '1'; + else + parseBuffer[parseIndex] = '0'; + + parseIndex++; + + if(widthCount == getWidth()-1) + { + parseBuffer[parseIndex] = '\n'; + widthCount = 0; + } + else + { + parseBuffer[parseIndex] = ','; + widthCount++; + } + + parseIndex++; + bitmapPtr++; + } + + return ManagedString(parseBuffer); +} + +/** + * Crops the image to the given dimensions. + * + * @param startx the location to start the crop in the x-axis + * + * @param starty the location to start the crop in the y-axis + * + * @param width the width of the desired cropped region + * + * @param height the height of the desired cropped region + * + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.crop(0,0,2,2).toString() // "0,1\n1,1\n" + * @endcode + */ +MicroBitImage MicroBitImage::crop(int startx, int starty, int cropWidth, int cropHeight) +{ + int newWidth = startx + cropWidth; + int newHeight = starty + cropHeight; + + if (newWidth >= getWidth() || newWidth <=0) + newWidth = getWidth(); + + if (newHeight >= getHeight() || newHeight <= 0) + newHeight = getHeight(); + + //allocate our storage. + uint8_t cropped[newWidth * newHeight]; + + //calculate the pointer to where we want to begin cropping + uint8_t *copyPointer = getBitmap() + (getWidth() * starty) + startx; + + //get a reference to our storage + uint8_t *pastePointer = cropped; + + //go through row by row and select our image. + for (int i = starty; i < newHeight; i++) + { + memcpy(pastePointer, copyPointer, newWidth); + + copyPointer += getWidth(); + pastePointer += newHeight; + } + + return MicroBitImage(newWidth, newHeight, cropped); +} + +/** + * Check if image is read-only (i.e., residing in flash). + */ +bool MicroBitImage::isReadOnly() +{ + return ptr->isReadOnly(); +} + +/** + * Create a copy of the image bitmap. Used particularly, when isReadOnly() is true. + * + * @return an instance of MicroBitImage which can be modified independently of the current instance + */ +MicroBitImage MicroBitImage::clone() +{ + return MicroBitImage(getWidth(), getHeight(), getBitmap()); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/types/PacketBuffer.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,326 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "MicroBitConfig.h" +#include "PacketBuffer.h" +#include "ErrorNo.h" + +// Create the EmptyPacket reference. +PacketBuffer PacketBuffer::EmptyPacket = PacketBuffer(1); + +/** + * Default Constructor. + * Creates an empty Packet Buffer. + * + * @code + * PacketBuffer p(); + * @endcode + */ +PacketBuffer::PacketBuffer() +{ + this->init(NULL, 0, 0); +} + +/** + * Constructor. + * Creates a new PacketBuffer of the given size. + * + * @param length The length of the buffer to create. + * + * @code + * PacketBuffer p(16); // Creates a PacketBuffer 16 bytes long. + * @endcode + */ +PacketBuffer::PacketBuffer(int length) +{ + this->init(NULL, length, 0); +} + +/** + * Constructor. + * Creates an empty Packet Buffer of the given size, + * and fills it with the data provided. + * + * @param data The data with which to fill the buffer. + * + * @param length The length of the buffer to create. + * + * @param rssi The radio signal strength at the time this packet was recieved. Defaults to 0. + * + * @code + * uint8_t buf = {13,5,2}; + * PacketBuffer p(buf, 3); // Creates a PacketBuffer 3 bytes long. + * @endcode + */ +PacketBuffer::PacketBuffer(uint8_t *data, int length, int rssi) +{ + this->init(data, length, rssi); +} + +/** + * Copy Constructor. + * Add ourselves as a reference to an existing PacketBuffer. + * + * @param buffer The PacketBuffer to reference. + * + * @code + * PacketBuffer p(); + * PacketBuffer p2(p); // Refers to the same packet as p. + * @endcode + */ +PacketBuffer::PacketBuffer(const PacketBuffer &buffer) +{ + ptr = buffer.ptr; + ptr->incr(); +} + +/** + * Internal constructor-initialiser. + * + * @param data The data with which to fill the buffer. + * + * @param length The length of the buffer to create. + * + * @param rssi The radio signal strength at the time this packet was recieved. + */ +void PacketBuffer::init(uint8_t *data, int length, int rssi) +{ + if (length < 0) + length = 0; + + ptr = (PacketData *) malloc(sizeof(PacketData) + length); + ptr->init(); + + ptr->length = length; + ptr->rssi = rssi; + + // Copy in the data buffer, if provided. + if (data) + memcpy(ptr->payload, data, length); +} + +/** + * Destructor. + * + * Removes buffer resources held by the instance. + */ +PacketBuffer::~PacketBuffer() +{ + ptr->decr(); +} + +/** + * Copy assign operation. + * + * Called when one PacketBuffer is assigned the value of another using the '=' operator. + * + * Decrements our reference count and free up the buffer as necessary. + * + * Then, update our buffer to refer to that of the supplied PacketBuffer, + * and increase its reference count. + * + * @param p The PacketBuffer to reference. + * + * @code + * uint8_t buf = {13,5,2}; + * PacketBuffer p1(16); + * PacketBuffer p2(buf, 3); + * + * p1 = p2; + * @endcode + */ +PacketBuffer& PacketBuffer::operator = (const PacketBuffer &p) +{ + if(ptr == p.ptr) + return *this; + + ptr->decr(); + ptr = p.ptr; + ptr->incr(); + + return *this; +} + +/** + * Array access operation (read). + * + * Called when a PacketBuffer is dereferenced with a [] operation. + * + * Transparently map this through to the underlying payload for elegance of programming. + * + * @code + * PacketBuffer p1(16); + * uint8_t data = p1[0]; + * @endcode + */ +uint8_t PacketBuffer::operator [] (int i) const +{ + return ptr->payload[i]; +} + +/** + * Array access operation (modify). + * + * Called when a PacketBuffer is dereferenced with a [] operation. + * + * Transparently map this through to the underlying payload for elegance of programming. + * + * @code + * PacketBuffer p1(16); + * p1[0] = 42; + * @endcode + */ +uint8_t& PacketBuffer::operator [] (int i) +{ + return ptr->payload[i]; +} + +/** + * Equality operation. + * + * Called when one PacketBuffer is tested to be equal to another using the '==' operator. + * + * @param p The PacketBuffer to test ourselves against. + * + * @return true if this PacketBuffer is identical to the one supplied, false otherwise. + * + * @code + * MicroBitDisplay display; + * uint8_t buf = {13,5,2}; + * PacketBuffer p1(); + * PacketBuffer p2(); + * + * if(p1 == p2) // will be true + * display.scroll("same!"); + * @endcode + */ +bool PacketBuffer::operator== (const PacketBuffer& p) +{ + if (ptr == p.ptr) + return true; + else + return (ptr->length == p.ptr->length && (memcmp(ptr->payload, p.ptr->payload, ptr->length)==0)); +} + +/** + * Sets the byte at the given index to value provided. + * + * @param position The index of the byte to change. + * + * @param value The new value of the byte (0-255). + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * PacketBuffer p1(16); + * p1.setByte(0,255); // Sets the first byte in the buffer to the value 255. + * @endcode + */ +int PacketBuffer::setByte(int position, uint8_t value) +{ + if (position < ptr->length) + { + ptr->payload[position] = value; + return MICROBIT_OK; + } + else + { + return MICROBIT_INVALID_PARAMETER; + } +} + +/** + * Determines the value of the given byte in the packet. + * + * @param position The index of the byte to read. + * + * @return The value of the byte at the given position, or MICROBIT_INVALID_PARAMETER. + * + * @code + * PacketBuffer p1(16); + * p1.setByte(0,255); // Sets the first byte in the buffer to the value 255. + * p1.getByte(0); // Returns 255. + * @endcode + */ +int PacketBuffer::getByte(int position) +{ + if (position < ptr->length) + return ptr->payload[position]; + else + return MICROBIT_INVALID_PARAMETER; +} + +/** + * Provide a pointer to a memory location containing the packet data. + * + * @return The contents of this packet, as an array of bytes. + */ +uint8_t*PacketBuffer::getBytes() +{ + return ptr->payload; +} + +/** + * Gets number of bytes in this buffer + * + * @return The size of the buffer in bytes. + * + * @code + * PacketBuffer p1(16); + * p1.length(); // Returns 16. + * @endcode + */ +int PacketBuffer::length() +{ + return ptr->length; +} + +/** + * Retrieves the received signal strength of this packet. + * + * @return The signal strength of the radio when this packet was received, in -dbM. + * + * @code + * PacketBuffer p1(16); + * p1.getRSSI(); // Returns the received signal strength. + * @endcode + */ +int PacketBuffer::getRSSI() +{ + return ptr->rssi; +} + +/** + * Sets the received signal strength of this packet. + * + * @code + * PacketBuffer p1(16); + * p1.setRSSI(37); + * @endcode + */ +void PacketBuffer::setRSSI(uint8_t rssi) +{ + ptr->rssi = rssi; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/types/RefCounted.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,98 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Base class for payload for ref-counted objects. Used by ManagedString and MicroBitImage. + * There is no constructor, as this struct is typically malloc()ed. + */ +#include "mbed.h" +#include "MicroBitConfig.h" +#include "RefCounted.h" +#include "MicroBitDisplay.h" + +/** + * Initializes for one outstanding reference. + */ +void RefCounted::init() +{ + // Initialize to one reference (lowest bit set to 1) + refCount = 3; +} + +/** + * Checks if the object resides in flash memory. + * + * @param t the object to check. + * + * @return true if the object resides in flash memory, false otherwise. + */ +static inline bool isReadOnlyInline(RefCounted *t) +{ + uint32_t refCount = t->refCount; + + if (refCount == 0xffff) + return true; // object in flash + + // Do some sanity checking while we're here + if (refCount == 1 || // object should have been deleted + (refCount & 1) == 0) // refCount doesn't look right + microbit_panic(MICROBIT_HEAP_ERROR); + + // Not read only + return false; +} + +/** + * Checks if the object resides in flash memory. + * + * @return true if the object resides in flash memory, false otherwise. + */ +bool RefCounted::isReadOnly() +{ + return isReadOnlyInline(this); +} + +/** + * Increment reference count. + */ +void RefCounted::incr() +{ + if (!isReadOnlyInline(this)) + refCount += 2; +} + +/** + * Decrement reference count. + */ +void RefCounted::decr() +{ + if (isReadOnlyInline(this)) + return; + + refCount -= 2; + if (refCount == 1) { + free(this); + } +}