Arduboy lib for NRF and mbed

Dependencies:   Adafruit_GFX

Revision:
0:2b6d7af79c9c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core.cpp	Tue Dec 27 11:27:14 2016 +0000
@@ -0,0 +1,606 @@
+#include "core.h"
+
+unsigned char Arduboy::sBuffer[];
+
+Arduboy::Arduboy() :   
+  i2c(SDA,SCL),
+  Adafruit_SSD1306_I2c(i2c,p13)
+{ }
+
+void Arduboy::start()
+{
+#if F_CPU == 8000000L
+  slowCPU();
+#endif
+
+  // init pin
+  DigitalOut led1(LED1);
+
+  bootLCD();
+
+#ifdef SAFE_MODE
+  if (pressed(LEFT_BUTTON + UP_BUTTON))
+    safeMode();
+#endif
+
+
+  audio.setup();
+  saveMuchPower();
+}
+
+#if F_CPU == 8000000L
+// if we're compiling for 8Mhz we need to slow the CPU down because the
+// hardware clock on the Arduboy is 16MHz
+void Arduboy::slowCPU()
+{
+
+}
+#endif
+
+void Arduboy::bootLCD()
+{
+  clearDisplay();
+  display();
+}
+
+// Safe Mode is engaged by holding down both the LEFT button and UP button
+// when plugging the device into USB.  It puts your device into a tight
+// loop and allows it to be reprogrammed even if you have uploaded a very
+// broken sketch that interferes with the normal USB triggered auto-reboot
+// functionality of the device.
+void Arduboy::safeMode()
+{
+  display(); // too avoid random gibberish
+  while (true) {};
+}
+
+/* Power Management */
+
+void Arduboy::idle()
+{
+}
+
+void Arduboy::saveMuchPower()
+{
+}
+
+
+/* Frame management */
+
+void Arduboy::setFrameRate(uint8_t rate)
+{
+  frameRate = rate;
+  eachFrameMillis = 1000 / rate;
+}
+
+bool Arduboy::everyXFrames(uint8_t frames)
+{
+  return frameCount % frames == 0;
+}
+
+bool Arduboy::nextFrame()
+{
+  long now = millis();
+  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
+
+  // technically next frame should be last frame + each frame but if we're
+  // running a slow render we would constnatly be behind the clock
+  // keep an eye on this and see how it works.  If it works well the
+  // lastFrameStart variable could be eliminated completely
+  nextFrameStart = now + eachFrameMillis;
+  lastFrameStart = now;
+  post_render = true;
+  return post_render;
+}
+
+// returns the load on the CPU as a percentage
+// this is based on how much of the time your app is spends rendering
+// frames.  This number can be higher than 100 if your app is rendering
+// really slowly.
+int Arduboy::cpuLoad()
+{
+  return lastFrameDurationMs * 100 / eachFrameMillis;
+}
+
+// seed the random number generator with entropy from the temperature,
+// voltage reading, and microseconds since boot.
+// this method is still most effective when called semi-randomly such
+// as after a user hits a button to start a game or other semi-random
+// events
+void Arduboy::initRandomSeed()
+{
+}
+
+uint16_t Arduboy::rawADC(byte adc_bits)
+{
+  return 0;
+}
+
+
+unsigned char* Arduboy::getBuffer() {
+  return sBuffer;
+}
+
+uint8_t Arduboy::width() {
+  return WIDTH;
+}
+
+uint8_t Arduboy::height() {
+  return HEIGHT;
+}
+
+
+void Arduboy::poll()
+{
+  previousButtonState = currentButtonState;
+  currentButtonState = getInput();
+}
+
+// returns true if the button mask passed in is pressed
+//
+//   if (pressed(LEFT_BUTTON + A_BUTTON))
+boolean Arduboy::pressed(uint8_t buttons)
+{
+  uint8_t button_state = getInput();
+  return (button_state & buttons) == buttons;
+}
+
+// returns true if the button mask passed in not pressed
+//
+//   if (not_pressed(LEFT_BUTTON))
+boolean Arduboy::notPressed(uint8_t buttons)
+{
+  uint8_t button_state = getInput();
+  return (button_state & buttons) == 0;
+}
+
+// returns true if a button has just been pressed
+// if the button has been held down for multiple frames this will return
+// false.  You should only use this to poll a single button.
+boolean Arduboy::justPressed(uint8_t button)
+{
+  uint8_t button_state = getInput();
+  return (!(previousButtonState & button) && (currentButtonState & button));
+}
+
+
+
+uint8_t Arduboy::getInput()
+{
+  uint8_t buttons;
+
+  return buttons;
+}
+
+void Arduboy::swap(int16_t& a, int16_t& b) {
+  int temp = a;
+  a = b;
+  b = temp;
+}
+
+
+/* AUDIO */
+
+void ArduboyAudio::on() {
+
+  audio_enabled = true;
+}
+
+bool ArduboyAudio::enabled() {
+  return audio_enabled;
+}
+
+void ArduboyAudio::off() {
+
+  audio_enabled = false;
+}
+
+void ArduboyAudio::saveOnOff() {
+}
+
+void ArduboyAudio::setup() {
+
+}
+
+void ArduboyAudio::tone(unsigned int frequency, unsigned long duration)
+{
+
+}
+
+
+/////////////////////////
+// Sprites by Dreamer3 //
+/////////////////////////
+Sprites::Sprites(Arduboy &a)
+{
+  arduboy = &a;
+  sBuffer = arduboy->getBuffer();
+}
+
+// new API
+
+void Sprites::drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap,
+                               const uint8_t *mask, uint8_t frame, uint8_t mask_frame)
+{
+  draw(x, y, bitmap, frame, mask, mask_frame, SPRITE_MASKED);
+}
+
+void Sprites::drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
+{
+  draw(x, y, bitmap, frame, NULL, 0, SPRITE_OVERWRITE);
+}
+
+void Sprites::drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
+{
+  draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK_ERASE);
+}
+
+void Sprites::drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
+{
+  draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK);
+}
+
+void Sprites::drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
+{
+  draw(x, y, bitmap, frame, NULL, 0, SPRITE_PLUS_MASK);
+}
+
+
+//common functions
+void Sprites::draw(int16_t x, int16_t y,
+                   const uint8_t *bitmap, uint8_t frame,
+                   const uint8_t *mask, uint8_t sprite_frame,
+                   uint8_t drawMode
+                  )
+{
+  unsigned int frame_offset;
+
+  if (bitmap == NULL)
+    return;
+
+  uint8_t width = pgm_read_byte(bitmap);
+  uint8_t height = pgm_read_byte(++bitmap);
+  bitmap++;
+  if (frame > 0 || sprite_frame > 0) {
+    frame_offset = (width * ( height / 8 + ( height % 8 == 0 ? 0 : 1)));
+    // sprite plus mask uses twice as much space for each frame
+    if (drawMode == SPRITE_PLUS_MASK) {
+      frame_offset *= 2;
+    } else if (mask != NULL) {
+      mask += sprite_frame * frame_offset;
+    }
+    bitmap += frame * frame_offset;
+  }
+
+  // if we're detecting the draw mode then base it on whether a mask
+  // was passed as a separate object
+  if (drawMode == SPRITE_AUTO_MODE) {
+    drawMode = mask == NULL ? SPRITE_UNMASKED : SPRITE_MASKED;
+  }
+
+  drawBitmap(x, y, bitmap, mask, width, height, drawMode);
+}
+
+void Sprites::drawBitmap(int16_t x, int16_t y,
+                         const uint8_t *bitmap, const uint8_t *mask,
+                         int8_t w, int8_t h, uint8_t draw_mode) {
+  // no need to draw at all of we're offscreen
+  if (x + w <= 0 || x > WIDTH - 1 || y + h <= 0 || y > HEIGHT - 1)
+    return;
+
+  if (bitmap == NULL)
+    return;
+
+  // xOffset technically doesn't need to be 16 bit but the math operations
+  // are measurably faster if it is
+  uint16_t xOffset, ofs;
+  int8_t yOffset = abs(y) % 8;
+  int8_t sRow = y / 8;
+  uint8_t loop_h, start_h, rendered_width;
+
+  if (y < 0 && yOffset > 0) {
+    sRow--;
+    yOffset = 8 - yOffset;
+  }
+
+  // if the left side of the render is offscreen skip those loops
+  if (x < 0) {
+    xOffset = abs(x);
+  } else {
+    xOffset = 0;
+  }
+
+  // if the right side of the render is offscreen skip those loops
+  if (x + w > WIDTH - 1) {
+    rendered_width = ((WIDTH - x) - xOffset);
+  } else {
+    rendered_width = (w - xOffset);
+  }
+
+  // if the top side of the render is offscreen skip those loops
+  if (sRow < -1) {
+    start_h = abs(sRow) - 1;
+  } else {
+    start_h = 0;
+  }
+
+  loop_h = h / 8 + (h % 8 > 0 ? 1 : 0); // divide, then round up
+
+  // if (sRow + loop_h - 1 > (HEIGHT/8)-1)
+  if (sRow + loop_h > (HEIGHT / 8)) {
+    loop_h = (HEIGHT / 8) - sRow;
+  }
+
+  // prepare variables for loops later so we can compare with 0
+  // instead of comparing two variables
+  loop_h -= start_h;
+
+  sRow += start_h;
+  ofs = (sRow * WIDTH) + x + xOffset;
+  uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset;
+  uint8_t *mask_ofs;
+  if (mask != 0)
+    mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset;
+  uint8_t data;
+
+  uint8_t mul_amt = 1 << yOffset;
+  uint16_t mask_data;
+  uint16_t bitmap_data;
+
+  switch (draw_mode) {
+    case SPRITE_UNMASKED:
+      // we only want to mask the 8 bits of our own sprite, so we can
+      // calculate the mask before the start of the loop
+      mask_data = ~(0xFF * mul_amt);
+      // really if yOffset = 0 you have a faster case here that could be
+      // optimized
+      for (uint8_t a = 0; a < loop_h; a++) {
+        for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
+          bitmap_data = pgm_read_byte(bofs) * mul_amt;
+
+          if (sRow >= 0) {
+            data = sBuffer[ofs];
+            data &= (uint8_t)(mask_data);
+            data |= (uint8_t)(bitmap_data);
+            sBuffer[ofs] = data;
+          }
+          if (yOffset != 0 && sRow < 7) {
+            data = sBuffer[ofs + WIDTH];
+            data &= (*((unsigned char *) (&mask_data) + 1));
+            data |= (*((unsigned char *) (&bitmap_data) + 1));
+            sBuffer[ofs + WIDTH] = data;
+          }
+          ofs++;
+          bofs++;
+        }
+        sRow++;
+        bofs += w - rendered_width;
+        ofs += WIDTH - rendered_width;
+      }
+      break;
+
+    case SPRITE_IS_MASK:
+      for (uint8_t a = 0; a < loop_h; a++) {
+        for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
+          bitmap_data = pgm_read_byte(bofs) * mul_amt;
+          if (sRow >= 0) {
+            sBuffer[ofs] |= (uint8_t)(bitmap_data);
+          }
+          if (yOffset != 0 && sRow < 7) {
+            sBuffer[ofs + WIDTH] |= (*((unsigned char *) (&bitmap_data) + 1));
+          }
+          ofs++;
+          bofs++;
+        }
+        sRow++;
+        bofs += w - rendered_width;
+        ofs += WIDTH - rendered_width;
+      }
+      break;
+
+    case SPRITE_IS_MASK_ERASE:
+      for (uint8_t a = 0; a < loop_h; a++) {
+        for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
+          bitmap_data = pgm_read_byte(bofs) * mul_amt;
+          if (sRow >= 0) {
+            sBuffer[ofs]  &= ~(uint8_t)(bitmap_data);
+          }
+          if (yOffset != 0 && sRow < 7) {
+            sBuffer[ofs + WIDTH] &= ~(*((unsigned char *) (&bitmap_data) + 1));
+          }
+          ofs++;
+          bofs++;
+        }
+        sRow++;
+        bofs += w - rendered_width;
+        ofs += WIDTH - rendered_width;
+      }
+      break;
+
+    case SPRITE_MASKED:
+      for (uint8_t a = 0; a < loop_h; a++) {
+        for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
+          // NOTE: you might think in the yOffset==0 case that this results
+          // in more effort, but in all my testing the compiler was forcing
+          // 16-bit math to happen here anyways, so this isn't actually
+          // compiling to more code than it otherwise would. If the offset
+          // is 0 the high part of the word will just never be used.
+
+          // load data and bit shift
+          // mask needs to be bit flipped
+          mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt);
+          bitmap_data = pgm_read_byte(bofs) * mul_amt;
+
+          if (sRow >= 0) {
+            data = sBuffer[ofs];
+            data &= (uint8_t)(mask_data);
+            data |= (uint8_t)(bitmap_data);
+            sBuffer[ofs] = data;
+          }
+          if (yOffset != 0 && sRow < 7) {
+            data = sBuffer[ofs + WIDTH];
+            data &= (*((unsigned char *) (&mask_data) + 1));
+            data |= (*((unsigned char *) (&bitmap_data) + 1));
+            sBuffer[ofs + WIDTH] = data;
+          }
+          ofs++;
+          mask_ofs++;
+          bofs++;
+        }
+        sRow++;
+        bofs += w - rendered_width;
+        mask_ofs += w - rendered_width;
+        ofs += WIDTH - rendered_width;
+      }
+      break;
+
+
+    case SPRITE_PLUS_MASK:
+      // *2 because we use double the bits (mask + bitmap)
+      bofs = (uint8_t *)(bitmap + ((start_h * w) + xOffset) * 2);
+
+      uint8_t xi = rendered_width; // used for x loop below
+      uint8_t yi = loop_h; // used for y loop below
+
+      asm volatile(
+        "push r28\n" // save Y
+        "push r29\n"
+        "mov r28, %A[buffer_page2_ofs]\n" // Y = buffer page 2 offset
+        "mov r29, %B[buffer_page2_ofs]\n"
+        "loop_y:\n"
+        "loop_x:\n"
+        // load bitmap and mask data
+        "lpm %A[bitmap_data], Z+\n"
+        "lpm %A[mask_data], Z+\n"
+
+        // shift mask and buffer data
+        "tst %[yOffset]\n"
+        "breq skip_shifting\n"
+        "mul %A[bitmap_data], %[mul_amt]\n"
+        "mov %A[bitmap_data], r0\n"
+        "mov %B[bitmap_data], r1\n"
+        "mul %A[mask_data], %[mul_amt]\n"
+        "mov %A[mask_data], r0\n"
+        // "mov %B[mask_data], r1\n"
+
+
+        // SECOND PAGE
+        // if yOffset != 0 && sRow < 7
+        "cpi %[sRow], 7\n"
+        "brge end_second_page\n"
+        // then
+        "ld %[data], Y\n"
+        // "com %B[mask_data]\n" // invert high byte of mask
+        "com r1\n"
+        "and %[data], r1\n" // %B[mask_data]
+        "or %[data], %B[bitmap_data]\n"
+        // update buffer, increment
+        "st Y+, %[data]\n"
+
+        "end_second_page:\n"
+        "skip_shifting:\n"
+
+
+        // FIRST PAGE
+        "ld %[data], %a[buffer_ofs]\n"
+        // if sRow >= 0
+        "tst %[sRow]\n"
+        "brmi end_first_page\n"
+        // then
+        "com %A[mask_data]\n"
+        "and %[data], %A[mask_data]\n"
+        "or %[data], %A[bitmap_data]\n"
+
+        "end_first_page:\n"
+        // update buffer, increment
+        "st %a[buffer_ofs]+, %[data]\n"
+
+
+        // "x_loop_next:\n"
+        "dec %[xi]\n"
+        "brne loop_x\n"
+
+        // increment y
+        "next_loop_y:\n"
+        "dec %[yi]\n"
+        "breq finished\n"
+        "mov %[xi], %[x_count]\n" // reset x counter
+        // sRow++;
+        "inc %[sRow]\n"
+        "clr __zero_reg__\n"
+        // sprite_ofs += (w - rendered_width) * 2;
+        "add %A[sprite_ofs], %A[sprite_ofs_jump]\n"
+        "adc %B[sprite_ofs], __zero_reg__\n"
+        // buffer_ofs += WIDTH - rendered_width;
+        "add %A[buffer_ofs], %A[buffer_ofs_jump]\n"
+        "adc %B[buffer_ofs], __zero_reg__\n"
+        // buffer_ofs_page_2 += WIDTH - rendered_width;
+        "add r28, %A[buffer_ofs_jump]\n"
+        "adc r29, __zero_reg__\n"
+
+        "rjmp loop_y\n"
+        "finished:\n"
+        // put the Y register back in place
+        "pop r29\n"
+        "pop r28\n"
+        "clr __zero_reg__\n" // just in case
+        : [xi] "+&r" (xi),
+        [yi] "+&r" (yi),
+        [sRow] "+&a" (sRow), // CPI requires an upper register
+        [data] "+&r" (data),
+        [mask_data] "+&r" (mask_data),
+        [bitmap_data] "+&r" (bitmap_data)
+        :
+        [x_count] "r" (rendered_width),
+        [y_count] "r" (loop_h),
+        [sprite_ofs] "z" (bofs),
+        [buffer_ofs] "x" (sBuffer+ofs),
+        [buffer_page2_ofs] "r" (sBuffer+ofs+WIDTH), // Y pointer
+        [buffer_ofs_jump] "r" (WIDTH-rendered_width),
+        [sprite_ofs_jump] "r" ((w-rendered_width)*2),
+        [yOffset] "r" (yOffset),
+        [mul_amt] "r" (mul_amt)
+        :
+      );
+      break;
+
+  }
+}
+
+
+/////////////////////////////////
+// Basic Collision by Dreamer3 //
+/////////////////////////////////
+bool Arduboy::collide(Point point, Rect rect)
+{
+  // does point fall within the bounds of rect
+  return ((point.x >= rect.x) && (point.x < rect.x + rect.width) &&
+      (point.y >= rect.y) && (point.y < rect.y + rect.height));
+}
+
+bool Arduboy::collide(Rect rect1, Rect rect2)
+{
+  return !( rect2.x                 >=  rect1.x + rect1.width    ||
+            rect2.x + rect2.width   <=  rect1.x                ||
+            rect2.y                 >=  rect1.y + rect1.height ||
+            rect2.y + rect2.height  <=  rect1.y);
+}