diff --git a/.gitignore b/.gitignore index 80152a5..05fe5a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build .yotta.json yotta_modules yotta_targets +*.swp +Makefile diff --git a/inc/ManagedString.h b/inc/ManagedString.h index 81c06bf..bf2017c 100644 --- a/inc/ManagedString.h +++ b/inc/ManagedString.h @@ -1,7 +1,14 @@ #ifndef MANAGED_STRING_H #define MANAGED_STRING_H -#include "mbed.h" +#include "RefCounted.h" + +struct StringData : RefCounted +{ + uint16_t len; + char data[0]; +}; + /** * Class definition for a ManagedString. @@ -14,21 +21,43 @@ * 1) std::shared_ptr is not yet availiable on the ARMCC compiler * 2) to reduce memory footprint - we don't need many of the other features in the std library * 3) it makes an interestin case study for anyone interested in seeing how it works! + * 4) we need explicit reference counting to inter-op with low-level application langauge runtimes + * 5) the reference counting needs to also work for read-only, flash-resident strings */ class ManagedString { - // Internally we record the string as a char *, but control access to this to proide immutability - // and reference counting. - char *data; - int16_t *ref; - int16_t len; + // StringData contains the reference count, the length, follwed by char[] data, all in one block. + // When referece count is 0xffff, then it's read only and should not be counted. + // Otherwise the block was malloc()ed. + // We control access to this to proide immutability and reference counting. + StringData *ptr; public: + /** + * Constructor. + * Create a managed string from a specially prepared string literal. It will ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then the length in little endian, then the literal. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const char hello[] __attribute__ ((aligned (4))) = "\xff\xff\x05\x00" "Hello"; + * ManagedString s((StringData*)(void*)hello); + * @endcode + */ + ManagedString(StringData *ptr); + + /** + * Get current ptr, do not decr() it, and set the current instance to empty string. + * This is to be used by specialized runtimes which pass StringData around. + */ + StringData *leakData(); + /** * Constructor. * Create a managed string from a pointer to an 8-bit character buffer. - * The buffer is copied to ensure sage memory management (the supplied + * The buffer is copied to ensure safe memory management (the supplied * character buffer may be decalred on the stack for instance). * * @param str The character array on which to base the new ManagedString. @@ -115,7 +144,7 @@ class ManagedString * * Free this ManagedString, and decrement the reference count to the * internal character buffer. If we're holding the last reference, - * also free the character buffer and reference counter. + * also free the character buffer. */ ~ManagedString(); @@ -251,11 +280,14 @@ class ManagedString /** - * Provides an immutable 8 bit wide haracter buffer representing this string. + * Provides an immutable 8 bit wide character buffer representing this string. * * @return a pointer to the character buffer. */ - const char *toCharArray(); + const char *toCharArray() const + { + return ptr->data; + } /** * Determines the length of this ManagedString in characters. @@ -269,7 +301,10 @@ class ManagedString * print(s.length()) // prints "4" * @endcode */ - int16_t length(); + int16_t length() const + { + return ptr->len; + } /** * Empty String constant diff --git a/inc/MicroBitConfig.h b/inc/MicroBitConfig.h index ebc57c4..dfef5eb 100644 --- a/inc/MicroBitConfig.h +++ b/inc/MicroBitConfig.h @@ -253,7 +253,7 @@ // Selects the default brightness for the display // in the region of zero (off) to 255 (full brightness) #ifndef MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS -#define MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS ((MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS - MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) / 2) +#define MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS #endif // Selects the default scroll speed for the display. diff --git a/inc/MicroBitImage.h b/inc/MicroBitImage.h index bb5af02..26aabea 100644 --- a/inc/MicroBitImage.h +++ b/inc/MicroBitImage.h @@ -2,6 +2,14 @@ #define MICROBIT_IMAGE_H #include "mbed.h" +#include "RefCounted.h" + +struct ImageData : RefCounted +{ + uint16_t width; // Width in pixels + uint16_t height; // Height in pixels + uint8_t data[0]; // 2D array representing the bitmap image +}; /** * Class definition for a MicroBitImage. @@ -11,9 +19,7 @@ */ class MicroBitImage { - int16_t width; // Width of the bitmap, in pixels. - int16_t height; // Height of the bitmap, in pixels. - int16_t *ref; // Reference count. + ImageData *ptr; // Pointer to payload data /** @@ -32,7 +38,34 @@ class MicroBitImage public: static MicroBitImage EmptyImage; // Shared representation of a null image. - uint8_t *bitmap; // 2D array representing the bitmap image. + + /** + * Get current ptr, do not decr() it, and set the current instance to empty image. + * This is to be used by specialized runtimes which pass ImageData around. + */ + ImageData *leakData(); + + /** + * Return a 2D array representing the bitmap image. + */ + uint8_t *getBitmap() + { + return ptr->data; + } + + /** + * Constructor. + * Create an image from a specially prepared constant array, with no copying. Will call ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then width, 0, height, 0, and the bitmap. Width and height are 16 bit. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const uint8_t heart[] __attribute__ ((aligned (4))) = { 0xff, 0xff, 10, 0, 5, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i((ImageData*)(void*)heart); + * @endcode + */ + MicroBitImage(ImageData *ptr); /** * Default Constructor. @@ -325,7 +358,10 @@ class MicroBitImage * i.getWidth(); //equals 10... * @endcode */ - int getWidth(); + int getWidth() const + { + return ptr->width; + } /** * Gets the height of this image. @@ -339,8 +375,28 @@ class MicroBitImage * i.getHeight(); //equals 5... * @endcode */ - int getHeight(); + int getHeight() const + { + return ptr->height; + } + /** + * Gets number of bytes in the bitmap, ie., width * height. + * + * @return The size of the bitmap. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getSize(); //equals 50... + * @endcode + */ + int getSize() const + { + return ptr->width * ptr->height; + } + /** * Converts the bitmap to a csv string. * @@ -352,7 +408,7 @@ class MicroBitImage * @endcode */ ManagedString toString(); - + /** * Crops the image to the given dimensions * @@ -372,6 +428,17 @@ class MicroBitImage */ MicroBitImage crop(int startx, int starty, int finx, int finy); + /** + * Check if image is read-only (i.e., residing in flash). + */ + bool isReadOnly(); + + /** + * Create a copy of the image bitmap. Used particularly, when isReadOnly() is true. + * + * @return an instance of MicroBitImage which can be modified independently of the current instance + */ + MicroBitImage clone(); }; #endif diff --git a/inc/RefCounted.h b/inc/RefCounted.h new file mode 100644 index 0000000..9d332ad --- /dev/null +++ b/inc/RefCounted.h @@ -0,0 +1,34 @@ +#ifndef REF_COUNTED_H +#define REF_COUNTED_H + +#include "mbed.h" + +/** + * Base class for payload for ref-counted objects. Used by ManagedString and MicroBitImage. + * There is no constructor, as this struct is typically malloc()ed. + */ +struct RefCounted +{ +public: + /** + * The high 15 bits hold the number of outstanding references. The lowest bit is always 1 + * to make sure it doesn't look like vtable. + * Should never be even or one (object should be deleted then). + * When it's set to 0xffff, it means the object sits in flash and should not be counted. + */ + uint16_t refCount; + + /** Increment reference count. */ + void incr(); + + /** Decrement reference count. */ + void decr(); + + /** Initializes for one outstanding reference. */ + void init(); + + /** Checks if the object sits in flash memory. */ + bool isReadOnly(); +}; + +#endif diff --git a/module.json b/module.json index 097d9a0..f8fdbf8 100644 --- a/module.json +++ b/module.json @@ -1,6 +1,6 @@ { "name": "microbit-dal", - "version": "1.3.6", + "version": "1.3.10", "license": "Apache2", "description": "The runtime library for the BBC micro:bit, developed by Lancaster University", "keywords": [ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 4e2ad95..d8a846d 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -28,6 +28,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "MicroBitSerial.cpp" "MicroBitHeapAllocator.cpp" "MicroBitListener.cpp" + "RefCounted.cpp" "MemberFunctionCallback.cpp" "ble-services/MicroBitBLEManager.cpp" "ble-services/MicroBitDFUService.cpp" diff --git a/source/ManagedString.cpp b/source/ManagedString.cpp index 829b65a..e200ce0 100644 --- a/source/ManagedString.cpp +++ b/source/ManagedString.cpp @@ -3,6 +3,7 @@ #include "mbed.h" #include "MicroBit.h" +static const char empty[] __attribute__ ((aligned (4))) = "\xff\xff\0\0\0"; /** * Internal constructor helper. @@ -10,11 +11,7 @@ */ void ManagedString::initEmpty() { - data = ManagedString::EmptyString.data; - ref = ManagedString::EmptyString.ref; - len = ManagedString::EmptyString.len; - - (*ref)++; + ptr = (StringData*)(void*)empty; } /** @@ -25,13 +22,41 @@ void ManagedString::initString(const char *str) { // Initialise this ManagedString as a new string, using the data provided. // We assume the string is sane, and null terminated. - len = strlen(str); - data = (char *) malloc(len+1); - memcpy(data, str, len+1); - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + int len = strlen(str); + ptr = (StringData *) malloc(4+len+1); + ptr->init(); + ptr->len = len; + memcpy(ptr->data, str, len+1); } +/** + * Constructor. + * Create a managed string from a specially prepared string literal. It will ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then the length in little endian, then the literal. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const char hello[] __attribute__ ((aligned (4))) = "\xff\xff\x05\x00" "Hello"; + * ManagedString s((StringData*)(void*)hello); + * @endcode + */ +ManagedString::ManagedString(StringData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Get current ptr, do not decr() it, and set the current instance to empty string. + * This is to be used by specialized runtimes which pass StringData around. + */ +StringData* ManagedString::leakData() +{ + StringData *res = ptr; + initEmpty(); + return res; +} /** * Constructor. @@ -65,7 +90,6 @@ ManagedString::ManagedString(const int value) */ ManagedString::ManagedString(const char value) { - char str[2] = {value, 0}; initString(str); } @@ -82,7 +106,7 @@ ManagedString::ManagedString(const char value) ManagedString::ManagedString(const char *str) { // Sanity check. Return EmptyString for anything distasteful - if ((str == NULL || *str == 0) && this != &ManagedString::EmptyString) + if (str == NULL || *str == 0) { initEmpty(); return; @@ -94,19 +118,17 @@ ManagedString::ManagedString(const char *str) ManagedString::ManagedString(const ManagedString &s1, const ManagedString &s2) { // Calculate length of new string. - len = s1.len + s2.len; + int len = s1.length() + s2.length(); // Create a new buffer for holding the new string data. - data = (char *) malloc(len+1); + ptr = (StringData*) malloc(4+len+1); + ptr->init(); + ptr->len = len; // Enter the data, and terminate the string. - memcpy(data, s1.data, s1.len); - memcpy(data + s1.len, s2.data, s2.len); - data[len] = 0; - - // Initialise the ref count and we're done. - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + memcpy(ptr->data, s1.toCharArray(), s1.length()); + memcpy(ptr->data + s1.length(), s2.toCharArray(), s2.length()); + ptr->data[len] = 0; } @@ -133,17 +155,14 @@ ManagedString::ManagedString(const char *str, const int16_t length) return; } - // Store the length of the new string - len = length; // Allocate a new buffer, and create a NULL terminated string. - data = (char *) malloc(len+1); - memcpy(data, str, len); - data[len] = 0; - - // Initialize a refcount and we're done. - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + ptr = (StringData*) malloc(4+length+1); + ptr->init(); + // Store the length of the new string + ptr->len = length; + memcpy(ptr->data, str, length); + ptr->data[length] = 0; } /** @@ -161,11 +180,8 @@ ManagedString::ManagedString(const char *str, const int16_t length) */ ManagedString::ManagedString(const ManagedString &s) { - data = s.data; - ref = s.ref; - len = s.len; - - (*ref)++; + ptr = s.ptr; + ptr->incr(); } @@ -193,11 +209,7 @@ ManagedString::ManagedString() */ ManagedString::~ManagedString() { - if(--(*ref) == 0) - { - free(data); - free(ref); - } + ptr->decr(); } /** @@ -220,19 +232,12 @@ ManagedString::~ManagedString() */ ManagedString& ManagedString::operator = (const ManagedString& s) { - if(this == &s) + if (this->ptr == s.ptr) return *this; - if(--(*ref) == 0) - { - free(data); - free(ref); - } - - data = s.data; - ref = s.ref; - len = s.len; - (*ref)++; + ptr->decr(); + ptr = s.ptr; + ptr->incr(); return *this; } @@ -258,7 +263,7 @@ ManagedString& ManagedString::operator = (const ManagedString& s) */ bool ManagedString::operator== (const ManagedString& s) { - return ((len == s.len) && (memcmp(data,s.data,len)==0)); + return ((length() == s.length()) && (strcmp(toCharArray(),s.toCharArray())==0)); } /** @@ -282,7 +287,7 @@ bool ManagedString::operator== (const ManagedString& s) */ bool ManagedString::operator< (const ManagedString& s) { - return (memcmp(data, s.data,min(len,s.len))<0); + return (strcmp(toCharArray(), s.toCharArray())<0); } /** @@ -306,7 +311,7 @@ bool ManagedString::operator< (const ManagedString& s) */ bool ManagedString::operator> (const ManagedString& s) { - return (memcmp(data, s.data,min(len,s.len))>0); + return (strcmp(toCharArray(), s.toCharArray())>0); } /** @@ -326,14 +331,14 @@ bool ManagedString::operator> (const ManagedString& s) ManagedString ManagedString::substring(int16_t start, int16_t length) { // If the parameters are illegal, just return a reference to the empty string. - if (start >= len) + if (start >= this->length()) return ManagedString(ManagedString::EmptyString); // Compute a safe copy length; - length = min(len-start, length); + length = min(this->length()-start, length); // Build a ManagedString from this. - return ManagedString(data+start, length); + return ManagedString(toCharArray()+start, length); } /** @@ -353,19 +358,13 @@ ManagedString ManagedString::substring(int16_t start, int16_t length) ManagedString ManagedString::operator+ (ManagedString& s) { // If the other string is empty, nothing to do! - if(s.len == 0) + if(s.length() == 0) return *this; - if (len == 0) - return s; - - if(s == ManagedString::EmptyString) - return *this; - - if(*this == ManagedString::EmptyString) + if (length() == 0) return s; - return ManagedString(data, s.data); + return ManagedString(*this, s); } @@ -384,39 +383,10 @@ ManagedString ManagedString::operator+ (ManagedString& s) */ char ManagedString::charAt(int16_t index) { - return (index >=0 && index < len) ? data[index] : 0; -} - -/** - * Provides an immutable 8 bit wide haracter buffer representing this string. - * - * @return a pointer to the charcter buffer. - */ -const char *ManagedString::toCharArray() -{ - return data; -} - -/** - * Determines the length of this ManagedString in characters. - * - * @return the length of the string in characters. - * - * Example: - * @code - * ManagedString s("abcd"); - * - * print(s.length()) // prints "4" - * @endcode - */ -int16_t ManagedString::length() -{ - return len; + return (index >=0 && index < length()) ? ptr->data[index] : 0; } /** * Empty string constant literal */ -ManagedString ManagedString::EmptyString("\0"); - - +ManagedString ManagedString::EmptyString((StringData*)(void*)empty); diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp index 0703f23..9ddd368 100644 --- a/source/MicroBit.cpp +++ b/source/MicroBit.cpp @@ -39,6 +39,13 @@ microbit_reset() NVIC_SystemReset(); } +void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *reason) +{ + (void) reason; /* -Wunused-param */ + + uBit.ble->startAdvertising(); +} + /** * Constructor. @@ -174,7 +181,7 @@ ManagedString MicroBit::getSerial() ManagedString s1 = ManagedString(n1); ManagedString s2 = ManagedString(n2); - return s1+s2; + return s1 + s2; } /** @@ -242,17 +249,44 @@ int MicroBit::sleep(int milliseconds) */ int MicroBit::random(int max) { + uint32_t m, result; + //return MICROBIT_INVALID_VALUE if max is <= 0... if(max <= 0) return MICROBIT_INVALID_PARAMETER; - - // Cycle the LFSR (Linear Feedback Shift Register). - // We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneider here (a true legend in the field!), - // For those interested, it's documented in his paper: - // "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent generator for 32-bit Microprocessors" - - randomValue = ((((randomValue >> 31) ^ (randomValue >> 6) ^ (randomValue >> 4) ^ (randomValue >> 2) ^ (randomValue >> 1) ^ randomValue) & 0x0000001) << 31 ) | (randomValue >> 1); - return randomValue % max; + + // Our maximum return value is actually one less than passed + max--; + + do { + m = (uint32_t)max; + result = 0; + do { + // Cycle the LFSR (Linear Feedback Shift Register). + // We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneier here (a true legend in the field!), + // For those interested, it's documented in his paper: + // "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent generator for 32-bit Microprocessors" + // https://www.schneier.com/paper-pseudorandom-sequence.html + uint32_t rnd = randomValue; + + rnd = ((((rnd >> 31) + ^ (rnd >> 6) + ^ (rnd >> 4) + ^ (rnd >> 2) + ^ (rnd >> 1) + ^ rnd) + & 0x0000001) + << 31 ) + | (rnd >> 1); + + randomValue = rnd; + + result = ((result << 1) | (rnd & 0x00000001)); + } while(m >>= 1); + } while (result > (uint32_t)max); + + + return result; } diff --git a/source/MicroBitDisplay.cpp b/source/MicroBitDisplay.cpp index 1337071..1513c10 100644 --- a/source/MicroBitDisplay.cpp +++ b/source/MicroBitDisplay.cpp @@ -126,7 +126,7 @@ void MicroBitDisplay::render() y = height - 1 - t; } - if(image.bitmap[y*(width*2)+x]) + if(image.getBitmap()[y*(width*2)+x]) coldata |= (1 << i); } @@ -175,7 +175,7 @@ void MicroBitDisplay::renderGreyscale() y = height - 1 - t; } - if(min(image.bitmap[y * (width * 2) + x],brightness) & greyscaleBitMsk) + if(min(image.getBitmap()[y * (width * 2) + x],brightness) & greyscaleBitMsk) coldata |= (1 << i); } //write the new bit pattern diff --git a/source/MicroBitHeapAllocator.cpp b/source/MicroBitHeapAllocator.cpp index e8b4dac..34f5d6e 100644 --- a/source/MicroBitHeapAllocator.cpp +++ b/source/MicroBitHeapAllocator.cpp @@ -77,7 +77,7 @@ void microbit_heap_print(HeapDefinition &heap) while (block < heap.heap_end) { blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE; - uBit.serialpc.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4); + uBit.serial.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4); if (cols++ == 20) { uBit.serial.printf("\n"); @@ -332,7 +332,7 @@ void *microbit_malloc(size_t size) if (p != NULL) { #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) - pc.uBit.serial("microbit_malloc: ALLOCATED: %d [%p]\n", size, p); + uBit.serial.printf("microbit_malloc: ALLOCATED: %d [%p]\n", size, p); #endif return p; } @@ -348,7 +348,7 @@ void *microbit_malloc(size_t size) #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) // Keep everything trasparent if we've not been initialised yet if (microbit_active_heaps()) - pc.uBit.serial("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p); + uBit.serial.printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p); #endif return p; } @@ -357,7 +357,7 @@ void *microbit_malloc(size_t size) #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) // Keep everything trasparent if we've not been initialised yet if (microbit_active_heaps()) - pc.uBit.serial("microbit_malloc: OUT OF MEMORY\n"); + uBit.serial.printf("microbit_malloc: OUT OF MEMORY\n"); #endif #if CONFIG_ENABLED(MICROBIT_PANIC_HEAP_FULL) @@ -378,7 +378,7 @@ void microbit_free(void *mem) #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) if (microbit_active_heaps()) - pc.uBit.serial("microbit_free: %p\n", mem); + uBit.serial.printf("microbit_free: %p\n", mem); #endif // Sanity check. if (memory == NULL) diff --git a/source/MicroBitImage.cpp b/source/MicroBitImage.cpp index 43a8a93..d2e9684 100644 --- a/source/MicroBitImage.cpp +++ b/source/MicroBitImage.cpp @@ -7,10 +7,12 @@ #include "MicroBit.h" + /* * The null image. We actally create a small one byte buffer here, just to keep NULL pointers out of the equation. */ -MicroBitImage MicroBitImage::EmptyImage(1,1); +static const uint16_t empty[] __attribute__ ((aligned (4))) = { 0xffff, 1, 1, 0, }; +MicroBitImage MicroBitImage::EmptyImage((ImageData*)(void*)empty); /** * Default Constructor. @@ -65,12 +67,8 @@ MicroBitImage::MicroBitImage(const int16_t x, const int16_t y) */ MicroBitImage::MicroBitImage(const MicroBitImage &image) { - bitmap = image.bitmap; - width = image.width; - height = image.height; - ref = image.ref; - - (*ref)++; + ptr = image.ptr; + ptr->incr(); } /** @@ -138,17 +136,12 @@ MicroBitImage::MicroBitImage(const char *s) parseReadPtr++; } - // Store the geomtery. - this->width = width; - this->height = height; - this->bitmap = (uint8_t *) malloc(width * height); - this->ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + this->init(width, height, NULL); // Second pass: collect the data. parseReadPtr = s; parseWritePtr = parseBuf; - bitmapPtr = this->bitmap; + bitmapPtr = this->getBitmap(); while (*parseReadPtr) { @@ -172,6 +165,36 @@ MicroBitImage::MicroBitImage(const char *s) } } +/** + * Constructor. + * Create an image from a specially prepared constant array, with no copying. Will call ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then width, 0, height, 0, and the bitmap. Width and height are 16 bit. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const uint8_t heart[] __attribute__ ((aligned (4))) = { 0xff, 0xff, 10, 0, 5, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i((ImageData*)(void*)heart); + * @endcode + */ +MicroBitImage::MicroBitImage(ImageData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Get current ptr, do not decr() it, and set the current instance to empty image. + * This is to be used by specialized runtimes which pass ImageData around. + */ +ImageData *MicroBitImage::leakData() +{ + ImageData* res = ptr; + init_empty(); + return res; +} + + /** * Constructor. * Create a bitmap representation of a given size, based on a given buffer. @@ -197,11 +220,7 @@ MicroBitImage::MicroBitImage(const int16_t x, const int16_t y, const uint8_t *bi */ MicroBitImage::~MicroBitImage() { - if(--(*ref) == 0) - { - free(bitmap); - free(ref); - } + ptr->decr(); } /** @@ -209,12 +228,7 @@ MicroBitImage::~MicroBitImage() */ void MicroBitImage::init_empty() { - bitmap = MicroBitImage::EmptyImage.bitmap; - width = MicroBitImage::EmptyImage.width; - height = MicroBitImage::EmptyImage.height; - ref = MicroBitImage::EmptyImage.ref; - - (*ref)++; + ptr = (ImageData*)(void*)empty; } /** @@ -232,23 +246,21 @@ void MicroBitImage::init(const int16_t x, const int16_t y, const uint8_t *bitmap init_empty(); return; } + // Create a copy of the array - this->width = x; - this->height = y; + ptr = (ImageData*)malloc(sizeof(ImageData) + x * y); + ptr->init(); + ptr->width = x; + ptr->height = y; // create a linear buffer to represent the image. We could use a jagged/2D array here, but experimentation // showed this had a negative effect on memory management (heap fragmentation etc). - this->bitmap = (uint8_t *) malloc(width*height); - if (bitmap) this->printImage(x,y,bitmap); else this->clear(); - - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; } /** @@ -271,21 +283,12 @@ void MicroBitImage::init(const int16_t x, const int16_t y, const uint8_t *bitmap */ MicroBitImage& MicroBitImage::operator = (const MicroBitImage& i) { - if(this == &i) + if(ptr == i.ptr) return *this; - if(--(*ref) == 0) - { - free(bitmap); - free(ref); - } - - bitmap = i.bitmap; - width = i.width; - height = i.height; - ref = i.ref; - - (*ref)++; + ptr->decr(); + ptr = i.ptr; + ptr->incr(); return *this; } @@ -309,10 +312,10 @@ MicroBitImage& MicroBitImage::operator = (const MicroBitImage& i) */ bool MicroBitImage::operator== (const MicroBitImage& i) { - if (bitmap == i.bitmap) + if (ptr == i.ptr) return true; else - return ((width == i.width) && (height == i.height) && (memcmp(bitmap, i.bitmap,width*height)==0)); + return (ptr->width == i.ptr->width && ptr->height == i.ptr->height && (memcmp(getBitmap(), i.ptr->data, getSize())==0)); } @@ -327,7 +330,7 @@ bool MicroBitImage::operator== (const MicroBitImage& i) */ void MicroBitImage::clear() { - memclr(this->bitmap, width*height); + memclr(getBitmap(), getSize()); } /** @@ -346,10 +349,10 @@ void MicroBitImage::clear() int MicroBitImage::setPixelValue(int16_t x , int16_t y, uint8_t value) { //sanity check - if(x >= width || y >= height || x < 0 || y < 0) + if(x >= getWidth() || y >= getHeight() || x < 0 || y < 0) return MICROBIT_INVALID_PARAMETER; - this->bitmap[y*width+x] = value; + this->getBitmap()[y*getWidth()+x] = value; return MICROBIT_OK; } @@ -369,10 +372,10 @@ int MicroBitImage::setPixelValue(int16_t x , int16_t y, uint8_t value) int MicroBitImage::getPixelValue(int16_t x , int16_t y) { //sanity check - if(x >= width || y >= height || x < 0 || y < 0) + if(x >= getWidth() || y >= getHeight() || x < 0 || y < 0) return MICROBIT_INVALID_PARAMETER; - return this->bitmap[y*width+x]; + return this->getBitmap()[y*getWidth()+x]; } /** @@ -403,18 +406,18 @@ int MicroBitImage::printImage(int16_t width, int16_t height, const uint8_t *bitm return MICROBIT_INVALID_PARAMETER; // Calcualte sane start pointer. - pixelsToCopyX = min(width,this->width); - pixelsToCopyY = min(height,this->height); + pixelsToCopyX = min(width,this->getWidth()); + pixelsToCopyY = min(height,this->getHeight()); pIn = bitmap; - pOut = this->bitmap; + pOut = this->getBitmap(); // Copy the image, stride by stride. for (int i=0; iwidth; + pOut += this->getWidth(); } return MICROBIT_OK; @@ -445,21 +448,21 @@ int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8 // Sanity check. // We permit writes that overlap us, but ones that are clearly out of scope we can filter early. - if (x >= width || y >= height || x+image.width <= 0 || y+image.height <= 0) + if (x >= getWidth() || y >= getHeight() || x+image.getWidth() <= 0 || y+image.getHeight() <= 0) return 0; //Calculate the number of byte we need to copy in each dimension. - cx = x < 0 ? min(image.width + x, width) : min(image.width, width - x); - cy = y < 0 ? min(image.height + y, height) : min(image.height, height - y); + cx = x < 0 ? min(image.getWidth() + x, getWidth()) : min(image.getWidth(), getWidth() - x); + cy = y < 0 ? min(image.getHeight() + y, getHeight()) : min(image.getHeight(), getHeight() - y); // Calculate sane start pointer. - pIn = image.bitmap; + pIn = image.ptr->data; pIn += (x < 0) ? -x : 0; - pIn += (y < 0) ? -image.width*y : 0; + pIn += (y < 0) ? -image.getWidth()*y : 0; - pOut = bitmap; + pOut = getBitmap(); pOut += (x > 0) ? x : 0; - pOut += (y > 0) ? width*y : 0; + pOut += (y > 0) ? getWidth()*y : 0; // Copy the image, stride by stride // If we want primitive transparecy, we do this byte by byte. @@ -478,8 +481,8 @@ int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8 } } - pIn += image.width; - pOut += width; + pIn += image.getWidth(); + pOut += getWidth(); } } else @@ -489,8 +492,8 @@ int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8 memcpy(pOut, pIn, cx); pxWritten += cx; - pIn += image.width; - pOut += width; + pIn += image.getWidth(); + pOut += getWidth(); } } @@ -519,7 +522,7 @@ int MicroBitImage::print(char c, int16_t x, int16_t y) MicroBitFont font = uBit.display.getFont(); // Sanity check. Silently ignore anything out of bounds. - if (x >= width || y >= height || c < MICROBIT_FONT_ASCII_START || c > font.asciiEnd) + if (x >= getWidth() || y >= getHeight() || c < MICROBIT_FONT_ASCII_START || c > font.asciiEnd) return MICROBIT_INVALID_PARAMETER; // Paste. @@ -539,8 +542,8 @@ int MicroBitImage::print(char c, int16_t x, int16_t y) // Update our X co-ord write position x1 = x+col; - if (x1 < width && y1 < height) - this->bitmap[y1*width+x1] = (v & (0x10 >> col)) ? 255 : 0; + if (x1 < getWidth() && y1 < getHeight()) + this->getBitmap()[y1*getWidth()+x1] = (v & (0x10 >> col)) ? 255 : 0; } } @@ -563,24 +566,24 @@ int MicroBitImage::print(char c, int16_t x, int16_t y) */ int MicroBitImage::shiftLeft(int16_t n) { - uint8_t *p = bitmap; - int pixels = width-n; + uint8_t *p = getBitmap(); + int pixels = getWidth()-n; if (n <= 0 ) return MICROBIT_INVALID_PARAMETER; - if(n >= width) + if(n >= getWidth()) { clear(); return MICROBIT_OK; } - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the rightmost column. memcpy(p, p+n, pixels); memclr(p+pixels, n); - p += width; + p += getWidth(); } return MICROBIT_OK; @@ -602,24 +605,24 @@ int MicroBitImage::shiftLeft(int16_t n) */ int MicroBitImage::shiftRight(int16_t n) { - uint8_t *p = bitmap; - int pixels = width-n; + uint8_t *p = getBitmap(); + int pixels = getWidth()-n; if (n <= 0) return MICROBIT_INVALID_PARAMETER; - if(n >= width) + if(n >= getWidth()) { clear(); return MICROBIT_OK; } - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the leftmost column. memmove(p+n, p, pixels); memclr(p, n); - p += width; + p += getWidth(); } return MICROBIT_OK; @@ -646,25 +649,25 @@ int MicroBitImage::shiftUp(int16_t n) if (n <= 0 ) return MICROBIT_INVALID_PARAMETER; - if(n >= height) + if(n >= getHeight()) { clear(); return MICROBIT_OK; } - pOut = bitmap; - pIn = bitmap+width*n; + pOut = getBitmap(); + pIn = getBitmap()+getWidth()*n; - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the leftmost column. - if (y < height-n) - memcpy(pOut, pIn, width); + if (y < getHeight()-n) + memcpy(pOut, pIn, getWidth()); else - memclr(pOut, width); + memclr(pOut, getWidth()); - pIn += width; - pOut += width; + pIn += getWidth(); + pOut += getWidth(); } return MICROBIT_OK; @@ -691,63 +694,30 @@ int MicroBitImage::shiftDown(int16_t n) if (n <= 0 ) return MICROBIT_INVALID_PARAMETER; - if(n >= height) + if(n >= getHeight()) { clear(); return MICROBIT_OK; } - pOut = bitmap + width*(height-1); - pIn = pOut - width*n; + pOut = getBitmap() + getWidth()*(getHeight()-1); + pIn = pOut - getWidth()*n; - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the leftmost column. - if (y < height-n) - memcpy(pOut, pIn, width); + if (y < getHeight()-n) + memcpy(pOut, pIn, getWidth()); else - memclr(pOut, width); + memclr(pOut, getWidth()); - pIn -= width; - pOut -= width; + pIn -= getWidth(); + pOut -= getWidth(); } return MICROBIT_OK; } -/** - * Gets the width of this image. - * - * @return The width of this image. - * - * Example: - * @code - * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart - * MicroBitImage i(10,5,heart); - * i.getWidth(); //equals 10... - * @endcode - */ -int MicroBitImage::getWidth() -{ - return width; -} - -/** - * Gets the height of this image. - * - * @return The height of this image. - * - * Example: - * @code - * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart - * MicroBitImage i(10,5,heart); - * i.getHeight(); //equals 5... - * @endcode - */ -int MicroBitImage::getHeight() -{ - return height; -} /** * Converts the bitmap to a csv string. @@ -762,14 +732,14 @@ int MicroBitImage::getHeight() ManagedString MicroBitImage::toString() { //width including commans and \n * height - int stringSize = ((width * 2) * height); + int stringSize = getSize() * 2; //plus one for string terminator char parseBuffer[stringSize + 1]; parseBuffer[stringSize] = '\0'; - uint8_t *bitmapPtr = bitmap; + uint8_t *bitmapPtr = getBitmap(); int parseIndex = 0; int widthCount = 0; @@ -783,7 +753,7 @@ ManagedString MicroBitImage::toString() parseIndex++; - if(widthCount == width-1) + if(widthCount == getWidth()-1) { parseBuffer[parseIndex] = '\n'; widthCount = 0; @@ -821,17 +791,17 @@ MicroBitImage MicroBitImage::crop(int startx, int starty, int cropWidth, int cro int newWidth = startx + cropWidth; int newHeight = starty + cropHeight; - if (newWidth >= width || newWidth <=0) - newWidth = width; + if (newWidth >= getWidth() || newWidth <=0) + newWidth = getWidth(); - if (newHeight >= height || newHeight <= 0) - newHeight = height; + if (newHeight >= getHeight() || newHeight <= 0) + newHeight = getHeight(); //allocate our storage. uint8_t cropped[newWidth * newHeight]; //calculate the pointer to where we want to begin cropping - uint8_t *copyPointer = bitmap + (width * starty) + startx; + uint8_t *copyPointer = getBitmap() + (getWidth() * starty) + startx; //get a reference to our storage uint8_t *pastePointer = cropped; @@ -841,9 +811,27 @@ MicroBitImage MicroBitImage::crop(int startx, int starty, int cropWidth, int cro { memcpy(pastePointer, copyPointer, newWidth); - copyPointer += width; + copyPointer += getWidth(); pastePointer += newHeight; } return MicroBitImage(newWidth, newHeight, cropped); } + +/** + * Check if image is read-only (i.e., residing in flash). + */ +bool MicroBitImage::isReadOnly() +{ + return ptr->isReadOnly(); +} + +/** + * Create a copy of the image bitmap. Used particularly, when isReadOnly() is true. + * + * @return an instance of MicroBitImage which can be modified independently of the current instance + */ +MicroBitImage MicroBitImage::clone() +{ + return MicroBitImage(getWidth(), getHeight(), getBitmap()); +} diff --git a/source/MicroBitSuperMain.cpp b/source/MicroBitSuperMain.cpp index a20a2ae..bf26f0c 100644 --- a/source/MicroBitSuperMain.cpp +++ b/source/MicroBitSuperMain.cpp @@ -10,7 +10,7 @@ int main() // Bring up soft reset button. resetButton.mode(PullUp); resetButton.fall(microbit_reset); - + #if CONFIG_ENABLED(MICROBIT_DBG) // For diagnostics. Gives time to open the console window. :-) @@ -30,7 +30,7 @@ int main() // Bring up fiber scheduler scheduler_init(); - + // Bring up random number generator, BLE, display and system timers. uBit.init(); @@ -44,27 +44,26 @@ int main() { uBit.sleep(100); i++; - + if (i == 10) { - // Bring up the BLE stack if it isn't alredy done. - if (!uBit.ble) - uBit.bleManager.init(uBit.getName(), uBit.getSerial()); + // Bring up the BLE stack if it isn't alredy done. + if (!uBit.ble) + uBit.bleManager.init(uBit.getName(), uBit.getSerial()); // Enter pairing mode, using the LED matrix for any necessary pairing operations - uBit.bleManager.pairingMode(uBit.display); - + uBit.bleManager.pairingMode(uBit.display); } } #endif - + app_main(); // If app_main exits, there may still be other fibers running, registered event handlers etc. // Simply release this fiber, which will mean we enter the scheduler. Worse case, we then // sit in the idle task forever, in a power efficient sleep. release_fiber(); - + // We should never get here, but just in case. while(1); } diff --git a/source/RefCounted.cpp b/source/RefCounted.cpp new file mode 100644 index 0000000..3d4105e --- /dev/null +++ b/source/RefCounted.cpp @@ -0,0 +1,46 @@ +#include "mbed.h" +#include "MicroBit.h" + +void RefCounted::init() +{ + // Initialize to one reference (lowest bit set to 1) + refCount = 3; +} + +static inline bool isReadOnlyInline(RefCounted *t) +{ + uint32_t refCount = t->refCount; + + if (refCount == 0xffff) + return true; // object in flash + + // Do some sanity checking while we're here + if (refCount == 1 || // object should have been deleted + (refCount & 1) == 0) // refCount doesn't look right + uBit.panic(MICROBIT_HEAP_ERROR); + + // Not read only + return false; +} + +bool RefCounted::isReadOnly() +{ + return isReadOnlyInline(this); +} + +void RefCounted::incr() +{ + if (!isReadOnlyInline(this)) + refCount += 2; +} + +void RefCounted::decr() +{ + if (isReadOnlyInline(this)) + return; + + refCount -= 2; + if (refCount == 1) { + free(this); + } +} diff --git a/source/ble-services/MicroBitDFUService.cpp b/source/ble-services/MicroBitDFUService.cpp index e604a22..028e1b9 100644 --- a/source/ble-services/MicroBitDFUService.cpp +++ b/source/ble-services/MicroBitDFUService.cpp @@ -1,13 +1,13 @@ /** - * Class definition for a MicroBit Device Firmware Update loader. - * - * This is actually just a frontend to a memory resident nordic DFU loader. - * - * We rely on the BLE standard pairing processes to provide encryption and authentication. - * We assume any device that is paied with the micro:bit is authorized to reprogram the device. - * - */ - + * Class definition for a MicroBit Device Firmware Update loader. + * + * This is actually just a frontend to a memory resident nordic DFU loader. + * + * We rely on the BLE standard pairing processes to provide encryption and authentication. + * We assume any device that is paied with the micro:bit is authorized to reprogram the device. + * + */ + #include "MicroBit.h" #include "ble/UUID.h" @@ -36,19 +36,19 @@ extern "C" { /** - * Constructor. - * Create a representation of a MicroBit device. - * @param messageBus callback function to receive MicroBitMessageBus events. - */ + * Constructor. + * Create a representation of a MicroBit device. + * @param messageBus callback function to receive MicroBitMessageBus events. + */ MicroBitDFUService::MicroBitDFUService(BLEDevice &_ble) : - ble(_ble) + ble(_ble) { // Opcodes can be issued here to control the MicroBitDFU Service, as defined above. GattCharacteristic microBitDFUServiceControlCharacteristic(MicroBitDFUServiceControlCharacteristicUUID, &controlByte, 0, sizeof(uint8_t), - GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); + GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); controlByte = 0x00; - + // Set default security requirements microBitDFUServiceControlCharacteristic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); @@ -66,41 +66,41 @@ MicroBitDFUService::MicroBitDFUService(BLEDevice &_ble) : /** - * Callback. Invoked when any of our attributes are written via BLE. - */ + * Callback. Invoked when any of our attributes are written via BLE. + */ void MicroBitDFUService::onDataWritten(const GattWriteCallbackParams *params) { if (params->handle == microBitDFUServiceControlCharacteristicHandle) { if(params->len > 0 && params->data[0] == MICROBIT_DFU_OPCODE_START_DFU) - { - uBit.display.stopAnimation(); + { + uBit.display.stopAnimation(); uBit.display.clear(); #if CONFIG_ENABLED(MICROBIT_DBG) uBit.serial.printf(" ACTIVATING BOOTLOADER.\n"); #endif - // Perform an explicit disconnection to assist our peer to reconnect to the DFU service - ble.disconnect(Gap::LOCAL_HOST_TERMINATED_CONNECTION); + // Perform an explicit disconnection to assist our peer to reconnect to the DFU service + ble.disconnect(Gap::LOCAL_HOST_TERMINATED_CONNECTION); - // Call bootloader_start implicitly trough a event handler call - // it is a work around for bootloader_start not being public in sdk 8.1 - ble_dfu_t p_dfu; - ble_dfu_evt_t p_evt; + // Call bootloader_start implicitly trough a event handler call + // it is a work around for bootloader_start not being public in sdk 8.1 + ble_dfu_t p_dfu; + ble_dfu_evt_t p_evt; - p_dfu.conn_handle = params->connHandle; - p_evt.ble_dfu_evt_type = BLE_DFU_START; + p_dfu.conn_handle = params->connHandle; + p_evt.ble_dfu_evt_type = BLE_DFU_START; - dfu_app_on_dfu_evt(&p_dfu, &p_evt); - } - } + dfu_app_on_dfu_evt(&p_dfu, &p_evt); + } + } } /** - * UUID definitions for BLE Services and Characteristics. - */ + * UUID definitions for BLE Services and Characteristics. + */ const uint8_t MicroBitDFUServiceUUID[] = { 0xe9,0x5d,0x93,0xb0,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8