diff --git a/inc/MESEvents.h b/inc/MESEvents.h index 641429e..44d5554 100644 --- a/inc/MESEvents.h +++ b/inc/MESEvents.h @@ -68,4 +68,27 @@ #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 diff --git a/inc/MicroBit.h b/inc/MicroBit.h index 135f15a..903c976 100644 --- a/inc/MicroBit.h +++ b/inc/MicroBit.h @@ -24,6 +24,7 @@ #include "MicroBitCompass.h" #include "MicroBitAccelerometer.h" #include "MicroBitThermometer.h" +#include "MicroBitLightSensor.h" #include "MicroBitMultiButton.h" #include "MicroBitSerial.h" @@ -34,6 +35,7 @@ #include "MicroBitMessageBus.h" #include "MicroBitBLEManager.h" +#include "MicroBitRadio.h" // MicroBit::flags values #define MICROBIT_FLAG_SCHEDULER_RUNNING 0x00000001 @@ -46,12 +48,14 @@ #define MICROBIT_NAME_CODE_LETTERS 5 // Random number generator -#define NRF51822_RNG_ADDRESS 0x4000D000 +#define NRF51822_RNG_ADDRESS 0x4000D000 // mbed pin assignments of core components. -#define MICROBIT_PIN_SDA P0_30 -#define MICROBIT_PIN_SCL P0_0 +#define MICROBIT_PIN_SDA P0_30 +#define MICROBIT_PIN_SCL P0_0 + +#define MICROBIT_DEFAULT_TICK_PERIOD FIBER_TICK_PERIOD_MS /** * Class definition for a MicroBit device. @@ -65,6 +69,8 @@ class MicroBit void compassCalibrator(MicroBitEvent e); uint32_t randomValue; + //the current tick period in MS + int tickPeriod; public: @@ -103,6 +109,7 @@ class MicroBit // Bluetooth related member variables. MicroBitBLEManager bleManager; + MicroBitRadio radio; BLEDevice *ble; /** @@ -259,6 +266,21 @@ class MicroBit */ int removeIdleComponent(MicroBitComponent *component); + + /* + * Reconfigures the ticker to the given speed in milliseconds. + * @param speedMs the speed in milliseconds + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if speedUs < 1 + * + * @note this will also modify the value that is added to ticks in MiroBitFiber:scheduler_tick() + */ + int setTickPeriod(int speedMs); + + /* + * Returns the currently used tick speed in milliseconds + */ + int getTickPeriod(); + /** * Determine the time since this MicroBit was last reset. * diff --git a/inc/MicroBitBLEManager.h b/inc/MicroBitBLEManager.h index 3a897a9..8bac02b 100644 --- a/inc/MicroBitBLEManager.h +++ b/inc/MicroBitBLEManager.h @@ -39,6 +39,11 @@ #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 +#define MICROBIT_BLE_REQUIRE_MITM true +extern const int8_t MICROBIT_BLE_POWER_LEVEL[]; /** * Class definition for the MicroBitBLEManager. diff --git a/inc/MicroBitComponent.h b/inc/MicroBitComponent.h index bbac2bd..650b66a 100644 --- a/inc/MicroBitComponent.h +++ b/inc/MicroBitComponent.h @@ -44,6 +44,8 @@ #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_NOTIFY 1023 // Notfication channel, for general purpose synchronisation #define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation diff --git a/inc/MicroBitDisplay.h b/inc/MicroBitDisplay.h index 539569b..7847058 100644 --- a/inc/MicroBitDisplay.h +++ b/inc/MicroBitDisplay.h @@ -1,17 +1,12 @@ #ifndef MICROBIT_DISPLAY_H #define MICROBIT_DISPLAY_H - -/** - * Core Configuration settings. - */ -#define MICROBIT_DISPLAY_REFRESH_PERIOD ((float)FIBER_TICK_PERIOD_MS / (float)1000) - /** * MessageBus Event Codes */ #define MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE 1 #define MICROBIT_DISPLAY_EVT_FREE 2 +#define MICROBIT_DISPLAY_EVT_LIGHT_SENSE 4 /** * I/O configurations for common devices. @@ -43,6 +38,7 @@ #define MICROBIT_DISPLAY_COLUMN_COUNT 9 #define MICROBIT_DISPLAY_COLUMN_PINS P0_4, P0_5, P0_6, P0_7, P0_8, P0_9, P0_10, P0_11, P0_12 #define MICROBIT_DISPLAY_COLUMN_START P0_4 +#define MICROBIT_DISPLAY_ROW_START P0_13 #endif // @@ -55,6 +51,8 @@ #define MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH 8 #define MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS -255 +#define MICROBIT_DISPLAY_ROW_RESET 0x20 + #include "mbed.h" #include "ManagedString.h" #include "MicroBitComponent.h" @@ -73,7 +71,8 @@ enum AnimationMode { enum DisplayMode { DISPLAY_MODE_BLACK_AND_WHITE, - DISPLAY_MODE_GREYSCALE + DISPLAY_MODE_GREYSCALE, + DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE }; enum DisplayRotation { @@ -161,6 +160,9 @@ class MicroBitDisplay : public MicroBitComponent // 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; @@ -185,6 +187,12 @@ class MicroBitDisplay : public MicroBitComponent */ 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. */ @@ -473,7 +481,7 @@ public: /** * Sets the mode of the display. - * @param mode The mode to swap the display into. (can be either DISPLAY_MODE_GREYSCALE, or DISPLAY_MODE_NORMAL) + * @param mode The mode to swap the display into. (can be either DISPLAY_MODE_GREYSCALE, DISPLAY_MODE_BLACK_AND_WHITE, DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) * * Example: * @code @@ -482,6 +490,12 @@ public: */ void setDisplayMode(DisplayMode mode); + /** + * Gets 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. @@ -563,6 +577,19 @@ public: */ MicroBitImage screenShot(); + /** + * 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. + * + * @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, so that we deregister ourselves as a systemComponent */ diff --git a/inc/MicroBitLightSensor.h b/inc/MicroBitLightSensor.h new file mode 100644 index 0000000..a149efa --- /dev/null +++ b/inc/MicroBitLightSensor.h @@ -0,0 +1,104 @@ +#ifndef MICROBIT_LIGHT_SENSOR_H +#define MICROBIT_LIGHT_SENSOR_H + +#include "mbed.h" +#include "MicroBitComponent.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 uBit.display. + */ +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; + + /** + * 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(); + + /** + * The method that is invoked by sending MICROBIT_DISPLAY_EVT_LIGHT_SENSE + * using the id MICROBIT_ID_DISPLAY. + * + * If you want to manually trigger this method, you should use the event bus. + */ + void startSensing(MicroBitEvent); + + public: + + /** + * Constructor. + * Create a representation of the light sensor + */ + MicroBitLightSensor(); + + /** + * 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 - 100 where 0 is dark, and 100 + * is very bright + * + * @note currently returns a value in the range 0 - 100 where 0 is dark, and 100 + * is very bright perhaps we should normalise the returned values into an SI unit! + * TODO. + */ + int read(); + + /** + * The destructor restores the default Display Mode and tick speed, and also + * removes the listener from the MessageBus. + */ + ~MicroBitLightSensor(); +}; + +#endif diff --git a/inc/MicroBitRadio.h b/inc/MicroBitRadio.h new file mode 100644 index 0000000..d34263f --- /dev/null +++ b/inc/MicroBitRadio.h @@ -0,0 +1,174 @@ +#ifndef MICROBIT_RADIO_H +#define MICROBIT_RADIO_H + +#include "mbed.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 PacketBuffer +{ + 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 + PacketBuffer *next; // Linkage, to allow this and other protocols to queue packets pending processing. +}; + +#include "MicroBitRadioDatagram.h" +#include "MicroBitRadioEvent.h" + +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. + PacketBuffer *rxQueue; // A linear list of incoming packets, queued awaiting processing. + PacketBuffer *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 that this class is demand activated, so most resources are only + * committed if send/recv or event registrations calls are made. + */ + MicroBitRadio(uint16_t id); + + /** + * 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 recieve 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 + */ + PacketBuffer* 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(); + + /** + * Initialises the radio for use as a multipoint sender/receiver + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if SoftDevice is enabled. + */ + int enable(); + + /** + * Disables the radio for use as a multipoint sender/receiver. + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if SoftDevice is enabled. + */ + 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. + * + * We provide optimised handling of well known, simple protocols and events on the MicroBitMessageBus + * to provide extensibility to other protocols that may be written in the future. + */ + 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. + * + * NOTE: Once recv() has been called, it is the callers resposibility to + * delete the buffer when appropriate. + * + * @return The buffer containing the the packet. If no data is available, NULL is returned. + */ + PacketBuffer* 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(PacketBuffer *buffer); +}; + +#endif diff --git a/inc/MicroBitRadioDatagram.h b/inc/MicroBitRadioDatagram.h new file mode 100644 index 0000000..1521e72 --- /dev/null +++ b/inc/MicroBitRadioDatagram.h @@ -0,0 +1,77 @@ +#ifndef MICROBIT_RADIO_DATAGRAM_H +#define MICROBIT_RADIO_DATAGRAM_H + +#include "mbed.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. + */ + +class MicroBitRadioDatagram +{ + PacketBuffer *rxQueue; // A linear list of incoming packets, queued awaiting processing. + + public: + + /** + * Constructor. + */ + MicroBitRadioDatagram(); + + /** + * Retreives 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 the EmptyString is returned, 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 string. If no data is available the EmptyString is returned. + * + * @return the data received, or the EmptyString if no data is available. + */ + ManagedString recv(); + + /** + * Transmits the given buffer onto the broadcast radio. + * The call 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. + */ + int send(uint8_t *buffer, int len); + + /** + * Transmits the given string 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. + */ + 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 diff --git a/inc/MicroBitRadioEvent.h b/inc/MicroBitRadioEvent.h new file mode 100644 index 0000000..48d0259 --- /dev/null +++ b/inc/MicroBitRadioEvent.h @@ -0,0 +1,67 @@ +#ifndef MICROBIT_RADIO_EVENT_H +#define MICROBIT_RADIO_EVENT_H + +#include "mbed.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 MessageBus 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. + + public: + + /** + * Constructor. + */ + MicroBitRadioEvent(); + + /** + * Associates the given MessageBus events with the radio channel. + * Once registered, all events matching the given registration sent to this micro:bit's + * MessageBus will be automatically retrasmitted on the radio. + * + * @param id The ID of the events to register. + * @param value the VALUE of the event to register. use MICROBIT_EVT_ANY for all event values matching the given ID. + * + * @return MICROBIT_OK on success. + */ + int listen(uint16_t id, uint16_t value); + + /** + * Disassociates the given MessageBus events with the radio channel. + * + * @param id The ID of the events to deregister. + * @param value the VALUE of the event to deregister. use MICROBIT_EVT_ANY for all event values matching the given ID. + * + * @return MICROBIT_OK on success. + */ + int ignore(uint16_t id, uint16_t value); + + /** + * 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 local MessageBus. + */ + 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 othe rmicro:bits in the same group. + */ + void eventReceived(MicroBitEvent e); +}; + +#endif diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 615e6fd..b73cb45 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -29,6 +29,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "MicroBitSerial.cpp" "MicroBitHeapAllocator.cpp" "MicroBitListener.cpp" + "MicroBitLightSensor.cpp" "RefCounted.cpp" "MemberFunctionCallback.cpp" "ble-services/MicroBitBLEManager.cpp" @@ -40,6 +41,9 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "ble-services/MicroBitButtonService.cpp" "ble-services/MicroBitIOPinService.cpp" "ble-services/MicroBitTemperatureService.cpp" + "ble-services/MicroBitRadio.cpp" + "ble-services/MicroBitRadioDatagram.cpp" + "ble-services/MicroBitRadioEvent.cpp" ) execute_process(WORKING_DIRECTORY "../../yotta_modules/${PROJECT_NAME}" COMMAND "git" "log" "--pretty=format:%h" "-n" "1" OUTPUT_VARIABLE git_hash) diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp index a77079a..bdd9083 100644 --- a/source/MicroBit.cpp +++ b/source/MicroBit.cpp @@ -1,4 +1,4 @@ -/* +/* * 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 @@ -105,6 +105,7 @@ MicroBit::MicroBit() : MICROBIT_ID_IO_P15,MICROBIT_ID_IO_P16,MICROBIT_ID_IO_P19, MICROBIT_ID_IO_P20), bleManager(), + radio(MICROBIT_ID_RADIO), ble(NULL) { } @@ -133,8 +134,10 @@ void MicroBit::init() // Seed our random number generator seedRandom(); + tickPeriod = MICROBIT_DEFAULT_TICK_PERIOD; + // Start refreshing the Matrix Display - systemTicker.attach(this, &MicroBit::systemTick, MICROBIT_DISPLAY_REFRESH_PERIOD); + systemTicker.attach_us(this, &MicroBit::systemTick, tickPeriod * 1000); // Register our compass calibration algorithm. MessageBus.listen(MICROBIT_ID_COMPASS, MICROBIT_COMPASS_EVT_CALIBRATE, this, &MicroBit::compassCalibrator, MESSAGE_BUS_LISTENER_IMMEDIATE); @@ -608,6 +611,35 @@ int MicroBit::removeIdleComponent(MicroBitComponent *component) return MICROBIT_OK; } +/* + * Reconfigures the ticker to the given speed in milliseconds. + * @param speedMs the speed in milliseconds + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if speedUs < 1 + * + * @note this will also modify the value that is added to ticks in MiroBitFiber:scheduler_tick() + */ +int MicroBit::setTickPeriod(int speedMs) +{ + if(speedMs < 1) + return MICROBIT_INVALID_PARAMETER; + + uBit.systemTicker.detach(); + + uBit.systemTicker.attach_us(this, &MicroBit::systemTick, speedMs * 1000); + + tickPeriod = speedMs; + + return MICROBIT_OK; +} + +/* + * Returns the currently used tick speed in milliseconds + */ +int MicroBit::getTickPeriod() +{ + return tickPeriod; +} + /** * Determine the time since this MicroBit was last reset. * diff --git a/source/MicroBitDisplay.cpp b/source/MicroBitDisplay.cpp index 1ad22d2..48c93d2 100644 --- a/source/MicroBitDisplay.cpp +++ b/source/MicroBitDisplay.cpp @@ -34,7 +34,7 @@ MicroBitDisplay::MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y) : this->width = x; this->height = y; this->strobeRow = 0; - this->strobeBitMsk = 0x20; + this->strobeBitMsk = MICROBIT_DISPLAY_ROW_RESET; this->rotation = MICROBIT_DISPLAY_ROTATION_0; this->greyscaleBitMsk = 0x01; this->timingCount = 0; @@ -44,6 +44,8 @@ MicroBitDisplay::MicroBitDisplay(uint16_t id, uint8_t x, uint8_t y) : this->mode = DISPLAY_MODE_BLACK_AND_WHITE; this->animationMode = ANIMATION_MODE_NONE; + this->lightSensor = NULL; + uBit.flags |= MICROBIT_FLAG_DISPLAY_RUNNING; } @@ -58,6 +60,12 @@ void MicroBitDisplay::systemTick() if(!(uBit.flags & MICROBIT_FLAG_DISPLAY_RUNNING)) return; + if(mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + renderWithLightSense(); + return; + } + // Move on to the next row. strobeBitMsk <<= 1; strobeRow++; @@ -65,7 +73,7 @@ void MicroBitDisplay::systemTick() //reset the row counts and bit mask when we have hit the max. if(strobeRow == MICROBIT_DISPLAY_ROW_COUNT){ strobeRow = 0; - strobeBitMsk = 0x20; + strobeBitMsk = MICROBIT_DISPLAY_ROW_RESET; } if(mode == DISPLAY_MODE_BLACK_AND_WHITE) @@ -139,13 +147,37 @@ void MicroBitDisplay::render() //timer does not have enough resolution for brightness of 1. 23.53 us if(brightness != MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS && brightness > MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) - renderTimer.attach(this, &MicroBitDisplay::renderFinish, (((float)brightness) / ((float)MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * (float)MICROBIT_DISPLAY_REFRESH_PERIOD); + renderTimer.attach_us(this, &MicroBitDisplay::renderFinish, (((brightness * 100) / (MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * uBit.getTickPeriod())); //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 == MICROBIT_DISPLAY_ROW_COUNT + 1) + { + + MicroBitEvent(id, MICROBIT_DISPLAY_EVT_LIGHT_SENSE); + + strobeRow = 0; + strobeBitMsk = MICROBIT_DISPLAY_ROW_RESET; + } + else + { + + render(); + this->animationUpdate(); + + // Move on to the next row. + strobeBitMsk <<= 1; + strobeRow++; + } + +} + void MicroBitDisplay::renderGreyscale() { int coldata = 0; @@ -883,7 +915,7 @@ int MicroBitDisplay::setBrightness(int b) /** * Sets the mode of the display. - * @param mode The mode to swap the display into. (can be either DISPLAY_MODE_GREYSCALE, or DISPLAY_MODE_NORMAL) + * @param mode The mode to swap the display into. (can be either DISPLAY_MODE_GREYSCALE, DISPLAY_MODE_BLACK_AND_WHITE, DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) * * Example: * @code @@ -892,9 +924,37 @@ int MicroBitDisplay::setBrightness(int b) */ 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(uBit.getTickPeriod() != MICROBIT_LIGHT_SENSOR_TICK_PERIOD) + uBit.setTickPeriod(MICROBIT_LIGHT_SENSOR_TICK_PERIOD); + } + + if(this->mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE && mode != DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + + //if we previously were in light sense mode - return to our default. + if(uBit.getTickPeriod() != MICROBIT_DEFAULT_TICK_PERIOD) + uBit.setTickPeriod(MICROBIT_DEFAULT_TICK_PERIOD); + + delete this->lightSensor; + + this->lightSensor = NULL; + } + this->mode = mode; } +/** + * Gets 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. @@ -992,7 +1052,7 @@ void MicroBitDisplay::error(int statusCode) disable(); //relinquish PWMOut's control uint8_t strobeRow = 0; - uint8_t strobeBitMsk = 0x20; + uint8_t strobeBitMsk = MICROBIT_DISPLAY_ROW_RESET; //point to the font stored in Flash const unsigned char * fontLocation = MicroBitFont::defaultFont; @@ -1019,7 +1079,7 @@ void MicroBitDisplay::error(int statusCode) if(strobeRow == 3) { strobeRow = 0; - strobeBitMsk = 0x20; + strobeBitMsk = MICROBIT_DISPLAY_ROW_RESET; } // Calculate the bitpattern to write. @@ -1087,6 +1147,28 @@ MicroBitImage MicroBitDisplay::screenShot() return image.crop(0,0,MICROBIT_DISPLAY_WIDTH,MICROBIT_DISPLAY_HEIGHT); } +/** + * 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. + * + * @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(); + } + + return this->lightSensor->read(); +} + /** * Destructor for MicroBitDisplay, so that we deregister ourselves as a systemComponent */ diff --git a/source/MicroBitFiber.cpp b/source/MicroBitFiber.cpp index 26816de..bebe05a 100644 --- a/source/MicroBitFiber.cpp +++ b/source/MicroBitFiber.cpp @@ -6,7 +6,7 @@ * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). * 2) To provide ISR decoupling for Messagebus events generted in an ISR context. */ - + #include "MicroBit.h" /* @@ -37,7 +37,7 @@ unsigned long ticks = 0; uint8_t fiber_flags = 0; /** - * Utility function to add the currenty running fiber to the given queue. + * 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. @@ -62,7 +62,7 @@ void queue_fiber(Fiber *f, Fiber **queue) } else { - // Scan to the end of the queue. + // 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; @@ -78,7 +78,7 @@ void queue_fiber(Fiber *f, Fiber **queue) } /** - * Utility function to the given fiber from whichever queue it is currently stored on. + * 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) @@ -87,34 +87,34 @@ void dequeue_fiber(Fiber *f) if (f->queue == NULL) return; - // Remove this fiber fromm whichever queue it is on. + // 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; @@ -124,17 +124,17 @@ Fiber *getFiberContext() 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. + } + + // Ensure this fiber is in suitable state for reuse. f->flags = 0; f->tcb.stack_base = CORTEX_M0_STACK_BASE; @@ -143,7 +143,7 @@ Fiber *getFiberContext() /** - * Initialises the Fiber scheduler. + * 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. @@ -152,14 +152,14 @@ void scheduler_init() { // 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.SP = CORTEX_M0_STACK_BASE - 0x04; idleFiber->tcb.LR = (uint32_t) &idle_task; // Register to receive events in the NOTIFY channel - this is used to implement wait-notify semantics @@ -172,36 +172,36 @@ void scheduler_init() /** * Timer callback. Called from interrupt context, once every FIBER_TICK_PERIOD_MS milliseconds. - * Simply checks to determine if any fibers blocked on the sleep queue need to be woken up + * Simply checks to determine if any fibers blocked on the sleep queue need to be woken up * and made runnable. */ void scheduler_tick() { Fiber *f = sleepQueue; Fiber *t; - + // increment our real-time counter. - ticks += FIBER_TICK_PERIOD_MS; - + ticks += uBit.getTickPeriod(); + // Check the sleep queue, and wake up any fibers as necessary. while (f != NULL) { - t = f->next; - + t = f->next; + if (ticks >= f->context) { // Wakey wakey! dequeue_fiber(f); queue_fiber(f,&runQueue); } - + f = t; } } /** - * Event callback. Called from the message bus whenever an event is raised. - * Checks to determine if any fibers blocked on the wait queue need to be woken up + * Event callback. Called from the message bus whenever an event is raised. + * Checks to determine if any fibers blocked on the wait queue need to be woken up * and made runnable due to the event. */ void scheduler_event(MicroBitEvent evt) @@ -209,20 +209,20 @@ void scheduler_event(MicroBitEvent evt) Fiber *f = waitQueue; Fiber *t; int notifyOneComplete = 0; - + // Check the wait queue, and wake up any fibers as necessary. while (f != NULL) { - t = f->next; - - // extract the event data this fiber is blocked on. + 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) + if (!notifyOneComplete) { // Wakey wakey! dequeue_fiber(f); @@ -238,7 +238,7 @@ void scheduler_event(MicroBitEvent evt) dequeue_fiber(f); queue_fiber(f,&runQueue); } - + f = t; } @@ -250,9 +250,9 @@ void scheduler_event(MicroBitEvent evt) /** * Blocks the calling thread for the given period of time. - * The calling thread will be immediatley descheduled, and placed onto a - * wait queue until the requested amount of time has elapsed. - * + * The calling thread will be immediatley descheduled, and placed onto a + * wait queue until the requested amount of time has elapsed. + * * n.b. the fiber will not be be made runnable until after the elasped time, but there * are no guarantees precisely when the fiber will next be scheduled. * @@ -269,8 +269,8 @@ void fiber_sleep(unsigned long t) // 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. + + // 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; @@ -278,22 +278,22 @@ void fiber_sleep(unsigned long t) // Calculate and store the time we want to wake up. f->context = ticks + 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 immediatley descheduled, and placed onto a + * The calling thread will be immediatley descheduled, and placed onto a * wait queue until the requested event is received. - * + * * n.b. the fiber will not be be made runnable until after the event is raised, but there * are no guarantees precisely when the fiber will next be scheduled. * @@ -311,8 +311,8 @@ void fiber_wait_for_event(uint16_t id, uint16_t value) // 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. + + // 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; @@ -320,13 +320,13 @@ void fiber_wait_for_event(uint16_t id, uint16_t value) // Encode the event data in the context field. It's handy having a 32 bit core. :-) 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) @@ -337,16 +337,16 @@ void fiber_wait_for_event(uint16_t id, uint16_t value) } /** - * Executes the given function asynchronously. - * + * Executes the given function asynchronously. + * * 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. + * 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. + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. */ int invoke(void (*entry_fn)(void)) { @@ -361,14 +361,14 @@ int invoke(void (*entry_fn)(void)) 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. + // 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) @@ -382,7 +382,7 @@ int invoke(void (*entry_fn)(void)) // 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(); + 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. @@ -394,17 +394,17 @@ int invoke(void (*entry_fn)(void)) } /** - * Executes the given parameterized function asynchronously. - * + * Executes the given parameterized function asynchronously. + * * 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 attempt to optimistically call the given function directly. - * We only create an additional fiber if that function performs a block operation. + * This function takes a snapshot of the current processor 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. - * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. */ int invoke(void (*entry_fn)(void *), void *param) { @@ -419,14 +419,14 @@ int invoke(void (*entry_fn)(void *), void *param) 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. + // 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) @@ -440,7 +440,7 @@ int invoke(void (*entry_fn)(void *), void *param) // 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); + 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. @@ -480,26 +480,26 @@ 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.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; } @@ -507,7 +507,7 @@ Fiber *__create_fiber(uint32_t ep, uint32_t cp, uint32_t pm, int parameterised) * 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. + * @param completion_fn The function called when the thread completes execution of entry_fn. * @return The new Fiber. */ Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) @@ -521,7 +521,7 @@ Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) * * @param entry_fn The function the new Fiber will begin execution in. * @param param an untyped parameter passed into the entry_fn anf completion_fn. - * @param completion_fn The function called when the thread completes execution of entry_fn. + * @param completion_fn The function called when the thread completes execution of entry_fn. * @return The new Fiber. */ Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *)) @@ -543,15 +543,15 @@ void release_fiber(void *) * Any fiber reaching the end of its entry function will return here for recycling. */ void release_fiber(void) -{ +{ // Remove ourselves form the runqueue. dequeue_fiber(currentFiber); // Add ourselves to the list of free fibers queue_fiber(currentFiber, &fiberPool); - + // Find something else to do! - schedule(); + schedule(); } /** @@ -571,7 +571,7 @@ void verify_stack_size(Fiber *f) // Calculate the stack depth. stackDepth = f->tcb.stack_base - ((uint32_t) __get_MSP()); - // Calculate the size of our allocated stack buffer + // Calculate the size of our allocated stack buffer bufferSize = f->stack_top - f->stack_bottom; // If we're too small, increase our buffer size. @@ -624,10 +624,10 @@ void schedule() // 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 + // Ensure the stack allocation of the new fiber is large enough verify_stack_size(forkedFiber); - // Store the full context of this fiber. + // 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. @@ -638,7 +638,7 @@ void schedule() // If we're the new thread, we must have been unblocked by the scheduler, so simply return // and continue processing. - return; + return; } // We're in a normal scheduling context, so perform a round robin algorithm across runnable fibers. @@ -653,7 +653,7 @@ void schedule() 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. @@ -676,12 +676,12 @@ void schedule() // 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) + 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.SP = CORTEX_M0_STACK_BASE - 0x04; idleFiber->tcb.LR = (uint32_t) &idle_task; } @@ -692,7 +692,7 @@ void schedule() } else { - // Ensure the stack allocation of the fiber being scheduled out is large enough + // Ensure the stack allocation of the fiber being scheduled out is large enough verify_stack_size(oldFiber); // Schedule in the new fiber. diff --git a/source/MicroBitLightSensor.cpp b/source/MicroBitLightSensor.cpp new file mode 100644 index 0000000..efe29ce --- /dev/null +++ b/source/MicroBitLightSensor.cpp @@ -0,0 +1,143 @@ +/** + * Class definition for MicroBitLightSensor. + * + * This is an object that interleaves light sensing with uBit.display. + */ + +#include "MicroBit.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)(MICROBIT_DISPLAY_COLUMN_START + 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); +} + +/** + * The method that is invoked by sending MICROBIT_DISPLAY_EVT_LIGHT_SENSE + * using the id MICROBIT_ID_DISPLAY. + * + * If you want to manually trigger this method, you should use the event bus. + * + * @note This is currently too churny, and allocates a lot of stuff on the stack + * however, this makes this chunk of code platform agnostic in mbed land. + */ +void MicroBitLightSensor::startSensing(MicroBitEvent) +{ + for(int rowCount = 0; rowCount < MICROBIT_DISPLAY_ROW_COUNT; rowCount++) + DigitalOut((PinName)(MICROBIT_DISPLAY_ROW_START + rowCount)).write(0); + + PinName currentPin = (PinName)(MICROBIT_DISPLAY_COLUMN_START + 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); +} + +/** + * Constructor. + * Create a representation of the light sensor + */ +MicroBitLightSensor::MicroBitLightSensor() : analogTrigger() +{ + this->chan = 0; + + uBit.MessageBus.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 - 100 where 0 is dark, and 100 + * is very bright + * + * @note currently returns a value in the range 0 - 100 where 0 is dark, and 100 + * is very bright perhaps we should normalise the returned values into an SI unit! + * TODO. + */ +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 normalised = ((inverted - MICROBIT_LIGHT_SENSOR_MIN_VALUE) * 100) / (MICROBIT_LIGHT_SENSOR_MAX_VALUE - MICROBIT_LIGHT_SENSOR_MIN_VALUE); + + return normalised; +} + + +/** + * The destructor restores the default Display Mode and tick speed, and also + * removes the listener from the MessageBus. + */ +MicroBitLightSensor::~MicroBitLightSensor() +{ + uBit.MessageBus.ignore(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_LIGHT_SENSE, this, &MicroBitLightSensor::startSensing); +} diff --git a/source/ble-services/MicroBitBLEManager.cpp b/source/ble-services/MicroBitBLEManager.cpp index 9a698aa..f16f78f 100644 --- a/source/ble-services/MicroBitBLEManager.cpp +++ b/source/ble-services/MicroBitBLEManager.cpp @@ -21,13 +21,7 @@ #pragma GCC diagnostic pop #endif -#define MICROBIT_BLE_ENABLE_BONDING true -#define MICROBIT_BLE_REQUIRE_MITM true - #define MICROBIT_PAIRING_FADE_SPEED 4 -#define MICROBIT_BLE_POWER_LEVELS 8 -#define MICROBIT_BLE_MAXIMUM_BONDS 4 - const char* MICROBIT_BLE_MANUFACTURER = NULL; const char* MICROBIT_BLE_MODEL = "BBC micro:bit"; diff --git a/source/ble-services/MicroBitRadio.cpp b/source/ble-services/MicroBitRadio.cpp new file mode 100644 index 0000000..e118182 --- /dev/null +++ b/source/ble-services/MicroBitRadio.cpp @@ -0,0 +1,424 @@ +#include "MicroBit.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(); + + // Start listening for the next packet. + NRF_RADIO->EVENTS_END = 0; + NRF_RADIO->TASKS_START = 1; +} + +/** + * Constructor. + * + * Initialise the MicroBitRadio. Note that this class is demand activated, so most resources are only committed + * if send/recv or event registrations calls are made. + */ +MicroBitRadio::MicroBitRadio(uint16_t id) : datagram() +{ + this->id = id; + this->status = 0; + this->group = 0; + this->queueDepth = 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 (uBit.ble) + 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 + */ +PacketBuffer* 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; + + // Ensure that a replacement buffer is available before queuing. + PacketBuffer *newRxBuf = new PacketBuffer(); + + 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 + { + PacketBuffer *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; +} + +/** + * Initialises the radio for use as a multipoint sender/receiver. + * This is currently only possible if the BLE stack (Soft Device) is disabled. + * + * @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if SoftDevice is enabled. + */ +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 (uBit.ble) + return MICROBIT_NOT_SUPPORTED; + + // If this is the first time we've been enable, allocate out receive buffers. + if (rxBuf == NULL) + rxBuf = new PacketBuffer(); + + 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); + + // 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. + uBit.addIdleComponent(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 SoftDevice is enabled. + */ +int MicroBitRadio::disable() +{ + // Only attempt to enable.disable the radio if the protocol is alreayd running. + if (uBit.ble) + 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. + uBit.removeIdleComponent(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 (uBit.ble) + 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. + * + * We provide optimised handling of well known, simple protocols and events on the MicroBitMessageBus + * to provide extensibility to other protocols that may be written in the future. + */ +void MicroBitRadio::idleTick() +{ + // Walk the list of packets and process each one. + while(rxQueue) + { + PacketBuffer *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. + * + * NOTE: Once recv() has been called, it is the callers resposibility to + * delete the buffer when appropriate. + * + * @return The buffer containing the the packet. If no data is available, NULL is returned. + */ +PacketBuffer* MicroBitRadio::recv() +{ + PacketBuffer *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(PacketBuffer *buffer) +{ + if (uBit.ble) + 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; +} + diff --git a/source/ble-services/MicroBitRadioDatagram.cpp b/source/ble-services/MicroBitRadioDatagram.cpp new file mode 100644 index 0000000..29091aa --- /dev/null +++ b/source/ble-services/MicroBitRadioDatagram.cpp @@ -0,0 +1,141 @@ +#include "MicroBit.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. + */ +MicroBitRadioDatagram::MicroBitRadioDatagram() +{ + rxQueue = NULL; +} + +/** + * Retreives 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 the EmptyString is returned, 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. + PacketBuffer *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 string. If no data is available the EmptyString is returned. + * + * @return the data received, or the EmptyString if no data is available. + */ +ManagedString MicroBitRadioDatagram::recv() +{ + PacketBuffer *p = rxQueue; + rxQueue = rxQueue->next; + + ManagedString s((const char *)p->payload, p->length - MICROBIT_RADIO_HEADER_SIZE - 1); + + delete p; + return s; +} + +/** + * Transmits the given buffer onto the broadcast radio. + * The call 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. + */ +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; + + PacketBuffer 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 uBit.radio.send(&buf); +} + +/** + * Transmits the given string 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. + */ +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() +{ + PacketBuffer *packet = uBit.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 + { + PacketBuffer *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); +} + diff --git a/source/ble-services/MicroBitRadioEvent.cpp b/source/ble-services/MicroBitRadioEvent.cpp new file mode 100644 index 0000000..e3a1bec --- /dev/null +++ b/source/ble-services/MicroBitRadioEvent.cpp @@ -0,0 +1,88 @@ +#include "MicroBit.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 MessageBus 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. + * + * 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. + */ +MicroBitRadioEvent::MicroBitRadioEvent() +{ + suppressForwarding = false; +} + +/** + * Associates the given MessageBus events with the radio channel. + * Once registered, all events matching the given registration sent to this micro:bit's + * MessageBus will be automaticlaly retrasmitted on the radio. + * + * @param id The ID of the events to register. + * @param value the VALUE of the event to register. use MICROBIT_EVT_ANY for all event values matching the given id. + * + * @return MICROBIT_OK on success. + */ +int MicroBitRadioEvent::listen(uint16_t id, uint16_t value) +{ + return uBit.MessageBus.listen(id, value, this, &MicroBitRadioEvent::eventReceived, MESSAGE_BUS_LISTENER_IMMEDIATE); +} + +/** + * Disassociates the given MessageBus events with the radio channel. + * + * @param id The ID of the events to deregister. + * @param value the VALUE of the event to deregister. use MICROBIT_EVT_ANY for all event values matching the given id. + * + * @return MICROBIT_OK on success. + */ +int MicroBitRadioEvent::ignore(uint16_t id, uint16_t value) +{ + return uBit.MessageBus.ignore(id, value, this, &MicroBitRadioEvent::eventReceived); +} + +/** + * Protocol handler callback. This is called when the radio receives a packet marked as an event + * This function process this packet, and fires the event contained inside onto the local MessageBus. + */ +void MicroBitRadioEvent::packetReceived() +{ + PacketBuffer *p = uBit.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 othe rmicro:bits in the same group. + */ +void MicroBitRadioEvent::eventReceived(MicroBitEvent e) +{ + if(suppressForwarding) + return; + + PacketBuffer 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)); + + uBit.radio.send(&buf); +} +