MBED NRF51 Arduboy port

arduboy.hpp

Committer:
lmayencou
Date:
2017-01-07
Revision:
7:fb7e549d1cf6
Parent:
1:c53e766082b4

File content as of revision 7:fb7e549d1cf6:

#define WIDTH 128
#define HEIGHT 32

#define WHITE 1
#define BLACK 0

#define COLUMN_ADDRESS_END (WIDTH - 1) & 0x7F
#define PAGE_ADDRESS_END ((HEIGHT/8)-1) & 0x07

#define _BV(b) (1UL << (b))
#define min(a,b) (((a)<(b))?(a):(b))
#define max(a,b) (((a)>(b))?(a):(b))
#define pgm_read_byte(a) 1

SPI _spi(p30, p31, p29); // mosi, miso, sclk

DigitalOut _cs(p3);
DigitalOut _dc(p4);
DigitalOut _rst(p28);

class Arduboy
{
  public:
    Arduboy()
    {
        // frame management
        setFrameRate(60);
        frameCount = 0;
        lastFrameStart = 0;
        nextFrameStart = 0;
        lastFrameDurationMs = 0;
        post_render = false;
    }
    
    void begin()
    {
        // init SPI
        _spi.format(8,3);
        _spi.frequency(1000000);
        
        // init pin LCD
        _dc = 0;
        _cs = 0;
        _rst = 1;
        wait(1);
        _rst = 0;
        wait(10);
        _rst = 1;
        
        bootLCD();
    }
    
    void Arduboy::bootLCD()
    {
      LCDCommandMode();
      _spi.write(0xAE);  // Display Off
      _spi.write(0XD5);  // Set Display Clock Divisor v
      _spi.write(0xF0);  //   0x80 is default
      _spi.write(0xA8);  // Set Multiplex Ratio v
      _spi.write(0x3F);
      _spi.write(0xD3);  // Set Display Offset v
      _spi.write(0x0);
      _spi.write(0x40);  // Set Start Line (0)
      _spi.write(0x8D);  // Charge Pump Setting v
      _spi.write(0x14);  //   Enable
      // why are we running this next pair twice?
      _spi.write(0x20);  // Set Memory Mode v
      _spi.write(0x00);  //   Horizontal Addressing
      _spi.write(0xA1);  // Set Segment Re-map (A0) | (b0001)
      _spi.write(0xC8);  // Set COM Output Scan Direction
      _spi.write(0xDA);  // Set COM Pins v
      _spi.write(0x12);
      _spi.write(0x81);  // Set Contrast v
      _spi.write(0xCF);
      _spi.write(0xD9);  // Set Precharge
      _spi.write(0xF1);
      _spi.write(0xDB);  // Set VCom Detect
      _spi.write(0x40);
      _spi.write(0xA4);  // Entire Display ON
      _spi.write(0xA6);  // Set normal/inverse display
      _spi.write(0xAF);  // Display On
    
      LCDCommandMode();
      _spi.write(0x20);     // set display mode
      _spi.write(0x00);     // horizontal addressing mode
    
      _spi.write(0x21);     // set col address
      _spi.write(0x00);
      _spi.write(COLUMN_ADDRESS_END);
    
      _spi.write(0x22); // set page address
      _spi.write(0x00);
      _spi.write(PAGE_ADDRESS_END);
    
      LCDDataMode();
    }
    
    void LCDCommandMode()
    {
        _cs = 1;
        _dc = 0;
        _cs = 0;
    }

    void LCDDataMode()
    {
        _dc = 1;
        _cs = 0;
    };
    
    void idle()
    {}
    
    /* Frame management */

void setFrameRate(uint8_t rate)
{
  frameRate = rate;
  eachFrameMillis = 1000/rate;
}

bool everyXFrames(uint8_t frames)
{
  return frameCount % frames == 0;
}

bool newFrame()
{
  long now = time(NULL);
  uint8_t remaining;

  // post render
  if (post_render)
  {
    lastFrameDurationMs = now - lastFrameStart;
    frameCount++;
    post_render = false;
  }

  // if it's not time for the next frame yet
  if (now < nextFrameStart)
  {
    remaining = nextFrameStart - now;
    // if we have more than 1ms to spare, lets sleep
    // we should be woken up by timer0 every 1ms, so this should be ok
    if (remaining > 1)
      idle();
    return false;
  }

  // pre-render

  // next frame should start from last frame start + frame duration
  nextFrameStart = lastFrameStart + eachFrameMillis;
  // If we're running CPU at 100%+ (too slow to complete each loop within
  // the frame duration) then it's possible that we get "behind"... Say we
  // took 5ms too long, resulting in nextFrameStart being 5ms in the PAST.
  // In that case we simply schedule the next frame to start immediately.
  //
  // If we were to let the nextFrameStart slide further and further into
  // the past AND eventually the CPU usage dropped then frame management
  // would try to "catch up" (by speeding up the game) to make up for all
  // that lost time.  That would not be good.  We allow frames to take too
  // long (what choice do we have?), but we do not allow super-fast frames
  // to make up for slow frames in the past.
  if (nextFrameStart < now)
    nextFrameStart = now;

  lastFrameStart = now;

  post_render = true;
  return post_render;
}

// This function is deprecated.
// It should remain as is for backwards compatibility.
// New code should use newFrame().
bool nextFrame()
{
  long now = time(NULL);
  uint8_t remaining;

  if (post_render) {
    lastFrameDurationMs = now - lastFrameStart;
    frameCount++;
    post_render = false;
  }

  if (now < nextFrameStart) {
    remaining = nextFrameStart - now;
    if (remaining > 1)
      idle();
    return false;
  }

  nextFrameStart = now + eachFrameMillis;
  lastFrameStart = now;
  post_render = true;
  return post_render;
}

/* Graphics */

    void blank()
    {
    for (int a = 0; a < (HEIGHT * WIDTH) / 8; a++) _spi.write(0x00);
    }

    void clearDisplay()
    {
    this->fillScreen(0);
    }

    void drawPixel(int x, int y, uint8_t color)
    {
    #ifdef PIXEL_SAFE_MODE
      if (x < 0 || x > (WIDTH - 1) || y < 0 || y > (HEIGHT - 1))
      {
        return;
      }
    #endif
    
      uint8_t row = (uint8_t)y / 8;
      if (color)
      {
        sBuffer[(row * WIDTH) + (uint8_t)x] |=   _BV((uint8_t)y % 8);
      }
      else
      {
        sBuffer[(row * WIDTH) + (uint8_t)x] &= ~ _BV((uint8_t)y % 8);
      }
    }
    
    uint8_t getPixel(uint8_t x, uint8_t y)
    {
      uint8_t row = y / 8;
      uint8_t bit_position = y % 8;
      return (sBuffer[(row * WIDTH) + x] & _BV(bit_position)) >> bit_position;
    }
    
    static inline void swap(int16_t &a, int16_t &b)
    {
    int16_t t = a;
    
    a = b;
    b = t;
    }
    
    void drawCircle(int16_t x0, int16_t y0, int16_t r, uint8_t color)
    {
      int16_t f = 1 - r;
      int16_t ddF_x = 1;
      int16_t ddF_y = -2 * r;
      int16_t x = 0;
      int16_t y = r;
    
      drawPixel(x0, y0 + r, color);
      drawPixel(x0, y0 - r, color);
      drawPixel(x0 + r, y0, color);
      drawPixel(x0 - r, y0, color);
    
      while (x < y)
      {
        if (f >= 0)
        {
          y--;
          ddF_y += 2;
          f += ddF_y;
        }
    
        x++;
        ddF_x += 2;
        f += ddF_x;
    
        drawPixel(x0 + x, y0 + y, color);
        drawPixel(x0 - x, y0 + y, color);
        drawPixel(x0 + x, y0 - y, color);
        drawPixel(x0 - x, y0 - y, color);
        drawPixel(x0 + y, y0 + x, color);
        drawPixel(x0 - y, y0 + x, color);
        drawPixel(x0 + y, y0 - x, color);
        drawPixel(x0 - y, y0 - x, color);
      }
    }
    
    void drawCircleHelper
    (int16_t x0, int16_t y0, int16_t r, uint8_t cornername, uint8_t color)
    {
      int16_t f = 1 - r;
      int16_t ddF_x = 1;
      int16_t ddF_y = -2 * r;
      int16_t x = 0;
      int16_t y = r;
    
      while (x < y)
      {
        if (f >= 0)
        {
          y--;
          ddF_y += 2;
          f += ddF_y;
        }
    
        x++;
        ddF_x += 2;
        f += ddF_x;
    
        if (cornername & 0x4)
        {
          drawPixel(x0 + x, y0 + y, color);
          drawPixel(x0 + y, y0 + x, color);
        }
        if (cornername & 0x2)
        {
          drawPixel(x0 + x, y0 - y, color);
          drawPixel(x0 + y, y0 - x, color);
        }
        if (cornername & 0x8)
        {
          drawPixel(x0 - y, y0 + x, color);
          drawPixel(x0 - x, y0 + y, color);
        }
        if (cornername & 0x1)
        {
          drawPixel(x0 - y, y0 - x, color);
          drawPixel(x0 - x, y0 - y, color);
        }
      }
    }
    
    void fillCircle(int16_t x0, int16_t y0, int16_t r, uint8_t color)
    {
      drawFastVLine(x0, y0 - r, 2 * r + 1, color);
      fillCircleHelper(x0, y0, r, 3, 0, color);
    }
    
    void fillCircleHelper
    (
      int16_t x0,
      int16_t y0,
      int16_t r,
      uint8_t cornername,
      int16_t delta,
      uint8_t color
    )
    {
      // used to do circles and roundrects!
      int16_t f = 1 - r;
      int16_t ddF_x = 1;
      int16_t ddF_y = -2 * r;
      int16_t x = 0;
      int16_t y = r;
    
      while (x < y)
      {
        if (f >= 0)
        {
          y--;
          ddF_y += 2;
          f += ddF_y;
        }
    
        x++;
        ddF_x += 2;
        f += ddF_x;
    
        if (cornername & 0x1)
        {
          drawFastVLine(x0 + x, y0 - y, 2 * y + 1 + delta, color);
          drawFastVLine(x0 + y, y0 - x, 2 * x + 1 + delta, color);
        }
    
        if (cornername & 0x2)
        {
          drawFastVLine(x0 - x, y0 - y, 2 * y + 1 + delta, color);
          drawFastVLine(x0 - y, y0 - x, 2 * x + 1 + delta, color);
        }
      }
    }
    
    void drawLine
    (int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color)
    {
      // bresenham's algorithm - thx wikpedia
      bool steep = abs(y1 - y0) > abs(x1 - x0);
      if (steep) {
        swap(x0, y0);
        swap(x1, y1);
      }
    
      if (x0 > x1) {
        swap(x0, x1);
        swap(y0, y1);
      }
    
      int16_t dx, dy;
      dx = x1 - x0;
      dy = abs(y1 - y0);
    
      int16_t err = dx / 2;
      int8_t ystep;
    
      if (y0 < y1)
      {
        ystep = 1;
      }
      else
      {
        ystep = -1;
      }
    
      for (; x0 <= x1; x0++)
      {
        if (steep)
        {
          drawPixel(y0, x0, color);
        }
        else
        {
          drawPixel(x0, y0, color);
        }
    
        err -= dy;
        if (err < 0)
        {
          y0 += ystep;
          err += dx;
        }
      }
    }
    
    void drawRect
    (int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
    {
      drawFastHLine(x, y, w, color);
      drawFastHLine(x, y + h - 1, w, color);
      drawFastVLine(x, y, h, color);
      drawFastVLine(x + w - 1, y, h, color);
    }
    
    void drawFastVLine
    (int16_t x, int16_t y, int16_t h, uint8_t color)
    {
      int end = y + h;
      for (int a = max(0, y); a < min(end, HEIGHT); a++)
      {
        drawPixel(x, a, color);
      }
    }
    
    void drawFastHLine
    (int16_t x, int16_t y, int16_t w, uint8_t color)
    {
      int end = x + w;
      for (int a = max(0, x); a < min(end, WIDTH); a++)
      {
        drawPixel(a, y, color);
      }
    }
    
    void fillRect
    (int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
    {
      // stupidest version - update in subclasses if desired!
      for (int16_t i = x; i < x + w; i++)
      {
        drawFastVLine(i, y, h, color);
      }
    }
    
    void fillScreen(uint8_t color)
    {
        // C version :
        if(color != 0) color = 0xFF;  //change any nonzero argument to b11111111 and insert into screen array.
          for(int16_t i=0; i<1024; i++)  { sBuffer[i] = color; }  //sBuffer = (128*64) = 8192/8 = 1024 bytes.
    }

    
  uint8_t frameRate;
  uint16_t frameCount;
  uint8_t eachFrameMillis;
  long lastFrameStart;
  long nextFrameStart;
  bool post_render;
  uint8_t lastFrameDurationMs;

protected:
  unsigned char sBuffer[(HEIGHT*WIDTH)/8];
};