/** * @file Arduboy2.cpp * \brief * The Arduboy2Base and Arduboy2 classes and support objects and definitions. */ #include "Arduboy2.h" #include "ab_logo.c" #include "glcdfont.c" //======================================== //========== class Arduboy2Base ========== //======================================== uint8_t Arduboy2Base::sBuffer[]; Arduboy2Base::Arduboy2Base() { currentButtonState = 0; previousButtonState = 0; // frame management setFrameRate(60); frameCount = -1; nextFrameStart = 0; justRendered = false; // init not necessary, will be reset after first use // lastFrameStart // lastFrameDurationMs } // functions called here should be public so users can create their // own init functions if they need different behavior than `begin` // provides by default void Arduboy2Base::begin() { boot(); // raw hardware blank(); // blank the display flashlight(); // light the RGB LED and screen if UP button is being held. // check for and handle buttons held during start up for system control systemButtons(); audio.begin(); bootLogo(); // wait for all buttons to be released do { delayShort(50); } while (buttonsState()); } void Arduboy2Base::flashlight() { if (!pressed(UP_BUTTON)) { return; } sendLCDCommand(OLED_ALL_PIXELS_ON); // smaller than allPixelsOn() digitalWriteRGB(RGB_ON, RGB_ON, RGB_ON); // prevent the bootloader magic number from being overwritten by timer 0 // when a timer variable overlaps the magic number location, for when // flashlight mode is used for upload problem recovery power_timer0_disable(); while (true) { idle(); } } void Arduboy2Base::systemButtons() { while (pressed(B_BUTTON)) { digitalWriteRGB(BLUE_LED, RGB_ON); // turn on blue LED sysCtrlSound(UP_BUTTON + B_BUTTON, GREEN_LED, 0xff); sysCtrlSound(DOWN_BUTTON + B_BUTTON, RED_LED, 0); delayShort(200); } digitalWriteRGB(BLUE_LED, RGB_OFF); // turn off blue LED } void Arduboy2Base::sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal) { if (pressed(buttons)) { digitalWriteRGB(BLUE_LED, RGB_OFF); // turn off blue LED delayShort(200); digitalWriteRGB(led, RGB_ON); // turn on "acknowledge" LED EEPROM.update(EEPROM_AUDIO_ON_OFF, eeVal); delayShort(500); digitalWriteRGB(led, RGB_OFF); // turn off "acknowledge" LED while (pressed(buttons)) { } // Wait for button release } } void Arduboy2Base::bootLogo() { bootLogoShell(drawLogoBitmap); } void Arduboy2Base::drawLogoBitmap(int16_t y) { drawBitmap(20, y, arduboy_logo, 88, 16); } void Arduboy2Base::bootLogoCompressed() { bootLogoShell(drawLogoCompressed); } void Arduboy2Base::drawLogoCompressed(int16_t y) { drawCompressed(20, y, arduboy_logo_compressed); } void Arduboy2Base::bootLogoSpritesSelfMasked() { bootLogoShell(drawLogoSpritesSelfMasked); } void Arduboy2Base::drawLogoSpritesSelfMasked(int16_t y) { Sprites::drawSelfMasked(20, y, arduboy_logo_sprite, 0); } void Arduboy2Base::bootLogoSpritesOverwrite() { bootLogoShell(drawLogoSpritesOverwrite); } void Arduboy2Base::drawLogoSpritesOverwrite(int16_t y) { Sprites::drawOverwrite(20, y, arduboy_logo_sprite, 0); } // bootLogoText() should be kept in sync with bootLogoShell() // if changes are made to one, equivalent changes should be made to the other void Arduboy2Base::bootLogoShell(void (*drawLogo)(int16_t)) { digitalWriteRGB(RED_LED, RGB_ON); for (int16_t y = -18; y <= 24; y++) { if (pressed(RIGHT_BUTTON)) { digitalWriteRGB(RGB_OFF, RGB_OFF, RGB_OFF); // all LEDs off return; } if (y == -4) { digitalWriteRGB(RED_LED, RGB_OFF); // red LED off digitalWriteRGB(GREEN_LED, RGB_ON); // green LED on } else if (y == 24) { digitalWriteRGB(GREEN_LED, RGB_OFF); // green LED off digitalWriteRGB(BLUE_LED, RGB_ON); // blue LED on } clear(); (*drawLogo)(y); // call the function that actually draws the logo display(); delayShort(27); // longer delay post boot, we put it inside the loop to // save the flash calling clear/delay again outside the loop if (y==-16) { delayShort(250); } } delayShort(700); digitalWriteRGB(BLUE_LED, RGB_OFF); bootLogoExtra(); } // Virtual function overridden by derived class void Arduboy2Base::bootLogoExtra() { } /* Frame management */ void Arduboy2Base::setFrameRate(uint8_t rate) { eachFrameMillis = 1000 / rate; } bool Arduboy2Base::everyXFrames(uint8_t frames) { return frameCount % frames == 0; } bool Arduboy2Base::nextFrame() { unsigned long now = millis(); bool tooSoonForNextFrame = now < nextFrameStart; if (justRendered) { lastFrameDurationMs = now - lastFrameStart; justRendered = false; return false; } else if (tooSoonForNextFrame) { // if we have MORE than 1ms to spare (hence our comparison with 2), // lets sleep for power savings. We don't compare against 1 to avoid // potential rounding errors - say we're actually 0.5 ms away, but a 1 // is returned if we go to sleep we might sleep a full 1ms and then // we'd be running the frame slighly late. So the last 1ms we stay // awake for perfect timing. // This is likely trading power savings for absolute timing precision // and the power savings might be the better goal. At 60 FPS trusting // chance here might actually achieve a "truer" 60 FPS than the 16ms // frame duration we get due to integer math. // We should be woken up by timer0 every 1ms, so it's ok to sleep. if ((uint8_t)(nextFrameStart - now) >= 2) idle(); return false; } // pre-render justRendered = true; lastFrameStart = now; nextFrameStart = now + eachFrameMillis; frameCount++; return true; } bool Arduboy2Base::nextFrameDEV() { bool ret = nextFrame(); if (ret) { if (lastFrameDurationMs > eachFrameMillis) TXLED1; else TXLED0; } return ret; } int Arduboy2Base::cpuLoad() { return lastFrameDurationMs*100 / eachFrameMillis; } void Arduboy2Base::initRandomSeed() { power_adc_enable(); // ADC on // do an ADC read from an unconnected input pin ADCSRA |= _BV(ADSC); // start conversion (ADMUX has been pre-set in boot()) while (bit_is_set(ADCSRA, ADSC)) { } // wait for conversion complete randomSeed(((unsigned long)ADC << 16) + micros()); power_adc_disable(); // ADC off } /* Graphics */ void Arduboy2Base::clear() { fillScreen(BLACK); } // Used by drawPixel to help with left bitshifting since AVR has no // multiple bit shift instruction. We can bit shift from a lookup table // in flash faster than we can calculate the bit shifts on the CPU. PROGMEM const unsigned char bitshift_left[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color) { #ifdef PIXEL_SAFE_MODE if (x < 0 || x > (WIDTH-1) || y < 0 || y > (HEIGHT-1)) { return; } #endif uint16_t row_offset; uint8_t bit; // uint8_t row = (uint8_t)y / 8; // row_offset = (row*WIDTH) + (uint8_t)x; // bit = _BV((uint8_t)y % 8); // the above math can also be rewritten more simply as; // (y * WIDTH/8) & ~0b01111111 + (uint8_t)x; // which is what the below assembler does asm volatile( "mul %[width_offset],%[y]\n" "movw %[row_offset], r0\n" "andi %A[row_offset], 0x80\n" // row_offset &= (~0b01111111); "clr __zero_reg__\n" "add %A[row_offset], %[x]\n" "adc %B[row_offset], __zero_reg__\n" // mask for only 0-7 "andi %[y], 0x07\n" // Z += y "add r30, %[y]\n" "adc r31, __zero_reg__\n" // load correct bitshift from program RAM "lpm %[bit], Z\n" : [row_offset] "=a" (row_offset), // upper register (ANDI) [bit] "=r" (bit) : [width_offset] "r" ((uint8_t)(WIDTH/8)), [y] "a" ((uint8_t)y), // upper register (ANDI) [x] "r" ((uint8_t)x), "z" (bitshift_left) : "r1", "r0"); if (color) { sBuffer[row_offset] |= bit; } else { sBuffer[row_offset] &= ~ bit; } } uint8_t Arduboy2Base::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; } void Arduboy2Base::drawCircle(int16_t x0, int16_t y0, uint8_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= 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 Arduboy2Base::drawCircleHelper (int16_t x0, int16_t y0, uint8_t r, uint8_t corners, 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= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (corners & 0x4) // lower right { drawPixel(x0 + x, y0 + y, color); drawPixel(x0 + y, y0 + x, color); } if (corners & 0x2) // upper right { drawPixel(x0 + x, y0 - y, color); drawPixel(x0 + y, y0 - x, color); } if (corners & 0x8) // lower left { drawPixel(x0 - y, y0 + x, color); drawPixel(x0 - x, y0 + y, color); } if (corners & 0x1) // upper left { drawPixel(x0 - y, y0 - x, color); drawPixel(x0 - x, y0 - y, color); } } } void Arduboy2Base::fillCircle(int16_t x0, int16_t y0, uint8_t r, uint8_t color) { drawFastVLine(x0, y0-r, 2*r+1, color); fillCircleHelper(x0, y0, r, 3, 0, color); } void Arduboy2Base::fillCircleHelper (int16_t x0, int16_t y0, uint8_t r, uint8_t sides, int16_t delta, 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 (sides & 0x1) // right side { drawFastVLine(x0+x, y0-y, 2*y+1+delta, color); drawFastVLine(x0+y, y0-x, 2*x+1+delta, color); } if (sides & 0x2) // left side { drawFastVLine(x0-x, y0-y, 2*y+1+delta, color); drawFastVLine(x0-y, y0-x, 2*x+1+delta, color); } } } void Arduboy2Base::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 Arduboy2Base::drawRect (int16_t x, int16_t y, uint8_t w, uint8_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 Arduboy2Base::drawFastVLine (int16_t x, int16_t y, uint8_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 Arduboy2Base::drawFastHLine (int16_t x, int16_t y, uint8_t w, uint8_t color) { int16_t xEnd; // last x point + 1 // Do y bounds checks if (y < 0 || y >= HEIGHT) return; xEnd = x + w; // Check if the entire line is not on the display if (xEnd <= 0 || x >= WIDTH) return; // Don't start before the left edge if (x < 0) x = 0; // Don't end past the right edge if (xEnd > WIDTH) xEnd = WIDTH; // calculate actual width (even if unchanged) w = xEnd - x; // buffer pointer plus row offset + x offset register uint8_t *pBuf = sBuffer + ((y / 8) * WIDTH) + x; // pixel mask register uint8_t mask = 1 << (y & 7); switch (color) { case WHITE: while (w--) { *pBuf++ |= mask; } break; case BLACK: mask = ~mask; while (w--) { *pBuf++ &= mask; } break; } } void Arduboy2Base::fillRect (int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t color) { // stupidest version - update in subclasses if desired! for (int16_t i=x; i= y1 >= y0) if (y0 > y1) { swap(y0, y1); swap(x0, x1); } if (y1 > y2) { swap(y2, y1); swap(x2, x1); } if (y0 > y1) { swap(y0, y1); swap(x0, x1); } if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing a = b = x0; if(x1 < a) { a = x1; } else if(x1 > b) { b = x1; } if(x2 < a) { a = x2; } else if(x2 > b) { b = x2; } drawFastHLine(a, y0, b-a+1, color); return; } int16_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, dx12 = x2 - x1, dy12 = y2 - y1, sa = 0, sb = 0; // For upper part of triangle, find scanline crossings for segments // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 // is included here (and second loop will be skipped, avoiding a /0 // error there), otherwise scanline y1 is skipped here and handled // in the second loop...which also avoids a /0 error here if y0=y1 // (flat-topped triangle). if (y1 == y2) { last = y1; // Include y1 scanline } else { last = y1-1; // Skip it } for(y = y0; y <= last; y++) { a = x0 + sa / dy01; b = x0 + sb / dy02; sa += dx01; sb += dx02; if(a > b) { swap(a,b); } drawFastHLine(a, y, b-a+1, color); } // For lower part of triangle, find scanline crossings for segments // 0-2 and 1-2. This loop is skipped if y1=y2. sa = dx12 * (y - y1); sb = dx02 * (y - y0); for(; y <= y2; y++) { a = x1 + sa / dy12; b = x0 + sb / dy02; sa += dx12; sb += dx02; if(a > b) { swap(a,b); } drawFastHLine(a, y, b-a+1, color); } } void Arduboy2Base::drawBitmap (int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, uint8_t color) { // no need to draw at all if we're offscreen if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1) return; int yOffset = abs(y) % 8; int sRow = y / 8; if (y < 0) { sRow--; yOffset = 8 - yOffset; } int rows = h/8; if (h%8!=0) rows++; for (int a = 0; a < rows; a++) { int bRow = sRow + a; if (bRow > (HEIGHT/8)-1) break; if (bRow > -2) { for (int iCol = 0; iCol (WIDTH-1)) break; if (iCol + x >= 0) { if (bRow >= 0) { if (color == WHITE) sBuffer[(bRow*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; else if (color == BLACK) sBuffer[(bRow*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) << yOffset); else sBuffer[(bRow*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; } if (yOffset && bRow<(HEIGHT/8)-1 && bRow > -2) { if (color == WHITE) sBuffer[((bRow+1)*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset); else if (color == BLACK) sBuffer[((bRow+1)*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset)); else sBuffer[((bRow+1)*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset); } } } } } } void Arduboy2Base::drawSlowXYBitmap (int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, uint8_t color) { // no need to draw at all of we're offscreen if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1) return; int16_t xi, yi, byteWidth = (w + 7) / 8; for(yi = 0; yi < h; yi++) { for(xi = 0; xi < w; xi++ ) { if(pgm_read_byte(bitmap + yi * byteWidth + xi / 8) & (128 >> (xi & 7))) { drawPixel(x + xi, y + yi, color); } } } } typedef struct CSESSION { int byte; int bit; const uint8_t *src; int src_pos; } CSESSION; static CSESSION cs; static int getval(int bits) { int val = 0; int i; for (i = 0; i < bits; i++) { if (cs.bit == 0x100) { cs.bit = 0x1; cs.byte = pgm_read_byte(&cs.src[cs.src_pos]); cs.src_pos ++; } if (cs.byte & cs.bit) val += (1 << i); cs.bit <<= 1; } return val; } void Arduboy2Base::drawCompressed(int16_t sx, int16_t sy, const uint8_t *bitmap, uint8_t color) { int bl, len; int col; int i; int a, iCol; int byte = 0; int bit = 0; int w, h; // set up decompress state cs.src = bitmap; cs.bit = 0x100; cs.byte = 0; cs.src_pos = 0; // read header w = getval(8) + 1; h = getval(8) + 1; col = getval(1); // starting colour // no need to draw at all if we're offscreen if (sx + w < 0 || sx > WIDTH - 1 || sy + h < 0 || sy > HEIGHT - 1) return; // sy = sy - (frame*h); int yOffset = abs(sy) % 8; int sRow = sy / 8; if (sy < 0) { sRow--; yOffset = 8 - yOffset; } int rows = h / 8; if (h % 8 != 0) rows++; a = 0; // +(frame*rows); iCol = 0; byte = 0; bit = 1; while (a < rows) // + (frame*rows)) { bl = 1; while (!getval(1)) bl += 2; len = getval(bl) + 1; // span length // draw the span for (i = 0; i < len; i++) { if (col) byte |= bit; bit <<= 1; if (bit == 0x100) // reached end of byte { // draw int bRow = sRow + a; //if (byte) // possible optimisation if (bRow <= (HEIGHT / 8) - 1) if (bRow > -2) if (iCol + sx <= (WIDTH - 1)) if (iCol + sx >= 0) { if (bRow >= 0) { if (color) sBuffer[(bRow * WIDTH) + sx + iCol] |= byte << yOffset; else sBuffer[(bRow * WIDTH) + sx + iCol] &= ~(byte << yOffset); } if (yOffset && bRow < (HEIGHT / 8) - 1 && bRow > -2) { if (color) sBuffer[((bRow + 1)*WIDTH) + sx + iCol] |= byte >> (8 - yOffset); else sBuffer[((bRow + 1)*WIDTH) + sx + iCol] &= ~(byte >> (8 - yOffset)); } } // iterate iCol ++; if (iCol >= w) { iCol = 0; a ++; } // reset byte byte = 0; bit = 1; } } col = 1 - col; // toggle colour for next span } } void Arduboy2Base::display() { paintScreen(sBuffer); } void Arduboy2Base::display(bool clear) { paintScreen(sBuffer, clear); } uint8_t* Arduboy2Base::getBuffer() { return sBuffer; } bool Arduboy2Base::pressed(uint8_t buttons) { return (buttonsState() & buttons) == buttons; } bool Arduboy2Base::notPressed(uint8_t buttons) { return (buttonsState() & buttons) == 0; } void Arduboy2Base::pollButtons() { previousButtonState = currentButtonState; currentButtonState = buttonsState(); } bool Arduboy2Base::justPressed(uint8_t button) { return (!(previousButtonState & button) && (currentButtonState & button)); } bool Arduboy2Base::justReleased(uint8_t button) { return ((previousButtonState & button) && !(currentButtonState & button)); } bool Arduboy2Base::collide(Point point, Rect rect) { return ((point.x >= rect.x) && (point.x < rect.x + rect.width) && (point.y >= rect.y) && (point.y < rect.y + rect.height)); } bool Arduboy2Base::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); } uint16_t Arduboy2Base::readUnitID() { return EEPROM.read(EEPROM_UNIT_ID) | (((uint16_t)(EEPROM.read(EEPROM_UNIT_ID + 1))) << 8); } void Arduboy2Base::writeUnitID(uint16_t id) { EEPROM.update(EEPROM_UNIT_ID, (uint8_t)(id & 0xff)); EEPROM.update(EEPROM_UNIT_ID + 1, (uint8_t)(id >> 8)); } uint8_t Arduboy2Base::readUnitName(char* name) { char val; uint8_t dest; uint8_t src = EEPROM_UNIT_NAME; for (dest = 0; dest < ARDUBOY_UNIT_NAME_LEN; dest++) { val = EEPROM.read(src); name[dest] = val; src++; if (val == 0x00 || (byte)val == 0xFF) { break; } } name[dest] = 0x00; return dest; } void Arduboy2Base::writeUnitName(char* name) { bool done = false; uint8_t dest = EEPROM_UNIT_NAME; for (uint8_t src = 0; src < ARDUBOY_UNIT_NAME_LEN; src++) { if (name[src] == 0x00) { done = true; } // write character or 0 pad if finished EEPROM.update(dest, done ? 0x00 : name[src]); dest++; } } bool Arduboy2Base::readShowUnitNameFlag() { return (EEPROM.read(EEPROM_SYS_FLAGS) & SYS_FLAG_UNAME_MASK); } void Arduboy2Base::writeShowUnitNameFlag(bool val) { uint8_t flags = EEPROM.read(EEPROM_SYS_FLAGS); bitWrite(flags, SYS_FLAG_UNAME, val); EEPROM.update(EEPROM_SYS_FLAGS, flags); } void Arduboy2Base::swap(int16_t& a, int16_t& b) { int16_t temp = a; a = b; b = temp; } //==================================== //========== class Arduboy2 ========== //==================================== Arduboy2::Arduboy2() { cursor_x = 0; cursor_y = 0; textColor = 1; textBackground = 0; textSize = 1; textWrap = 0; } // bootLogoText() should be kept in sync with bootLogoShell() // if changes are made to one, equivalent changes should be made to the other void Arduboy2::bootLogoText() { digitalWriteRGB(RED_LED, RGB_ON); textSize = 2; for (int8_t y = -18; y <= 24; y++) { if (pressed(RIGHT_BUTTON)) { digitalWriteRGB(RGB_OFF, RGB_OFF, RGB_OFF); // all LEDs off textSize = 1; return; } if (y == -4) { digitalWriteRGB(RED_LED, RGB_OFF); // red LED off digitalWriteRGB(GREEN_LED, RGB_ON); // green LED on } else if (y == 24) { digitalWriteRGB(GREEN_LED, RGB_OFF); // green LED off digitalWriteRGB(BLUE_LED, RGB_ON); // blue LED on } clear(); cursor_x = 23; cursor_y = y; print("ARDUBOY"); display(); delayShort(27); // longer delay post boot, we put it inside the loop to // save the flash calling clear/delay again outside the loop if (y==-16) { delayShort(250); } } delayShort(700); digitalWriteRGB(BLUE_LED, RGB_OFF); textSize = 1; bootLogoExtra(); } void Arduboy2::bootLogoExtra() { uint8_t c; if (!readShowUnitNameFlag()) { return; } c = EEPROM.read(EEPROM_UNIT_NAME); if (c != 0xFF && c != 0x00) { uint8_t i = EEPROM_UNIT_NAME; cursor_x = 50; cursor_y = 56; do { write(c); c = EEPROM.read(++i); } while (i < EEPROM_UNIT_NAME + ARDUBOY_UNIT_NAME_LEN); display(); delayShort(1000); } } size_t Arduboy2::write(uint8_t c) { if (c == '\n') { cursor_y += textSize * 8; cursor_x = 0; } else if (c == '\r') { // skip em } else { drawChar(cursor_x, cursor_y, c, textColor, textBackground, textSize); cursor_x += textSize * 6; if (textWrap && (cursor_x > (WIDTH - textSize * 6))) { // calling ourselves recursively for 'newline' is // 12 bytes smaller than doing the same math here write('\n'); } } return 1; } void Arduboy2::drawChar (int16_t x, int16_t y, unsigned char c, uint8_t color, uint8_t bg, uint8_t size) { uint8_t line; bool draw_background = bg != color; const unsigned char* bitmap = font + c * 5; if ((x >= WIDTH) || // Clip right (y >= HEIGHT) || // Clip bottom ((x + 5 * size - 1) < 0) || // Clip left ((y + 8 * size - 1) < 0) // Clip top ) { return; } for (uint8_t i = 0; i < 6; i++ ) { line = pgm_read_byte(bitmap++); if (i == 5) { line = 0x0; } for (uint8_t j = 0; j < 8; j++) { uint8_t draw_color = (line & 0x1) ? color : bg; if (draw_color || draw_background) { for (uint8_t a = 0; a < size; a++ ) { for (uint8_t b = 0; b < size; b++ ) { drawPixel(x + (i * size) + a, y + (j * size) + b, draw_color); } } } line >>= 1; } } } void Arduboy2::setCursor(int16_t x, int16_t y) { cursor_x = x; cursor_y = y; } int16_t Arduboy2::getCursorX() { return cursor_x; } int16_t Arduboy2::getCursorY() { return cursor_y; } void Arduboy2::setTextColor(uint8_t color) { textColor = color; } void Arduboy2::setTextBackground(uint8_t bg) { textBackground = bg; } void Arduboy2::setTextSize(uint8_t s) { // size must always be 1 or higher textSize = max(1, s); } void Arduboy2::setTextWrap(bool w) { textWrap = w; } void Arduboy2::clear() { Arduboy2Base::clear(); cursor_x = cursor_y = 0; }