Forked LEDMatrix and added horizontal scrolling

Fork of LEDMatrix by Yihui Xiong

LEDMatrix.cpp

Committer:
Bobty
Date:
2016-01-15
Revision:
5:334fc002e200
Parent:
4:40d4afefcd74

File content as of revision 5:334fc002e200:

/**
 * LED Matrix library for http://www.seeedstudio.com/depot/ultrathin-16x32-red-led-matrix-panel-p-1582.html
 * The LED Matrix panel has 32x16 pixels. Several panel can be combined together as a large screen.
 * 
 * Coordinate & Connection (mbed -> panel 0 -> panel 1 -> ...)
 *   (0, 0)                                     (0, 0)
 *     +--------+--------+--------+               +--------+--------+
 *     |   5    |    3   |    1   |               |    1   |    0   |
 *     |        |        |        |               |        |        |<----- mbed
 *     +--------+--------+--------+               +--------+--------+
 *     |   4    |    2   |    0   |                              (64, 16)
 *     |        |        |        |<----- mbed
 *     +--------+--------+--------+
 *                             (96, 32)
 *  Copyright (c) 2013 Seeed Technology Inc.
 *  @auther     Yihui Xiong
 *  @date       Nov 8, 2013
 *  @license    Apache
 *
 * Heavily modified by Rob Dobson, 2016
 */

#include "LEDMatrix.h"
#include "mbed.h"
#include "fontBig.h"
#include "font5x7.h"

#if 0
#define ASSERT(e)   if (!(e)) { Serial.println(#e); while (1); }
#else
#define ASSERT(e)
#endif

LEDMatrix::LEDMatrix(PinName pinA, PinName pinB, PinName pinC, PinName pinD, PinName pinOE, PinName pinR1, PinName pinSTB, PinName pinCLK) :
        a(pinA), b(pinB), c(pinC), d(pinD), oe(pinOE), r1(pinR1), stb(pinSTB), clk(pinCLK)
{
    this->clk = clk;
    this->r1 = r1;
    this->stb = stb;
    this->oe = oe;
    this->a = a;
    this->b = b;
    this->c = c;
    this->d = d;

    mask = 0xff;
    _isEnabled = false;
    for (int i = 0; i < LED_MATRIX_LEDS_VERTICALLY; i++)
    {
        _hScrollPos[i] = 0;
        _hScrollWidth[i] = LED_MATRIX_LEDS_HORIZONTALLY;
    }
    for (int i = 0; i < LED_MATRIX_MAX_LINES; i++)
    {
        _lineScrollInc[i] = 0;
        _lineHighlight[i] = false;
        _lineInvert[i] = false;
    }
    _isBusy = false;
    _charSeparation = 1;
    _flashCounter = 0;
    _flashRate = 50;
    _flashState = false;
    _scrollCounter = 0;
    _scrollRate = 7;
}

void LEDMatrix::begin(uint8_t *_pDisplayBuf, uint16_t width, uint16_t height, uint16_t dispBufWidth, 
                uint16_t numLines, int charSeparation, int flashRate, int scrollRate)
{
    ASSERT(0 == (width % LED_MATRIX_LEDS_HORIZONTALLY));
    ASSERT(0 == (scroll_width % LED_MATRIX_LEDS_HORIZONTALLY));
    ASSERT(0 == (height % LED_MATRIX_LEDS_VERTICALLY));

    this->_pDisplayBuf = _pDisplayBuf;
    this->width = width;
    this->height = height;
    this->dispBufWidth = dispBufWidth;
    this->numLines = numLines;
    this->_charSeparation = charSeparation;
    if (flashRate > 0)
        this->_flashRate = flashRate;
    if (scrollRate > 0)
        this->_scrollRate = scrollRate;
    if (numLines > LED_MATRIX_MAX_LINES)
        this->numLines = LED_MATRIX_MAX_LINES;
    for (int i = 0; i < LED_MATRIX_LEDS_VERTICALLY; i++)
    {
        this->_hScrollWidth[i] = dispBufWidth;
    }
    
    _isEnabled = true;
}

void LEDMatrix::drawPoint(uint16_t x, uint16_t y, uint8_t pixel)
{
    ASSERT(dispBufWidth > x);
    ASSERT(height > y);

    uint8_t *byte = _pDisplayBuf + (height - 1 - y) * dispBufWidth / 8 + x / 8;
    uint8_t  bit = x % 8;

    if (pixel)
    {
        *byte |= 0x80 >> bit;
    }
    else 
    {
        *byte &= ~(0x80 >> bit);
    }
}

void LEDMatrix::drawRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t pixel)
{
    for (uint16_t x = x1; x < x2; x++) 
    {
        for (uint16_t y = y1; y < y2; y++) 
        {
            drawPoint(x, y, pixel);
        }
    }
}

void LEDMatrix::drawImage(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t *image)
{
    ASSERT(0 == ((x2 - x1) % 8));

    for (uint16_t x = x1; x < x2; x++) 
    {
        for (uint16_t y = y1; y < y2; y++) 
        {
            uint8_t *byte = image + x * 8 + y / 8;
            uint8_t  bit = 7 - (y % 8);
            uint8_t  pixel = (*byte >> bit) & 1;
            drawPoint(x, y, pixel);
        }
    }
}

//void LEDMatrix::clear()
//{
//    uint8_t *ptr = _pDisplayBuf;
//    for (uint16_t i = 0; i < (dispBufWidth * height / 8); i++) 
//    {
//        *ptr = 0x00;
//        ptr++;
//    }
//}

void LEDMatrix::invertColour()
{
    mask = ~mask;
}

uint8_t LEDMatrix::isInvertedColour()
{
    return mask;
}

void LEDMatrix::scan()
{
    // On each call this function handles one row of the display
    static uint8_t scanRowIdx = 0;

    // Check if being updated
    if (_isBusy)
        return;

    // Check if display enabled
    if (!_isEnabled)
        return;

    // Base of this scan row in buffer to display
    uint8_t *pBufBase = _pDisplayBuf + scanRowIdx * (dispBufWidth / 8);
    
    // Handle multiple units vertically
    for (int rowIdx = 0; rowIdx < (height / LED_MATRIX_LEDS_VERTICALLY); rowIdx++) 
    {
        // Calculate buffer position within this unit
        uint8_t *pBuf = pBufBase + rowIdx * (dispBufWidth / 8) * LED_MATRIX_LEDS_VERTICALLY;

        // Work out which display line we're in
        uint8_t dispMask = mask;
        int rowsInLine = (height/numLines);
        int dispLine = scanRowIdx / rowsInLine;
        if (dispLine < numLines)
        {
            if (_lineInvert[dispLine])
                dispMask = ~dispMask;
        }
                
        // Work along the display row sending out data as we go
        // Noting that the first data we send out will be shunted to the end of the row
        int hScrollPos = _hScrollPos[scanRowIdx];
        int hScrollWidth = _hScrollWidth[scanRowIdx];
        if (hScrollWidth > dispBufWidth)
            hScrollWidth = dispBufWidth;
        int bitOffset = hScrollPos % 8;
        for (int byteIdx = (width / 8) - 1; byteIdx >= 0; byteIdx--)
        {
            // When a row is scrolled a single byte on the display can be a combination of two buffer bytes
            uint8_t* pByte1 = pBuf + ((byteIdx*8 + hScrollPos) % hScrollWidth) / 8;
            uint8_t* pByte2 = pBuf + (((byteIdx+1)*8 + hScrollPos) % hScrollWidth) / 8;
            uint8_t pixels = ((*pByte1) << bitOffset) + (((*pByte2) >> (8 - bitOffset)) % 256);

            // Reverse the bits so they come out in the right order                        
            pixels = reverseBits(pixels) ^ dispMask;   // reverse: mask = 0xff, normal: mask =0x00 
            for (uint8_t bit = 0; bit < 8; bit++) 
            {
                clk = 0;
                r1 = pixels & (0x80 >> bit);
                clk = 1;
            }
        }
    }

    // Disable display
    oe = 1;

    // Select row (rows are muxed)
    int rowSel = LED_MATRIX_LEDS_VERTICALLY - 1 - scanRowIdx;
    a = (rowSel & 0x01);
    b = (rowSel & 0x02);
    c = (rowSel & 0x04);
    d = (rowSel & 0x08);

    // Latch data
    stb = 0;
    stb = 1;

    // Reenable display
    oe = 0;

    // Move the scan row on for next time
    scanRowIdx = (scanRowIdx + 1) & 0x0F;
}

void LEDMatrix::on()
{
    _isEnabled = true;
}

void LEDMatrix::off()
{
    _isEnabled = false;
    oe = 1;
}

bool LEDMatrix::getRowsToWorkOn(int lineIdx, int &startRow, int &numRows)
{
    // lineIdx == -1 means all lines
    startRow = 0;
    numRows = LED_MATRIX_LEDS_VERTICALLY;
    if (lineIdx != -1)
    {
        if ((lineIdx < 0) || (lineIdx >= numLines))
            return false;
        numRows = height / numLines;
        startRow = lineIdx * numRows;
    }
    return true;
}    

void LEDMatrix::scrollReset(int lineIdx)
{
    // Interpret param
    int startRow = 0;
    int numRows = LED_MATRIX_LEDS_VERTICALLY;
    if (!getRowsToWorkOn(lineIdx, startRow, numRows))
        return;
    // Reset scroll position all rows
    for (int i = startRow; i < startRow+numRows; i++)
        _hScrollPos[i] = 0;
}

void LEDMatrix::scroll(int lineIdx, bool scrollLeft)
{
    // Interpret param
    int startRow = 0;
    int numRows = LED_MATRIX_LEDS_VERTICALLY;
    if (!getRowsToWorkOn(lineIdx, startRow, numRows))
        return;
    // Reset scroll position all rows
    for (int i = startRow; i < startRow+numRows; i++)
    {
        if (scrollLeft)
        {
            _hScrollPos[i]++;
            if (_hScrollPos[i] >= _hScrollWidth[i])
                _hScrollPos[i] = 0;
        }
        else
        {
            _hScrollPos[i]--;
            if (_hScrollPos[i] < 0)
                _hScrollPos[i] = _hScrollWidth[i]-1;
        }
    }
}

void LEDMatrix::scrollToPos(int lineIdx, int pos)
{
    // Interpret param
    int startRow = 0;
    int numRows = LED_MATRIX_LEDS_VERTICALLY;
    if (!getRowsToWorkOn(lineIdx, startRow, numRows))
        return;
    // Check pos
    if ((pos < 0) || (pos >= dispBufWidth))
        return;
    // Reset scroll position all rows
    for (int i = startRow; i < startRow+numRows; i++)
        _hScrollPos[i] = pos;
}

void LEDMatrix::clear(int lineIdx)
{
    // Interpret param
    int startRow = 0;
    int numRows = LED_MATRIX_LEDS_VERTICALLY;
    if (!getRowsToWorkOn(lineIdx, startRow, numRows))
        return;
    // Clear section
    uint8_t *ptr = _pDisplayBuf + startRow * (dispBufWidth / 8);
    int bytesToZero = numRows * (dispBufWidth / 8);
    for (int i = 0; i < bytesToZero; i++) 
        *ptr++ = 0x00;
    // Stop invert
    for (int i = 0; i < LED_MATRIX_MAX_LINES; i++)
        _lineInvert[i] = false;
}

void LEDMatrix::setScrollRate(int rate)
{
    _scrollRate = rate;
}

void LEDMatrix::setFlashRate(int rate)
{
    _flashRate = rate;
}

void LEDMatrix::setupScroll(int lineIdx, int scrollWidth, int scrollInc)
{
    // Interpret param
    int startRow = 0;
    int numRows = LED_MATRIX_LEDS_VERTICALLY;
    if (!getRowsToWorkOn(lineIdx, startRow, numRows))
        return;
    scrollWidth = (((scrollWidth / 8) + 1) * 8);
    if (scrollWidth <= 0)
        return;
    for (int i = startRow; i < startRow+numRows; i++)
    {
        _hScrollPos[i] = 0;
        _hScrollWidth[i] = scrollWidth;
    }
    // Store the scroll increment
    int startLine = 0;
    int linesToProcess = numLines;
    if (lineIdx != -1)
    {
        if ((lineIdx < 0) || (lineIdx >= numLines))
            return;
        startLine = lineIdx;
        linesToProcess = 1;
    }
    for (int i = 0; i < linesToProcess; i++)
        _lineScrollInc[i+startLine] = scrollInc;
}

void LEDMatrix::setHighlight(int lineIdx, bool highlightOn)
{
    // Store the highlight status
    int startLine = 0;
    int linesToProcess = numLines;
    if (lineIdx != -1)
    {
        if ((lineIdx < 0) || (lineIdx >= numLines))
            return;
        startLine = lineIdx;
        linesToProcess = 1;
    }
    for (int i = 0; i < linesToProcess; i++)
        _lineHighlight[i+startLine] = highlightOn;
}
    
void LEDMatrix::serviceEffects()
{
    // Scroll logic
    _scrollCounter++;
    if (_scrollCounter >= _scrollRate)
    {
        _scrollCounter = 0;
        int rowsInLine = (height / numLines);
        for (int i = 0; i < numLines; i++)
        {
            for (int j = 0; j < rowsInLine; j++)
                _hScrollPos[i*rowsInLine+j] += _lineScrollInc[i];
        }
    }
    
    // Update flash state
    _flashCounter++;
    if (_flashCounter >= _flashRate)
    {
        _flashCounter = 0;
        _flashState = !_flashState;
        for (int i = 0; i < numLines; i++)
            _lineInvert[i] = _lineHighlight[i] ? _flashState : false;
    }
}
    
// Reverse bits in byte
uint8_t LEDMatrix::reverseBits(uint8_t b) 
{
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
   b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
   return b;
}

// Display ASCII char
int LEDMatrix::displayChar(int xPos, int yPos, char ch)
{
    const int FONT_HEIGHT = 7;
    const int FONT_WIDTH = 5;
    
    // Find the width of the character
    uint8_t chTotalBits = 0;
    int chIdxInFont = (ch % 128) - 0x20;
    // Special case for £, euro and yen signs
    if ((ch >= 0xa3) && (ch <= 0xa5))
        chIdxInFont = ch - 0xa3 + 128 - ' ';
    for (int i = 0; i < FONT_HEIGHT; i++)
    {
        uint8_t chBits = font5x7[chIdxInFont][i];
        chTotalBits |= chBits;
    }
    uint8_t rightShift = 0;
    for (int j = 0; j < FONT_WIDTH; j++)
    {
        if (chTotalBits & 0x01)
            break;
        chTotalBits = chTotalBits >> 1;
        rightShift++;
    }
    uint8_t charWidth = 8;
    for (int j = 0; j < FONT_WIDTH+1; j++)
    {
        if (chTotalBits & 0x80)
            break;
        chTotalBits = chTotalBits << 1;
        charWidth--;
    }
    
    // display character
    int xOffset = xPos / 8;
    int bitOffset = xPos % 8;
//    printf("%c %d %d %d %d\n", ch, charPos, charWidth, xOffset, bitOffset);
    if (xOffset >= (dispBufWidth/8))
        return 0;
        
    // Copy the bits of the character into the buffer
    for (int i = 0; i < FONT_HEIGHT; i++)
    {
        // Check we don't go off the display buffer
        int rowIdx = i + yPos;
        if (rowIdx > height)
            break;
            
        // 
        uint8_t chBits = font5x7[chIdxInFont][i] >> rightShift;
        int dispBufPos = (rowIdx * (dispBufWidth/8) + xOffset);
        _pDisplayBuf[dispBufPos] |= ((chBits << (8 - charWidth)) >> bitOffset);
        if (xOffset + 1 < (dispBufWidth/8))
            _pDisplayBuf[dispBufPos + 1] |= ((chBits << (8-bitOffset)) << (8 - charWidth));
    }

    return charWidth;
}

// Display a large digit
int LEDMatrix::displayLargeDigit(int curX, int curY, char ch)
{
    // Bounds check
    if ((ch < '0') || (ch > '9'))
        return 0;
        
    // Get character data
    const uint8_t* charData = bigFont[ch-'0'];
    int numLines = sizeof(bigFont[0])/sizeof(bigFont[0][0]);
    
    // Find the position and width of the character
    uint16_t chTotalBits = 0;
    for (int i = 0; i < numLines; i++)
        chTotalBits |= charData[i];
    uint16_t rightShift = 0;
    for (; rightShift < 16; rightShift++)
    {
        if (chTotalBits & 0x01)
            break;
        chTotalBits = chTotalBits >> 1;
    }
    uint16_t charWidth = 16;
    for (; charWidth > 0; charWidth--)
    {
        if (chTotalBits & 0x8000)
            break;
        chTotalBits = chTotalBits << 1;
    }

    // display character
    int xOffset = curX / 8;
    int bitOffset = curX % 8;
//    printf("%c %d %d %d %d %d\n", ch, curX, charWidth, rightShift, xOffset, bitOffset);
    if (xOffset >= (dispBufWidth/8))
        return 0;
    for (int i = 0; i < numLines; i++)
    {
        uint32_t chBits = charData[i] >> rightShift;
        chBits = (chBits << (24-charWidth)) >> bitOffset;
        for (int j = 0; j < 3; j++)
        {
            if (xOffset + j >= (dispBufWidth/8))
                break;
            _pDisplayBuf[(i + curY) * (dispBufWidth/8) + xOffset + j] |= ((chBits >> (16-j*8)) & 0xff);
        }
    }

    return charWidth;
}

// Display line of characters
int LEDMatrix::displayLine(int lineIdx, const char* line)
{
    if (lineIdx >= numLines)
        lineIdx = 0;
    _isBusy = true;
    int curX = 0;
    int curY = lineIdx * height/numLines;
    for (int chIdx = 0; chIdx < strlen(line); chIdx++)
    {
        curX += displayChar(curX, curY, line[chIdx]) + _charSeparation;
    }
    _isBusy = false;
    return curX;
}