x
Dependencies: BLE_API mbed-dev-bin nRF51822
Fork of microbit-dal by
Diff: source/drivers/MicroBitDisplay.cpp
- Revision:
- 1:8aa5cdb4ab67
- Child:
- 20:ad2a5c7debf4
diff -r fb15f7887843 -r 8aa5cdb4ab67 source/drivers/MicroBitDisplay.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/source/drivers/MicroBitDisplay.cpp Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,1218 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Class definition for MicroBitDisplay. + * + * A MicroBitDisplay represents the LED matrix array on the micro:bit. + */ +#include "MicroBitConfig.h" +#include "MicroBitDisplay.h" +#include "MicroBitSystemTimer.h" +#include "MicroBitFiber.h" +#include "ErrorNo.h" +#include "NotifyEvents.h" + +const int greyScaleTimings[MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH] = {1, 23, 70, 163, 351, 726, 1476, 2976}; + +/** + * Constructor. + * + * Create a software representation the micro:bit's 5x5 LED matrix. + * The display is initially blank. + * + * @param id The id the display should use when sending events on the MessageBus. Defaults to MICROBIT_ID_DISPLAY. + * + * @param map The mapping information that relates pin inputs/outputs to physical screen coordinates. + * Defaults to microbitMatrixMap, defined in MicroBitMatrixMaps.h. + * + * @code + * MicroBitDisplay display; + * @endcode + */ +MicroBitDisplay::MicroBitDisplay(uint16_t id, const MatrixMap &map) : + matrixMap(map), + image(map.width*2,map.height) +{ + uint32_t row_mask; + + this->id = id; + this->width = map.width; + this->height = map.height; + this->rotation = MICROBIT_DISPLAY_ROTATION_0; + + row_mask = 0; + col_mask = 0; + strobeRow = 0; + row_mask = 0; + + for (int i = matrixMap.rowStart; i < matrixMap.rowStart + matrixMap.rows; i++) + row_mask |= 0x01 << i; + + for (int i = matrixMap.columnStart; i < matrixMap.columnStart + matrixMap.columns; i++) + col_mask |= 0x01 << i; + + LEDMatrix = new PortOut(Port0, row_mask | col_mask); + + this->greyscaleBitMsk = 0x01; + this->timingCount = 0; + this->setBrightness(MICROBIT_DISPLAY_DEFAULT_BRIGHTNESS); + this->mode = DISPLAY_MODE_BLACK_AND_WHITE; + this->animationMode = ANIMATION_MODE_NONE; + this->lightSensor = NULL; + + system_timer_add_component(this); + + status |= MICROBIT_COMPONENT_RUNNING; +} + +/** + * Internal frame update method, used to strobe the display. + * + * TODO: Write a more efficient, complementary variation of this method for the case where + * MICROBIT_DISPLAY_ROW_COUNT > MICROBIT_DISPLAY_COLUMN_COUNT. + */ +void MicroBitDisplay::systemTick() +{ + if(!(status & MICROBIT_COMPONENT_RUNNING)) + return; + + if(mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + renderWithLightSense(); + return; + } + + // Move on to the next row. + strobeRow++; + + //reset the row counts and bit mask when we have hit the max. + if(strobeRow == matrixMap.rows) + strobeRow = 0; + + if(mode == DISPLAY_MODE_BLACK_AND_WHITE) + render(); + + if(mode == DISPLAY_MODE_GREYSCALE) + { + greyscaleBitMsk = 0x01; + timingCount = 0; + renderGreyscale(); + } + + // Update text and image animations if we need to. + this->animationUpdate(); +} + +void MicroBitDisplay::renderFinish() +{ + *LEDMatrix = 0; +} + +void MicroBitDisplay::render() +{ + // Simple optimisation. + // If display is at zero brightness, there's nothing to do. + if(brightness == 0) + return; + + // Calculate the bitpattern to write. + uint32_t row_data = 0x01 << (microbitMatrixMap.rowStart + strobeRow); + uint32_t col_data = 0; + + for (int i = 0; i < matrixMap.columns; i++) + { + int index = (i * matrixMap.rows) + strobeRow; + + int x = matrixMap.map[index].x; + int y = matrixMap.map[index].y; + int t = x; + + if(rotation == MICROBIT_DISPLAY_ROTATION_90) + { + x = width - 1 - y; + y = t; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_180) + { + x = width - 1 - x; + y = height - 1 - y; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_270) + { + x = y; + y = height - 1 - t; + } + + if(image.getBitmap()[y*(width*2)+x]) + col_data |= (1 << i); + } + + // Invert column bits (as we're sinking not sourcing power), and mask off any unused bits. + col_data = ~col_data << matrixMap.columnStart & col_mask; + + // Write the new bit pattern + *LEDMatrix = col_data | row_data; + + //timer does not have enough resolution for brightness of 1. 23.53 us + if(brightness != MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS && brightness > MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) + renderTimer.attach_us(this, &MicroBitDisplay::renderFinish, (((brightness * 950) / (MICROBIT_DISPLAY_MAXIMUM_BRIGHTNESS)) * system_timer_get_period())); + + //this will take around 23us to execute + if(brightness <= MICROBIT_DISPLAY_MINIMUM_BRIGHTNESS) + renderFinish(); +} + +void MicroBitDisplay::renderWithLightSense() +{ + //reset the row counts and bit mask when we have hit the max. + if(strobeRow == matrixMap.rows + 1) + { + MicroBitEvent(id, MICROBIT_DISPLAY_EVT_LIGHT_SENSE); + strobeRow = 0; + } + else + { + render(); + this->animationUpdate(); + + // Move on to the next row. + strobeRow++; + } + +} + +void MicroBitDisplay::renderGreyscale() +{ + uint32_t row_data = 0x01 << (microbitMatrixMap.rowStart + strobeRow); + uint32_t col_data = 0; + + // Calculate the bitpattern to write. + for (int i = 0; i < matrixMap.columns; i++) + { + int index = (i * matrixMap.rows) + strobeRow; + + int x = matrixMap.map[index].x; + int y = matrixMap.map[index].y; + int t = x; + + if(rotation == MICROBIT_DISPLAY_ROTATION_90) + { + x = width - 1 - y; + y = t; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_180) + { + x = width - 1 - x; + y = height - 1 - y; + } + + if(rotation == MICROBIT_DISPLAY_ROTATION_270) + { + x = y; + y = height - 1 - t; + } + + if(min(image.getBitmap()[y * (width * 2) + x],brightness) & greyscaleBitMsk) + col_data |= (1 << i); + } + + // Invert column bits (as we're sinking not sourcing power), and mask off any unused bits. + col_data = ~col_data << matrixMap.columnStart & col_mask; + + // Write the new bit pattern + *LEDMatrix = col_data | row_data; + + if(timingCount > MICROBIT_DISPLAY_GREYSCALE_BIT_DEPTH-1) + return; + + greyscaleBitMsk <<= 1; + + if(timingCount < 3) + { + wait_us(greyScaleTimings[timingCount++]); + renderGreyscale(); + return; + } + renderTimer.attach_us(this,&MicroBitDisplay::renderGreyscale, greyScaleTimings[timingCount++]); +} + +/** + * Periodic callback, that we use to perform any animations we have running. + */ +void +MicroBitDisplay::animationUpdate() +{ + // If there's no ongoing animation, then nothing to do. + if (animationMode == ANIMATION_MODE_NONE) + return; + + animationTick += system_timer_get_period(); + + if(animationTick >= animationDelay) + { + animationTick = 0; + + if (animationMode == ANIMATION_MODE_SCROLL_TEXT) + this->updateScrollText(); + + if (animationMode == ANIMATION_MODE_PRINT_TEXT) + this->updatePrintText(); + + if (animationMode == ANIMATION_MODE_SCROLL_IMAGE) + this->updateScrollImage(); + + if (animationMode == ANIMATION_MODE_ANIMATE_IMAGE) + this->updateAnimateImage(); + + if(animationMode == ANIMATION_MODE_PRINT_CHARACTER) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + } + } +} + +/** + * Broadcasts an event onto the defult EventModel indicating that the + * current animation has completed. + */ +void MicroBitDisplay::sendAnimationCompleteEvent() +{ + // Signal that we've completed an animation. + MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + + // Wake up a fiber that was blocked on the animation (if any). + MicroBitEvent(MICROBIT_ID_NOTIFY_ONE, MICROBIT_DISPLAY_EVT_FREE); +} + +/** + * Internal scrollText update method. + * Shift the screen image by one pixel to the left. If necessary, paste in the next char. + */ +void MicroBitDisplay::updateScrollText() +{ + image.shiftLeft(1); + scrollingPosition++; + + if (scrollingPosition == width + MICROBIT_DISPLAY_SPACING) + { + scrollingPosition = 0; + + image.print(scrollingChar < scrollingText.length() ? scrollingText.charAt(scrollingChar) : ' ',width,0); + + if (scrollingChar > scrollingText.length()) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + return; + } + scrollingChar++; + } +} + +/** + * Internal printText update method. + * Paste the next character in the string. + */ +void MicroBitDisplay::updatePrintText() +{ + image.print(printingChar < printingText.length() ? printingText.charAt(printingChar) : ' ',0,0); + + if (printingChar > printingText.length()) + { + animationMode = ANIMATION_MODE_NONE; + + this->sendAnimationCompleteEvent(); + return; + } + + printingChar++; +} + +/** + * Internal scrollImage update method. + * Paste the stored bitmap at the appropriate point. + */ +void MicroBitDisplay::updateScrollImage() +{ + image.clear(); + + if (((image.paste(scrollingImage, scrollingImagePosition, 0, 0) == 0) && scrollingImageRendered) || scrollingImageStride == 0) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + + return; + } + + scrollingImagePosition += scrollingImageStride; + scrollingImageRendered = true; +} + +/** + * Internal animateImage update method. + * Paste the stored bitmap at the appropriate point and stop on the last frame. + */ +void MicroBitDisplay::updateAnimateImage() +{ + //wait until we have rendered the last position to give a continuous animation. + if (scrollingImagePosition <= -scrollingImage.getWidth() + (MICROBIT_DISPLAY_WIDTH + scrollingImageStride) && scrollingImageRendered) + { + animationMode = ANIMATION_MODE_NONE; + this->clear(); + this->sendAnimationCompleteEvent(); + return; + } + + if(scrollingImagePosition > 0) + image.shiftLeft(-scrollingImageStride); + + image.paste(scrollingImage, scrollingImagePosition, 0, 0); + + if(scrollingImageStride == 0) + { + animationMode = ANIMATION_MODE_NONE; + this->sendAnimationCompleteEvent(); + } + + scrollingImageRendered = true; + + scrollingImagePosition += scrollingImageStride; +} + +/** + * Resets the current given animation. + */ +void MicroBitDisplay::stopAnimation() +{ + // Reset any ongoing animation. + if (animationMode != ANIMATION_MODE_NONE) + { + animationMode = ANIMATION_MODE_NONE; + + // Indicate that we've completed an animation. + MicroBitEvent(id,MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE); + + // Wake up aall fibers that may blocked on the animation (if any). + MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); + } + + // Clear the display and setup the animation timers. + this->image.clear(); +} + +/** + * Blocks the current fiber until the display is available (i.e. does not effect is being displayed). + * Animations are queued until their time to display. + */ +void MicroBitDisplay::waitForFreeDisplay() +{ + // If there's an ongoing animation, wait for our turn to display. + if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + fiber_wait_for_event(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); +} + +/** + * Blocks the current fiber until the current animation has finished. + * If the scheduler is not running, this call will essentially perform a spinning wait. + */ +void MicroBitDisplay::fiberWait() +{ + if (fiber_wait_for_event(MICROBIT_ID_DISPLAY, MICROBIT_DISPLAY_EVT_ANIMATION_COMPLETE) == MICROBIT_NOT_SUPPORTED) + while(animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + __WFE(); +} + +/** + * Prints the given character to the display, if it is not in use. + * + * @param c The character to display. + * + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever, + * or until the Displays next use. + * + * @return MICROBIT_OK, MICROBIT_BUSY is the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync('p'); + * display.printAsync('p',100); + * @endcode + */ +int MicroBitDisplay::printCharAsync(char c, int delay) +{ + //sanitise this value + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + image.print(c, 0, 0); + + if (delay > 0) + { + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_PRINT_CHARACTER; + } + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Prints the given ManagedString to the display, one character at a time. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Must be > 0. + * Defaults to: MICROBIT_DEFAULT_PRINT_SPEED. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync("abc123",400); + * @endcode + */ +int MicroBitDisplay::printAsync(ManagedString s, int delay) +{ + if (s.length() == 1) + return printCharAsync(s.charAt(0)); + + //sanitise this value + if (delay <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + printingChar = 0; + printingText = s; + animationDelay = delay; + animationTick = 0; + + animationMode = ANIMATION_MODE_PRINT_TEXT; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Prints the given image to the display, if the display is not in use. + * Returns immediately, and executes the animation asynchronously. + * + * @param i The image to display. + * + * @param x The horizontal position on the screen to display the image. Defaults to 0. + * + * @param y The vertical position on the screen to display the image. Defaults to 0. + * + * @param alpha Treats the brightness level '0' as transparent. Defaults to 0. + * + * @param delay The time to delay between characters, in milliseconds. Defaults to 0. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.print(i,400); + * @endcode + */ +int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int delay) +{ + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + image.paste(i, x, y, alpha); + + if(delay > 0) + { + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_PRINT_CHARACTER; + } + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Prints the given character to the display. + * + * @param c The character to display. + * + * @param delay Optional parameter - the time for which to show the character. Zero displays the character forever, + * or until the Displays next use. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync('p'); + * display.printAsync('p',100); + * @endcode + */ +int MicroBitDisplay::printChar(char c, int delay) +{ + if (delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printCharAsync(c, delay); + + if (delay > 0) + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Prints the given string to the display, one character at a time. + * + * Blocks the calling thread until all the text has been displayed. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_PRINT_SPEED. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.print("abc123",400); + * @endcode + */ +int MicroBitDisplay::print(ManagedString s, int delay) +{ + //sanitise this value + if(delay <= 0 ) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + if (s.length() == 1) + { + return printCharAsync(s.charAt(0)); + } + else + { + this->printAsync(s, delay); + fiberWait(); + } + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Prints the given image to the display. + * Blocks the calling thread until all the image has been displayed. + * + * @param i The image to display. + * + * @param x The horizontal position on the screen to display the image. Defaults to 0. + * + * @param y The vertical position on the screen to display the image. Defaults to 0. + * + * @param alpha Treats the brightness level '0' as transparent. Defaults to 0. + * + * @param delay The time to display the image for, or zero to show the image forever. Defaults to 0. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.print(i,400); + * @endcode + */ +int MicroBitDisplay::print(MicroBitImage i, int x, int y, int alpha, int delay) +{ + if(delay < 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + this->printAsync(i, x, y, alpha, delay); + + if (delay > 0) + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given string to the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.scrollAsync("abc123",100); + * @endcode + */ +int MicroBitDisplay::scrollAsync(ManagedString s, int delay) +{ + //sanitise this value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + scrollingPosition = width-1; + scrollingChar = 0; + scrollingText = s; + + animationDelay = delay; + animationTick = 0; + animationMode = ANIMATION_MODE_SCROLL_TEXT; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given image across the display, from right to left. + * Returns immediately, and executes the animation asynchronously. + * + * @param image The image to display. + * + * @param delay The time between updates, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @param stride The number of pixels to shift by in each update. Defaults to MICROBIT_DEFAULT_SCROLL_STRIDE. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the display is already in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.scrollAsync(i,100,1); + * @endcode + */ +int MicroBitDisplay::scrollAsync(MicroBitImage image, int delay, int stride) +{ + //sanitise the delay value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, it's our turn to display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + scrollingImagePosition = stride < 0 ? width : -image.getWidth(); + scrollingImageStride = stride; + scrollingImage = image; + scrollingImageRendered = false; + + animationDelay = stride == 0 ? 0 : delay; + animationTick = 0; + animationMode = ANIMATION_MODE_SCROLL_IMAGE; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given string across the display, from right to left. + * Blocks the calling thread until all text has been displayed. + * + * @param s The string to display. + * + * @param delay The time to delay between characters, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.scroll("abc123",100); + * @endcode + */ +int MicroBitDisplay::scroll(ManagedString s, int delay) +{ + //sanitise this value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->scrollAsync(s, delay); + + // Wait for completion. + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * Scrolls the given image across the display, from right to left. + * Blocks the calling thread until all the text has been displayed. + * + * @param image The image to display. + * + * @param delay The time between updates, in milliseconds. Defaults + * to: MICROBIT_DEFAULT_SCROLL_SPEED. + * + * @param stride The number of pixels to shift by in each update. Defaults to MICROBIT_DEFAULT_SCROLL_STRIDE. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * MicrobitImage i("1,1,1,1,1\n1,1,1,1,1\n"); + * display.scroll(i,100,1); + * @endcode + */ +int MicroBitDisplay::scroll(MicroBitImage image, int delay, int stride) +{ + //sanitise the delay value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->scrollAsync(image, delay, stride); + + // Wait for completion. + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + +/** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Returns immediately. + * + * @param image The image to display. + * + * @param delay The time to delay between each update of the display, in milliseconds. + * + * @param stride The number of pixels to shift by in each update. + * + * @param startingPosition the starting position on the display for the animation + * to begin at. Defaults to MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS. + * + * @return MICROBIT_OK, MICROBIT_BUSY if the screen is in use, or MICROBIT_INVALID_PARAMETER. + * + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * display.animateAsync(i,100,5); + * @endcode + */ +int MicroBitDisplay::animateAsync(MicroBitImage image, int delay, int stride, int startingPosition) +{ + //sanitise the delay value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If the display is free, we can display. + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) + { + // Assume right to left functionality, to align with scrollString() + stride = -stride; + + //calculate starting position which is offset by the stride + scrollingImagePosition = (startingPosition == MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS) ? MICROBIT_DISPLAY_WIDTH + stride : startingPosition; + scrollingImageStride = stride; + scrollingImage = image; + scrollingImageRendered = false; + + animationDelay = stride == 0 ? 0 : delay; + animationTick = delay-1; + animationMode = ANIMATION_MODE_ANIMATE_IMAGE; + } + else + { + return MICROBIT_BUSY; + } + + return MICROBIT_OK; +} + +/** + * "Animates" the current image across the display with a given stride, finishing on the last frame of the animation. + * Blocks the calling thread until the animation is complete. + * + * + * @param delay The time to delay between each update of the display, in milliseconds. + * + * @param stride The number of pixels to shift by in each update. + * + * @param startingPosition the starting position on the display for the animation + * to begin at. Defaults to MICROBIT_DISPLAY_ANIMATE_DEFAULT_POS. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * const int heart_w = 10; + * const int heart_h = 5; + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; + * + * MicroBitImage i(heart_w,heart_h,heart); + * display.animate(i,100,5); + * @endcode + */ +int MicroBitDisplay::animate(MicroBitImage image, int delay, int stride, int startingPosition) +{ + //sanitise the delay value + if(delay <= 0) + return MICROBIT_INVALID_PARAMETER; + + // If there's an ongoing animation, wait for our turn to display. + this->waitForFreeDisplay(); + + // If the display is free, it's our turn to display. + // If someone called stopAnimation(), then we simply skip... + if (animationMode == ANIMATION_MODE_NONE) + { + // Start the effect. + this->animateAsync(image, delay, stride, startingPosition); + + // Wait for completion. + //TODO: Put this in when we merge tight-validation + //if (delay > 0) + fiberWait(); + } + else + { + return MICROBIT_CANCELLED; + } + + return MICROBIT_OK; +} + + +/** + * Configures the brightness of the display. + * + * @param b The brightness to set the brightness to, in the range 0 - 255. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER + * + * @code + * display.setBrightness(255); //max brightness + * @endcode + */ +int MicroBitDisplay::setBrightness(int b) +{ + //sanitise the brightness level + if(b < 0 || b > 255) + return MICROBIT_INVALID_PARAMETER; + + this->brightness = b; + + return MICROBIT_OK; +} + +/** + * Configures the mode of the display. + * + * @param mode The mode to swap the display into. One of: DISPLAY_MODE_GREYSCALE, + * DISPLAY_MODE_BLACK_AND_WHITE, DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE + * + * @code + * display.setDisplayMode(DISPLAY_MODE_GREYSCALE); //per pixel brightness + * @endcode + */ +void MicroBitDisplay::setDisplayMode(DisplayMode mode) +{ + if(mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + //to reduce the artifacts on the display - increase the tick + if(system_timer_get_period() != MICROBIT_LIGHT_SENSOR_TICK_PERIOD) + system_timer_set_period(MICROBIT_LIGHT_SENSOR_TICK_PERIOD); + } + + if(this->mode == DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE && mode != DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + delete this->lightSensor; + + this->lightSensor = NULL; + } + + this->mode = mode; +} + +/** + * Retrieves the mode of the display. + * + * @return the current mode of the display + */ +int MicroBitDisplay::getDisplayMode() +{ + return this->mode; +} + +/** + * Fetches the current brightness of this display. + * + * @return the brightness of this display, in the range 0..255. + * + * @code + * display.getBrightness(); //the current brightness + * @endcode + */ +int MicroBitDisplay::getBrightness() +{ + return this->brightness; +} + +/** + * Rotates the display to the given position. + * + * Axis aligned values only. + * + * @code + * display.rotateTo(MICROBIT_DISPLAY_ROTATION_180); //rotates 180 degrees from original orientation + * @endcode + */ +void MicroBitDisplay::rotateTo(DisplayRotation rotation) +{ + this->rotation = rotation; +} + +/** + * Enables or disables the display entirely, and releases the pins for other uses. + * + * @param enableDisplay true to enabled the display, or false to disable it. + */ +void MicroBitDisplay::setEnable(bool enableDisplay) +{ + // If we're already in the correct state, then there's nothing to do. + if(((status & MICROBIT_COMPONENT_RUNNING) && enableDisplay) || (!(status & MICROBIT_COMPONENT_RUNNING) && !enableDisplay)) + return; + + uint32_t rmask = 0; + uint32_t cmask = 0; + + for (int i = matrixMap.rowStart; i < matrixMap.rowStart + matrixMap.rows; i++) + rmask |= 0x01 << i; + + for (int i = matrixMap.columnStart; i < matrixMap.columnStart + matrixMap.columns; i++) + cmask |= 0x01 << i; + + if (enableDisplay) + { + PortOut p(Port0, rmask | cmask); + status |= MICROBIT_COMPONENT_RUNNING; + } + else + { + PortIn p(Port0, rmask | cmask); + p.mode(PullNone); + status &= ~MICROBIT_COMPONENT_RUNNING; + } +} + +/** + * Enables the display, should only be called if the display is disabled. + * + * @code + * display.enable(); //Enables the display mechanics + * @endcode + * + * @note Only enables the display if the display is currently disabled. + */ +void MicroBitDisplay::enable() +{ + setEnable(true); +} + +/** + * Disables the display, which releases control of the GPIO pins used by the display, + * which are exposed on the edge connector. + * + * @code + * display.disable(); //disables the display + * @endcode + * + * @note Only disables the display if the display is currently enabled. + */ +void MicroBitDisplay::disable() +{ + setEnable(false); +} + +/** + * Clears the display of any remaining pixels. + * + * `display.image.clear()` can also be used! + * + * @code + * display.clear(); //clears the display + * @endcode + */ +void MicroBitDisplay::clear() +{ + image.clear(); +} + +/** + * Updates the font that will be used for display operations. + * + * @param font the new font that will be used to render characters. + * + * @note DEPRECATED! Please use MicroBitFont::setSystemFont() instead. + */ +void MicroBitDisplay::setFont(MicroBitFont font) +{ + MicroBitFont::setSystemFont(font); +} + +/** + * Retrieves the font object used for rendering characters on the display. + * + * @note DEPRECATED! Please use MicroBitFont::getSystemFont() instead. + */ +MicroBitFont MicroBitDisplay::getFont() +{ + return MicroBitFont::getSystemFont(); +} + +/** + * Captures the bitmap currently being rendered on the display. + * + * @return a MicroBitImage containing the captured data. + */ +MicroBitImage MicroBitDisplay::screenShot() +{ + return image.crop(0,0,MICROBIT_DISPLAY_WIDTH,MICROBIT_DISPLAY_HEIGHT); +} + +/** + * Gives a representative figure of the light level in the current environment + * where are micro:bit is situated. + * + * Internally, it constructs an instance of a MicroBitLightSensor if not already configured + * and sets the display mode to DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE. + * + * This also changes the tickPeriod to MICROBIT_LIGHT_SENSOR_TICK_SPEED so + * that the display does not suffer from artifacts. + * + * @return an indicative light level in the range 0 - 255. + * + * @note this will return 0 on the first call to this method, a light reading + * will be available after the display has activated the light sensor for the + * first time. + */ +int MicroBitDisplay::readLightLevel() +{ + if(mode != DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE) + { + setDisplayMode(DISPLAY_MODE_BLACK_AND_WHITE_LIGHT_SENSE); + this->lightSensor = new MicroBitLightSensor(matrixMap); + } + + return this->lightSensor->read(); +} + +/** + * Destructor for MicroBitDisplay, where we deregister this instance from the array of system components. + */ +MicroBitDisplay::~MicroBitDisplay() +{ + system_timer_remove_component(this); +}