microbit: Memory Optimisation Mega Update

This release contains a widespread set of updates and optimisations to the micro:bit
runtime, with a view to reducing the SRAM footprint of the whole system. This is to
provide as much usable HEAP storage for application programs as possible.

Specific updates and optimisations include:

  - Additional compilation flags to allow the core micro:bit runtime to be configured.
    These are defined in MicroBitConfig.h

  - A custom heap allocator. This is now included for two reasons:

    1) To provide a simple mechanism to to utilise both the mbed heap space and other memory
       regions (such as unused memory in the SoftDevice region) as a single virtual heap.
    2) To address some issues that have been noted that are attributable to heap fragmentation.
       The micro:bit heap allocator has a simple algorithm, but one that is chosen to respond
       well to the relativelt high 'heap churn' found in the micro:bit environment.

    All micro:bit components and user programs now use this heap allocator trasparently.

  - Updates to BLE services to remove persistent references to their GATT services. This consumes
    vast amounts SRAM, rather unecessarily. Instead only handles to the relevant GATT characteristics
    are now stored. This specifically includes:
      + MicroBitDFUService
      + MicroBitEventService
      + DeviceInformationService

  - Updates to the Fiber scheduler to save SRAM. More specifically:
      + Removed the need to hold an empty processor context to intialise fibers.
      + The IDLE fiber now runs without a stack
      + fiber stacks are now only created when a fiber is descheduled for the first time, thereby reducing heap churn.
      + the 'main' fiber is now recycled into the fiber_pool if it leaves app_main()
      + fibers created through invoke() now only maintains the necessary part of teh parent stack that is needed, thereby
        reducing the stack size of spawned fibers.

  - Updates to the Message Bus to reduce the overall memory footprint of processing events. More specifically:
      + Event handlers are now always called using invoke(), such that non-blocking event handlers no longer need
        a dedicated fiber to execute - thereby saving SRAM and processor time.
      + Processing of events from the event queue is now rate paced. Events only continue to be processed as long as there
        are no fibers on the run queue. i.e. event processing is no longer greedy, thereby reducing the number of fibers
        created on the runqueue.

  - Updates to BLUEZOENE code to bring up core BLE services even if they are not enabled by default. This allows
    programs that do not require BLE to operate to benefit from the full range of SRAM, whilst still allowing the
    device to be programmed over BLE.

  - Updates to the Soft Device initialisation configuration, reducing the size of the GATT table held in the top 1.8K
    of its 8K memory region to around 800 bytes. This is sufficient to run the default set of BLE services on the micro:bit
    so the additional memory is configured as HEAP storage by MicroBitHeapAllocator.

  - Minor changes to a range of components to integrate with the above changes.
    + rename of free() to release() in DynamicPWM to avoid namespace collision with MicroBitHeap free()
    + rename of fork_on_block to invoke() to enhance readbility.

  - Many code cleanups and updates to out of date comments.
This commit is contained in:
Joe Finney 2015-08-31 23:25:10 +01:00
parent 42f43b2255
commit 857a626ed0
27 changed files with 1054 additions and 555 deletions

View File

@ -70,10 +70,10 @@ class DynamicPwm : public PwmOut
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate();
* pwm->free();
* pwm->release();
* @endcode
*/
void free();
void release();
/**
* Retreives the pin name associated with this DynamicPwm instance.

View File

@ -2,14 +2,26 @@
#define ERROR_NO_H
/**
* Error codes using in the Micro:bit runtime.
* Error codes used in the micro:bit runtime.
*/
enum Error{
MICROBIT_INVALID_VALUE = -1, // currently only used in MicroBit.cpp rand function when the max is less or equal to 0.
MICROBIT_IO_OP_NA = -2, // used in MicroBitPin.cpp for when a pin cannot perform a transition. (microbit io operation not allowed)
MICROBIT_COMPASS_IS_CALIBRATING = -3, // used in MicroBitPin.cpp for when a pin cannot perform a transition. (microbit io operation not allowed)
// Invalid parameter given.
MICROBIT_INVALID_VALUE = -1,
// Invalid I/O operation requested.
MICROBIT_IO_OP_NA = -2,
// Device calibration errors
MICROBIT_COMPASS_IS_CALIBRATING = -3,
MICROBIT_COMPASS_CALIBRATE_REQUIRED = -4,
MICROBIT_OOM = 20, // the MicroBit Out of memory error code...
MICROBIT_I2C_LOCKUP = 10 // the MicroBit I2C bus has locked up
// I2C Communication error occured (typically I2C module on processor has locked up.)
MICROBIT_I2C_LOCKUP = 10,
// 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
};
#endif

View File

@ -1,30 +1,16 @@
#ifndef MICROBIT_H
#define MICROBIT_H
// DEBUG. Enable this to get debug message routed through the USB serial interface.
//#define MICROBIT_DBG
#include "mbed.h"
#include "MicroBitConfig.h"
#include "MicroBitPanic.h"
#ifndef NO_BLE
#include "ble/BLE.h"
#endif
#include "ble/services/DeviceInformationService.h"
//error number enumeration
#include "ErrorNo.h"
/**
* Displays "=(" and an accompanying status code
* @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255.
*/
void panic(int statusCode);
void reset(int statusCode);
#include "MicroBitMalloc.h"
#include "MicroBitHeapAllocator.h"
#include "MicroBitCompat.h"
#include "MicroBitFiber.h"
#include "ManagedType.h"
@ -55,11 +41,10 @@ void reset(int statusCode);
#define MICROBIT_FLAG_DISPLAY_RUNNING 0x00000004
#define MICROBIT_FLAG_COMPASS_RUNNING 0x00000008
// Random number generator
#define NRF51822_RNG_ADDRESS 0x4000D000
#define MICROBIT_IO_PINS 20 // TODO: Need to change for live, currently 3 for test
#define MICROBIT_IO_PINS 20
// Enumeration of core components.
#define MICROBIT_ID_BUTTON_A 1
@ -73,7 +58,7 @@ void reset(int statusCode);
#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_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
@ -93,14 +78,10 @@ void reset(int statusCode);
#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton
// mBed pin assignments of core components.
//TODO: When platform is built for MB2 - pins will be defined by default, these will change...
#define MICROBIT_PIN_SDA P0_30
#define MICROBIT_PIN_SCL P0_0
#define MICROBIT_SYSTEM_COMPONENTS 10
#define MICROBIT_IDLE_COMPONENTS 6
#ifdef MICROBIT_DEBUG
#ifdef MICROBIT_DBG
extern Serial pc;
#endif
@ -128,7 +109,9 @@ class MicroBit
MicroBitI2C i2c;
// Serial Interface
#ifndef MICROBIT_DBG
MicroBitSerial serial;
#endif
// Array of components which are iterated during a system tick
MicroBitComponent* systemTickComponents[MICROBIT_SYSTEM_COMPONENTS];
@ -151,10 +134,9 @@ class MicroBit
MicroBitIO io;
// Bluetooth related member variables.
BLEDevice *ble;
DeviceInformationService *ble_device_information_service;
MicroBitDFUService *ble_firmware_update_service;
MicroBitEventService *ble_event_service;
BLEDevice *ble;
MicroBitDFUService *ble_firmware_update_service;
MicroBitEventService *ble_event_service;
/**
@ -284,9 +266,16 @@ class MicroBit
// Definition of the global instance of the MicroBit class.
// Using this as a variation on the singleton pattern, just to make
// code integration a little bit easier for 3rd parties.
// code integration a little bit easier for third parties.
extern MicroBit uBit;
//
// BLE callback when an active GATT session with another device is terminated.
// Used to reset state and restart advertising ourselves.
//
void bleDisconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason);
extern char MICROBIT_BLE_DEVICE_NAME[];
// Entry point for application programs. Called after the super-main function
// has initialized the device and runtime environment.
extern "C" void app_main();

119
inc/MicroBitConfig.h Normal file
View File

@ -0,0 +1,119 @@
/**
* Compile time configuration options for the micro:bit runtime.
*/
#ifndef MICROBIT_CONFIG_H
#define MICROBIT_CONFIG_H
#include "mbed.h"
//
// Memory configuration
//
// Physical address of the top of SRAM.
#define MICROBIT_SRAM_END 0x20004000
// Physical address of the top of the system stack (on mbed-classic this is the top of SRAM)
#define CORTEX_M0_STACK_BASE MICROBIT_SRAM_END
// Amount of memory reserved for the stack at the end of memory (bytes).
#define MICROBIT_STACK_SIZE 2000
// Physical address of the end of heap space.
#define MICROBIT_HEAP_END (CORTEX_M0_STACK_BASE - MICROBIT_STACK_SIZE)
// Block size used by the allocator in bytes.
// n.b. Currently only 32 bits (4 bytes) is supported.
#define MICROBIT_HEAP_BLOCK_SIZE 4
// The proportion of SRAM available on the mbed heap to reserve for the micro:bit heap.
#define MICROBIT_HEAP_SIZE 0.95
// if defined, reuse the 8K of SRAM 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.
#define MICROBIT_HEAP_REUSE_SD
// The lowest address of memory that is safe to use as heap storage when BLE is DISABLED
// Used to define the base of the heap when MICROBIT_HEAP_REUSE_SD is defined.
#define MICROBIT_HEAP_BASE_BLE_DISABLED 0x20000008
// The lowest address of memory that is safe to use as heap storage when BLE is ENABLED
// This is permissable if SD is configured to release some of its internal storage that
// is normally reserved for its BLE GATT table.
#define MICROBIT_HEAP_BASE_BLE_ENABLED 0x20001C00
// The highest address of memory normally reserved for Soft Device that is safe to use as heap storage
#define MICROBIT_HEAP_SD_LIMIT 0x20002000
//
// Fiber scheduler configuration
//
// Scheduling quantum (milliseconds)
// Also used to drive the micro:bit runtime system ticker.
#define FIBER_TICK_PERIOD_MS 6
//
// 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.
#define MICROBIT_SYSTEM_COMPONENTS 10
// 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.
#define MICROBIT_IDLE_COMPONENTS 6
//
// 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 'BLUEZONE' mode regardless of the settings below.
//
// Enable/Disable BLE during normal operation.
#define MICROBIT_BLE_ENABLED
// Enable/Disable BLUEZONE mode at power up.
#define MICROBIT_BLE_BLUEZONE
// Enable/Disable BLE Service: MicroBitDFU
// This allows over the air programming during normal operation.
#define MICROBIT_BLE_DFU_SERVICE
// Enable/Disable BLE Service: MicroBitEventService
// This allows routing of events from the micro:bit message bus over BLE.
#define MICROBIT_BLE_EVENT_SERVICE
// Enable/Disable BLE Service: MicroBitDeviceInformationService
// This enables the standard BLE device information service.
#define MICROBIT_BLE_DEVICE_INFORMATION_SERVICE
//
// Panic options
//
// Enable this to invoke a panic on out of memory conditions.
#define MICROBIT_PANIC_HEAP_FULL
//
// Debug options
//
// Enable this to route debug messages through the USB serial interface.
// n.b. This also disables the user serial port 'uBit.serial'.
//#define MICROBIT_DBG
// Enable this to receive diagnostic messages from the heap allocator via the USB serial interface.
// n.b. This requires MICROBIT_DBG to be defined.
//#define MICROBIT_HEAP_DBG
#endif

View File

@ -13,7 +13,6 @@
#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[];
@ -81,13 +80,10 @@ class MicroBitDFUService
// memory for our 8 bit control characteristics.
uint8_t controlByte;
// Opcodes can be issued here to control the MicroBitDFU Service, as defined above.
WriteOnlyGattCharacteristic<uint8_t> microBitDFUServiceControlCharacteristic;
// Read/Write characteristic to enable unlocking and discovery of the MicroBit's flashcode.
GattCharacteristic microBitDFUServiceFlashCodeCharacteristic;
GattAttribute::Handle_t microBitDFUServiceControlCharacteristicHandle;
GattAttribute::Handle_t microBitDFUServiceFlashCodeCharacteristicHandle;
// Displays the device's ID code as a histogram on the LED matrix display.
void showNameHistogram();

View File

@ -49,9 +49,8 @@ class MicroBitEventService
EventServiceEvent clientEventBuffer;
EventServiceEvent microBitEventBuffer;
// BLE GATT Characteristics for sending and receiving events to an attached device
GattCharacteristic microBitEventCharacteristic;
GattCharacteristic clientEventCharacteristic;
GattAttribute::Handle_t microBitEventCharacteristicHandle;
GattAttribute::Handle_t clientEventCharacteristicHandle;
};

View File

@ -11,24 +11,16 @@
#define MICROBIT_FIBER_H
#include "mbed.h"
#include "MicroBitConfig.h"
#include "MicroBitMessageBus.h"
// Typical stack size of each fiber.
// A physical stack of anything less than 1024 will likely hit overflow issues during ISR/mBed calls.
// However, as we're running a co=operative fiber scheduler, the size of the stack at the point of
// context switching is normally *very* small (circa 64 bytes!). Also, as we're likely to have many short lived threads
// being used, be actually perform a stack duplication on context switch, which keeps the RAM footprint of a fiber
// down to a minimum, without constraining what can be done insode a fiber context.
//
// TODO: Consider a split mode scheduler, that monitors used stack size, and maintains a dedicated, persistent
// stack for any long lived fibers with large stacks.
//
#define FIBER_STACK_SIZE 64
#define FIBER_TICK_PERIOD_MS 6
#define CORTEX_M0_STACK_BASE (0x20004000 - 4)
// stack for any long lived fibers with large stack
#define MICROBIT_FLAG_DATA_READ 0x01
// Fiber Scheduler Flags
#define MICROBIT_FLAG_DATA_READY 0x01
// Fiber Flags
#define MICROBIT_FIBER_FLAG_FOB 0x01
#define MICROBIT_FIBER_FLAG_PARENT 0x02
#define MICROBIT_FIBER_FLAG_CHILD 0x04
@ -39,7 +31,6 @@
* This is probably overkill, but the ARMCC compiler uses a lot register optimisation
* in its calling conventions, so better safe than sorry. :-)
*
* TODO: Check with ARM guys to see is they have suggestions to optimize this context block
*/
struct Cortex_M0_TCB
{
@ -58,6 +49,7 @@ struct Cortex_M0_TCB
uint32_t R12;
uint32_t SP;
uint32_t LR;
uint32_t stack_base;
};
/**
@ -66,8 +58,9 @@ struct Cortex_M0_TCB
struct Fiber
{
uint32_t stack_top, stack_bottom; // Address of this Fiber's stack. Stack is heap allocated, and full descending.
Cortex_M0_TCB tcb; // Thread context when last scheduled out.
uint32_t stack_bottom; // The start sddress of this Fiber's stack. 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.
@ -88,17 +81,18 @@ void scheduler_init();
* Any fiber reaching the end of its entry function will return here for recycling.
*/
void release_fiber(void);
/**
* Exit point for parameterised fibers.
* A wrapper around release_fiber() to enable transparent operaiton.
*/
void release_fiber(void *param);
/**
* Launches a fiber
*/
void launch_new_fiber()
*/
void launch_new_fiber(void (*ep)(void), void (*cp)(void))
#ifdef __GCC__
__attribute__((naked))
#endif
;
void launch_new_fiber_param(void (*ep)(void *), void (*cp)(void *), void *pm)
#ifdef __GCC__
__attribute__((naked))
#endif
@ -113,14 +107,7 @@ void launch_new_fiber()
*/
Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void) = release_fiber);
/**
* Launches a paramaterised fiber
*/
void launch_new_fiber_param()
#ifdef __GCC__
__attribute__((naked))
#endif
;
/**
* Creates a new parameterised Fiber, and launches it.
*
@ -173,7 +160,7 @@ void scheduler_tick();
void fiber_wait_for_event(uint16_t id, uint16_t value);
/**
* Executes the given function asynchronously.
* 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.
@ -183,10 +170,10 @@ void fiber_wait_for_event(uint16_t id, uint16_t value);
*
* @param entry_fn The function to execute.
*/
void fork_on_block(void (*entry_fn)(void));
void invoke(void (*entry_fn)(void));
/**
* Executes the given function asynchronously.
* 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
@ -197,17 +184,19 @@ void fork_on_block(void (*entry_fn)(void));
* @param entry_fn The function to execute.
* @param param an untyped parameter passed into the entry_fn anf completion_fn.
*/
void fork_on_block(void (*entry_fn)(void *), void *param);
void 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 allocaiton 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 the message bus whenever an event is raised.
* Checks to determine if any fibers blocked on the wait queue need to be woken up
@ -215,6 +204,11 @@ inline void verify_stack_size(Fiber *f);
*/
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.

View File

@ -0,0 +1,86 @@
/**
* 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! :-)
*
* N.B. The need for this should be reviewed in the future, should a different memory allocator be
* made availiable in the mbed platform.
*
* 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...
*
* TODO: Consider caching recently freed blocks to improve allocation time.
*/
#ifndef MICROBIT_HEAP_ALLOCTOR_H
#define MICROBIT_HEAP_ALLOCTOR_H
#include "Microbit.h"
#include <new>
// The number of heap segments created.
#define MICROBIT_HEAP_COUNT 2
// Flag to indicate that a given block is FREE/USED
#define MICROBIT_HEAP_BLOCK_FREE 0x80000000
int microbit_heap_init();
void *microbit_malloc(size_t size);
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 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 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

View File

@ -1,39 +0,0 @@
#ifndef MICROBIT_MALLOC_H
#define MICROBIT_MALLOC_H
#include "MicroBit.h"
#include <new>
/**
\brief Overrides malloc globally, and fires the panic function if we run out of memory!
*/
inline void* ubit_malloc(size_t size)
{
void *ptr;
ptr = malloc(size);
if(ptr == NULL)
panic(MICROBIT_OOM);
return ptr;
}
/**
\brief Overrides malloc globally, and fires the panic function if we run out of memory!
*/
inline void* operator new(size_t size) throw(std::bad_alloc)
{
void *ptr;
ptr = malloc(size);
if(ptr == NULL)
panic(MICROBIT_OOM);
return ptr;
}
#define malloc(X) ubit_malloc( X ) //macro! Override malloc! Hehehe
#endif

View File

@ -37,12 +37,6 @@ struct MicroBitListener
MicroBitListener(uint16_t id, uint16_t value, void *handler, void* arg);
};
struct MicroBitMessageBusCache
{
int seq;
MicroBitListener *ptr;
};
struct MicroBitEventQueueItem
{
MicroBitEvent evt;
@ -53,7 +47,7 @@ struct MicroBitEventQueueItem
* Creates a new MicroBitEventQueueItem.
* @param evt The event that is to be queued.
*/
MicroBitEventQueueItem(MicroBitEvent &evt);
MicroBitEventQueueItem(MicroBitEvent evt);
};
/**
@ -85,12 +79,11 @@ class MicroBitMessageBus : public MicroBitComponent
MicroBitMessageBus();
/**
* Send the given event to all regstered recipients.
* Queues the given event to be sent to all registered recipients.
*
* @param The event to send. This structure is assumed to be heap allocated, and will
* be automatically freed once all recipients have been notified.
* @param The event to send.
*
* THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE...
* n.b. THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE...
*
* Example:
* @code
@ -100,17 +93,18 @@ class MicroBitMessageBus : public MicroBitComponent
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN);
* @endcode
*/
void send(MicroBitEvent &evt);
void send(MicroBitEvent evt);
/**
* Send the given event to all regstered recipients, using a cached entry to minimize lookups.
* This is particularly useful for optimizing sensors that frequently send to the same channel.
* 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.
*
* IT IS RECOMMENDED THAT ALL EXTERNAL CODE USE THE send() FUNCTIONS INSTEAD OF THIS FUNCTION.
*
* @param evt The event to send. This structure is assumed to be heap allocated, and will
* be automatically freed once all recipients have been notified.
* @param c Cache entry to reduce lookups for commonly used channels.
* @param evt The event to send.
* @param c The cache entry to reduce lookups for commonly used channels.
*/
void send(MicroBitEvent &evt, MicroBitMessageBusCache *c);
void process(MicroBitEvent evt);
/**
* Register a listener function.

17
inc/MicroBitPanic.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef MICROBIT_PANIC_H
#define MICROBIT_PANIC_H
/**
* Displays "=(" and an accompanying status code.
* @param statusCode the appropriate status code - 0 means no code will be displayed. Status codes must be in the range 0-255.
*/
void panic(int statusCode);
/**
* Resets the micro:bit.
* @param statusCode the appropriate status code. Status codes must be in the range 0-255.
*/
void reset(int statusCode);
#endif

View File

@ -18,6 +18,10 @@ 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]
@ -47,16 +51,15 @@ swap_context
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.
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.
MOVS R7, #0x20 ; Load R8 with top of System Stack space.
LSLS R7, #24
MOVS R4, #0x40
LSLS R4, #8
ORRS R7, R4
MOV R4, R7
; 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
@ -66,7 +69,9 @@ store_stack
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.
@ -79,9 +84,12 @@ store_stack
; Copy the stack in.
; n.b. we do this after setting the SP to make comparisons easier.
MOV R4, R7 ; Load R4 with top of System Stack space.
; 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
@ -92,6 +100,7 @@ restore_stack
CMP R4, R6
BNE restore_stack
restore_stack_complete
LDR R4, [R1, #48]
MOV R12, R4
LDR R4, [R1, #44]
@ -116,8 +125,6 @@ restore_stack
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
@ -158,13 +165,8 @@ save_context
; Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point
; of sceduling, but we need a lot of capacity for interrupt handling and other functions.
MOVS R5, #0x20 ; Load R8 with top of System Stack space.
LSLS R5, #24
MOVS R4, #0x40
LSLS R4, #8
ORRS R5, R4
MOV R4, R5
LDR R4, [R0,#60] ; Load R4 with the fiber's defined stack_base.
store_stack1
SUBS R4, #4
SUBS R1, #4
@ -186,8 +188,7 @@ store_stack1
BX LR
; R0 Contains a pointer to the TCB of the fibre to snapshot
; R0 Contains a pointer to the TCB of the fiber to snapshot
save_register_context
; Write our core registers into the TCB

View File

@ -20,6 +20,10 @@ 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]
@ -49,16 +53,15 @@ swap_context:
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.
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.
MOVS R7, #0x20 @ Load R8 with top of System Stack space.
LSLS R7, #24
MOVS R4, #0x40
LSLS R4, #8
ORRS R7, R4
MOV R4, R7
@ 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
@ -68,7 +71,9 @@ store_stack:
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.
@ -81,9 +86,12 @@ store_stack:
@ Copy the stack in.
@ n.b. we do this after setting the SP to make comparisons easier.
MOV R4, R7 @ Load R4 with top of System Stack space.
@ 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
@ -94,6 +102,7 @@ restore_stack:
CMP R4, R6
BNE restore_stack
restore_stack_complete:
LDR R4, [R1, #48]
MOV R12, R4
LDR R4, [R1, #44]
@ -118,8 +127,6 @@ restore_stack:
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
@ -160,13 +167,8 @@ save_context:
@ Finally, Copy the stack. We do this to reduce RAM footprint, as stackis usually very small at the point
@ of sceduling, but we need a lot of capacity for interrupt handling and other functions.
MOVS R5, #0x20 @ Load R8 with top of System Stack space.
LSLS R5, #24
MOVS R4, #0x40
LSLS R4, #8
ORRS R5, R4
MOV R4, R5
LDR R4, [R0,#60] @ Load R4 with the fiber's defined stack_base.
store_stack1:
SUBS R4, #4
SUBS R1, #4
@ -186,9 +188,9 @@ store_stack1:
@ Return to caller (scheduler).
BX LR
@ R0 Contains a pointer to the TCB of the fibre to snapshot
@ R0 Contains a pointer to the TCB of the fiber to snapshot
save_register_context:
@ Write our core registers into the TCB

View File

@ -1,3 +1,4 @@
#include "MicroBitHeapAllocator.h"
#include "DynamicPwm.h"
@ -126,12 +127,11 @@ DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence, int pe
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate();
* pwm->free();
* pwm->release();
* @endcode
*/
void DynamicPwm::free()
void DynamicPwm::release()
{
//free the pwm instance.
NRF_GPIOTE->CONFIG[(uint8_t) _pwm.pwm] = 0;
pwmout_free(&_pwm);

View File

@ -1,5 +1,7 @@
#include <string.h>
#include <stdlib.h>
#include "mbed.h"
#include "MicroBitHeapAllocator.h"
#include "MicroBitCompat.h"
#include "ManagedString.h"

View File

@ -1,12 +1,15 @@
#include "MicroBit.h"
char MICROBIT_BLE_DEVICE_NAME[] = "BBC MicroBit [xxxxx]";
#if defined (MICROBIT_BLE_ENABLED) && defined (MICROBIT_BLE_DEVICE_INFORMATION_SERVICE)
const char MICROBIT_BLE_MANUFACTURER[] = "The Cast of W1A";
const char MICROBIT_BLE_MODEL[] = "Microbit SB2";
const char MICROBIT_BLE_SERIAL[] = "SN1";
const char MICROBIT_BLE_HARDWARE_VERSION[] = "0.2";
const char MICROBIT_BLE_FIRMWARE_VERSION[] = "1.1";
const char MICROBIT_BLE_SOFTWARE_VERSION[] = "1.0";
#endif
/**
* custom function for panic for malloc & new due to scoping issue.
@ -21,7 +24,7 @@ void panic(int statusCode)
*/
void bleDisconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
uBit.ble->startAdvertising(); // restart advertising!
uBit.ble->startAdvertising();
}
/**
@ -46,7 +49,9 @@ void bleDisconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t r
MicroBit::MicroBit() :
flags(0x00),
i2c(MICROBIT_PIN_SDA, MICROBIT_PIN_SCL),
#ifndef MICROBIT_DBG
serial(USBTX, USBRX),
#endif
MessageBus(),
display(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_WIDTH, MICROBIT_DISPLAY_HEIGHT),
buttonA(MICROBIT_ID_BUTTON_A,MICROBIT_PIN_BUTTON_A),
@ -88,20 +93,35 @@ void MicroBit::init()
// Seed our random number generator
seedRandom();
#ifndef NO_BLE
#ifdef MICROBIT_BLE_ENABLED
// Start the BLE stack.
ble = new BLEDevice();
ble = new BLEDevice();
ble->init();
ble->onDisconnection(bleDisconnectionCallback);
// Add our auxiliary services.
// Bring up any configured auxiliary services.
#ifdef MICROBIT_BLE_DFU_SERVICE
ble_firmware_update_service = new MicroBitDFUService(*ble);
ble_device_information_service = new DeviceInformationService(*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, MICROBIT_BLE_SERIAL, MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION);
ble_event_service = new MicroBitEventService(*ble);
// Compute our auto-generated MicroBit device name.
ble_firmware_update_service->getName(MICROBIT_BLE_DEVICE_NAME+14);
#endif
#ifdef MICROBIT_BLE_DEVICE_INFORMATION_SERVICE
#ifdef OLD
ble_device_information_service = new DeviceInformationService(*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, MICROBIT_BLE_SERIAL, MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION);
#endif
DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, MICROBIT_BLE_SERIAL, MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION);
#endif
#ifdef MICROBIT_BLE_EVENT_SERVICE
ble_event_service = new MicroBitEventService(*ble);
#endif
// Setup advertising.
ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
@ -109,12 +129,7 @@ void MicroBit::init()
ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
ble->setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000));
ble->startAdvertising();
#else
ble = NULL;
ble_firmware_update_service = NULL;
ble_device_information_service = NULL;
ble_event_service = NULL;
#endif
#endif
// Start refreshing the Matrix Display
systemTicker.attach(this, &MicroBit::systemTick, MICROBIT_DISPLAY_REFRESH_PERIOD);
@ -239,7 +254,7 @@ void MicroBit::systemTick()
for(int i = 0; i < MICROBIT_IDLE_COMPONENTS; i++)
if(idleThreadComponents[i] != NULL && idleThreadComponents[i]->isIdleCallbackNeeded())
{
fiber_flags |= MICROBIT_FLAG_DATA_READ;
fiber_flags |= MICROBIT_FLAG_DATA_READY;
break;
}
@ -259,7 +274,7 @@ void MicroBit::systemTasks()
if(idleThreadComponents[i] != NULL)
idleThreadComponents[i]->idleTick();
fiber_flags &= ~MICROBIT_FLAG_DATA_READ;
fiber_flags &= ~MICROBIT_FLAG_DATA_READY;
}
/**

View File

@ -1,6 +1,4 @@
#include "inc/MicroBit.h"
#include "inc/MicroBitButton.h"
#include "inc/MicroBitMessageBus.h"
#include "MicroBit.h"
/**
* Constructor.

View File

@ -1,4 +1,4 @@
#include "inc/MicroBit.h"
#include "MicroBit.h"
/**
* Constructor.

View File

@ -23,11 +23,15 @@
* @param messageBus callback function to receive MicroBitMessageBus events.
*/
MicroBitDFUService::MicroBitDFUService(BLEDevice &_ble) :
ble(_ble),
microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte),
microBitDFUServiceFlashCodeCharacteristic(MicroBitDFUServiceFlashCodeCharacteristicUUID, (uint8_t *)&flashCode, 0, sizeof(uint32_t),
GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
ble(_ble)
{
// Opcodes can be issued here to control the MicroBitDFU Service, as defined above.
WriteOnlyGattCharacteristic<uint8_t> microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte);
// Read/Write characteristic to enable unlocking and discovery of the MicroBit's flashcode.
GattCharacteristic microBitDFUServiceFlashCodeCharacteristic(MicroBitDFUServiceFlashCodeCharacteristicUUID, (uint8_t *)&flashCode, 0, sizeof(uint32_t),
GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
authenticated = false;
flashCodeRequested = false;
@ -39,6 +43,9 @@ MicroBitDFUService::MicroBitDFUService(BLEDevice &_ble) :
ble.addService(service);
microBitDFUServiceControlCharacteristicHandle = microBitDFUServiceControlCharacteristic.getValueHandle();
microBitDFUServiceFlashCodeCharacteristicHandle = microBitDFUServiceFlashCodeCharacteristic.getValueHandle();
ble.onDataWritten(this, &MicroBitDFUService::onDataWritten);
}
@ -69,10 +76,6 @@ int MicroBitDFUService::getName(char *name)
int d = MICROBIT_DFU_HISTOGRAM_HEIGHT;
int h;
#ifdef MICROBIT_DEBUG
pc.printf("MicroBitDFUService::getName: Called [%.8x]\n",n);
#endif
for (int i=0; i<MICROBIT_DFU_HISTOGRAM_WIDTH;i++)
{
h = (n % d) / ld;
@ -91,10 +94,6 @@ int MicroBitDFUService::getName(char *name)
*/
void MicroBitDFUService::pair()
{
#ifdef MICROBIT_DEBUG
pc.printf("MicroBitDFUService::pair: Called\n");
#endif
ManagedString blueZoneString("BLUE ZONE...");
ManagedString pairString("PAIR?");
@ -142,29 +141,10 @@ void MicroBitDFUService::pair()
*/
void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params)
{
#ifdef MICROBIT_DEBUG
pc.printf("MicroBitDFUService::onDataWritten: Called... ");
#endif
if (params->handle == microBitDFUServiceControlCharacteristic.getValueHandle()) {
#ifdef MICROBIT_DEBUG
pc.printf("Control Point:\n ");
#endif
if (params->handle == microBitDFUServiceControlCharacteristicHandle)
{
if (params->len < 1)
{
#ifdef MICROBIT_DEBUG
pc.printf(" invalid. Ignoring.\n");
#endif
return;
}
#ifdef MICROBIT_DEBUG
for (int i=0; i<params->len; i++)
pc.printf("%.2x ", params->data[i]);
pc.printf("\n");
#endif
switch(params->data[0])
{
@ -172,7 +152,7 @@ void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params)
if (authenticated)
{
#ifdef MICROBIT_DEBUG
#ifdef MICROBIT_DBG
pc.printf(" ACTIVATING BOOTLOADER.\n");
#endif
bootloader_start();
@ -181,35 +161,25 @@ void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params)
break;
case MICROBIT_DFU_OPCODE_START_PAIR:
#ifdef MICROBIT_DEBUG
pc.printf(" START_PAIR: ");
#endif
flashCodeRequested = true;
break;
}
}
if (params->handle == microBitDFUServiceFlashCodeCharacteristic.getValueHandle()) {
#ifdef MICROBIT_DEBUG
pc.printf("FlashCode\n\n");
#endif
if (params->handle == microBitDFUServiceFlashCodeCharacteristicHandle)
{
if (params->len >= 4)
{
uint32_t lockCode=0;
memcpy(&lockCode, params->data, 4);
if (lockCode == NRF_FICR->DEVICEID[0])
{
#ifdef MICROBIT_DEBUG
pc.printf("AUTHENTICATED\n");
#ifdef MICROBIT_DBG
pc.printf("MicroBitDFU: FLASHCODE AUTHENTICATED\n");
#endif
authenticated = true;
}else{
#ifdef MICROBIT_DEBUG
pc.printf("NOT AUTHENTICATED: %8x\n", lockCode);
#endif
authenticated = false;
}
}
@ -236,10 +206,6 @@ void MicroBitDFUService::showTick()
*/
void MicroBitDFUService::showNameHistogram()
{
#ifdef MICROBIT_DEBUG
pc.printf("MicroBitDFUService::showNameHistogram: Called\n");
#endif
uBit.display.resetAnimation(0);
uint32_t n = NRF_FICR->DEVICEID[1];
@ -266,11 +232,9 @@ void MicroBitDFUService::showNameHistogram()
*/
void MicroBitDFUService::releaseFlashCode()
{
#ifdef MICROBIT_DEBUG
pc.printf("MicroBitDFUService::releaseFlashCode: Called\n");
#endif
flashCode = NRF_FICR->DEVICEID[0];
ble.updateCharacteristicValue(microBitDFUServiceFlashCodeCharacteristic.getValueHandle(), (uint8_t *)&flashCode, sizeof(uint32_t));
ble.updateCharacteristicValue(microBitDFUServiceFlashCodeCharacteristicHandle, (uint8_t *)&flashCode, sizeof(uint32_t));
}
/**

View File

@ -15,12 +15,14 @@
* @param _ble The instance of a BLE device that we're running on.
*/
MicroBitEventService::MicroBitEventService(BLEDevice &_ble) :
ble(_ble),
microBitEventCharacteristic(MicroBitEventServiceMicroBitEventCharacteristicUUID, (uint8_t *)&microBitEventBuffer, 0, sizeof(EventServiceEvent),
GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY),
clientEventCharacteristic(MicroBitEventServiceClientEventCharacteristicUUID, (uint8_t *)&clientEventBuffer, 0, sizeof(EventServiceEvent),
GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE)
ble(_ble)
{
GattCharacteristic microBitEventCharacteristic(MicroBitEventServiceMicroBitEventCharacteristicUUID, (uint8_t *)&microBitEventBuffer, 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);
clientEventBuffer.type = 0x00;
clientEventBuffer.reason = 0x00;
@ -32,6 +34,9 @@ MicroBitEventService::MicroBitEventService(BLEDevice &_ble) :
ble.addService(service);
microBitEventCharacteristicHandle = microBitEventCharacteristic.getValueHandle();
clientEventCharacteristicHandle = clientEventCharacteristic.getValueHandle();
ble.onDataWritten(this, &MicroBitEventService::onDataWritten);
}
@ -44,7 +49,7 @@ void MicroBitEventService::onDataWritten(const GattWriteCallbackParams *params)
int len = params->len;
EventServiceEvent *e = (EventServiceEvent *)params->data;
if (params->handle == clientEventCharacteristic.getValueHandle()) {
if (params->handle == clientEventCharacteristicHandle) {
// Read and fire all events...
while (len >= 4)
@ -67,7 +72,7 @@ void MicroBitEventService::onMicroBitEvent(MicroBitEvent evt)
e->type = evt.source;
e->reason = evt.value;
ble.updateCharacteristicValue(microBitEventCharacteristic.getValueAttribute().getHandle(), (const uint8_t *)e, sizeof(EventServiceEvent));
ble.updateCharacteristicValue(microBitEventCharacteristicHandle, (const uint8_t *)e, sizeof(EventServiceEvent));
}
}

View File

@ -13,27 +13,27 @@
* 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.
Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing.
Fiber *idle = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks.
/*
* Scheduler state.
*/
Fiber *currentFiber = NULL; // The context in which the current fiber is executing.
Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing.
Fiber *runQueue = NULL; // The list of runnable fibers.
Fiber *sleepQueue = NULL; // The list of blocked fibers waiting on a fiber_sleep() operation.
Fiber *waitQueue = NULL; // The list of blocked fibers waiting on an event.
Fiber *idle = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks.
Fiber *fiberPool = NULL; // Pool of unused fibers, just waiting for a job to do.
Cortex_M0_TCB *emptyContext = NULL; // Initialized context for fiber entry state.
/*
* Time since power on. Measured in milliseconds.
* When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-)
*/
unsigned long ticks = 0;
/*
* Scheduler wide flags
*/
uint8_t fiber_flags = 0;
/**
@ -49,14 +49,30 @@ void queue_fiber(Fiber *f, Fiber **queue)
{
__disable_irq();
// Record which queue this fiber is on.
f->queue = queue;
f->next = *queue;
f->prev = NULL;
if(*queue != NULL)
(*queue)->prev = f;
*queue = f;
// 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();
}
@ -104,7 +120,6 @@ Fiber *getFiberContext()
{
f = fiberPool;
dequeue_fiber(f);
// dequeue_fiber() exits with irqs enabled, so no need to do this again!
}
else
@ -116,17 +131,13 @@ Fiber *getFiberContext()
if (f == NULL)
return NULL;
f->stack_bottom = (uint32_t) malloc(FIBER_STACK_SIZE);
f->stack_top = f->stack_bottom + FIBER_STACK_SIZE;
if (f->stack_bottom == NULL)
{
delete f;
return NULL;
}
f->stack_bottom = NULL;
f->stack_top = NULL;
}
// Ensure this fiber is in suitable state for reuse.
f->flags = 0;
f->tcb.stack_base = CORTEX_M0_STACK_BASE;
return f;
}
@ -141,21 +152,16 @@ Fiber *getFiberContext()
void scheduler_init()
{
// Create a new fiber context
currentFiber = new Fiber();
currentFiber->stack_bottom = (uint32_t) malloc(FIBER_STACK_SIZE);
currentFiber->stack_top = ((uint32_t) currentFiber->stack_bottom) + FIBER_STACK_SIZE;
currentFiber->flags = 0;
currentFiber = getFiberContext();
// Add ourselves to the run queue.
queue_fiber(currentFiber, &runQueue);
// Build a fiber context around the current thread.
swap_context(&currentFiber->tcb, &currentFiber->tcb, currentFiber->stack_top, currentFiber->stack_top);
// Create the IDLE task. This will actually scheduk the IDLE task, but it will immediately yeld back to us.
// Remove it from the run queue though, as IDLE is a special case.
idle = create_fiber(idle_task);
dequeue_fiber(idle);
// Create the IDLE fiber.
// Configure the fiber to directly enter the idle task.
idle = getFiberContext();
idle->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
idle->tcb.LR = (uint32_t) &idle_task;
// Flag that we now have a scheduler running
uBit.flags |= MICROBIT_FLAG_SCHEDULER_RUNNING;
@ -239,7 +245,6 @@ void fiber_sleep(unsigned long t)
// 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();
@ -316,7 +321,7 @@ void fiber_wait_for_event(uint16_t id, uint16_t value)
*
* @param entry_fn The function to execute.
*/
void fork_on_block(void (*entry_fn)(void))
void invoke(void (*entry_fn)(void))
{
// Validate our parameters.
if (entry_fn == NULL)
@ -334,7 +339,7 @@ void fork_on_block(void (*entry_fn)(void))
// refer to our calling function.
save_register_context(&currentFiber->tcb);
// If we're here, there are three possibilities:
// 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.
@ -369,17 +374,17 @@ void fork_on_block(void (*entry_fn)(void))
* 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 anf completion_fn.
* @param param an untyped parameter passed into the entry_fn.
*/
void fork_on_block(void (*entry_fn)(void *), void *param)
void invoke(void (*entry_fn)(void *), void *param)
{
// Validate our parameters.
if (entry_fn == NULL)
return;
if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB)
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 fork n block context,
// 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;
@ -389,7 +394,7 @@ void fork_on_block(void (*entry_fn)(void *), void *param)
// refer to our calling function.
save_register_context(&currentFiber->tcb);
// If we're here, there are three possibilities:
// 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.
@ -411,28 +416,61 @@ void fork_on_block(void (*entry_fn)(void *), void *param)
// 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();
release_fiber(param);
}
void launch_new_fiber()
void launch_new_fiber(void (*ep)(void), void (*cp)(void))
{
// Launch into the entry point, now we're in the correct context.
//uint32_t ep = currentFiber->stack_bottom;
uint32_t ep = *((uint32_t *)(currentFiber->stack_bottom + 0));
uint32_t cp = *((uint32_t *)(currentFiber->stack_bottom + 4));
// Execute the thread's entrypoint
(*(void(*)())(ep))();
ep();
// Execute the thread's completion routine;
(*(void(*)())(cp))();
cp();
// If we get here, then the completion routine didn't recycle the fiber
// so do it anyway. :-)
// If we get here, then the completion routine didn't recycle the fiber... so do it anyway. :-)
release_fiber();
}
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.
*
@ -442,64 +480,9 @@ void launch_new_fiber()
*/
Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void))
{
// Validate our parameters.
if (entry_fn == NULL || completion_fn == NULL)
return NULL;
// Allocate a TCB from the new fiber. This will come from the tread pool if availiable,
// else a new one will be allocated on the heap.
Fiber *newFiber = getFiberContext();
// If we're out of memory, there's nothing we can do.
if (newFiber == NULL)
return NULL;
*((uint32_t *)newFiber->stack_bottom) = (uint32_t) entry_fn;
*((uint32_t *)(newFiber->stack_bottom+4)) = (uint32_t) completion_fn;
// Use cache fiber state if we have it. This is faster, and safer if we're called from
// an interrupt context.
if (emptyContext != NULL)
{
memcpy(&newFiber->tcb, emptyContext, sizeof(Cortex_M0_TCB));
}
else
{
// Otherwise, initialize the TCB from the current context.
save_context(&newFiber->tcb, newFiber->stack_top);
// Assign the new stack pointer and entry point.
newFiber->tcb.SP = CORTEX_M0_STACK_BASE;
newFiber->tcb.LR = (uint32_t) &launch_new_fiber;
// Store this context for later use.
emptyContext = (Cortex_M0_TCB *) malloc (sizeof(Cortex_M0_TCB));
memcpy(emptyContext, &newFiber->tcb, sizeof(Cortex_M0_TCB));
}
// Add new fiber to the run queue.
queue_fiber(newFiber, &runQueue);
return newFiber;
return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, NULL, 0);
}
void launch_new_fiber_param()
{
// Launch into the entry point, now we're in the correct context.
uint32_t ep = *((uint32_t *)(currentFiber->stack_bottom + 0));
uint32_t pm = *((uint32_t *)(currentFiber->stack_bottom + 4));
uint32_t cp = *((uint32_t *)(currentFiber->stack_bottom + 8));
// Execute the thread's entry routine.
(*(void(*)(void *))(ep))((void *)pm);
// Execute the thread's completion routine.
// Execute the thread's entry routine.
(*(void(*)(void *))(cp))((void *)pm);
// If we get here, then recycle the fiber context.
release_fiber((void *)pm);
}
/**
* Creates a new parameterised Fiber, and launches it.
@ -511,55 +494,28 @@ void launch_new_fiber_param()
*/
Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *))
{
// Validate our parameters.
if (entry_fn == NULL || completion_fn == NULL)
return NULL;
// Allocate a TCB from the new fiber. This will come from the tread pool if availiable,
// else a new one will be allocated on the heap.
Fiber *newFiber = getFiberContext();
// If we're out of memory, there's nothing we can do.
if (newFiber == NULL)
return NULL;
*((uint32_t *)newFiber->stack_bottom) = (uint32_t) entry_fn;
*((uint32_t *)(newFiber->stack_bottom+4)) = (uint32_t) param;
*((uint32_t *)(newFiber->stack_bottom+8)) = (uint32_t) completion_fn;
// Use cache fiber state. This is safe, as the empty context is always created in the non-paramterised
// version of create_fiber before we're ever called.
memcpy(&newFiber->tcb, emptyContext, sizeof(Cortex_M0_TCB));
// Assign the link register to refer to the thread entry point in THIS function.
newFiber->tcb.LR = (uint32_t) &launch_new_fiber_param;
// Add new fiber to the run queue.
queue_fiber(newFiber, &runQueue);
return newFiber;
return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, (uint32_t) param, 1);
}
/**
* Exit point for parameterised fibers.
* A wrapper around release_fiber() to enable transparent operaiton.
* Default exit point for all parameterised fibers.
* Any fiber reaching the end of its entry function will return here for recycling.
*/
void release_fiber(void *param)
{
void release_fiber(void * param)
{
release_fiber();
}
/**
* Exit point for all fibers.
* Any fiber reaching the end of its entry function will return here for recycling.
* Default exit point for all fibers.
* Any fiber reaching the end of its entry function will return here for recycling.
*/
void release_fiber(void)
{
// Remove ourselves form the runqueue.
dequeue_fiber(currentFiber);
// Add ourselves to the list of free fibers
queue_fiber(currentFiber, &fiberPool);
// Find something else to do!
@ -571,6 +527,8 @@ void release_fiber(void)
*
* If the stack allocaiton 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.
*/
void verify_stack_size(Fiber *f)
{
@ -579,21 +537,38 @@ void verify_stack_size(Fiber *f)
uint32_t bufferSize;
// Calculate the stack depth.
stackDepth = CORTEX_M0_STACK_BASE - ((uint32_t) __get_MSP());
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 exponentially.
// If we're too small, increase our buffer size.
if (bufferSize < stackDepth)
{
while (bufferSize < stackDepth)
bufferSize = bufferSize << 1;
// To ease heap churn, we choose the next largest multple of 32 bytes.
bufferSize = (stackDepth + 32) & 0xffffffe0;
free((void *)f->stack_bottom);
// 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 '1' if there is at least one fiber currently on the run queue, and '0' otherwise.
*/
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.
@ -605,53 +580,72 @@ void schedule()
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 nrely created fiber, and restore the context of the
// currently running fiber.
// 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)
{
// Ensure the stack allocation of the new fiber is large enough
verify_stack_size(forkedFiber);
// 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 FORK_ON_BLOCK flag is still set, we're the old thread, so
// restore the current fiber to its stored context, and we're done.
// if we're the new thread, we must have been unblocked by the scheduler, so simply return.
// 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(&currentFiber->tcb);
else
return;
// 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 || fiber_flags & MICROBIT_FLAG_DATA_READ)
if (runQueue == NULL || fiber_flags & MICROBIT_FLAG_DATA_READY)
currentFiber = idle;
// If the current fiber is on the run queue, round robin.
else if (currentFiber->queue == &runQueue)
// If the current fiber is on the run queue, round robin.
currentFiber = currentFiber->next == NULL ? runQueue : currentFiber->next;
// Otherwise, just pick the head of the run queue.
else
// Otherwise, just pick the head of the run queue.
currentFiber = runQueue;
// Swap to the context of the chosen fiber, and we're done.
// Don't bother with the overhead of switching if there's only one fiber on the runqueue!
if (currentFiber != oldFiber)
{
// Ensure the stack allocation of the fiber being scheduled out is large enough
verify_stack_size(oldFiber);
// Special case for the idle task, as we don't maintain a stack context (just to save memory).
if (currentFiber == idle)
{
idle->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
idle->tcb.LR = (uint32_t) &idle_task;
}
// Schedule in the new fiber.
swap_context(&oldFiber->tcb, &currentFiber->tcb, oldFiber->stack_top, currentFiber->stack_top);
if (oldFiber == idle)
{
// Just swap in the new fiber, and discard changes to stack and register context.
swap_context(NULL, &currentFiber->tcb, NULL, 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, &currentFiber->tcb, oldFiber->stack_top, currentFiber->stack_top);
}
}
}
@ -664,13 +658,16 @@ void idle_task()
{
while(1)
{
if (uBit.ble)
uBit.ble->waitForEvent();
else
__WFI();
uBit.systemTasks();
if(scheduler_runqueue_empty())
{
if (uBit.ble)
uBit.ble->waitForEvent();
else
__WFI();
}
schedule();
}
}

View File

@ -0,0 +1,360 @@
#include "MicroBit.h"
/**
* 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! :-)
*
* N.B. The need for this should be reviewed in the future, should a different memory allocator be
* made availiable in the mbed platform.
*
* 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...
*
* TODO: Consider caching recently freed blocks to improve allocation time.
*/
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.
};
// Create the necessary heap definitions.
// We use two heaps by default: one for SoftDevice reuse, and one to run inside the mbed heap.
HeapDefinition heap[MICROBIT_HEAP_COUNT] = {NULL};
#if defined(MICROBIT_DBG) && defined(MICROBIT_HEAP_DBG)
// Internal diagnostics function.
// 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)
{
pc.printf("--- HEAP NOT INITIALISED ---\n");
return;
}
// Disable IRQ temporarily to ensure no race conditions!
__disable_irq();
pc.printf("heap_start : %p\n", heap.heap_start);
pc.printf("heap_end : %p\n", heap.heap_end);
pc.printf("heap_size : %d\n", (int)heap.heap_end - (int)heap.heap_start);
block = heap.heap_start;
while (block < heap.heap_end)
{
blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE;
pc.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4);
if (cols++ == 20)
{
pc.printf("\n");
cols = 0;
}
if (*block & MICROBIT_HEAP_BLOCK_FREE)
totalFreeBlock += blockSize;
else
totalUsedBlock += blockSize;
block += blockSize;
}
pc.printf("\n");
pc.printf("mb_total_free : %d\n", totalFreeBlock*4);
pc.printf("mb_total_used : %d\n", totalUsedBlock*4);
}
// Internal diagnostics function.
// Diplays a usage summary about all known heaps...
void microbit_heap_print()
{
for (int i=0; i < MICROBIT_HEAP_COUNT; i++)
{
pc.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;
}
int
microbit_create_sd_heap(HeapDefinition &heap)
{
#if !defined(MICROBIT_HEAP_REUSE_SD)
// We're not configure to use memory of this sort.
return 0;
#endif
// OK, see how much of the RAM assigned to Soft Device we can reclaim.
#ifdef MICROBIT_BLE_ENABLED
heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_ENABLED;
heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT;
#else
heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_DISABLED;
heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT;
#endif
microbit_initialise_heap(heap);
return 1;
}
int
microbit_create_nested_heap(HeapDefinition &heap)
{
uint32_t mb_heap_max;
// Snapshot something at the top of the main heap.
void *p = native_malloc(sizeof(uint32_t));
// Compute the size left in our heap, taking care to ensure it lands on a word boundary.
mb_heap_max = (uint32_t) (((float)(MICROBIT_HEAP_END - (uint32_t)p)) * MICROBIT_HEAP_SIZE);
mb_heap_max &= 0xFFFFFFFC;
// Release our reference pointer.
native_free(p);
// Allocate memory for our heap.
// We do this iteratively, as some build configurations seem to have static limits
// on heap size... This allows us to be keep going anyway!
while (heap.heap_start == NULL)
{
heap.heap_start = (uint32_t *)native_malloc(mb_heap_max);
if (heap.heap_start == NULL)
{
mb_heap_max -= 32;
if (mb_heap_max <= 0)
return 0;
}
}
heap.heap_end = heap.heap_start + mb_heap_max / MICROBIT_HEAP_BLOCK_SIZE;
microbit_initialise_heap(heap);
return 1;
}
/**
* Initialise the microbit heap according to the parameters defined in MicroBitConfig.h
* After this is called, any future calls to malloc, new, free or delete will use the new heap.
* n.b. 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 strandard mbed heap.
*/
int
microbit_heap_init()
{
int r = 0;
// Disable IRQ temporarily to ensure no race conditions!
__disable_irq();
r += microbit_create_nested_heap(heap[0]);
r += microbit_create_sd_heap(heap[1]);
// Enable Interrupts
__enable_irq();
#if defined(MICROBIT_DBG) && defined(MICROBIT_HEAP_DBG)
microbit_heap_print();
#endif
return r;
}
/**
* Attempt to allocate a given amount of memory from the given heap.
* @param size The amount of memory, in bytes, to allocate.
* @param heap The heap the memory is to be allocated from.
* @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;
}
/**
* 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)
{
void *p;
// Assign the memory from the first heap created that has space.
for (int i=0; i < MICROBIT_HEAP_COUNT; i++)
{
if(heap[i].heap_start != NULL)
{
p = microbit_malloc(size, heap[i]);
if (p != NULL)
{
#ifdef MICROBIT_HEAP_DBG
pc.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)
{
#ifdef MICROBIT_HEAP_DBG
pc.printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p);
#endif
return p;
}
// We're totally out of options (and memory!).
#ifdef MICROBIT_HEAP_DBG
pc.printf("microbit_malloc: OUT OF MEMORY\n");
#endif
#ifdef MICROBIT_PANIC_HEAP_FULL
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;
#ifdef MICROBIT_HEAP_DBG
pc.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 < MICROBIT_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);
}

View File

@ -4,7 +4,7 @@
* Represents a single IO pin on the edge connector.
*/
#include "inc/MicroBitIO.h"
#include "MicroBitIO.h"
/**
* Constructor.

View File

@ -9,9 +9,9 @@
/**
* Constructor.
* Create a new MicroBitEventQueueItem.
* @param evt The event that is to be queued.
* @param evt The event to be queued.
*/
MicroBitEventQueueItem::MicroBitEventQueueItem(MicroBitEvent &evt)
MicroBitEventQueueItem::MicroBitEventQueueItem(MicroBitEvent evt)
{
this->evt = evt;
this->next = NULL;
@ -54,14 +54,13 @@ MicroBitMessageBus::MicroBitMessageBus()
this->listeners = NULL;
this->evt_queue_head = NULL;
this->evt_queue_tail = NULL;
this->seq = 0;
}
/**
* Invokes a callback on a given MicroBitListener
*
* Internal wrapper function, used to enable
* parameterized callbacks through the fiber scheduler.
* parameterised callbacks through the fiber scheduler.
*/
void async_callback(void *param)
{
@ -122,20 +121,27 @@ MicroBitEventQueueItem* MicroBitMessageBus::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.
*/
void MicroBitMessageBus::idleTick()
{
MicroBitEventQueueItem *item = this->dequeueEvent();
// Whilst there are events to process, pull them off the queue and process them.
while (item != NULL)
// 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.
this->send(item->evt);
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();
}
@ -151,68 +157,45 @@ int MicroBitMessageBus::isIdleCallbackNeeded()
}
/**
* Send the given event to all regstered recipients.
* Queues the given event to be sent to all registered recipients.
*
* @param The event to send. This structure is assumed to be heap allocated, and will
* be automatically freed once all recipients have been notified.
*/
void MicroBitMessageBus::send(MicroBitEvent &evt)
{
this->send(evt, NULL);
}
/**
* Send the given event to all registered recipients, using a cached entry to minimize lookups.
* This is particularly useful for optimizing sensors that frequently send to the same channel.
* @param The event to send.
*
* @param evt The event to send. This structure is assumed to be heap allocated, and will
* be automatically freed once all recipients have been notified.
* @param c Cache entry to reduce lookups for commonly used channels.
*
* TODO: For now, this is unbuffered. We should consider scheduling events here, and operating
* a different thread that empties the queue. This would perhaps provide greater opportunities
* for aggregation.
* n.b. THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE...
*
* Example:
* @code
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,NULL);
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,false);
* evt.fire();
* //OR YOU CAN DO THIS...
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN,ticks,NULL,true);
* MicroBitEvent evt(id,MICROBIT_BUTTON_EVT_DOWN);
* @endcode
*
* @note THIS IS NOW WRAPPED BY THE MicroBitEvent CLASS FOR CONVENIENCE...
*/
void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c)
void 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;
}
/*
* Deliver the given event to all registered event handlers.
* Event handlers are called using the invoke() mechanism provided by the fier scheduler
* This will attempt to call the event handler directly, but spawn a fiber should that
* event handler attempt a blocking operation.
* @param evt The event to be delivered.
*/
void MicroBitMessageBus::process(MicroBitEvent evt)
{
MicroBitListener *l;
MicroBitListener *start;
// Firstly, determine if we're operating in interrupt context...
// If so, we queue processing of the event until we're scheduled in normal thread context.
// We do this to avoid executing event handler code in IRQ context, which may bring
// hidden race condition to kids code.
if (inInterruptContext())
{
this->queueEvent(evt);
return;
}
// Find the start of the sublist where we'll send this event.
// Ideally, we'll have a valid, cached entry. Use it if we do.
if ( c != NULL && c->seq == this->seq)
{
l = c->ptr;
}
else
{
l = listeners;
while (l != NULL && l->id != evt.source)
l = l->next;
}
start = l;
l = listeners;
while (l != NULL && l->id != evt.source)
l = l->next;
// Now, send the event to all listeners registered for this event.
while (l != NULL && l->id == evt.source)
@ -220,7 +203,7 @@ void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c)
if(l->value == MICROBIT_EVT_ANY || l->value == evt.value)
{
l->evt = evt;
fork_on_block(async_callback, (void *)l);
invoke(async_callback, (void *)l);
}
l = l->next;
@ -231,24 +214,20 @@ void MicroBitMessageBus::send(MicroBitEvent &evt, MicroBitMessageBusCache *c)
while (l != NULL && l->id == MICROBIT_ID_ANY)
{
l->evt = evt;
fork_on_block(async_callback, (void *)l);
invoke(async_callback, (void *)l);
l = l->next;
}
// If we were given a cached entry that's now invalid, update it.
if ( c != NULL && c->seq != this->seq)
{
c->ptr = start;
c->seq = this->seq;
}
// Finally, forward the event to any other internal subsystems that may be interested.
// We *could* do this through the message bus of course, but this saves additional RAM,
// and procssor time (as we know these are non-blocking calls).
// Wake up any fibers that are blocked on this event
if (uBit.flags & MICROBIT_FLAG_SCHEDULER_RUNNING)
scheduler_event(evt);
// Finally, see if this event needs to be propogated through our BLE interface
// See if this event needs to be propogated through our BLE interface
if (uBit.ble_event_service)
uBit.ble_event_service->onMicroBitEvent(evt);
}
@ -332,8 +311,6 @@ void MicroBitMessageBus::listen(int id, int value, void* handler, void* arg)
l = l->next;
}
//add at front of list
if (p == listeners && (id < p->id || (p->id == id && p->value > value)))
{
@ -348,9 +325,5 @@ void MicroBitMessageBus::listen(int id, int value, void* handler, void* arg)
newListener->next = p->next;
p->next = newListener;
}
// Increase our sequence number and we're done.
// This will lazily invalidate any cached entries to the listener list.
this->seq++;
}

View File

@ -1,4 +1,4 @@
#include "inc/MicroBit.h"
#include "MicroBit.h"
void
onMultiButtonEvent(MicroBitEvent evt)
@ -8,7 +8,7 @@ onMultiButtonEvent(MicroBitEvent evt)
/**
* Constructor.
* Create a representation of a vurtual button, that generates events based upon the combination
* Create a representation of a virtual button, that generates events based upon the combination
* of two given buttons.
* @param id the ID of the new MultiButton object.
* @param button1 the ID of the first button to integrate.

View File

@ -1,5 +1,5 @@
#include "MicroBit.h"
#include "inc/MicroBitPin.h"
#include "MicroBitPin.h"
/**
* Constructor.
@ -49,7 +49,7 @@ void MicroBitPin::disconnect()
if (status & IO_STATUS_ANALOG_OUT)
{
if(((DynamicPwm *)pin)->getPinName() == name)
((DynamicPwm *)pin)->free();
((DynamicPwm *)pin)->release();
}
if (status & IO_STATUS_TOUCH_IN)

View File

@ -23,39 +23,26 @@ int main()
pc.baud(115200);
// For diagnostics. Gives time to open the console window. :-)
for (int i=10; i>0; i--)
for (int i=3; i>0; i--)
{
pc.printf("=== SUPERMAIN: Starting in %d ===\n", i);
wait(1.0);
}
#endif
pc.printf("=== scheduler init... ");
scheduler_init();
pc.printf("complete ===\n");
pc.printf("=== uBit init... ");
uBit.init();
pc.printf("complete ===\n");
pc.printf("=== Launching app_main ===\n");
app_main();
pc.printf("=== app_main exited! ===\n");
while(1)
uBit.sleep(100);
#else
// Bring up our nested heap allocator.
microbit_heap_init();
// Bring up fiber scheduler
scheduler_init();
// bring up random number generator, BLE, display and system timers.
// Bring up random number generator, BLE, display and system timers.
uBit.init();
// Provide time for all threaded initialisers to complete.
uBit.sleep(100);
#ifndef NO_BLE
#ifdef MICROBIT_BLE_BLUEZONE
// Test if we need to enter BLE pairing mode...
int i=0;
while (uBit.buttonA.isPressed() && uBit.buttonB.isPressed() && i<10)
@ -63,16 +50,44 @@ int main()
uBit.sleep(100);
i++;
if (i == 10 && uBit.ble_firmware_update_service != NULL)
if (i == 10)
{
// OK - we need to enter BLUE ZONE mode.
// Test to see if BLE and the necessary services have been brought up already.
// If not, start them.
if (!uBit.ble)
{
uBit.ble = new BLEDevice();
uBit.ble->init();
uBit.ble->onDisconnection(bleDisconnectionCallback);
}
if (!uBit.ble_firmware_update_service)
{
uBit.ble_firmware_update_service = new MicroBitDFUService(*uBit.ble);
uBit.ble_firmware_update_service->getName(MICROBIT_BLE_DEVICE_NAME+14);
}
// Ensure we're advertising.
uBit.ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
uBit.ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)MICROBIT_BLE_DEVICE_NAME, strlen(MICROBIT_BLE_DEVICE_NAME)+1);
uBit.ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
uBit.ble->setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000));
uBit.ble->startAdvertising();
// enter BLUE ZONE mode.
uBit.ble_firmware_update_service->pair();
}
}
#endif
app_main();
while(1)
uBit.sleep(100);
#endif
// If app_main exits, there may still be other fibers running, registered event handlers etc.
// Simply release this fiber, which will mean we enter the scheduler. Worse case, we then
// sit in the idle task forever, in a power efficient sleep.
release_fiber();
// We should never get here, but just in case.
while(1);
}