text: #include "ArduboyFX.h"
  #include <wiring.c>
  
  uint16_t FX::programDataPage; // program read only data location in flash memory
  uint16_t FX::programSavePage; // program read and write data location in flash memory
  
  
  uint8_t FX::writeByte(uint8_t data)
  {
    writeByteBeforeWait(data);
    return SPDR;
  }
  
  
  uint8_t FX::readByte()
  {
    return writeByte(0);
  }
  
  
  void FX::begin()
  {
    wakeUp();
  }
  
  
  void FX::begin(uint16_t developmentDataPage)
  {
    if (pgm_read_word(FX_DATA_VECTOR_KEY_POINTER) == FX_VECTOR_KEY_VALUE)
    {
      programDataPage = (pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER) << 8) | pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER + 1);
    }
    else
    {
      programDataPage = developmentDataPage;
    }
    wakeUp();
  }
  
  
  void FX::begin(uint16_t developmentDataPage, uint16_t developmentSavePage)
  {
    if (pgm_read_word(FX_DATA_VECTOR_KEY_POINTER) == FX_VECTOR_KEY_VALUE)
    {
      programDataPage = (pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER) << 8) | pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER + 1);
    }
    else
    {
      programDataPage = developmentDataPage;
    }
    if (pgm_read_word(FX_SAVE_VECTOR_KEY_POINTER) == FX_VECTOR_KEY_VALUE)
    {
      programSavePage = (pgm_read_byte(FX_SAVE_VECTOR_PAGE_POINTER) << 8) | pgm_read_byte(FX_SAVE_VECTOR_PAGE_POINTER + 1);
    }
    else
    {
      programSavePage = developmentSavePage;
    }
    wakeUp();
  }
  
  void FX::readJedecID(JedecID* id)
  {
    enable();
    writeByte(SFC_JEDEC_ID);
    id -> manufacturer = readByte();
    id -> device = readByte();
    id -> size = readByte();
    disable();
  }
  
  bool FX::detect()
  {
    seekCommand(SFC_READ, 0);
    SPDR = 0;
    return readPendingLastUInt16() == 0x4152;
  }
  
  
  void FX::noFXReboot()
    {
      if (!detect())
      {
        do
        {
          if (*(uint8_t *)&timer0_millis & 0x80) bitSet(PORTB, RED_LED_BIT);
          else bitClear(PORTB, RED_LED_BIT);
        } 
        while (bitRead(DOWN_BUTTON_PORTIN, DOWN_BUTTON_BIT)); // wait for DOWN button to enter bootloader
        Arduboy2Core::exitToBootloader();
      }
    }
  
  
  void FX::writeCommand(uint8_t command)
  {
    enable();
    writeByte(command);
    disable();
  }
  
  void FX::wakeUp()
  {
    writeCommand(SFC_RELEASE_POWERDOWN);
  }
  
  
  void FX::sleep()
  {
    writeCommand(SFC_POWERDOWN);
  }
  
  void FX::writeEnable()
  {
    writeCommand(SFC_WRITE_ENABLE);
  }
  
  
  void FX::seekCommand(uint8_t command, uint24_t address)
  {
    enable();
    writeByte(command);
    writeByte(address >> 16);
    writeByte(address >> 8);
    writeByte(address);
  }
  
  
  void FX::seekData(uint24_t address)
  {
   #ifdef ARDUINO_ARCH_AVR
    asm volatile( // assembly optimizer for AVR platform
      "lds  r0, %[page]+0 \n"
      "add  %B[addr], r0  \n"
      "lds  r0, %[page]+1 \n"
      "adc  %C[addr], r0  \n"
      :[addr] "+&r" (address)
      :[page] ""    (&programDataPage)
      :
    );
   #else // C++ version for non AVR platforms
    address += (uint24_t)programDataPage << 8;
   #endif
    seekCommand(SFC_READ, address);
    SPDR = 0;
  }
  
  
  void FX::seekDataArray(uint24_t address, uint8_t index, uint8_t offset, uint8_t elementSize)
  {
   #ifdef ARDUINO_ARCH_AVR
    asm volatile (
      "   mul     %[index], %[size]   \n"
      "   brne    .+2                 \n" //treat size 0 as size 256
      "   mov     r1, %[index]        \n"
      "   clr     r24                 \n" //use as alternative zero reg
      "   add     r0, %[offset]       \n"
      "   adc     r1, r24             \n"
      "   add     %A[address], r0     \n"
      "   adc     %B[address], r1     \n"
      "   adc     %C[address], r24    \n"
      "   clr     r1                  \n"
      : [address] "+r" (address)
      : [index]   "r"  (index),
        [offset]  "r"  (offset),
        [size]    "r"  (elementSize)
      : "r24"
    );
    #else
     address += size ? index * size + offset : index * 256 + offset;
    #endif
    seekData(address);
  }   
  
  
  void FX::seekSave(uint24_t address)
  {
   #ifdef ARDUINO_ARCH_AVR
    asm volatile( // assembly optimizer for AVR platform
      "lds  r0, %[page]+0 \n"
      "add  %B[addr], r0  \n"
      "lds  r0, %[page]+1 \n"
      "adc  %C[addr], r0  \n"
      :[addr] "+&r" (address)
      :[page] ""    (&programSavePage)
      :"r24"
    );
   #else // C++ version for non AVR platforms
    address += (uint24_t)programSavePage << 8;
   #endif
    seekCommand(SFC_READ, address);
    SPDR = 0;
  }
  
  
  uint8_t FX::readPendingUInt8()
  {
   #ifdef ARDUINO_ARCH_AVR
    asm volatile("ArduboyFX_cpp_readPendingUInt8:\n"); // create label for calls in FX::readPendingUInt16
   #endif
    wait();
    uint8_t result = SPDR;
    SPDR = 0;
    return result;
  }
  
  
  uint8_t FX::readPendingLastUInt8()
  {
   #ifdef ARDUINO_ARCH_AVR
    asm volatile("ArduboyFX_cpp_readPendingLastUInt8:\n"); // create label for calls in FX::readPendingUInt16
   #endif
    return readEnd();
  }
  
  
  uint16_t FX::readPendingUInt16()
  {
   #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
    uint16_t result asm("r24"); // we want result to be assigned to r24,r25
    asm volatile
    ( "ArduboyFX_cpp_readPendingUInt16:       \n"
      "call ArduboyFX_cpp_readPendingUInt8    \n"
      "mov  %B[val], r24                      \n"
      "call ArduboyFX_cpp_readPendingUInt8    \n"
      : [val] "=&r" (result)
      : "" (readPendingUInt8)
      :
    );
    return result;
   #else //C++ implementation for non AVR platforms
    return ((uint16_t)readPendingUInt8() << 8) | (uint16_t)readPendingUInt8();
   #endif
  }
  
  
  uint16_t FX::readPendingLastUInt16()
  {
   #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
    uint16_t result asm("r24"); // we want result to be assigned to r24,r25
    asm volatile
    ( "ArduboyFX_cpp_readPendingLastUInt16:    \n"
      "call ArduboyFX_cpp_readPendingUInt8     \n"
      "mov  %B[val], r24                       \n"
      "call ArduboyFX_cpp_readPendingLastUInt8 \n"
      : [val] "=&r" (result)
      : "" (readPendingUInt8),
        "" (readPendingLastUInt8)
      :
    );
    return result;
   #else //C++ implementation for non AVR platforms
    return ((uint16_t)readPendingUint8() << 8) | (uint16_t)readPendingLastUInt8();
   #endif
  }
  
  
  uint24_t FX::readPendingUInt24()
  {
   #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
    uint24_t result asm("r24"); // we want result to be assigned to r24,r25,r26
    asm volatile
    (
      "call ArduboyFX_cpp_readPendingUInt16   \n"
      "mov  %C[val], r25                      \n"
      "mov  %B[val], r24                      \n"
      "call ArduboyFX_cpp_readPendingUInt8    \n"
      : [val] "=&r" (result)
      : "" (readPendingUInt16),
        "" (readPendingUInt8)
      :
    );
    return result;
   #else //C++ implementation for non AVR platforms
    return ((uint24_t)readPendingUInt16() << 8) | readPendingUInt8();
   #endif
  }
  
  
  uint24_t FX::readPendingLastUInt24()
  {
   #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
    uint24_t result asm("r24"); // we want result to be assigned to r24,r25,r26
    asm volatile
    (
      "call ArduboyFX_cpp_readPendingUInt16    \n"
      "mov  %C[val], r25                       \n"
      "mov  %B[val], r24                       \n"
      "call ArduboyFX_cpp_readPendingLastUInt8 \n"
      : [val] "=&r" (result)
      : "" (readPendingUInt16),
        "" (readPendingLastUInt8)
      :
    );
    return result;
   #else //C++ implementation for non AVR platforms
    return ((uint24_t)readPendingUInt16() << 8) | readPendingLastUInt8();
   #endif
  }
  
  
  uint32_t FX::readPendingUInt32()
  {
   #ifdef ARDUINO_ARCH_AVR //Assembly implementation for AVR platform
    uint32_t result asm("r24"); // we want result to be assigned to r24,r25,r26,r27
    asm volatile
    (
      "call ArduboyFX_cpp_readPendingUInt16   \n"
      "movw  %C[val], r24                     \n"
      "call ArduboyFX_cpp_readPendingUInt16   \n"
      : [val] "=&r" (result)
      : "" (readPendingUInt16)
      :
    );
    return result;
   #else //C++ implementation for non AVR platforms
    return ((uint32_t)readPendingUInt16() << 16) | readPendingUInt16();
   #endif
  }
  
  
  uint32_t FX::readPendingLastUInt32()
  {
   #ifdef ARDUINO_ARCH_AVR //Assembly implementation for AVR platform
    uint32_t result asm("r24"); // we want result to be assigned to r24,r25,r26,r27
    asm volatile
    (
      "call ArduboyFX_cpp_readPendingUInt16       \n"
      "movw  %C[val], r24                         \n"
      "call ArduboyFX_cpp_readPendingLastUInt16   \n"
      : [val] "=&r" (result)
      : "" (readPendingUInt16)
      :
    );
    return result;
   #else //C++ implementation for non AVR platforms
    return ((uint32_t)readPendingUInt16() << 16) | readPendingLastUInt16();
   #endif
  }
  
  
  void FX::readBytes(uint8_t* buffer, size_t length)
  {
    for (size_t i = 0; i < length; i++)
    {
      buffer[i] = readPendingUInt8();
    }
  }
  
  
  void FX::readBytesEnd(uint8_t* buffer, size_t length)
  {
    for (size_t i = 0; i <= length; i++)
    {
      if ((i+1) != length)
      buffer[i] = readPendingUInt8();
      else
      {
        buffer[i] = readEnd();
        break;
      }
    }
  }
  
  
  uint8_t FX::readEnd()
  {
    wait();                 // wait for a pending read to complete
    return readUnsafeEnd(); // read last byte and disable flash
  }
  
  
  void FX::readDataBytes(uint24_t address, uint8_t* buffer, size_t length)
  {
    seekData(address);
    readBytesEnd(buffer, length);
  }
  
  
  void FX::readSaveBytes(uint24_t address, uint8_t* buffer, size_t length)
  {
    seekSave(address);
    readBytesEnd(buffer, length);
  }
  
  
  void  FX::eraseSaveBlock(uint16_t page)
  {
    writeEnable();
    seekCommand(SFC_ERASE, (uint24_t)(programSavePage + page) << 8);
    disable();
  }
  
  
  void FX::writeSavePage(uint16_t page, uint8_t* buffer)
  {
    writeEnable();
    seekCommand(SFC_WRITE, (uint24_t)(programSavePage + page) << 8);
    uint8_t i = 0;
    do
    {
      writeByte(buffer[i]);
    }
    while (i++ < 255);
    disable();
  }
  
  void FX::drawBitmap(int16_t x, int16_t y, uint24_t address, uint8_t frame, uint8_t mode)
  {
    // read bitmap dimensions from flash
    seekData(address);
    int16_t width  = readPendingUInt16();
    int16_t height = readPendingLastUInt16();
    // return if the bitmap is completely off screen
    if (x + width <= 0 || x >= WIDTH || y + height <= 0 || y >= HEIGHT) return;
  
    // determine render width
    int16_t skipleft = 0;
    uint8_t renderwidth;
    if (x<0)
    {
      skipleft = -x;
      if (width - skipleft < WIDTH) renderwidth = width - skipleft;
      else renderwidth = WIDTH;
    }
    else
    {
      if (x + width > WIDTH) renderwidth = WIDTH - x;
      else renderwidth = width;
    }
  
    //determine render height
    int16_t skiptop;     // pixel to be skipped at the top
    int8_t renderheight; // in pixels
    if (y < 0)
    {
      skiptop = -y & -8; // optimized -y / 8 * 8
      if (height - skiptop <= HEIGHT) renderheight = height - skiptop;
      else renderheight = HEIGHT + (y & 7);
      skiptop >>= 3;//pixels to displayrows
    }
    else
    {
      skiptop = 0;
      if (y + height > HEIGHT) renderheight = HEIGHT - y;
      else renderheight = height;
    }
    uint24_t offset = (uint24_t)(frame * ((height+7) / 8) + skiptop) * width + skipleft;
    if (mode & dbmMasked)
    {
      offset += offset; // double for masked bitmaps
      width += width;
    }
    address += offset + 4; // skip non rendered pixels, width, height
    int8_t displayrow = (y >> 3) + skiptop;
    uint16_t displayoffset = displayrow * WIDTH + x + skipleft;
    uint8_t yshift = bitShiftLeftUInt8(y); //shift by multiply
  #ifdef ARDUINO_ARCH_AVR
    uint8_t rowmask;
    uint16_t bitmap;
    asm volatile(
      "1: ;render_row:                                \n"
      "   cbi     %[fxport], %[fxbit]                 \n"
      "   ldi     r24, %[cmd]                         \n" // writeByte(SFC_READ);
      "   out     %[spdr], r24                        \n"
      "   lds     r24, %[datapage]+0                  \n" // address + programDataPage;
      "   lds     r25, %[datapage]+1                  \n"
      "   add     r24, %B[address]                    \n"
      "   adc     r25, %C[address]                    \n"
      "   in      r0, %[spsr]                         \n" // wait()
      "   sbrs    r0, %[spif]                         \n"
      "   rjmp    .-6                                 \n"
      "   out     %[spdr], r25                        \n" // writeByte(address >> 16);
      "   in      r0, %[spsr]                         \n" // wait()
      "   sbrs    r0, %[spif]                         \n"
      "   rjmp    .-6                                 \n"
      "   out     %[spdr], r24                        \n" // writeByte(address >> 8);
      "   in      r0, %[spsr]                         \n" // wait()
      "   sbrs    r0, %[spif]                         \n"
      "   rjmp    .-6                                 \n"
      "   out     %[spdr], %A[address]                \n" // writeByte(address);
      "                                               \n"
      "   add     %A[address], %A[width]              \n" // address += width;
      "   adc     %B[address], %B[width]              \n"
      "   adc     %C[address], r1                     \n"
      "   in      r0, %[spsr]                         \n" // wait();
      "   sbrs    r0, %[spif]                         \n"
      "   rjmp    .-6                                 \n"
      "   out     %[spdr], r1                         \n" // SPDR = 0;
      "                                               \n"
      "   lsl     %[mode]                             \n" // 'clear' mode dbfExtraRow by shifting into carry
      "   cpi     %[displayrow], %[lastrow]           \n"
      "   brge    .+4                                 \n" // row >= lastrow, clear carry
      "   sec                                         \n" // row < lastrow set carry
      "   sbrc    %[yshift], 0                        \n" // yshift != 1, don't change carry state
      "   clc                                         \n" // yshift == 1, clear carry
      "   ror     %[mode]                             \n" // carry to mode dbfExtraRow
      "                                               \n"
      "   ldi     %[rowmask], 0x02                    \n" // rowmask = 0xFF >> (height & 7);
      "   sbrs    %[height], 1                        \n"
      "   ldi     %[rowmask], 0x08                    \n"
      "   sbrs    %[height], 2                        \n"
      "   swap    %[rowmask]                          \n"
      "   sbrs    %[height], 0                        \n"
      "   lsl     %[rowmask]                          \n"
      "   dec     %[rowmask]                          \n"
      "   cpi     %[renderheight], 8                  \n" // if (renderheight >= 8) rowmask = 0xFF;
      "   brlt    .+2                                 \n"
      "   ldi     %[rowmask], 0xFF                    \n"
      "                                               \n"
      "   mov     r25, %[renderwidth]                 \n" // for (c < renderwidth)
      "2: ;render_column:                             \n"
      "   in      r0, %[spdr]                         \n" // read bitmap data
      "   out     %[spdr], r1                         \n" // start next read
      "                                               \n"
      "   sbrc    %[mode], %[reverseblack]            \n" // test reverse mode
      "   com     r0                                  \n" // reverse bitmap data
      "   mov     r24, %[rowmask]                     \n" // temporary move rowmask
      "   sbrc    %[mode], %[whiteblack]              \n" // for black and white modes:
      "   mov     r24, r0                             \n" // rowmask = bitmap
      "   sbrc    %[mode], %[black]                   \n" // for black mode:
      "   clr     r0                                  \n" // bitmap = 0
      "   mul     r0, %[yshift]                       \n"
      "   movw    %[bitmap], r0                       \n" // bitmap *= yshift
      "   bst     %[mode], %[masked]                  \n" // if bitmap has no mask:
      "   brtc    3f ;render_mask                     \n" // skip next part
      "                                               \n"
      "   lpm                                         \n" // above code took 11 cycles, wait 7 cycles more for SPI data ready
      "   lpm                                         \n"
      "   clr     r1                                  \n" // restore zero reg
      "                                               \n"
      "   in      r0, %[spdr]                         \n" // read mask data
      "   out     %[spdr],r1                          \n" // start next read
      "   sbrc    %[mode], %[whiteblack]              \n" //
      "3: ;render_mask:                               \n"
      "   mov     r0, r24                             \n" // get mask in r0
      "   mul     r0, %[yshift]                       \n" // mask *= yshift
      ";render_page0:                                 \n"
      "   cpi     %[displayrow], 0                    \n" // skip if displayrow < 0
      "   brlt    4f ;render_page1                    \n"
      "                                               \n"
      "   ld      r24, %a[buffer]                     \n" // do top row or to row half
      "   sbrs    %[mode],%[invert]                   \n" // skip 1st eor for invert mode
      "   eor     %A[bitmap], r24                     \n"
      "   and     %A[bitmap], r0                      \n" // and with mask LSB
      "   eor     %A[bitmap], r24                     \n"
      "   st      %a[buffer], %A[bitmap]              \n"
      "4: ;render_page1:                              \n"
      "   subi    %A[buffer], lo8(-%[displaywidth])   \n"
      "   sbci    %B[buffer], hi8(-%[displaywidth])   \n"
      "   sbrs    %[mode], %[extrarow]                \n" // test if ExtraRow mode:
      "   rjmp    5f ;render_next                     \n" // else skip
      "                                               \n"
      "   ld      r24, %a[buffer]                     \n" // do shifted 2nd half
      "   sbrs    %[mode], %[invert]                  \n" // skip 1st eor for invert mode
      "   eor     %B[bitmap], r24                     \n"
      "   and     %B[bitmap], r1                      \n"// and with mask MSB
      "   eor     %B[bitmap], r24                     \n"
      "   st      %a[buffer], %B[bitmap]              \n"
      "5: ;render_next:                               \n"
      "   clr     r1                                  \n" // restore zero reg
      "   subi    %A[buffer], lo8(%[displaywidth]-1)  \n" 
      "   sbci    %B[buffer], hi8(%[displaywidth]-1)  \n"
      "   dec     r25                                 \n"
      "   brne    2b ;render_column                   \n" // for (c < renderheigt) loop
      "                                               \n"
      "   subi    %A[buffer], lo8(-%[displaywidth])   \n" // buffer += WIDTH - renderwidth
      "   sbci    %B[buffer], hi8(-%[displaywidth])   \n"
      "   sub     %A[buffer], %[renderwidth]          \n"
      "   sbc     %B[buffer], r1                      \n"
      "   subi    %[renderheight], 8                  \n" // reinderheight -= 8
      "   inc     %[displayrow]                       \n" // displayrow++
      "   in      r0, %[spsr]                         \n" // clear SPI status
      "   sbi     %[fxport], %[fxbit]                 \n" // disable external flash
      "   cp      r1, %[renderheight]                 \n" // while (renderheight > 0)
      "   brge    .+2                                 \n"
      "   rjmp    1b ;render_row                      \n" 
     :
      [address]      "+r" (address),
      [mode]         "+r" (mode),
      [rowmask]      "=&d" (rowmask),
      [bitmap]       "=&r" (bitmap),
      [renderheight] "+d" (renderheight),
      [displayrow]   "+d" (displayrow)
     :
      [width]        "r" (width),
      [height]       "r" (height),
      [yshift]       "r" (yshift),
      [renderwidth]  "r" (renderwidth),
      [buffer]       "e" (Arduboy2Base::sBuffer + displayoffset),
      
      [fxport]       "I" (_SFR_IO_ADDR(FX_PORT)),
      [fxbit]        "I" (FX_BIT),
      [cmd]          "I" (SFC_READ),
      [spdr]         "I" (_SFR_IO_ADDR(SPDR)),
      [datapage]     ""  (&programDataPage),
      [spsr]         "I" (_SFR_IO_ADDR(SPSR)),
      [spif]         "I" (SPIF),
      [lastrow]      "I" (HEIGHT / 8 - 1),
      [displaywidth] ""  (WIDTH),
      [reverseblack] "I" (dbfReverseBlack),
      [whiteblack]   "I" (dbfWhiteBlack),
      [black]        "I" (dbfBlack),
      [masked]       "I" (dbfMasked),
      [invert]       "I" (dbfInvert),
      [extrarow]     "I" (dbfExtraRow)
     :
      "r24", "r25"
     );
  #else
    uint8_t lastmask = bitShiftRightMaskUInt8(height); // mask for bottom most pixels
    do
    {
      seekData(address);
      address += width;
      mode &= ~(_BV(dbfExtraRow));
      if (yshift != 1 && displayrow < (HEIGHT / 8 - 1)) mode |= _BV(dbfExtraRow);
      uint8_t rowmask = 0xFF;
      if (renderheight < 8) rowmask = lastmask;
      wait();
      for (uint8_t c = 0; c < renderwidth; c++)
      {
        uint8_t bitmapbyte = readUnsafe();
        if (mode & _BV(dbfReverseBlack)) bitmapbyte ^= 0xFF;
        uint8_t maskbyte = rowmask;
        if (mode & _BV(dbfWhiteBlack)) maskbyte = bitmapbyte;
        if (mode & _BV(dbfBlack)) bitmapbyte = 0;
        uint16_t bitmap = multiplyUInt8(bitmapbyte, yshift);
        if (mode & _BV(dbfMasked))
        {
          wait();
          uint8_t tmp = readUnsafe();
          if ((mode & dbfWhiteBlack) == 0) maskbyte = tmp;
        }
        uint16_t mask = multiplyUInt8(maskbyte, yshift);
        if (displayrow >= 0)
        {
          uint8_t pixels = bitmap;
          uint8_t display = Arduboy2Base::sBuffer[displayoffset];
          if ((mode & _BV(dbfInvert)) == 0) pixels ^= display;
          pixels &= mask;
          pixels ^= display;
          Arduboy2Base::sBuffer[displayoffset] = pixels;
        }
        if (mode & _BV(dbfExtraRow))
        {
          uint8_t display = Arduboy2Base::sBuffer[displayoffset + WIDTH];
          uint8_t pixels = bitmap >> 8;
          if ((mode & dbfInvert) == 0) pixels ^= display;
          pixels &= mask >> 8;
          pixels ^= display;
          Arduboy2Base::sBuffer[displayoffset + WIDTH] = pixels;
        }
        displayoffset++;
      }
      displayoffset += WIDTH - renderwidth;
      displayrow ++;
      renderheight -= 8;
      readEnd();
    } while (renderheight > 0);
  #endif
  }
  
  
  void FX::readDataArray(uint24_t address, uint8_t index, uint8_t offset, uint8_t elementSize, uint8_t* buffer, size_t length)
  {
    seekDataArray(address, index, offset, elementSize);
    readBytesEnd(buffer, length);
  }
  
  
  uint8_t FX::readIndexedUInt8(uint24_t address, uint8_t index)
  {
    seekDataArray(address, index, 0, sizeof(uint8_t));
    return readEnd();
  }
  
  
  uint16_t FX::readIndexedUInt16(uint24_t address, uint8_t index)
  {
    seekDataArray(address, index, 0, sizeof(uint16_t));
    return readPendingLastUInt16();
  }
  
  
  uint24_t FX::readIndexedUInt24(uint24_t address, uint8_t index)
  {
    seekDataArray(address, index, 0, sizeof(uint24_t));
    return readPendingLastUInt24();
  }
  
  
  uint32_t FX::readIndexedUInt32(uint24_t address, uint8_t index)
  {
    seekDataArray(address, index, 0, sizeof(uint24_t));
    return readPendingLastUInt32();
  }
  
  void FX::displayPrefetch(uint24_t address, uint8_t* target, uint16_t len, bool clear)
  {
    seekData(address);
    asm volatile
    ( "dbg:\n"
      "   ldi     r30, lo8(%[sbuf])               \n" // uint8_t* ptr = Arduboy2::sBuffer;
      "   ldi     r31, hi8(%[sbuf])               \n"
      "   ldi     r25, hi8(%[end])                \n" 
      "   in      r0, %[spsr]                     \n" // wait(); //for 1st target data recieved (can't enable OLED mid transfer)
      "   sbrs	r0, %[spif]                     \n"
      "   rjmp	.-6                             \n"
      "   cbi     %[csport], %[csbit]             \n" // enableOLED();
      "1:                                         \n" // while (true) {
      "   ld      r0, Z                   ;2  \   \n" // uint8_t displaydata = *ptr;
      "   in      r24, %[spdr]            ;1  /3  \n" // uint8_t targetdata = SPDR;
      "   out     %[spdr], r0             ;1  \   \n" // SPDR = displaydata;
      "   cpse    %[clear], r1            ;1-2    \n" // if (clear) displaydata = 0;
      "   mov     r0, r1                  ;1      \n"
      "   st      Z+, r0                  ;2      \n" // *ptr++ = displaydata;
      "   subi    %A[len], 1              ;1      \n" // if (--len >= 0) *target++ = targetdata;
      "   sbci    %B[len], 0              ;1      \n"
      "   brmi    3f                      ;1-2    \n" // branch ahead and back to 2 to burn 4 cycles
      "   nop                             ;1      \n"
      "   st      %a[target]+, r24        ;2  /11 \n"
      "2:                                         \n"
      "   cpi     r30, lo8(%[end])        ;1  \   \n" // if (ptr >= Arduboy2::sBuffer + WIDTH * HEIGHT / 8) break;
      "   cpc     r31, r25                ;1      \n"
      "   brcs    1b                      ;1-2/4  \n" // } 
      "3:                                         \n"
      "   brmi    2b                      ;1-2    \n" // branch only when coming from above brmi
      : [target]   "+e" (target),
        [len]      "+d" (len)
      : [sbuf]     ""   (Arduboy2::sBuffer),
        [end]      ""   (Arduboy2::sBuffer + WIDTH * HEIGHT / 8),
        [clear]    "r" (clear),
        [spsr]     "I" (_SFR_IO_ADDR(SPSR)),
        [spif]     "I" (SPIF),
        [spdr]     "I" (_SFR_IO_ADDR(SPDR)),
        [csport]   "I" (_SFR_IO_ADDR(CS_PORT)),
        [csbit]    "I" (CS_BIT)
      : "r24", "r25", "r30", "r31"
    );
    disableOLED();
    disable();
    SPSR;
  }
  