update Arduboy and Arduboy2 libraries

Add SSD1306 i2c display support to Arduboy and Arduboy2 libraries
This commit is contained in:
Mr.Blinky 2019-07-22 22:29:34 +02:00 committed by GitHub
parent fedcf36ff2
commit 1085fb7858
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1124 additions and 184 deletions

View File

@ -14,11 +14,17 @@ const uint8_t PROGMEM pinBootProgram[] = {
PIN_DOWN_BUTTON, INPUT_PULLUP, PIN_DOWN_BUTTON, INPUT_PULLUP,
PIN_A_BUTTON, INPUT_PULLUP, PIN_A_BUTTON, INPUT_PULLUP,
PIN_B_BUTTON, INPUT_PULLUP, PIN_B_BUTTON, INPUT_PULLUP,
#if (defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX))
//I2C
SDA, INPUT,
SCL, INPUT,
#else
// OLED SPI // OLED SPI
DC, OUTPUT, DC, OUTPUT,
CS, OUTPUT, CS, OUTPUT,
RST, OUTPUT, RST, OUTPUT,
#endif
0 0
}; };
@ -145,12 +151,13 @@ const uint8_t PROGMEM lcdBootProgram[] = {
// set display mode = horizontal addressing mode (0x00) // set display mode = horizontal addressing mode (0x00)
0x20, 0x00, 0x20, 0x00,
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
// set col address range // set col address range
// 0x21, 0x00, COLUMN_ADDRESS_END, 0x21, 0x00, COLUMN_ADDRESS_END,
// set page address range // set page address range
// 0x22, 0x00, PAGE_ADDRESS_END 0x22, 0x00, PAGE_ADDRESS_END
#endif
#endif #endif
}; };
@ -198,16 +205,30 @@ void ArduboyCore::bootPins()
if (pin==0) break; if (pin==0) break;
pinMode(pin, mode); pinMode(pin, mode);
} }
#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
I2C_SCL_LOW();
I2C_SDA_LOW();
#else
digitalWrite(RST, HIGH); digitalWrite(RST, HIGH);
delay(1); // VDD (3.3V) goes high at start, lets just chill for a ms delay(1); // VDD (3.3V) goes high at start, lets just chill for a ms
digitalWrite(RST, LOW); // bring reset low digitalWrite(RST, LOW); // bring reset low
delay(10); // wait 10ms delay(10); // wait 10ms
digitalWrite(RST, HIGH); // bring out of reset digitalWrite(RST, HIGH); // bring out of reset
#endif
} }
void ArduboyCore::bootLCD() 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 // setup the ports we need to talk to the OLED
//csport = portOutputRegister(digitalPinToPort(CS)); //csport = portOutputRegister(digitalPinToPort(CS));
*portOutputRegister(digitalPinToPort(CS)) &= ~cspinmask; *portOutputRegister(digitalPinToPort(CS)) &= ~cspinmask;
@ -227,6 +248,7 @@ void ArduboyCore::bootLCD()
SPI.transfer(pgm_read_byte(lcdBootProgram + i)); SPI.transfer(pgm_read_byte(lcdBootProgram + i));
} }
LCDDataMode(); LCDDataMode();
#endif
} }
void ArduboyCore::LCDDataMode() void ArduboyCore::LCDDataMode()
@ -242,7 +264,49 @@ void ArduboyCore::LCDCommandMode()
// *csport &= ~cspinmask; CS set once at bootLCD // *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() void ArduboyCore::safeMode()
{ {
@ -283,12 +347,23 @@ uint8_t ArduboyCore::height() { return HEIGHT; }
void ArduboyCore::paint8Pixels(uint8_t pixels) 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); SPI.transfer(pixels);
#endif
} }
void ArduboyCore::paintScreen(const unsigned char *image) 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++) for (uint8_t i = 0; i < HEIGHT / 8; i++)
{ {
LCDCommandMode(); LCDCommandMode();
@ -363,7 +438,117 @@ void ArduboyCore::paintScreen(const unsigned char *image)
// will be used by any buffer based subclass // will be used by any buffer based subclass
void ArduboyCore::paintScreen(unsigned char image[]) 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++) for (uint8_t i = 0; i < HEIGHT / 8; i++)
{ {
LCDCommandMode(); LCDCommandMode();
@ -498,21 +683,34 @@ void ArduboyCore::paintScreen(unsigned char image[])
void ArduboyCore::blank() void ArduboyCore::blank()
{ {
#ifdef OLED_SH1106 #if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
for (int i = 0; i < (HEIGHT * 132) / 8; i++) i2c_start(SSD1306_I2C_DATA);
#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++) 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); SPI.transfer(0x00);
#endif
} }
void ArduboyCore::sendLCDCommand(uint8_t command) 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(); LCDCommandMode();
SPI.transfer(command); SPI.transfer(command);
LCDDataMode(); LCDDataMode();
#endif
} }
// invert the display or set to normal // invert the display or set to normal

View File

@ -43,6 +43,38 @@
#define RST 6 #define RST 6
#endif #endif
#define DC 4 #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 #define RED_LED 10
#if defined AB_ALTERNATE_WIRING //Pro Micro Alternative GREEN LED pin #if defined AB_ALTERNATE_WIRING //Pro Micro Alternative GREEN LED pin
@ -193,9 +225,22 @@ public:
*/ */
void static LCDCommandMode(); 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 width(); //< return display width
uint8_t static height(); // < return display height uint8_t static height(); // < return display height
/// get current state of all buttons (bitmask) /// get current state of all buttons (bitmask)
/** /**
* Bit mask that is returned: * Bit mask that is returned:

View File

@ -32,6 +32,8 @@ bootLogoSpritesOverwrite KEYWORD2
bootLogoSpritesSelfMasked KEYWORD2 bootLogoSpritesSelfMasked KEYWORD2
bootLogoText KEYWORD2 bootLogoText KEYWORD2
buttonsState KEYWORD2 buttonsState KEYWORD2
checkBatteryState KEYWORD2
checkBatteryStateLED KEYWORD2
clear KEYWORD2 clear KEYWORD2
collide KEYWORD2 collide KEYWORD2
cpuLoad KEYWORD2 cpuLoad KEYWORD2
@ -165,3 +167,7 @@ RGB_ON LITERAL1
ARDUBOY_NO_USB LITERAL1 ARDUBOY_NO_USB LITERAL1
BATTERY_STATE_LOW LITERAL1
BATTERY_STATE_NORMAL LITERAL1
BATTERY_STATE_INVALID LITERAL1
FLASH_LED LITERAL1

View File

@ -7,7 +7,7 @@
"type": "git", "type": "git",
"url": "https://github.com/MLXXXp/Arduboy2.git" "url": "https://github.com/MLXXXp/Arduboy2.git"
}, },
"version": "5.1.0", "version": "5.2.1",
"export": "export":
{ {
"exclude": "extras" "exclude": "extras"

View File

@ -1,5 +1,5 @@
name=Arduboy2 name=Arduboy2
version=5.1.0 version=5.2.1
author=Chris J. Martinez, Kevin Bates, Josh Goebel, Scott Allen, Ross O. Shoger author=Chris J. Martinez, Kevin Bates, Josh Goebel, Scott Allen, Ross O. Shoger
maintainer=Scott Allen <saydisp-git@yahoo.ca> maintainer=Scott Allen <saydisp-git@yahoo.ca>
sentence=An alternative library for use with the Arduboy game system. sentence=An alternative library for use with the Arduboy game system.

View File

@ -8,12 +8,32 @@
#include "ab_logo.c" #include "ab_logo.c"
#include "glcdfont.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 ========== //========== class Arduboy2Base ==========
//======================================== //========================================
uint8_t Arduboy2Base::sBuffer[]; uint8_t Arduboy2Base::sBuffer[];
uint8_t Arduboy2Base::batteryLow = EEPROM.read(EEPROM_BATTERY_LOW); //Low battery bandgap value - 192
Arduboy2Base::Arduboy2Base() Arduboy2Base::Arduboy2Base()
{ {
currentButtonState = 0; currentButtonState = 0;
@ -31,8 +51,9 @@ void Arduboy2Base::begin()
{ {
boot(); // raw hardware 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. 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 // check for and handle buttons held during start up for system control
@ -323,14 +344,6 @@ void Arduboy2Base::clear()
fillScreen(BLACK); 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) void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color)
{ {
#ifdef PIXEL_SAFE_MODE #ifdef PIXEL_SAFE_MODE
@ -343,58 +356,57 @@ void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color)
uint16_t row_offset; uint16_t row_offset;
uint8_t bit; 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 asm volatile
( (
#if WIDTH == 128 // bit = 1 << (y & 7)
"mul %[width_offset], %A[y]\n" "ldi %[bit], 1 \n" //bit = 1;
"movw %[row_offset], r0\n" "sbrc %[y], 1 \n" //if (y & _BV(1)) bit = 4;
"andi %A[row_offset], 0x80\n" // row_offset &= (~0b01111111); "ldi %[bit], 4 \n"
"clr __zero_reg__\n" "sbrc %[y], 0 \n" //if (y & _BV(0)) bit = bit << 1;
"add %A[row_offset], %[x]\n" "lsl %[bit] \n"
// mask for only 0-7 "sbrc %[y], 2 \n" //if (y & _BV(2)) bit = (bit << 4) | (bit >> 4);
"andi %A[y], 0x07\n" "swap %[bit] \n"
#else //row_offset = y / 8 * WIDTH + x;
"mov r0, %A[y] \n" "andi %A[y], 0xf8 \n" //row_offset = (y & 0xF8) * WIDTH / 8
"andi %A[y], 0x07 \n" // mask for only 0-7 "mul %[width_offset], %A[y] \n"
"eor r0, %A[y] \n" // == and 0xF8
"mul %[width_offset], r0 \n"
"movw %[row_offset], r0 \n" "movw %[row_offset], r0 \n"
"clr __zero_reg__ \n" "clr __zero_reg__ \n"
"add %A[row_offset], %[x] \n" "add %A[row_offset], %[x] \n" //row_offset += x
"adc %B[row_offset], __zero_reg__ \n" #if WIDTH != 128
"adc %B[row_offset], __zero_reg__ \n" // only non 128 width can overflow
#endif #endif
// Z += y : [row_offset] "=&x" (row_offset), // upper register (ANDI)
"add r30, %A[y] \n" [bit] "=&d" (bit), // upper register (LDI)
"adc r31, __zero_reg__ \n" [y] "+d" (y) // upper register (ANDI), must be writable
// load correct bitshift from program RAM : [width_offset] "r" ((uint8_t)(WIDTH/8)),
"lpm %[bit], Z \n" [x] "r" ((uint8_t)x)
: [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)
: :
); );
uint8_t data = sBuffer[row_offset] | bit;
if (color) { if (!(color & _BV(0))) data ^= bit;
sBuffer[row_offset] |= bit; sBuffer[row_offset] = data;
} else {
sBuffer[row_offset] &= ~ bit;
}
} }
#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) uint8_t Arduboy2Base::getPixel(uint8_t x, uint8_t y)
{ {
@ -685,7 +697,7 @@ void Arduboy2Base::fillScreen(uint8_t color)
"ldi %[color], 0xFF\n" "ldi %[color], 0xFF\n"
// counter = WIDTH * HEIGHT / 8 / 8 // counter = WIDTH * HEIGHT / 8 / 8
"ldi r24, %[cnt]\n" "ldi r24, %[cnt]\n"
"loopto:\n" "1:\n"
// (4x/8x) store color into screen buffer, // (4x/8x) store color into screen buffer,
// then increment buffer position // then increment buffer position
"st Z+, %[color]\n" "st Z+, %[color]\n"
@ -701,7 +713,7 @@ void Arduboy2Base::fillScreen(uint8_t color)
// decrease counter // decrease counter
"subi r24, 1\n" "subi r24, 1\n"
// repeat for 256, 144 or 192 loops depending on screen resolution // repeat for 256, 144 or 192 loops depending on screen resolution
"brcc loopto\n" "brcc 1b\n"
: [color] "+d" (color), : [color] "+d" (color),
"+z" (bPtr) "+z" (bPtr)
#if defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128) || defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) #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) if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1)
return; return;
int yOffset = abs(y) % 8; int8_t yOffset = y & 7;
int sRow = y / 8; int8_t sRow = y;
if (y < 0) { uint8_t rows = h >> 3;
sRow--;
yOffset = 8 - yOffset;
}
int rows = h/8;
if (h%8!=0) rows++;
for (int a = 0; a < rows; a++) { for (int a = 0; a < rows; a++) {
int bRow = sRow + a; int bRow = sRow + a;
if (bRow > (HEIGHT/8)-1) break; if (bRow > (HEIGHT/8)-1) break;
@ -873,21 +880,22 @@ void Arduboy2Base::drawBitmap
for (int iCol = 0; iCol<w; iCol++) { for (int iCol = 0; iCol<w; iCol++) {
if (iCol + x > (WIDTH-1)) break; if (iCol + x > (WIDTH-1)) break;
if (iCol + x >= 0) { if (iCol + x >= 0) {
uint16_t data = pgm_read_byte(bitmap+(a*w)+iCol) << yOffset;
if (bRow >= 0) { if (bRow >= 0) {
if (color == WHITE) 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) else if (color == BLACK)
sBuffer[(bRow*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) << yOffset); sBuffer[(bRow*WIDTH) + x + iCol] &= ~data;
else 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 (yOffset && bRow<(HEIGHT/8)-1 && bRow > -2) {
if (color == WHITE) 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) 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 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) if ((this->byteBuffer & this->bitBuffer) != 0)
result |= (1 << i); // result |= bitshift_left[i]; result |= (1 << i);
this->bitBuffer += this->bitBuffer; this->bitBuffer += this->bitBuffer;
} }
@ -1192,6 +1200,88 @@ void Arduboy2Base::swap(int16_t& a, int16_t& b)
b = temp; 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 ========== //========== class Arduboy2 ==========

View File

@ -14,7 +14,6 @@
#include "Sprites.h" #include "Sprites.h"
#include "SpritesB.h" #include "SpritesB.h"
#include <Print.h> #include <Print.h>
#include <limits.h>
/** \brief /** \brief
* Library version * Library version
@ -34,7 +33,7 @@
* #endif * #endif
* \endcode * \endcode
*/ */
#define ARDUBOY_LIB_VER 50100 #define ARDUBOY_LIB_VER 50201
// EEPROM settings // EEPROM settings
#define ARDUBOY_UNIT_NAME_LEN 6 /**< The maximum length of the unit name string. */ #define ARDUBOY_UNIT_NAME_LEN 6 /**< The maximum length of the unit name string. */
@ -42,6 +41,8 @@
#define EEPROM_VERSION 0 #define EEPROM_VERSION 0
#define EEPROM_SYS_FLAGS 1 #define EEPROM_SYS_FLAGS 1
#define EEPROM_AUDIO_ON_OFF 2 #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_ID 8 // A uint16_t binary unit ID
#define EEPROM_UNIT_NAME 10 // An up to 6 character unit name. Cannot contain #define EEPROM_UNIT_NAME 10 // An up to 6 character unit name. Cannot contain
// 0x00 or 0xFF. Lengths less than 6 are padded // 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 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 /** \brief
* A rectangle object for collision functions. * A rectangle object for collision functions.
@ -97,6 +106,7 @@
* given width and height. * given width and height.
* *
* \see Arduboy2Base::collide(Point, Rect) Arduboy2Base::collide(Rect, Rect) * \see Arduboy2Base::collide(Point, Rect) Arduboy2Base::collide(Rect, Rect)
* Point
*/ */
struct Rect struct Rect
{ {
@ -104,20 +114,52 @@ struct Rect
int16_t y; /**< The Y coordinate of the top left corner */ int16_t y; /**< The Y coordinate of the top left corner */
uint8_t width; /**< The width of the rectangle */ uint8_t width; /**< The width of the rectangle */
uint8_t height; /**< The height 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 /** \brief
* An object to define a single point for collision functions. * An object to define a single point for collision functions.
* *
* \details * \details
* The location of the point is given by X and Y coordinates. * 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 struct Point
{ {
int16_t x; /**< The X coordinate of the point */ int16_t x; /**< The X coordinate of the point */
int16_t y; /**< The Y 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. * 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. * 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 /** \brief
* Returns the state of the given pixel in the screen buffer. * Returns the state of the given pixel in the screen buffer.
@ -1019,7 +1061,7 @@ class Arduboy2Base : public Arduboy2Core
* *
* \see Point Rect * \see Point Rect
*/ */
bool collide(Point point, Rect rect); static bool collide(Point point, Rect rect);
/** \brief /** \brief
* Test if a rectangle is intersecting with another rectangle. * Test if a rectangle is intersecting with another rectangle.
@ -1036,7 +1078,7 @@ class Arduboy2Base : public Arduboy2Core
* *
* \see Rect * \see Rect
*/ */
bool collide(Rect rect1, Rect rect2); static bool collide(Rect rect1, Rect rect2);
/** \brief /** \brief
* Read the unit ID from system EEPROM. * Read the unit ID from system EEPROM.
@ -1224,6 +1266,60 @@ class Arduboy2Base : public Arduboy2Core
*/ */
void writeShowBootLogoLEDsFlag(bool val); 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 /** \brief
* A counter which is incremented once per frame. * 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 sBuffer[(HEIGHT*WIDTH)/8];
static uint8_t batteryLow;
protected: protected:
// helper function for sound enable/disable system control // helper function for sound enable/disable system control
void sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal); void sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal);
@ -1354,6 +1452,7 @@ class Arduboy2 : public Print, public Arduboy2Base
* *
* \see Arduboy2::write() * \see Arduboy2::write()
*/ */
using Print::write;
/** \brief /** \brief
* Display the boot logo sequence using printed text instead of a bitmap. * Display the boot logo sequence using printed text instead of a bitmap.

View File

@ -6,13 +6,15 @@
#include "Arduboy2Core.h" #include "Arduboy2Core.h"
#include <avr/wdt.h>
const uint8_t PROGMEM lcdBootProgram[] = { const uint8_t PROGMEM lcdBootProgram[] = {
// boot defaults are commented out but left here in case they // boot defaults are commented out but left here in case they
// might prove useful for reference // might prove useful for reference
// //
// Further reading: https://www.adafruit.com/datasheets/SSD1306.pdf // Further reading: https://www.adafruit.com/datasheets/SSD1306.pdf
#ifdef OLED_SH1106 #if defined(OLED_SH1106)
0x8D, 0x14, // Charge Pump Setting v = enable (0x14) 0x8D, 0x14, // Charge Pump Setting v = enable (0x14)
0xA1, // Set Segment Re-map 0xA1, // Set Segment Re-map
0xC8, // Set COM Output Scan Direction 0xC8, // Set COM Output Scan Direction
@ -128,15 +130,17 @@ const uint8_t PROGMEM lcdBootProgram[] = {
// set display mode = horizontal addressing mode (0x00) // set display mode = horizontal addressing mode (0x00)
0x20, 0x00, 0x20, 0x00,
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
// set col address range // set col address range
// 0x21, 0x00, COLUMN_ADDRESS_END, 0x21, 0x00, COLUMN_ADDRESS_END,
// set page address range // set page address range
// 0x22, 0x00, PAGE_ADDRESS_END 0x22, 0x00, PAGE_ADDRESS_END
#endif
#endif #endif
}; };
Arduboy2Core::Arduboy2Core() { } Arduboy2Core::Arduboy2Core() { }
void Arduboy2Core::boot() void Arduboy2Core::boot()
@ -177,6 +181,7 @@ void Arduboy2Core::setCPUSpeed8MHz()
void Arduboy2Core::bootPins() void Arduboy2Core::bootPins()
{ {
#ifdef ARDUBOY_10 #ifdef ARDUBOY_10
// Port B INPUT_PULLUP or HIGH // Port B INPUT_PULLUP or HIGH
PORTB = (_BV(RED_LED_BIT) | _BV(BLUE_LED_BIT) | //RGB LED off PORTB = (_BV(RED_LED_BIT) | _BV(BLUE_LED_BIT) | //RGB LED off
#ifndef AB_ALTERNATE_WIRING #ifndef AB_ALTERNATE_WIRING
@ -203,33 +208,56 @@ void Arduboy2Core::bootPins()
// Port D INPUT_PULLUP or HIGH // Port D INPUT_PULLUP or HIGH
PORTD = ( PORTD = (
#ifdef AB_ALTERNATE_WIRING #if defined(AB_ALTERNATE_WIRING)
_BV(GREEN_LED_BIT) | _BV(GREEN_LED_BIT) |
#endif #endif
#ifndef ARDUINO_AVR_MICRO #if !(defined(ARDUINO_AVR_MICRO))
_BV(TX_LED_BIT) | //TX LED off for Arduboy and non Micro based Arduino _BV(TX_LED_BIT) | //TX LED off for Arduboy and non Micro based Arduino
#endif #endif
_BV(CART_BIT) | _BV(DC_BIT)) & //flash cart inactive, LCD data mode _BV(CART_BIT) |
// Port D INPUT or LOW #if !(defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX))
~(_BV(CS_BIT) | _BV(RST_BIT) //oled chip enabled, reset active _BV(DC_BIT) |
#ifdef AB_ALTERNATE_WIRING
| _BV(SPEAKER_2_BIT)
#endif #endif
#ifdef LCD_ST7565 0) & ~( // Port D INPUTs or LOW outputs
| _BV(POWER_LED_BIT) #if !(defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX))
_BV(CS_BIT) | // oled display enabled
_BV(RST_BIT) | // reset active
#endif #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 // Port D outputs
DDRD = _BV(RST_BIT) | _BV(CS_BIT) | _BV(DC_BIT) | DDRD = (
#ifdef AB_ALTERNATE_WIRING #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) | _BV(GREEN_LED_BIT) |
#endif #endif
#ifdef LCD_ST7565 #if defined(LCD_ST7565)
_BV(POWER_LED_BIT) | _BV(POWER_LED_BIT) |
#endif #endif
_BV(CART_BIT) | _BV(TX_LED_BIT); _BV(CART_BIT) |
// Port D inputs (none) _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 // Port E INPUT_PULLUP or HIGH
PORTE |= _BV(A_BUTTON_BIT); PORTE |= _BV(A_BUTTON_BIT);
@ -294,6 +322,12 @@ void Arduboy2Core::bootPins()
void Arduboy2Core::bootOLED() 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 // reset the display
uint8_t cmd; uint8_t cmd;
const void* ptr = lcdBootProgram; const void* ptr = lcdBootProgram;
@ -301,6 +335,7 @@ void Arduboy2Core::bootOLED()
"1: \n\t" //assembly loop for 2nd delayShort(5) "1: \n\t" //assembly loop for 2nd delayShort(5)
); );
delayShort(5); //for a short active low reset pulse delayShort(5); //for a short active low reset pulse
#if !(defined(AB_ALTERNATE_WIRING) && defined(CART_CS_SDA))
asm volatile( asm volatile(
" sbic %[rst_port], %[rst_bit] \n\t" //continue if reset is active " sbic %[rst_port], %[rst_bit] \n\t" //continue if reset is active
" rjmp 2f \n\t" //else break " rjmp 2f \n\t" //else break
@ -311,7 +346,8 @@ void Arduboy2Core::bootOLED()
: [rst_port] "I" (_SFR_IO_ADDR(RST_PORT)), : [rst_port] "I" (_SFR_IO_ADDR(RST_PORT)),
[rst_bit] "I" (RST_BIT) [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) #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 for (uint16_t i = 0; i < 8192; i++) SPItransfer(0); //make sure all display ram is cleared
#endif #endif
@ -335,16 +371,7 @@ void Arduboy2Core::bootOLED()
: "r25" : "r25"
); );
LCDDataMode(); LCDDataMode();
} #endif
void Arduboy2Core::LCDDataMode()
{
bitSet(DC_PORT, DC_BIT);
}
void Arduboy2Core::LCDCommandMode()
{
bitClear(DC_PORT, DC_BIT);
} }
// Initialize the SPI interface for the display // Initialize the SPI interface for the display
@ -370,6 +397,50 @@ uint8_t Arduboy2Core::SPItransfer(uint8_t data)
return SPDR; 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() void Arduboy2Core::safeMode()
{ {
if (buttonsState() == UP_BUTTON) if (buttonsState() == UP_BUTTON)
@ -403,18 +474,23 @@ void Arduboy2Core::bootPowerSaving()
PRR0 = _BV(PRTWI) | _BV(PRADC); PRR0 = _BV(PRTWI) | _BV(PRADC);
// disable USART1 // disable USART1
PRR1 = _BV(PRUSART1); PRR1 = _BV(PRUSART1);
// All other bits will be written with 0 so will be enabled
} }
// Shut down the display // Shut down the display
void Arduboy2Core::displayOff() 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(); LCDCommandMode();
SPItransfer(0xAE); // display off SPItransfer(0xAE); // display off
SPItransfer(0x8D); // charge pump: SPItransfer(0x8D); // charge pump:
SPItransfer(0x10); // disable SPItransfer(0x10); // disable
delayShort(250); #endif
bitClear(RST_PORT, RST_BIT); // set display reset pin low (reset state)
} }
// Restart the display after a displayOff() // Restart the display after a displayOff()
@ -432,12 +508,23 @@ uint8_t Arduboy2Core::height() { return HEIGHT; }
void Arduboy2Core::paint8Pixels(uint8_t pixels) 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); SPItransfer(pixels);
#endif
} }
void Arduboy2Core::paintScreen(const uint8_t *image) 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++) for (uint8_t i = 0; i < HEIGHT / 8; i++)
{ {
LCDCommandMode(); LCDCommandMode();
@ -486,7 +573,6 @@ void Arduboy2Core::paintScreen(const uint8_t *image)
for (uint8_t row = 0; row < HEIGHT / 8; row++) for (uint8_t row = 0; row < HEIGHT / 8; row++)
{ {
uint8_t b = pgm_read_byte(image + i); uint8_t b = pgm_read_byte(image + i);
if (clear) *(image + i) = 0;
for (uint8_t shift = 0; shift < 4; shift++) for (uint8_t shift = 0; shift < 4; shift++)
{ {
uint8_t c = 0xFF; uint8_t c = 0xFF;
@ -513,7 +599,129 @@ void Arduboy2Core::paintScreen(const uint8_t *image)
// will be used by any buffer based subclass // will be used by any buffer based subclass
void Arduboy2Core::paintScreen(uint8_t image[], bool clear) 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. //Assembly optimized page mode display code with clear support.
//Each byte transfer takes 18 cycles //Each byte transfer takes 18 cycles
asm volatile ( asm volatile (
@ -682,24 +890,77 @@ void Arduboy2Core::paintScreen(uint8_t image[], bool clear)
); );
#endif #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() void Arduboy2Core::blank()
{ {
#ifdef OLED_SH1106 #if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
for (int i = 0; i < (HEIGHT * 132) / 8; i++) i2c_start(SSD1306_I2C_DATA);
#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++) 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); SPItransfer(0x00);
#endif
} }
void Arduboy2Core::sendLCDCommand(uint8_t command) 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(); LCDCommandMode();
SPItransfer(command); SPItransfer(command);
LCDDataMode(); LCDDataMode();
#endif
} }
// invert the display or set to normal // invert the display or set to normal
@ -994,6 +1255,7 @@ void Arduboy2Core::exitToBootloader()
while (true) { } while (true) { }
#else #else
bootloader_timer = 120; //ms bootloader_timer = 120; //ms
while (true) { }
#endif #endif
} }

View File

@ -10,8 +10,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <avr/power.h> #include <avr/power.h>
#include <avr/sleep.h> #include <avr/sleep.h>
#include <avr/wdt.h>
#include <limits.h>
extern volatile unsigned char bootloader_timer; 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 #define RST_BIT PORTD7 // Display reset physical bit number
#endif #endif
#define PIN_DC 4 // Display D/C Arduino pin number #define PIN_DC 4 // Display D/C Arduino pin number
#define DC_PORT PORTD // Display D/C port #define DC_PORT PORTD // Display D/C port
#define DC_BIT PORTD4 // Display D/C physical bit number #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_PORT PORTD
#define CART_BIT PORTD2
#define SPI_MOSI_PORT PORTB #define SPI_MOSI_PORT PORTB
#define SPI_MOSI_BIT PORTB2 #define SPI_MOSI_BIT PORTB2
@ -72,6 +75,34 @@ extern volatile unsigned char bootloader_timer;
#define SPI_SCK_PORT PORTB #define SPI_SCK_PORT PORTB
#define SPI_SCK_BIT PORTB1 #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. */ #define RED_LED 10 /**< The pin number for the red color in the RGB LED. */
#ifdef AB_ALTERNATE_WIRING #ifdef AB_ALTERNATE_WIRING
#define GREEN_LED 3 // Pro Micro alternative green LED pin #define GREEN_LED 3 // Pro Micro alternative green LED pin
@ -429,7 +460,10 @@ class Arduboy2Core
* *
* \see LCDCommandMode() SPItransfer() * \see LCDCommandMode() SPItransfer()
*/ */
inline void static LCDDataMode() __attribute__((always_inline)); void static inline LCDDataMode() __attribute__((always_inline))
{
bitSet(DC_PORT, DC_BIT);
}
/** \brief /** \brief
* Put the display into command mode. * Put the display into command mode.
* *
@ -452,7 +486,10 @@ class Arduboy2Core
* *
* \see LCDDataMode() sendLCDCommand() SPItransfer() * \see LCDDataMode() sendLCDCommand() SPItransfer()
*/ */
inline void static LCDCommandMode() __attribute__((always_inline)); void static inline LCDCommandMode() __attribute__((always_inline))
{
bitClear(DC_PORT, DC_BIT);
}
/** \brief /** \brief
* Transfer a byte to the display. * Transfer a byte to the display.
* *
@ -468,6 +505,21 @@ class Arduboy2Core
*/ */
uint8_t static SPItransfer(uint8_t data); 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 /** \brief
* Turn the display off. * Turn the display off.
* *

View File

@ -180,53 +180,238 @@ void Sprites::drawBitmap(int16_t x, int16_t y,
sRow += start_h; sRow += start_h;
ofs = (sRow * WIDTH) + x + xOffset; ofs = (sRow * WIDTH) + x + xOffset;
uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset;
uint8_t data;
uint8_t mul_amt = 1 << yOffset; uint8_t mul_amt = 1 << yOffset;
uint16_t mask_data; uint16_t mask_data;
uint16_t bitmap_data; uint16_t bitmap_data;
const uint8_t ofs_step = draw_mode == SPRITE_PLUS_MASK ? 2 : 1; switch (draw_mode) {
const uint8_t ofs_stride = (w - rendered_width)*ofs_step; case SPRITE_UNMASKED:
const uint16_t initial_bofs = ((start_h * w) + xOffset)*ofs_step; // 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; if (sRow >= 0) {
const uint8_t *mask_ofs = !mask ? bitmap : mask; data = Arduboy2Base::sBuffer[ofs];
mask_ofs += initial_bofs + ofs_step - 1; data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
for (uint8_t a = 0; a < loop_h; a++) { Arduboy2Base::sBuffer[ofs] = data;
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) { }
uint8_t data; if (yOffset != 0 && sRow < 7) {
data = Arduboy2Base::sBuffer[ofs + WIDTH];
bitmap_data = pgm_read_byte(bofs) * mul_amt; data &= (*((unsigned char *) (&mask_data) + 1));
mask_data = ~bitmap_data; data |= (*((unsigned char *) (&bitmap_data) + 1));
Arduboy2Base::sBuffer[ofs + WIDTH] = data;
if (draw_mode == SPRITE_UNMASKED) { }
mask_data = ~(0xFF * mul_amt); ofs++;
} else if (draw_mode == SPRITE_IS_MASK_ERASE) { bofs++;
bitmap_data = 0; }
} else { sRow++;
mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt); bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
} }
break;
if (sRow >= 0) { case SPRITE_IS_MASK:
data = Arduboy2Base::sBuffer[ofs]; for (uint8_t a = 0; a < loop_h; a++) {
data &= (uint8_t)(mask_data); for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
data |= (uint8_t)(bitmap_data); bitmap_data = pgm_read_byte(bofs) * mul_amt;
Arduboy2Base::sBuffer[ofs] = data; 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)) { break;
data = Arduboy2Base::sBuffer[ofs + WIDTH];
data &= (*((unsigned char *) (&mask_data) + 1)); case SPRITE_IS_MASK_ERASE:
data |= (*((unsigned char *) (&bitmap_data) + 1)); for (uint8_t a = 0; a < loop_h; a++) {
Arduboy2Base::sBuffer[ofs + WIDTH] = data; 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++; break;
mask_ofs += ofs_step;
bofs += ofs_step; case SPRITE_MASKED:
} uint8_t *mask_ofs;
sRow++; mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset;
bofs += ofs_stride; for (uint8_t a = 0; a < loop_h; a++) {
mask_ofs += ofs_stride; for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
ofs += WIDTH - rendered_width; // 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;
} }
} }

View File

@ -89,6 +89,9 @@ class Sprites
* An array containing the image frames, and another array containing * An array containing the image frames, and another array containing
* corresponding mask frames, are used to draw a sprite. * 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 * 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 * value of the corresponding image bit. Bits set to 0 in the mask will be
* left unchanged. * left unchanged.