diff --git a/board-package-source/libraries/Arduboy/src/core/core.cpp b/board-package-source/libraries/Arduboy/src/core/core.cpp index 6f28f3f..863c36e 100644 --- a/board-package-source/libraries/Arduboy/src/core/core.cpp +++ b/board-package-source/libraries/Arduboy/src/core/core.cpp @@ -14,11 +14,17 @@ const uint8_t PROGMEM pinBootProgram[] = { PIN_DOWN_BUTTON, INPUT_PULLUP, PIN_A_BUTTON, INPUT_PULLUP, PIN_B_BUTTON, INPUT_PULLUP, - + +#if (defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)) + //I2C + SDA, INPUT, + SCL, INPUT, +#else // OLED SPI DC, OUTPUT, CS, OUTPUT, RST, OUTPUT, +#endif 0 }; @@ -145,12 +151,13 @@ const uint8_t PROGMEM lcdBootProgram[] = { // set display mode = horizontal addressing mode (0x00) 0x20, 0x00, - + #if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) // set col address range - // 0x21, 0x00, COLUMN_ADDRESS_END, + 0x21, 0x00, COLUMN_ADDRESS_END, // set page address range - // 0x22, 0x00, PAGE_ADDRESS_END + 0x22, 0x00, PAGE_ADDRESS_END + #endif #endif }; @@ -198,16 +205,30 @@ void ArduboyCore::bootPins() if (pin==0) break; pinMode(pin, mode); } - +#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + I2C_SCL_LOW(); + I2C_SDA_LOW(); +#else digitalWrite(RST, HIGH); delay(1); // VDD (3.3V) goes high at start, lets just chill for a ms digitalWrite(RST, LOW); // bring reset low delay(10); // wait 10ms digitalWrite(RST, HIGH); // bring out of reset +#endif } void ArduboyCore::bootLCD() { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_CMD); + for (uint8_t i = 0; i < sizeof(lcdBootProgram); i++) + i2c_sendByte(pgm_read_byte(lcdBootProgram + i)); + i2c_stop(); + i2c_start(SSD1306_I2C_DATA); + for (uint16_t i = 0; i < WIDTH * HEIGHT / 8; i++) + i2c_sendByte(0); + i2c_stop(); +#else // setup the ports we need to talk to the OLED //csport = portOutputRegister(digitalPinToPort(CS)); *portOutputRegister(digitalPinToPort(CS)) &= ~cspinmask; @@ -227,6 +248,7 @@ void ArduboyCore::bootLCD() SPI.transfer(pgm_read_byte(lcdBootProgram + i)); } LCDDataMode(); +#endif } void ArduboyCore::LCDDataMode() @@ -242,7 +264,49 @@ void ArduboyCore::LCDCommandMode() // *csport &= ~cspinmask; CS set once at bootLCD } +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) +void ArduboyCore::i2c_start(uint8_t mode) +{ + I2C_SDA_LOW(); // disable posible internal pullup, ensure SDA low on enabling output + I2C_SDA_AS_OUTPUT(); // SDA low before SCL for start condition + I2C_SCL_LOW(); + I2C_SCL_AS_OUTPUT(); + i2c_sendByte(SSD1306_I2C_ADDR << 1); + i2c_sendByte(mode); +} +void ArduboyCore::i2c_sendByte(uint8_t byte) +{ + uint8_t sda_clr = I2C_PORT & ~((1 << I2C_SDA) | (1 << I2C_SCL)); + uint8_t scl = 1 << I2C_SCL; + uint8_t sda = 1 << I2C_SDA; + uint8_t scl_bit = I2C_SCL; + asm volatile ( + " sec \n" // set carry for 8 shift counts + " rol %[byte] \n" // shift a bit out and count at the same time + "1: \n" + " out %[port], %[sda0] \n" // preemtively clear SDA + " brcc 2f \n" // skip if dealing with 0 bit + " out %[pin], %[sda] \n" + "2: \n" + " out %[pin], %[scl] \n" // toggle SCL on + " lsl %[byte] \n" // next bit to carry (moved here for 1 extra cycle delay) + " out %[pin], %[scl] \n" // toggle SCL off + " brne 1b \n" // initial set carry will be shifted out after 8 loops setting Z flag + " \n" + " out %[port],%[sda0] \n" // clear SDA for ACK + " sbi %[port], %[sclb] \n" // set SCL (extends ACK bit by 1 cycle) + " cbi %[port], %[sclb] \n" // clear SCL (extends SCL high by 1 cycle) + :[byte] "+r" (byte) + :[port] "i" (_SFR_IO_ADDR(I2C_PORT)), + [pin] "i" (_SFR_IO_ADDR(I2C_PIN)), + [sda0] "r" (sda_clr), + [scl] "r" (scl), + [sda] "r" (sda), + [sclb] "i" (scl_bit) + ); +} +#endif void ArduboyCore::safeMode() { @@ -283,12 +347,23 @@ uint8_t ArduboyCore::height() { return HEIGHT; } void ArduboyCore::paint8Pixels(uint8_t pixels) { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_DATA); + i2c_sendByte(pixels); + i2c_stop(); +#else SPI.transfer(pixels); +#endif } void ArduboyCore::paintScreen(const unsigned char *image) { -#if defined(OLED_SH1106) || defined(LCD_ST7565) +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_DATA); + for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++) + i2c_sendByte(pgm_read_byte(image+i)); + i2c_stop(); +#elif defined(OLED_SH1106) || defined(LCD_ST7565) for (uint8_t i = 0; i < HEIGHT / 8; i++) { LCDCommandMode(); @@ -363,7 +438,117 @@ void ArduboyCore::paintScreen(const unsigned char *image) // will be used by any buffer based subclass void ArduboyCore::paintScreen(unsigned char image[]) { -#if defined(OLED_SH1106) || defined(LCD_ST7565) +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + uint16_t length = WIDTH * HEIGHT / 8; + uint8_t sda_clr = I2C_PORT & ~((1 << I2C_SDA) | (1 << I2C_SCL)); + uint8_t scl = 1 << I2C_SCL; + uint8_t sda = 1 << I2C_SDA; + uint8_t scl_bit = I2C_SCL; + i2c_start(SSD1306_I2C_DATA); + #if defined (OLED_SSD1306_I2C) + //bitbanging I2C ~2Mbps (8 cycles per bit / 78 cycles per byte) + asm volatile ( + "1: \n" + " ld r0, %a[ptr]+ \n" // fetch display byte from buffer + " sec \n" // set carry for 8 shift counts + " rol r0 \n" // shift a bit out and count at the same time + "2: \n" + " out %[port], %[sda0] \n" // preemtively clear SDA + " brcc 3f \n" // skip if dealing with 0 bit + " out %[pin], %[sda] \n" + "3: \n" + " out %[pin], %[scl] \n" // toggle SCL on + " lsl r0 \n" // next bit to carry (moved here for 1 extra cycle delay) + " out %[pin], %[scl] \n" // toggle SCL off + " brne 2b \n" // initial set carry will be shifted out after 8 loops setting Z flag + " \n" + " out %[port], %[sda0] \n" // clear SDA for ACK + " subi %A[len], 1 \n" // len-- part1 (moved here for 1 cycle delay) + " out %[pin], %[scl] \n" // set SCL (2 cycles required) + " sbci %B[len], 0 \n" // len-- part2 (moved here for 1 cycle delay) + " out %[pin], %[scl] \n" // clear SCL (2 cycles required) + " brne 1b \n" + :[ptr] "+e" (image), + [len] "+d" (length) + :[port] "i" (_SFR_IO_ADDR(I2C_PORT)), + [pin] "i" (_SFR_IO_ADDR(I2C_PIN)), + [sda0] "r" (sda_clr), + [scl] "r" (scl), + [sda] "r" (sda) + ); + #else + //bitbanging I2C @ 2.66Mbps (6 cycles per bit / 56 cycles per byte) + asm volatile ( + " ld r0, %a[ptr]+ \n" // fetch display byte from buffer + "1: \n" + " sbrc r0, 7 \n" // MSB first comes first + " out %[pin], %[sda] \n" // toggle SDA on for 1-bit + " out %[pin], %[scl] \n" // toggle SCL high + " cbi %[port], %[sclb] \n" // set SCL low + " out %[port], %[sda0] \n" // preemptively clear SDA for next bit + " \n" + " sbrc r0, 6 \n" // repeat of above but for bit 6 + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 5 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 4 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 3 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 2 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 1 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port],%[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 0 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " subi %A[len], 1 \n" // length-- part 1 (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // + " out %[port], %[sda0] \n" // SDA low for ACK + + " sbci %B[len], 0 \n" // length-- part 2 (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // // clock ACK bit + " ld r0, %a[ptr]+ \n" // fetch next buffer byte (also serves as clock delay) + " out %[pin], %[scl] \n" // + " brne 1b \n" // length != 0 do next byte + :[ptr] "+e" (image), + [len] "+d" (length) + :[port] "i" (_SFR_IO_ADDR(I2C_PORT)), + [pin] "i" (_SFR_IO_ADDR(I2C_PIN)), + [sda0] "r" (sda_clr), + [scl] "r" (scl), + [sda] "r" (sda), + [sclb] "i" (scl_bit) + :"r24" + ); + #endif + i2c_stop(); + +#elif defined(OLED_SH1106) || defined(LCD_ST7565) for (uint8_t i = 0; i < HEIGHT / 8; i++) { LCDCommandMode(); @@ -498,21 +683,34 @@ void ArduboyCore::paintScreen(unsigned char image[]) void ArduboyCore::blank() { -#ifdef OLED_SH1106 - for (int i = 0; i < (HEIGHT * 132) / 8; i++) -#elif defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128) - for (int i = 0; i < (HEIGHT * WIDTH) / 2; i++) -#else //OLED SSD1306 and compatibles +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_DATA); for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++) -#endif + i2c_sendByte(0); + i2c_stop(); +#else + #if defined (OLED_SH1106) + for (int i = 0; i < (HEIGHT * 132) / 8; i++) + #elif defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128) + for (int i = 0; i < (HEIGHT * WIDTH) / 2; i++) + #else //OLED SSD1306 and compatibles + for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++) + #endif SPI.transfer(0x00); +#endif } void ArduboyCore::sendLCDCommand(uint8_t command) { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_CMD); + i2c_sendByte(command); + i2c_stop(); +#else LCDCommandMode(); SPI.transfer(command); LCDDataMode(); +#endif } // invert the display or set to normal diff --git a/board-package-source/libraries/Arduboy/src/core/core.h b/board-package-source/libraries/Arduboy/src/core/core.h index 2466959..1f6c66c 100644 --- a/board-package-source/libraries/Arduboy/src/core/core.h +++ b/board-package-source/libraries/Arduboy/src/core/core.h @@ -43,6 +43,38 @@ #define RST 6 #endif #define DC 4 +#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + //bitbanged I2C pins + #define I2C_PORT PORTD + #define I2C_DDR DDRD + #define I2C_PIN PIND + #ifdef AB_ALTERNATE_WIRING + #define SCL 1 + #define I2C_SCL PORTD3 + #else + #define SCL 6 + #define I2C_SCL PORTD7 + #endif + #define SDA 4 + #define I2C_SDA PORTD4 + + //port states + #define I2C_SDA_HIGH() I2C_PORT |= (1 << I2C_SDA) + #define I2C_SCL_HIGH() I2C_PORT |= (1 << I2C_SCL) + #define I2C_SDA_LOW() I2C_PORT &= ~(1 << I2C_SDA) + #define I2C_SCL_LOW() I2C_PORT &= ~(1 << I2C_SCL) + + //port directions + #define I2C_SDA_AS_INPUT() I2C_DDR &= ~(1 << I2C_SDA) + #define I2C_SCL_AS_INPUT() I2C_DDR &= ~(1 << I2C_SCL) + #define I2C_SDA_AS_OUTPUT() I2C_DDR |= (1 << I2C_SDA) + #define I2C_SCL_AS_OUTPUT() I2C_DDR |= (1 << I2C_SCL) + + // display address, commands + #define SSD1306_I2C_ADDR 0x3c //0x3c:default, 0x3d: alternative) + #define SSD1306_I2C_CMD 0x00 + #define SSD1306_I2C_DATA 0x40 +#endif #define RED_LED 10 #if defined AB_ALTERNATE_WIRING //Pro Micro Alternative GREEN LED pin @@ -193,9 +225,22 @@ public: */ void static LCDCommandMode(); +#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + void static i2c_start(uint8_t mode); + + void static inline i2c_stop() __attribute__((always_inline)) + { + // SDA and SCL both are already low, from writing ACK bit no need to change state + I2C_SDA_AS_INPUT(); // switch to input so SDA is pulled up externally first for stop condition + I2C_SCL_AS_INPUT(); // pull up SCL externally + } + + void static i2c_sendByte(uint8_t byte); +#endif + uint8_t static width(); //< return display width uint8_t static height(); // < return display height - + /// get current state of all buttons (bitmask) /** * Bit mask that is returned: diff --git a/board-package-source/libraries/Arduboy2/keywords.txt b/board-package-source/libraries/Arduboy2/keywords.txt index b5caf94..87f3f17 100644 --- a/board-package-source/libraries/Arduboy2/keywords.txt +++ b/board-package-source/libraries/Arduboy2/keywords.txt @@ -32,6 +32,8 @@ bootLogoSpritesOverwrite KEYWORD2 bootLogoSpritesSelfMasked KEYWORD2 bootLogoText KEYWORD2 buttonsState KEYWORD2 +checkBatteryState KEYWORD2 +checkBatteryStateLED KEYWORD2 clear KEYWORD2 collide KEYWORD2 cpuLoad KEYWORD2 @@ -165,3 +167,7 @@ RGB_ON LITERAL1 ARDUBOY_NO_USB LITERAL1 +BATTERY_STATE_LOW LITERAL1 +BATTERY_STATE_NORMAL LITERAL1 +BATTERY_STATE_INVALID LITERAL1 +FLASH_LED LITERAL1 \ No newline at end of file diff --git a/board-package-source/libraries/Arduboy2/library.json b/board-package-source/libraries/Arduboy2/library.json index 3a4e719..fe44581 100644 --- a/board-package-source/libraries/Arduboy2/library.json +++ b/board-package-source/libraries/Arduboy2/library.json @@ -7,7 +7,7 @@ "type": "git", "url": "https://github.com/MLXXXp/Arduboy2.git" }, - "version": "5.1.0", + "version": "5.2.1", "export": { "exclude": "extras" diff --git a/board-package-source/libraries/Arduboy2/library.properties b/board-package-source/libraries/Arduboy2/library.properties index b44b753..24ab125 100644 --- a/board-package-source/libraries/Arduboy2/library.properties +++ b/board-package-source/libraries/Arduboy2/library.properties @@ -1,5 +1,5 @@ name=Arduboy2 -version=5.1.0 +version=5.2.1 author=Chris J. Martinez, Kevin Bates, Josh Goebel, Scott Allen, Ross O. Shoger maintainer=Scott Allen sentence=An alternative library for use with the Arduboy game system. diff --git a/board-package-source/libraries/Arduboy2/src/Arduboy2.cpp b/board-package-source/libraries/Arduboy2/src/Arduboy2.cpp index 25bd2c4..84dc5f7 100644 --- a/board-package-source/libraries/Arduboy2/src/Arduboy2.cpp +++ b/board-package-source/libraries/Arduboy2/src/Arduboy2.cpp @@ -8,12 +8,32 @@ #include "ab_logo.c" #include "glcdfont.c" +//================================ +//========== class Rect ========== +//================================ + +Rect::Rect(int16_t x, int16_t y, uint8_t width, uint8_t height) + : x(x), y(y), width(width), height(height) +{ +} + +//================================= +//========== class Point ========== +//================================= + +Point::Point(int16_t x, int16_t y) + : x(x), y(y) +{ +} + //======================================== //========== class Arduboy2Base ========== //======================================== uint8_t Arduboy2Base::sBuffer[]; +uint8_t Arduboy2Base::batteryLow = EEPROM.read(EEPROM_BATTERY_LOW); //Low battery bandgap value - 192 + Arduboy2Base::Arduboy2Base() { currentButtonState = 0; @@ -31,8 +51,9 @@ void Arduboy2Base::begin() { boot(); // raw hardware - display(CLEAR_BUFFER); //sBuffer is global, so cleared automatically) - + //using CLEAR_BUFFER so a sketch can be optimized when using CLEAR_BUFFER exclusivly + display(CLEAR_BUFFER); //sBuffer is global, so cleared automatically. + 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 @@ -323,14 +344,6 @@ 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. -const uint8_t bitshift_left[] PROGMEM = { - _BV(0), _BV(1), _BV(2), _BV(3), _BV(4), _BV(5), _BV(6), _BV(7) -}; - void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color) { #ifdef PIXEL_SAFE_MODE @@ -343,58 +356,57 @@ void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color) 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; - // row_offset = (y * WIDTH/8) & ~0b01111111 + (uint8_t)x; - // which is what the below assembler does - - // local variable for the bitshift_left array pointer, - // which can be declared a read-write operand - const uint8_t* bsl = bitshift_left; - asm volatile ( -#if WIDTH == 128 - "mul %[width_offset], %A[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" - // mask for only 0-7 - "andi %A[y], 0x07\n" -#else - "mov r0, %A[y] \n" - "andi %A[y], 0x07 \n" // mask for only 0-7 - "eor r0, %A[y] \n" // == and 0xF8 - "mul %[width_offset], r0 \n" + // bit = 1 << (y & 7) + "ldi %[bit], 1 \n" //bit = 1; + "sbrc %[y], 1 \n" //if (y & _BV(1)) bit = 4; + "ldi %[bit], 4 \n" + "sbrc %[y], 0 \n" //if (y & _BV(0)) bit = bit << 1; + "lsl %[bit] \n" + "sbrc %[y], 2 \n" //if (y & _BV(2)) bit = (bit << 4) | (bit >> 4); + "swap %[bit] \n" + //row_offset = y / 8 * WIDTH + x; + "andi %A[y], 0xf8 \n" //row_offset = (y & 0xF8) * WIDTH / 8 + "mul %[width_offset], %A[y] \n" "movw %[row_offset], r0 \n" "clr __zero_reg__ \n" - "add %A[row_offset], %[x] \n" - "adc %B[row_offset], __zero_reg__ \n" + "add %A[row_offset], %[x] \n" //row_offset += x +#if WIDTH != 128 + "adc %B[row_offset], __zero_reg__ \n" // only non 128 width can overflow #endif - // Z += y - "add r30, %A[y] \n" - "adc r31, __zero_reg__ \n" - // load correct bitshift from program RAM - "lpm %[bit], Z \n" - : [row_offset] "=&x" (row_offset), // upper register (ANDI) - [bit] "=r" (bit), - [y] "+d" (y), // upper register (ANDI), must be writable - "+z" (bsl) // is modified to point to the proper shift array element - : [width_offset] "r" ((uint8_t)(WIDTH/8)), - [x] "r" ((uint8_t)x) + : [row_offset] "=&x" (row_offset), // upper register (ANDI) + [bit] "=&d" (bit), // upper register (LDI) + [y] "+d" (y) // upper register (ANDI), must be writable + : [width_offset] "r" ((uint8_t)(WIDTH/8)), + [x] "r" ((uint8_t)x) : ); - - if (color) { - sBuffer[row_offset] |= bit; - } else { - sBuffer[row_offset] &= ~ bit; - } + uint8_t data = sBuffer[row_offset] | bit; + if (!(color & _BV(0))) data ^= bit; + sBuffer[row_offset] = data; } +#if 0 +// For reference, this is the C++ equivalent +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; + + bit = 1 << (y & 7); + row_offset = (y & 0xF8) * WIDTH / 8 + x; + uint8_t data = sBuffer[row_offset] | bit; + if (!color) data ^= bit; + sBuffer[row_offset] = data; +} +#endif uint8_t Arduboy2Base::getPixel(uint8_t x, uint8_t y) { @@ -685,7 +697,7 @@ void Arduboy2Base::fillScreen(uint8_t color) "ldi %[color], 0xFF\n" // counter = WIDTH * HEIGHT / 8 / 8 "ldi r24, %[cnt]\n" - "loopto:\n" + "1:\n" // (4x/8x) store color into screen buffer, // then increment buffer position "st Z+, %[color]\n" @@ -701,7 +713,7 @@ void Arduboy2Base::fillScreen(uint8_t color) // decrease counter "subi r24, 1\n" // repeat for 256, 144 or 192 loops depending on screen resolution - "brcc loopto\n" + "brcc 1b\n" : [color] "+d" (color), "+z" (bPtr) #if defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128) || defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) @@ -858,14 +870,9 @@ void Arduboy2Base::drawBitmap 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++; + int8_t yOffset = y & 7; + int8_t sRow = y; + uint8_t rows = h >> 3; for (int a = 0; a < rows; a++) { int bRow = sRow + a; if (bRow > (HEIGHT/8)-1) break; @@ -873,21 +880,22 @@ void Arduboy2Base::drawBitmap for (int iCol = 0; iCol (WIDTH-1)) break; if (iCol + x >= 0) { + uint16_t data = pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; if (bRow >= 0) { if (color == WHITE) - sBuffer[(bRow*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; + sBuffer[(bRow*WIDTH) + x + iCol] |= data; else if (color == BLACK) - sBuffer[(bRow*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) << yOffset); + sBuffer[(bRow*WIDTH) + x + iCol] &= ~data; else - sBuffer[(bRow*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; + sBuffer[(bRow*WIDTH) + x + iCol] ^= data; } 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); + sBuffer[((bRow+1)*WIDTH) + x + iCol] |= (data >> 8); else if (color == BLACK) - sBuffer[((bRow+1)*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset)); + sBuffer[((bRow+1)*WIDTH) + x + iCol] &= ~(data >> 8); else - sBuffer[((bRow+1)*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset); + sBuffer[((bRow+1)*WIDTH) + x + iCol] ^= (data >> 8); } } } @@ -940,7 +948,7 @@ struct BitStreamReader } if ((this->byteBuffer & this->bitBuffer) != 0) - result |= (1 << i); // result |= bitshift_left[i]; + result |= (1 << i); this->bitBuffer += this->bitBuffer; } @@ -1192,6 +1200,88 @@ void Arduboy2Base::swap(int16_t& a, int16_t& b) b = temp; } +uint8_t Arduboy2Base::checkBatteryState() +{ + uint8_t state = BATTERY_STATE_INVALID; + asm volatile ( + " ldi r30, lo8(%[prr0]) \n" // if (bit_is_set(PRR0,PRADC)) //ADC power off + " ldi r31, hi8(%[prr0]) \n" // { + " ld r24, z \n" + " lds r25, %[adcsra] \n" + " sbrs r24, %[pradc] \n" + " rjmp 1f \n" + " \n" + " andi r24, ~(1<<%[pradc]) \n" // PRR0 &= ~_BV(PRADC); // ADC power on + " st z, r24 \n" + " ldi r24, %[admuxval] \n" // ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) + " sts %[admux], r24 \n" + " ori r25, 1<<%[adsc] \n" // ADCSRA |= _BV(ADSC) //start conversion + " sts %[adcsra], r25 \n" // } + " ;rjmp 2f \n" // bit is set so continue below to jump to 2f + "1: \n" + " sbrc r25, %[adsc] \n" // else if (!(ADCSRA & _BV(ADSC)) //ADC conversion ready + " rjmp 2f \n" // { + " \n" + " ori r24, 1<<%[pradc] \n" // PRR0 |= _BV(PRADC); // ADC power off + " st z, r24 \n" + " ldi r30, %[adcl] \n" // uint16_t bandgap = ADCL | (ADCH << 8); + " ld r24, z+ \n" + " ld r25, z \n" + " subi r24, 192 \n" // bandgap -= 192; + " sbci r25, 0 \n" + " and r25, r25 \n" // if (bandgap < 256) + " brne 2f \n" // { + " \n" + " ldi %[state],%[normal] \n" // state = BATTERY_STATE_NORMAL; + " lds r25, %[battlow] \n" + " cp r25, r24 \n" + " brcc 2f \n" // if (batteryLow < bandgap) state = BATTERY_STATE_LOW; + " ldi %[state],%[low] \n" // } + "2: \n" // } + :[state] "+d"(state) + :[prr0] "M" (_SFR_MEM_ADDR(PRR0)), + [adcsra] "M" (_SFR_MEM_ADDR(ADCSRA)), + [pradc] "I" (PRADC), + [admuxval] "M" (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1)), + [admux] "M" (_SFR_MEM_ADDR(ADMUX)), + [adsc] "I" (ADSC), + [adcl] "M" (_SFR_MEM_ADDR(ADCL)), + [battlow] "" (&batteryLow), + [normal] "I" (BATTERY_STATE_NORMAL), + [low] "I" (BATTERY_STATE_LOW) + : "r24", "r25", "r30", "r31" + ); +#if 0 +// For reference, this is the C++ equivalent + uint8_t state = BATTERY_STATE_UNDEFINED; + if (bit_is_set(PRR0,PRADC)) //only enable when ADC power is disabled + { + PRR0 &= ~_BV(PRADC); // ADC power on + ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); //meassure 1.1V bandgap against AVcc + ADCSRA |= _BV(ADSC); //start conversion + } + else if (!(ADCSRA & _BV(ADSC))) + { + PRR0 |= _BV(PRADC); // ADC power off + uint16_t bandgap = ADCL | (ADCH << 8); + bandgap -= 192; + if (bandgap < 256) + { + state = BATTERY_STATE_NORMAL; + if (batteryLow < (uint8_t)bandgap) state = BATTERY_STATE_LOW; + } + } +#endif + return state; +} + +uint8_t Arduboy2Base::checkBatteryStateLED(bool flash) +{ + uint8_t state = checkBatteryState(); + if (state == BATTERY_STATE_NORMAL | flash) TXLED0; + if (state == BATTERY_STATE_LOW) TXLED1; + return state; +} //==================================== //========== class Arduboy2 ========== diff --git a/board-package-source/libraries/Arduboy2/src/Arduboy2.h b/board-package-source/libraries/Arduboy2/src/Arduboy2.h index b7405a1..b4e6c03 100644 --- a/board-package-source/libraries/Arduboy2/src/Arduboy2.h +++ b/board-package-source/libraries/Arduboy2/src/Arduboy2.h @@ -14,7 +14,6 @@ #include "Sprites.h" #include "SpritesB.h" #include -#include /** \brief * Library version @@ -34,7 +33,7 @@ * #endif * \endcode */ -#define ARDUBOY_LIB_VER 50100 +#define ARDUBOY_LIB_VER 50201 // EEPROM settings #define ARDUBOY_UNIT_NAME_LEN 6 /**< The maximum length of the unit name string. */ @@ -42,6 +41,8 @@ #define EEPROM_VERSION 0 #define EEPROM_SYS_FLAGS 1 #define EEPROM_AUDIO_ON_OFF 2 +#define EEPROM_BANDGAP_CAL 6 //Bandgap calibration value +#define EEPROM_BATTERY_LOW 7 //Battery low threshold #define EEPROM_UNIT_ID 8 // A uint16_t binary unit ID #define EEPROM_UNIT_NAME 10 // An up to 6 character unit name. Cannot contain // 0x00 or 0xFF. Lengths less than 6 are padded @@ -88,6 +89,14 @@ #define CLEAR_BUFFER true /**< Value to be passed to `display()` to clear the screen buffer. */ +#define BATTERY_STATE_LOW 0 +#define BATTERY_STATE_NORMAL 1 +#define BATTERY_STATE_INVALID 0xFF +#define FLASH_LED true + +//============================================= +//========== Rect (rectangle) object ========== +//============================================= /** \brief * A rectangle object for collision functions. @@ -97,6 +106,7 @@ * given width and height. * * \see Arduboy2Base::collide(Point, Rect) Arduboy2Base::collide(Rect, Rect) + * Point */ struct Rect { @@ -104,20 +114,52 @@ struct Rect int16_t y; /**< The Y coordinate of the top left corner */ uint8_t width; /**< The width of the rectangle */ uint8_t height; /**< The height of the rectangle */ + + /** \brief + * The default constructor + */ + Rect() = default; + + /** \brief + * The fully initializing constructor + * + * \param x The X coordinate of the top left corner. Copied to variable `x`. + * \param y The Y coordinate of the top left corner. Copied to variable `y`. + * \param width The width of the rectangle. Copied to variable `width`. + * \param height The height of the rectangle. Copied to variable `height`. + */ + Rect(int16_t x, int16_t y, uint8_t width, uint8_t height); }; +//================================== +//========== Point object ========== +//================================== + /** \brief * An object to define a single point for collision functions. * * \details * The location of the point is given by X and Y coordinates. * - * \see Arduboy2Base::collide(Point, Rect) + * \see Arduboy2Base::collide(Point, Rect) Rect */ struct Point { int16_t x; /**< The X coordinate of the point */ int16_t y; /**< The Y coordinate of the point */ + + /** \brief + * The default constructor + */ + Point() = default; + + /** \brief + * The fully initializing constructor + * + * \param x The X coordinate of the point. Copied to variable `x`. + * \param y The Y coordinate of the point. Copied to variable `y`. + */ + Point(int16_t x, int16_t y); }; //================================== @@ -442,7 +484,7 @@ class Arduboy2Base : public Arduboy2Core * specified color. The values WHITE or BLACK can be used for the color. * If the `color` parameter isn't included, the pixel will be set to WHITE. */ - void drawPixel(int16_t x, int16_t y, uint8_t color = WHITE); + static void drawPixel(int16_t x, int16_t y, uint8_t color = WHITE); /** \brief * Returns the state of the given pixel in the screen buffer. @@ -1019,7 +1061,7 @@ class Arduboy2Base : public Arduboy2Core * * \see Point Rect */ - bool collide(Point point, Rect rect); + static bool collide(Point point, Rect rect); /** \brief * Test if a rectangle is intersecting with another rectangle. @@ -1036,7 +1078,7 @@ class Arduboy2Base : public Arduboy2Core * * \see Rect */ - bool collide(Rect rect1, Rect rect2); + static bool collide(Rect rect1, Rect rect2); /** \brief * Read the unit ID from system EEPROM. @@ -1224,6 +1266,60 @@ class Arduboy2Base : public Arduboy2Core */ void writeShowBootLogoLEDsFlag(bool val); + /** \brief + * Returns the battery state. + * \details + * This function is intended as a method to determine a low battery state + * + * Returns the following states: + * - BATTERY_STATE_LOW The battery low threshold has been reached. + * - BATTERY_STATE_NORMAL The battery is considered normal. + * - BATTERY_STATE_INVALID The ADC conversion is not ready yet or the + * result is out of range. + * + * This fucntion depends on the EEPROM_BATTERY_LOW value been set to the + * low batterly bandgap voltage value. The default value (0xFF) will + * disable the EEPROM_BATTERY_LOW state. + * + * example: + * \code{.cpp} + * void loop() { + * if (!arduboy.nextFrame()) return; + * if (arduboy.everyXFrames(FRAMERATE) && arduboy.checkBatteryState() == BATTERY_STATE_LOW) + * { + * batteryLowWarning = true; + * } + * \endcode + * + * \see everyXFrames() + */ + uint8_t checkBatteryState(); + + /** \brief + * Returns battery state and sets TXLED as a low battery indicator + * \param flash defaults to 'false' for no flashing. use 'FLASH_LED' or + * `true` for flashing. + * \details + * This function is intended as a method to determine a low battery state. + * The TXLED is used as a battery low indicator. The TXLED will light up + * continiously by default when the battery state is low. The optional + * FLASH_LED parameter can be passed to make the LED toggle on or off on low + * battery state. + * + * This function is a quick way of adding a low battery indicator to a sketch. + * + * example: + * \code{.cpp} + * void loop() { + * if (!arduboy.nextFrame()) return; + * //turn TXLED alternately on 1 second and off 1 second when battery is low + * if (arduboy.everyXFrames(FRAMERATE)) checkBatteryStateLED(FLASH_LED); + * \endcode + * + * \see checkBatteryState() everyXFrames() + */ + uint8_t checkBatteryStateLED(bool flash = false); + /** \brief * A counter which is incremented once per frame. * @@ -1273,6 +1369,8 @@ class Arduboy2Base : public Arduboy2Core */ static uint8_t sBuffer[(HEIGHT*WIDTH)/8]; + static uint8_t batteryLow; + protected: // helper function for sound enable/disable system control void sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal); @@ -1354,6 +1452,7 @@ class Arduboy2 : public Print, public Arduboy2Base * * \see Arduboy2::write() */ + using Print::write; /** \brief * Display the boot logo sequence using printed text instead of a bitmap. diff --git a/board-package-source/libraries/Arduboy2/src/Arduboy2Core.cpp b/board-package-source/libraries/Arduboy2/src/Arduboy2Core.cpp index c6e6ac5..ecaecda 100644 --- a/board-package-source/libraries/Arduboy2/src/Arduboy2Core.cpp +++ b/board-package-source/libraries/Arduboy2/src/Arduboy2Core.cpp @@ -6,13 +6,15 @@ #include "Arduboy2Core.h" +#include + const uint8_t PROGMEM lcdBootProgram[] = { // boot defaults are commented out but left here in case they // might prove useful for reference // // Further reading: https://www.adafruit.com/datasheets/SSD1306.pdf -#ifdef OLED_SH1106 +#if defined(OLED_SH1106) 0x8D, 0x14, // Charge Pump Setting v = enable (0x14) 0xA1, // Set Segment Re-map 0xC8, // Set COM Output Scan Direction @@ -128,15 +130,17 @@ const uint8_t PROGMEM lcdBootProgram[] = { // set display mode = horizontal addressing mode (0x00) 0x20, 0x00, - + #if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) // set col address range - // 0x21, 0x00, COLUMN_ADDRESS_END, + 0x21, 0x00, COLUMN_ADDRESS_END, // set page address range - // 0x22, 0x00, PAGE_ADDRESS_END + 0x22, 0x00, PAGE_ADDRESS_END + #endif #endif }; + Arduboy2Core::Arduboy2Core() { } void Arduboy2Core::boot() @@ -177,6 +181,7 @@ void Arduboy2Core::setCPUSpeed8MHz() void Arduboy2Core::bootPins() { #ifdef ARDUBOY_10 + // Port B INPUT_PULLUP or HIGH PORTB = (_BV(RED_LED_BIT) | _BV(BLUE_LED_BIT) | //RGB LED off #ifndef AB_ALTERNATE_WIRING @@ -203,33 +208,56 @@ void Arduboy2Core::bootPins() // Port D INPUT_PULLUP or HIGH PORTD = ( - #ifdef AB_ALTERNATE_WIRING + #if defined(AB_ALTERNATE_WIRING) _BV(GREEN_LED_BIT) | #endif - #ifndef ARDUINO_AVR_MICRO + #if !(defined(ARDUINO_AVR_MICRO)) _BV(TX_LED_BIT) | //TX LED off for Arduboy and non Micro based Arduino #endif - _BV(CART_BIT) | _BV(DC_BIT)) & //flash cart inactive, LCD data mode - // Port D INPUT or LOW - ~(_BV(CS_BIT) | _BV(RST_BIT) //oled chip enabled, reset active - #ifdef AB_ALTERNATE_WIRING - | _BV(SPEAKER_2_BIT) + _BV(CART_BIT) | + #if !(defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)) + _BV(DC_BIT) | #endif - #ifdef LCD_ST7565 - | _BV(POWER_LED_BIT) + 0) & ~( // Port D INPUTs or LOW outputs + #if !(defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)) + _BV(CS_BIT) | // oled display enabled + _BV(RST_BIT) | // reset active #endif - ); - + #if defined(AB_ALTERNATE_WIRING) + _BV(SPEAKER_2_BIT) | + #endif + #if defined(LCD_ST7565) + _BV(POWER_LED_BIT) | + #endif + #if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + _BV(I2C_SCL) | + _BV(I2C_SDA) | + #endif + 0); + // Port D outputs - DDRD = _BV(RST_BIT) | _BV(CS_BIT) | _BV(DC_BIT) | - #ifdef AB_ALTERNATE_WIRING + DDRD = ( + #if !(defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)) + _BV(DC_BIT) | + #endif + #if !(defined(AB_ALTERNATE_WIRING) && (CART_CS_SDA)) + _BV(RST_BIT) | + _BV(CS_BIT) | + #endif + #if defined(AB_ALTERNATE_WIRING) _BV(GREEN_LED_BIT) | #endif - #ifdef LCD_ST7565 - _BV(POWER_LED_BIT) | + #if defined(LCD_ST7565) + _BV(POWER_LED_BIT) | #endif - _BV(CART_BIT) | _BV(TX_LED_BIT); - // Port D inputs (none) + _BV(CART_BIT) | + _BV(TX_LED_BIT) | + 0) & ~(// Port D inputs + #if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + _BV(I2C_SCL) | // SDA and SCL as inputs without pullups + _BV(I2C_SDA) | // (both externally pulled up) + #endif + 0); // Port E INPUT_PULLUP or HIGH PORTE |= _BV(A_BUTTON_BIT); @@ -294,6 +322,12 @@ void Arduboy2Core::bootPins() void Arduboy2Core::bootOLED() { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_CMD); + for (uint8_t i = 0; i < sizeof(lcdBootProgram); i++) + i2c_sendByte(pgm_read_byte(lcdBootProgram + i)); + i2c_stop(); +#else // reset the display uint8_t cmd; const void* ptr = lcdBootProgram; @@ -301,6 +335,7 @@ void Arduboy2Core::bootOLED() "1: \n\t" //assembly loop for 2nd delayShort(5) ); delayShort(5); //for a short active low reset pulse + #if !(defined(AB_ALTERNATE_WIRING) && defined(CART_CS_SDA)) asm volatile( " sbic %[rst_port], %[rst_bit] \n\t" //continue if reset is active " rjmp 2f \n\t" //else break @@ -311,7 +346,8 @@ void Arduboy2Core::bootOLED() : [rst_port] "I" (_SFR_IO_ADDR(RST_PORT)), [rst_bit] "I" (RST_BIT) : - ); + ); + #endif #if defined(OLED_128X64_ON_96X96) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128) for (uint16_t i = 0; i < 8192; i++) SPItransfer(0); //make sure all display ram is cleared #endif @@ -335,16 +371,7 @@ void Arduboy2Core::bootOLED() : "r25" ); LCDDataMode(); -} - -void Arduboy2Core::LCDDataMode() -{ - bitSet(DC_PORT, DC_BIT); -} - -void Arduboy2Core::LCDCommandMode() -{ - bitClear(DC_PORT, DC_BIT); +#endif } // Initialize the SPI interface for the display @@ -370,6 +397,50 @@ uint8_t Arduboy2Core::SPItransfer(uint8_t data) return SPDR; } +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) +void Arduboy2Core::i2c_start(uint8_t mode) +{ + I2C_SDA_LOW(); // disable posible internal pullup, ensure SDA low on enabling output + I2C_SDA_AS_OUTPUT(); // SDA low before SCL for start condition + I2C_SCL_LOW(); + I2C_SCL_AS_OUTPUT(); + i2c_sendByte(SSD1306_I2C_ADDR << 1); + i2c_sendByte(mode); +} + +void Arduboy2Core::i2c_sendByte(uint8_t byte) +{ + uint8_t sda_clr = I2C_PORT & ~((1 << I2C_SDA) | (1 << I2C_SCL)); + uint8_t scl = 1 << I2C_SCL; + uint8_t sda = 1 << I2C_SDA; + uint8_t scl_bit = I2C_SCL; + asm volatile ( + " sec \n" // set carry for 8 shift counts + " rol %[byte] \n" // shift a bit out and count at the same time + "1: \n" + " out %[port], %[sda0] \n" // preemtively clear SDA + " brcc 2f \n" // skip if dealing with 0 bit + " out %[pin], %[sda] \n" + "2: \n" + " out %[pin], %[scl] \n" // toggle SCL on + " lsl %[byte] \n" // next bit to carry (moved here for 1 extra cycle delay) + " out %[pin], %[scl] \n" // toggle SCL off + " brne 1b \n" // initial set carry will be shifted out after 8 loops setting Z flag + " \n" + " out %[port], %[sda0] \n" // clear SDA for ACK + " sbi %[port], %[sclb] \n" // set SCL (extends ACK bit by 1 cycle) + " cbi %[port], %[sclb] \n" // clear SCL (extends SCL high by 1 cycle) + :[byte] "+r" (byte) + :[port] "i" (_SFR_IO_ADDR(I2C_PORT)), + [pin] "i" (_SFR_IO_ADDR(I2C_PIN)), + [sda0] "r" (sda_clr), + [scl] "r" (scl), + [sda] "r" (sda), + [sclb] "i" (scl_bit) + ); +} +#endif + void Arduboy2Core::safeMode() { if (buttonsState() == UP_BUTTON) @@ -403,18 +474,23 @@ void Arduboy2Core::bootPowerSaving() PRR0 = _BV(PRTWI) | _BV(PRADC); // disable USART1 PRR1 = _BV(PRUSART1); - // All other bits will be written with 0 so will be enabled } // Shut down the display void Arduboy2Core::displayOff() { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_CMD); + i2c_sendByte(0xAE); // display off + i2c_sendByte(0x8D); // charge pump: + i2c_sendByte(0x10); // disable + i2c_stop(); +#else LCDCommandMode(); SPItransfer(0xAE); // display off SPItransfer(0x8D); // charge pump: SPItransfer(0x10); // disable - delayShort(250); - bitClear(RST_PORT, RST_BIT); // set display reset pin low (reset state) +#endif } // Restart the display after a displayOff() @@ -432,12 +508,23 @@ uint8_t Arduboy2Core::height() { return HEIGHT; } void Arduboy2Core::paint8Pixels(uint8_t pixels) { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_DATA); + i2c_sendByte(pixels); + i2c_stop(); +#else SPItransfer(pixels); +#endif } void Arduboy2Core::paintScreen(const uint8_t *image) { -#if defined(OLED_SH1106) || defined(LCD_ST7565) +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_DATA); + for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++) + i2c_sendByte(pgm_read_byte(image+i)); + i2c_stop(); +#elif defined(OLED_SH1106) || defined(LCD_ST7565) for (uint8_t i = 0; i < HEIGHT / 8; i++) { LCDCommandMode(); @@ -486,7 +573,6 @@ void Arduboy2Core::paintScreen(const uint8_t *image) for (uint8_t row = 0; row < HEIGHT / 8; row++) { uint8_t b = pgm_read_byte(image + i); - if (clear) *(image + i) = 0; for (uint8_t shift = 0; shift < 4; shift++) { uint8_t c = 0xFF; @@ -513,7 +599,129 @@ void Arduboy2Core::paintScreen(const uint8_t *image) // will be used by any buffer based subclass void Arduboy2Core::paintScreen(uint8_t image[], bool clear) { -#if defined(OLED_SH1106) || defined(LCD_ST7565) +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + uint16_t length = WIDTH * HEIGHT / 8; + uint8_t sda_clr = I2C_PORT & ~((1 << I2C_SDA) | (1 << I2C_SCL)); + uint8_t scl = 1 << I2C_SCL; + uint8_t sda = 1 << I2C_SDA; + uint8_t scl_bit = I2C_SCL; + i2c_start(SSD1306_I2C_DATA); + #if defined (OLED_SSD1306_I2C) + //bitbanging I2C ~2Mbps (8 cycles per bit / 78 cycles per byte) + asm volatile ( + " dec %[clear] \n" // get clear mask 0:0xFF, 1:0x00 + "1: \n" + " ld r24, %a[ptr] \n" // fetch display byte from buffer + " mov r0, r24 \n" // move to shift register + " and r24, %[clear] \n" // apply clear mask + " st %a[ptr]+, r24 \n" // update buffer + " \n" + " sec \n" // set carry for 8 shift counts + " rol r0 \n" // shift a bit out and count at the same time + "2: \n" + " out %[port], %[sda0] \n" // preemtively clear SDA + " brcc 3f \n" // skip if dealing with 0 bit + " out %[pin], %[sda] \n" + "3: \n" + " out %[pin], %[scl] \n" // toggle SCL on + " lsl r0 \n" // next bit to carry (moved here for 1 extra cycle delay) + " out %[pin], %[scl] \n" // toggle SCL off + " brne 2b \n" // initial set carry will be shifted out after 8 loops setting Z flag + " \n" + " out %[port], %[sda0] \n" // clear SDA for ACK + " subi %A[len], 1 \n" // len-- part1 (moved here for 1 cycle delay) + " out %[pin], %[scl] \n" // set SCL (2 cycles required) + " sbci %B[len], 0 \n" // len-- part2 (moved here for 1 cycle delay) + " out %[pin], %[scl] \n" // clear SCL (2 cycles required) + " brne 1b \n" + :[ptr] "+e" (image), + [len] "+d" (length), + [clear] "+r" (clear) + :[port] "i" (_SFR_IO_ADDR(I2C_PORT)), + [pin] "i" (_SFR_IO_ADDR(I2C_PIN)), + [sda0] "r" (sda_clr), + [scl] "r" (scl), + [sda] "r" (sda) + :"r24" + ); + #else + //bitbanging I2C @ 2.66Mbps (6 cycles per bit / 56 cycles per byte) + asm volatile ( + " dec %[clear] \n" // get clear mask 0:0xFF, 1:0x00 + " ld r0, %a[ptr] \n" // fetch display byte from buffer + "1: \n" + " sbrc r0, 7 \n" // MSB first comes first + " out %[pin], %[sda] \n" // toggle SDA on for 1-bit + " out %[pin], %[scl] \n" // toggle SCL high + " mov r24, r0 \n" // duplicate byte (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // toggle SCL low + " out %[port], %[sda0] \n" // preemptively clear SDA for next bit + " \n" + " sbrc r0, 6 \n" // repeat of above but for bit 6 + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " and r24, %[clear] \n" // apply clear mask (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // + " out %[port], %[sda0] \n" // + + " sbrc r0, 5 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " st %a[ptr]+, r24 \n" // new buffer contents (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // + " out %[port], %[sda0] \n" // + + " sbrc r0, 4 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 3 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 2 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 1 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay + " out %[port], %[sda0] \n" // + + " sbrc r0, 0 \n" // + " out %[pin], %[sda] \n" // + " out %[pin], %[scl] \n" // + " subi %A[len], 1 \n" // length-- part 1 (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // + " out %[port], %[sda0] \n" // SDA low for ACK + + " sbci %B[len], 0 \n" // length-- part 2 (also serves as extra clock cycle delay) + " out %[pin], %[scl] \n" // // clock ACK bit + " ld r0, %a[ptr] \n" // fetch next buffer byte (also serves as clock delay) + " out %[pin], %[scl] \n" // + " brne 1b \n" // length != 0 do next byte + :[ptr] "+e" (image), + [len] "+d" (length), + [clear] "+r" (clear) + :[port] "i" (_SFR_IO_ADDR(I2C_PORT)), + [pin] "i" (_SFR_IO_ADDR(I2C_PIN)), + [sda0] "r" (sda_clr), + [scl] "r" (scl), + [sda] "r" (sda), + [sclb] "i" (scl_bit) + :"r24" + ); + #endif + i2c_stop(); + +#elif defined(OLED_SH1106) || defined(LCD_ST7565) //Assembly optimized page mode display code with clear support. //Each byte transfer takes 18 cycles asm volatile ( @@ -682,24 +890,77 @@ void Arduboy2Core::paintScreen(uint8_t image[], bool clear) ); #endif } +#if 0 +// For reference, this is the "closed loop" C++ version of paintScreen() +// used prior to the above version. +void Arduboy2Core::paintScreen(uint8_t image[], bool clear) +{ + uint8_t c; + int i = 0; + + if (clear) + { + SPDR = image[i]; // set the first SPI data byte to get things started + image[i++] = 0; // clear the first image byte + } + else + SPDR = image[i++]; + + // the code to iterate the loop and get the next byte from the buffer is + // executed while the previous byte is being sent out by the SPI controller + while (i < (HEIGHT * WIDTH) / 8) + { + // get the next byte. It's put in a local variable so it can be sent as + // as soon as possible after the sending of the previous byte has completed + if (clear) + { + c = image[i]; + // clear the byte in the image buffer + image[i++] = 0; + } + else + c = image[i++]; + + while (!(SPSR & _BV(SPIF))) { } // wait for the previous byte to be sent + + // put the next byte in the SPI data register. The SPI controller will + // clock it out while the loop continues and gets the next byte ready + SPDR = c; + } + while (!(SPSR & _BV(SPIF))) { } // wait for the last byte to be sent +} +#endif void Arduboy2Core::blank() { -#ifdef OLED_SH1106 - for (int i = 0; i < (HEIGHT * 132) / 8; i++) -#elif defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128)|| defined(OLED_128X64_ON_96X96) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128) - for (int i = 0; i < (HEIGHT * WIDTH) / 2; i++) -#else //OLED SSD1306 and compatibles +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_DATA); for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++) -#endif + i2c_sendByte(0); + i2c_stop(); +#else + #if defined (OLED_SH1106) + for (int i = 0; i < (HEIGHT * 132) / 8; i++) + #elif defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128)|| defined(OLED_128X64_ON_96X96) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128) + for (int i = 0; i < (HEIGHT * WIDTH) / 2; i++) + #else //OLED SSD1306 and compatibles + for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++) + #endif SPItransfer(0x00); +#endif } void Arduboy2Core::sendLCDCommand(uint8_t command) { +#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + i2c_start(SSD1306_I2C_CMD); + i2c_sendByte(command); + i2c_stop(); +#else LCDCommandMode(); SPItransfer(command); LCDDataMode(); +#endif } // invert the display or set to normal @@ -994,6 +1255,7 @@ void Arduboy2Core::exitToBootloader() while (true) { } #else bootloader_timer = 120; //ms + while (true) { } #endif } diff --git a/board-package-source/libraries/Arduboy2/src/Arduboy2Core.h b/board-package-source/libraries/Arduboy2/src/Arduboy2Core.h index 6705881..852b4a5 100644 --- a/board-package-source/libraries/Arduboy2/src/Arduboy2Core.h +++ b/board-package-source/libraries/Arduboy2/src/Arduboy2Core.h @@ -10,8 +10,6 @@ #include #include #include -#include -#include extern volatile unsigned char bootloader_timer; @@ -58,13 +56,18 @@ extern volatile unsigned char bootloader_timer; #define RST_BIT PORTD7 // Display reset physical bit number #endif -#define PIN_DC 4 // Display D/C Arduino pin number -#define DC_PORT PORTD // Display D/C port -#define DC_BIT PORTD4 // Display D/C physical bit number + #define PIN_DC 4 // Display D/C Arduino pin number + #define DC_PORT PORTD // Display D/C port + #define DC_BIT PORTD4 // Display D/C physical bit number -#define PIN_CART 0 // flash cart chip select +#ifdef CART_CS_SDA + #define PIN_CART 2 // SDA as alternative flash cart chip select + #define CART_BIT PORTD1 +#else + #define PIN_CART 0 // RX as default flash cart chip select + #define CART_BIT PORTD2 +#endif #define CART_PORT PORTD -#define CART_BIT PORTD2 #define SPI_MOSI_PORT PORTB #define SPI_MOSI_BIT PORTB2 @@ -72,6 +75,34 @@ extern volatile unsigned char bootloader_timer; #define SPI_SCK_PORT PORTB #define SPI_SCK_BIT PORTB1 +#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + #define I2C_PORT PORTD + #define I2C_DDR DDRD + #define I2C_PIN PIND + #ifdef AB_ALTERNATE_WIRING + #define I2C_SCL PORTD3 + #else + #define I2C_SCL PORTD7 + #endif + #define I2C_SDA PORTD4 + //port states + #define I2C_SDA_HIGH() I2C_PORT |= (1 << I2C_SDA) + #define I2C_SCL_HIGH() I2C_PORT |= (1 << I2C_SCL) + #define I2C_SDA_LOW() I2C_PORT &= ~(1 << I2C_SDA) + #define I2C_SCL_LOW() I2C_PORT &= ~(1 << I2C_SCL) + + //port directions + #define I2C_SDA_AS_INPUT() I2C_DDR &= ~(1 << I2C_SDA) + #define I2C_SCL_AS_INPUT() I2C_DDR &= ~(1 << I2C_SCL) + #define I2C_SDA_AS_OUTPUT() I2C_DDR |= (1 << I2C_SDA) + #define I2C_SCL_AS_OUTPUT() I2C_DDR |= (1 << I2C_SCL) + + // display address, commands + #define SSD1306_I2C_ADDR 0x3c //0x3c:default, 0x3d: alternative) + #define SSD1306_I2C_CMD 0x00 + #define SSD1306_I2C_DATA 0x40 +#endif + #define RED_LED 10 /**< The pin number for the red color in the RGB LED. */ #ifdef AB_ALTERNATE_WIRING #define GREEN_LED 3 // Pro Micro alternative green LED pin @@ -429,7 +460,10 @@ class Arduboy2Core * * \see LCDCommandMode() SPItransfer() */ - inline void static LCDDataMode() __attribute__((always_inline)); + void static inline LCDDataMode() __attribute__((always_inline)) + { + bitSet(DC_PORT, DC_BIT); + } /** \brief * Put the display into command mode. * @@ -452,7 +486,10 @@ class Arduboy2Core * * \see LCDDataMode() sendLCDCommand() SPItransfer() */ - inline void static LCDCommandMode() __attribute__((always_inline)); + void static inline LCDCommandMode() __attribute__((always_inline)) + { + bitClear(DC_PORT, DC_BIT); + } /** \brief * Transfer a byte to the display. * @@ -468,6 +505,21 @@ class Arduboy2Core */ uint8_t static SPItransfer(uint8_t data); +#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX) + void static i2c_start(uint8_t mode); + + void static inline i2c_stop() __attribute__((always_inline)) + { + // SDA and SCL both are already low, from writing ACK bit no need to change state + I2C_SDA_AS_INPUT(); // switch to input so SDA is pulled up externally first for stop condition + I2C_SCL_AS_INPUT(); // pull up SCL externally + } + + void static i2c_sendByte(uint8_t byte); +#endif + +//#endif + /** \brief * Turn the display off. * diff --git a/board-package-source/libraries/Arduboy2/src/Sprites.cpp b/board-package-source/libraries/Arduboy2/src/Sprites.cpp index d858944..1ee839c 100644 --- a/board-package-source/libraries/Arduboy2/src/Sprites.cpp +++ b/board-package-source/libraries/Arduboy2/src/Sprites.cpp @@ -180,53 +180,238 @@ void Sprites::drawBitmap(int16_t x, int16_t y, sRow += start_h; ofs = (sRow * WIDTH) + x + xOffset; + uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset; + uint8_t data; uint8_t mul_amt = 1 << yOffset; uint16_t mask_data; uint16_t bitmap_data; - const uint8_t ofs_step = draw_mode == SPRITE_PLUS_MASK ? 2 : 1; - const uint8_t ofs_stride = (w - rendered_width)*ofs_step; - const uint16_t initial_bofs = ((start_h * w) + xOffset)*ofs_step; + 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; - const uint8_t *bofs = bitmap + initial_bofs; - const uint8_t *mask_ofs = !mask ? bitmap : mask; - mask_ofs += initial_bofs + ofs_step - 1; - - for (uint8_t a = 0; a < loop_h; a++) { - for (uint8_t iCol = 0; iCol < rendered_width; iCol++) { - uint8_t data; - - bitmap_data = pgm_read_byte(bofs) * mul_amt; - mask_data = ~bitmap_data; - - if (draw_mode == SPRITE_UNMASKED) { - mask_data = ~(0xFF * mul_amt); - } else if (draw_mode == SPRITE_IS_MASK_ERASE) { - bitmap_data = 0; - } else { - mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt); + if (sRow >= 0) { + data = Arduboy2Base::sBuffer[ofs]; + data &= (uint8_t)(mask_data); + data |= (uint8_t)(bitmap_data); + Arduboy2Base::sBuffer[ofs] = data; + } + if (yOffset != 0 && sRow < 7) { + data = Arduboy2Base::sBuffer[ofs + WIDTH]; + data &= (*((unsigned char *) (&mask_data) + 1)); + data |= (*((unsigned char *) (&bitmap_data) + 1)); + Arduboy2Base::sBuffer[ofs + WIDTH] = data; + } + ofs++; + bofs++; + } + sRow++; + bofs += w - rendered_width; + ofs += WIDTH - rendered_width; } + break; - if (sRow >= 0) { - data = Arduboy2Base::sBuffer[ofs]; - data &= (uint8_t)(mask_data); - data |= (uint8_t)(bitmap_data); - Arduboy2Base::sBuffer[ofs] = data; + 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) { + Arduboy2Base::sBuffer[ofs] |= (uint8_t)(bitmap_data); + } + if (yOffset != 0 && sRow < 7) { + Arduboy2Base::sBuffer[ofs + WIDTH] |= (*((unsigned char *) (&bitmap_data) + 1)); + } + ofs++; + bofs++; + } + sRow++; + bofs += w - rendered_width; + ofs += WIDTH - rendered_width; } - if (yOffset != 0 && sRow < (HEIGHT / 8 - 1)) { - data = Arduboy2Base::sBuffer[ofs + WIDTH]; - data &= (*((unsigned char *) (&mask_data) + 1)); - data |= (*((unsigned char *) (&bitmap_data) + 1)); - Arduboy2Base::sBuffer[ofs + WIDTH] = data; + 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) { + Arduboy2Base::sBuffer[ofs] &= ~(uint8_t)(bitmap_data); + } + if (yOffset != 0 && sRow < 7) { + Arduboy2Base::sBuffer[ofs + WIDTH] &= ~(*((unsigned char *) (&bitmap_data) + 1)); + } + ofs++; + bofs++; + } + sRow++; + bofs += w - rendered_width; + ofs += WIDTH - rendered_width; } - ofs++; - mask_ofs += ofs_step; - bofs += ofs_step; - } - sRow++; - bofs += ofs_stride; - mask_ofs += ofs_stride; - ofs += WIDTH - rendered_width; + break; + + case SPRITE_MASKED: + uint8_t *mask_ofs; + mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset; + 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 = Arduboy2Base::sBuffer[ofs]; + data &= (uint8_t)(mask_data); + data |= (uint8_t)(bitmap_data); + Arduboy2Base::sBuffer[ofs] = data; + } + if (yOffset != 0 && sRow < 7) { + data = Arduboy2Base::sBuffer[ofs + WIDTH]; + data &= (*((unsigned char *) (&mask_data) + 1)); + data |= (*((unsigned char *) (&bitmap_data) + 1)); + Arduboy2Base::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; // counter for x loop below + + asm volatile( + "push r28\n" // save Y + "push r29\n" + "movw r28, %[buffer_ofs]\n" // Y = buffer_ofs_2 + "adiw r28, 63\n" // buffer_ofs_2 = buffer_ofs + 128 + "adiw r28, 63\n" + "adiw r28, 2\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" + "movw %[bitmap_data], r0\n" + "mul %A[mask_data], %[mul_amt]\n" + "movw %[mask_data], r0\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 + "and %[data], %B[mask_data]\n" + "or %[data], %B[bitmap_data]\n" + // update buffer, increment + "st Y+, %[data]\n" + + "end_second_page:\n" + "skip_shifting:\n" + + // FIRST PAGE + // if sRow >= 0 + "tst %[sRow]\n" + "brmi skip_first_page\n" + "ld %[data], %a[buffer_ofs]\n" + // then + "com %A[mask_data]\n" + "and %[data], %A[mask_data]\n" + "or %[data], %A[bitmap_data]\n" + // update buffer, increment + "st %a[buffer_ofs]+, %[data]\n" + "jmp end_first_page\n" + + "skip_first_page:\n" + // since no ST Z+ when skipped we need to do this manually + "adiw %[buffer_ofs], 1\n" + + "end_first_page:\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] "+&a" (xi), + [yi] "+&a" (loop_h), + [sRow] "+&a" (sRow), // CPI requires an upper register (r16-r23) + [data] "=&l" (data), + [mask_data] "=&l" (mask_data), + [bitmap_data] "=&l" (bitmap_data) + : + [screen_width] "M" (WIDTH), + [x_count] "l" (rendered_width), // lower register + [sprite_ofs] "z" (bofs), + [buffer_ofs] "x" (Arduboy2Base::sBuffer+ofs), + [buffer_ofs_jump] "a" (WIDTH-rendered_width), // upper reg (r16-r23) + [sprite_ofs_jump] "a" ((w-rendered_width)*2), // upper reg (r16-r23) + + // [sprite_ofs_jump] "r" (0), + [yOffset] "l" (yOffset), // lower register + [mul_amt] "l" (mul_amt) // lower register + // NOTE: We also clobber r28 and r29 (y) but sometimes the compiler + // won't allow us, so in order to make this work we don't tell it + // that we clobber them. Instead, we push/pop to preserve them. + // Then we need to guarantee that the the compiler doesn't put one of + // our own variables into r28/r29. + // We do that by specifying all the inputs and outputs use either + // lower registers (l) or simple (r16-r23) upper registers (a). + : // pushes/clobbers/pops r28 and r29 (y) + ); + break; } } diff --git a/board-package-source/libraries/Arduboy2/src/Sprites.h b/board-package-source/libraries/Arduboy2/src/Sprites.h index e0ba046..fb37b4a 100644 --- a/board-package-source/libraries/Arduboy2/src/Sprites.h +++ b/board-package-source/libraries/Arduboy2/src/Sprites.h @@ -89,6 +89,9 @@ class Sprites * An array containing the image frames, and another array containing * corresponding mask frames, are used to draw a sprite. * + * For the mask array, the width and height are not included but must + * contain data of the same dimensions as the corresponding image array. + * * Bits set to 1 in the mask indicate that the pixel will be set to the * value of the corresponding image bit. Bits set to 0 in the mask will be * left unchanged.