Multiple OLED support

added multiple oled display support
added flexible width and height support
optimized buttonsState with optional bootlkey support
This commit is contained in:
Mr.Blinky 2017-11-19 19:41:30 +01:00 committed by GitHub
parent 6360eb2b37
commit a0c58545e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 3350 additions and 0 deletions

1322
Arduboy2.cpp Normal file

File diff suppressed because it is too large Load Diff

696
Arduboy2Core.cpp Normal file
View File

@ -0,0 +1,696 @@
/**
* @file Arduboy2Core.cpp
* \brief
* The Arduboy2Core class for Arduboy hardware initilization and control.
*/
#include "Arduboy2Core.h"
#include <avr/wdt.h>
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
0x8D, 0x14, // Charge Pump Setting v = enable (0x14)
0xA1, // Set Segment Re-map
0xC8, // Set COM Output Scan Direction
0x81, 0xCF, // Set Contrast v = 0xCF
0xD9, 0xF1, // Set Precharge = 0xF1
OLED_SET_COLUMN_ADDRESS_LO, //Set column address for left most pixel
0xAF // Display On
#elif defined(OLED_96X96)
0x15, 0x10, 0x3f, //Set column start and end address
0x75, 0x00, 0x5f, //Set row start and end address
0xA0, 0x55, //set re-map: split odd-even COM signals|COM remap|vertical address increment|column address remap
0xA1, 0x00, //set display start line
0xA2, 0x60, //set display offset
//0xA4, //Normal display
0xA8, 0x5F, //Set MUX ratio 96MUX
//0xB2, 0x23,
//0xB3, 0xF0, //set devider clock | oscillator frequency
//0x81, 0x0F, //Set contrast
//0xBC, 0x1F, //set precharge voltage
//0x82, 0xFE, //set second Precharge speed
0xB1, 0x21, //reset and 1st precharge phase length phase 2:2 DCLKs, Phase 1: 1 DCLKs
//0xBB, 0x0F, //set 2nd precharge period: 15 DCLKs
//0xbe, 0x1F, //output level high voltage com signal
//0xB8, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, //set gray scale table
0xAF //Display on
#else
// for SSD1306 and compatible displays
//
// Display Off
// 0xAE,
// Set Display Clock Divisor v = 0xF0
// default is 0x80
0xD5, 0xF0,
// Set Multiplex Ratio v = 0x3F
// 0xA8, 0x3F,
// Set Display Offset v = 0
// 0xD3, 0x00,
// Set Start Line (0)
// 0x40,
// Charge Pump Setting v = enable (0x14)
// default is disabled
0x8D, 0x14,
// Set Segment Re-map (A0) | (b0001)
// default is (b0000)
0xA1,
// Set COM Output Scan Direction
0xC8,
// Set COM Pins v
// 0xDA, 0x12,
// Set Contrast v = 0xCF
0x81, 0xCF,
// Set Precharge = 0xF1
0xD9, 0xF1,
// Set VCom Detect
// 0xDB, 0x40,
// Entire Display ON
// 0xA4,
// Set normal/inverse display
// 0xA6,
// Display On
0xAF,
// set display mode = horizontal addressing mode (0x00)
0x20, 0x00,
// set col address range
// 0x21, 0x00, COLUMN_ADDRESS_END,
// set page address range
// 0x22, 0x00, PAGE_ADDRESS_END
#endif
#if defined OLED_SSD1309 //required additionally for SSD1309
0x21, 0x00, COLUMN_ADDRESS_END
#endif
};
Arduboy2Core::Arduboy2Core() { }
void Arduboy2Core::boot()
{
#ifdef ARDUBOY_SET_CPU_8MHZ
// ARDUBOY_SET_CPU_8MHZ will be set by the IDE using boards.txt
setCPUSpeed8MHz();
#endif
// Select the ADC input here so a delay isn't required in initRandomSeed()
ADMUX = RAND_SEED_IN_ADMUX;
bootPins();
bootSPI();
bootOLED();
bootPowerSaving();
}
#ifdef ARDUBOY_SET_CPU_8MHZ
// If we're compiling for 8MHz we need to slow the CPU down because the
// hardware clock on the Arduboy is 16MHz.
// We also need to readjust the PLL prescaler because the Arduino USB code
// likely will have incorrectly set it for an 8MHz hardware clock.
void Arduboy2Core::setCPUSpeed8MHz()
{
uint8_t oldSREG = SREG;
cli(); // suspend interrupts
PLLCSR = _BV(PINDIV); // dissable the PLL and set prescale for 16MHz)
CLKPR = _BV(CLKPCE); // allow reprogramming clock
CLKPR = 1; // set clock divisor to 2 (0b0001)
PLLCSR = _BV(PLLE) | _BV(PINDIV); // enable the PLL (with 16MHz prescale)
SREG = oldSREG; // restore interrupts
}
#endif
// Pins are set to the proper modes and levels for the specific hardware.
// This routine must be modified if any pins are moved to a different port
void Arduboy2Core::bootPins()
{
#ifdef ARDUBOY_10
// Port B INPUT_PULLUP or HIGH
PORTB |= _BV(RED_LED_BIT) |
#ifndef ARDUINO_AVR_PROMICRO
_BV(GREEN_LED_BIT) |
#endif
_BV(BLUE_LED_BIT) | _BV(B_BUTTON_BIT);
// Port B INPUT or LOW (none)
// Port B inputs
DDRB &= ~(_BV(B_BUTTON_BIT));
// Port B outputs
DDRB |= _BV(RED_LED_BIT) |
#ifndef ARDUINO_AVR_PROMICRO
_BV(GREEN_LED_BIT) |
#endif
_BV(BLUE_LED_BIT) | _BV(SPI_MOSI_BIT) | _BV(SPI_SCK_BIT);
// Port C
// Speaker: Not set here. Controlled by audio class
// Port D INPUT_PULLUP or HIGH
#ifdef ARDUINO_AVR_PROMICRO
PORTD |= _BV(CS_BIT) | _BV(GREEN_LED_BIT);
#else
PORTD |= _BV(CS_BIT);
#endif
// Port D INPUT or LOW
PORTD &= ~(_BV(RST_BIT));
// Port D inputs (none)
// Port D outputs
DDRD |= _BV(RST_BIT) | _BV(CS_BIT) |
#ifdef ARDUINO_AVR_PROMICRO
_BV(GREEN_LED_BIT) |
#endif
_BV(DC_BIT);
// Port E INPUT_PULLUP or HIGH
PORTE |= _BV(A_BUTTON_BIT);
// Port E INPUT or LOW (none)
// Port E inputs
DDRE &= ~(_BV(A_BUTTON_BIT));
// Port E outputs (none)
// Port F INPUT_PULLUP or HIGH
PORTF |= _BV(LEFT_BUTTON_BIT) | _BV(RIGHT_BUTTON_BIT) |
_BV(UP_BUTTON_BIT) | _BV(DOWN_BUTTON_BIT);
// Port F INPUT or LOW
PORTF &= ~(_BV(RAND_SEED_IN_BIT));
// Port F inputs
DDRF &= ~(_BV(LEFT_BUTTON_BIT) | _BV(RIGHT_BUTTON_BIT) |
_BV(UP_BUTTON_BIT) | _BV(DOWN_BUTTON_BIT) |
_BV(RAND_SEED_IN_BIT));
// Port F outputs (none)
#elif defined(AB_DEVKIT)
// Port B INPUT_PULLUP or HIGH
PORTB |= _BV(LEFT_BUTTON_BIT) | _BV(UP_BUTTON_BIT) | _BV(DOWN_BUTTON_BIT) |
_BV(BLUE_LED_BIT);
// Port B INPUT or LOW (none)
// Port B inputs
DDRB &= ~(_BV(LEFT_BUTTON_BIT) | _BV(UP_BUTTON_BIT) | _BV(DOWN_BUTTON_BIT));
// Port B outputs
DDRB |= _BV(BLUE_LED_BIT) | _BV(SPI_MOSI_BIT) | _BV(SPI_SCK_BIT);
// Port C INPUT_PULLUP or HIGH
PORTC |= _BV(RIGHT_BUTTON_BIT);
// Port C INPUT or LOW (none)
// Port C inputs
DDRC &= ~(_BV(RIGHT_BUTTON_BIT));
// Port C outputs (none)
// Port D INPUT_PULLUP or HIGH
PORTD |= _BV(CS_BIT);
// Port D INPUT or LOW
PORTD &= ~(_BV(RST_BIT));
// Port D inputs (none)
// Port D outputs
DDRD |= _BV(RST_BIT) | _BV(CS_BIT) | _BV(DC_BIT);
// Port E (none)
// Port F INPUT_PULLUP or HIGH
PORTF |= _BV(A_BUTTON_BIT) | _BV(B_BUTTON_BIT);
// Port F INPUT or LOW
PORTF &= ~(_BV(RAND_SEED_IN_BIT));
// Port F inputs
DDRF &= ~(_BV(A_BUTTON_BIT) | _BV(B_BUTTON_BIT) | _BV(RAND_SEED_IN_BIT));
// Port F outputs (none)
// Speaker: Not set here. Controlled by audio class
#endif
}
void Arduboy2Core::bootOLED()
{
// reset the display
delayShort(5); // reset pin should be low here. let it stay low a while
bitSet(RST_PORT, RST_BIT); // set high to come out of reset
delayShort(5); // wait a while
// select the display (permanently, since nothing else is using SPI)
bitClear(CS_PORT, CS_BIT);
// run our customized boot-up command sequence against the
// OLED to initialize it properly for Arduboy
LCDCommandMode();
for (uint8_t i = 0; i < sizeof(lcdBootProgram); i++) {
SPItransfer(pgm_read_byte(lcdBootProgram + i));
}
LCDDataMode();
}
void Arduboy2Core::LCDDataMode()
{
bitSet(DC_PORT, DC_BIT);
}
void Arduboy2Core::LCDCommandMode()
{
bitClear(DC_PORT, DC_BIT);
}
// Initialize the SPI interface for the display
void Arduboy2Core::bootSPI()
{
// master, mode 0, MSB first, CPU clock / 2 (8MHz)
SPCR = _BV(SPE) | _BV(MSTR);
SPSR = _BV(SPI2X);
}
// Write to the SPI bus (MOSI pin)
void Arduboy2Core::SPItransfer(uint8_t data)
{
SPDR = data;
/*
* The following NOP introduces a small delay that can prevent the wait
* loop form iterating when running at the maximum speed. This gives
* about 10% more speed, even if it seems counter-intuitive. At lower
* speeds it is unnoticed.
*/
asm volatile("nop");
while (!(SPSR & _BV(SPIF))) { } // wait
}
void Arduboy2Core::safeMode()
{
if (buttonsState() == UP_BUTTON)
{
digitalWriteRGB(RED_LED, RGB_ON);
// prevent the bootloader magic number from being overwritten by timer 0
// when a timer variable overlaps the magic number location
power_timer0_disable();
while (true) { }
}
}
/* Power Management */
void Arduboy2Core::idle()
{
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_mode();
}
void Arduboy2Core::bootPowerSaving()
{
// disable Two Wire Interface (I2C) and the ADC
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()
{
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)
}
// Restart the display after a displayOff()
void Arduboy2Core::displayOn()
{
bootOLED();
}
uint8_t Arduboy2Core::width() { return WIDTH; }
uint8_t Arduboy2Core::height() { return HEIGHT; }
/* Drawing */
void Arduboy2Core::paint8Pixels(uint8_t pixels)
{
SPItransfer(pixels);
}
void Arduboy2Core::paintScreen(const uint8_t *image)
{
#ifdef OLED_SH1106
for (uint8_t i = 0; i < HEIGHT / 8; i++)
{
LCDCommandMode();
SPDR = (OLED_SET_PAGE_ADDRESS + i);
while (!(SPSR & _BV(SPIF)));
SPDR = (OLED_SET_COLUMN_ADDRESS_HI); // only reset hi nibble to zero
while (!(SPSR & _BV(SPIF)));
LCDDataMode();
for (uint8_t j = WIDTH; j > 0; j--)
{
SPDR = pgm_read_byte(image++);
while (!(SPSR & _BV(SPIF)));
}
}
#elif defined(OLED_96X96)
uint16_t i = 0;
for (uint8_t col = 0; col < WIDTH / 2; col++)
{
for (uint8_t row = 0; row < HEIGHT / 8; row++)
{
uint8_t b1 = pgm_read_byte(image + i);
uint8_t b2 = pgm_read_byte(image + i + 1);
for (uint8_t shift = 0; shift < 8; shift++)
{
uint8_t c = 0xFF;
if ((b1 & 1) == 0) c &= 0x0F;
if ((b2 & 1) == 0) c &= 0xF0;
SPDR = c;
b1 = b1 >> 1;
b2 = b2 >> 1;
while (!(SPSR & _BV(SPIF)));
}
i += WIDTH;
}
i -= HEIGHT / 8 * WIDTH - 2;
}
#else
//OLED SSD1306 and compatibles
for (int i = 0; i < (HEIGHT*WIDTH)/8; i++)
{
SPItransfer(pgm_read_byte(image + i));
}
#endif
}
// paint from a memory buffer, this should be FAST as it's likely what
// will be used by any buffer based subclass
void Arduboy2Core::paintScreen(uint8_t image[], bool clear)
{
#ifdef OLED_SH1106
//Assembly optimized page mode display code with clear support.
//Each byte transfer takes 18 cycles
asm volatile (
" ldi r25, %[page_cmd] \n\t"
".l1: \n\t"
" ldi r24, %[width] ;1 \n\t"
" ldi r20,5 ;1 \n\t"
" cbi %[dc_port], %[dc_bit] ;2 cmd mode \n\t"
" out %[spdr], r25 ;1 \n\t"
".l2: subi r20,1 ;r20*3-1 : 14 \n\t"
" brne .l2 \n\t"
" rjmp .+0 ;2 \n\t"
" ldi r20,%[col_cmd] ;1 \n\t"
" out %[spdr], r20 ;1 \n\t"
".l3: rjmp .l7 ;2 \n\t"
".l4: ld r20, Z ;2 \n\t"
" cp %[clear], __zero_reg__ ;1 \n\t"
" brne .l5 ;1/2 \n\t"
" nop ;1 \n\t"
" rjmp .l6 ;2 \n\t"
".l5: st Z, __zero_reg__ ;2 : 7 \n\t"
".l6: sbi %[dc_port], %[dc_bit] ;2 data mode \n\t"
" out %[spdr], r20 ;1 \n\t"
" adiw r30, 1 ;2 \n\t"
".l7: rjmp .+0 ;2 \n\t"
" nop ;1 \n\t"
" subi r24, 1 ;1 \n\t"
" brne .l4 ;1/2 : 5/6 \n\t"
" rjmp .+0 ;2 \n\t"
" subi r25, -1 ;1 \n\t"
" cpi r25,%[page_end] ;1 \n\t"
" brne .l1 ;1/2 : 5/6 \n\t"
:
: [ptr] "z" (image),
[page_cmd] "M" (OLED_SET_PAGE_ADDRESS),
[page_end] "M" (OLED_SET_PAGE_ADDRESS + (HEIGHT / 8)),
[dc_port] "I" (_SFR_IO_ADDR(DC_PORT)),
[dc_bit] "I" (DC_BIT),
[spdr] "I" (_SFR_IO_ADDR(SPDR)),
[col_cmd] "M" (OLED_SET_COLUMN_ADDRESS_HI),
[width] "M" (WIDTH + 1),
[clear] "a" (clear)
: "r20", "r24", "r25"
);
#elif defined(OLED_96X96)
// 1 bit to 4-bit display code with clear support.
// Each transfer takes 18 cycles with additional 4 cycles for a column change.
asm volatile(
" ldi r25, %[col] \n\t"
".lcolumn: \n\t"
" ldi r24, %[row] ;1 \n\t"
".lrow: \n\t"
" ldi r21, 7 ;1 \n\t"
" ld r22, z ;2 \n\t"
" ldd r23, z+1 ;2 \n\t"
".lshiftstart: \n\t"
" ldi r20, 0xFF ;1 \n\t"
" sbrs r22, 0 ;1 \n\t"
" andi r20, 0x0f ;1 \n\t"
" sbrs r23, 0 ;1 \n\t"
" andi r20,0xf0 ;1 \n\t"
" out %[spdr], r20 ;1 \n\t"
" \n\t"
" cp %[clear], __zero_reg__ ;1 \n\t"
" brne .lclear1 ;1/2 \n\t"
".lshiftothers: \n\t"
" movw r18, %A[ptr] ;1 \n\t"
" rjmp .+0 ;2 \n\t"
" rjmp .lshiftnext ;2 \n\t"
".lclear1: \n\t"
" st z, __zero_reg__ ;2 \n\t"
" std z+1, __zero_reg__ ;2 \n\t"
".lshiftnext: \n\t"
" \n\t"
" lsr r22 ;1 \n\t"
" lsr r23 ;1 \n\t"
" \n\t"
" ldi r20, 0xFF ;1 \n\t"
" sbrs r22, 0 ;1/2 \n\t"
" andi r20, 0x0f ;1 \n\t"
" sbrs r23, 0 ;1/2 \n\t"
" andi r20,0xf0 ;1 \n\t"
" \n\t"
" subi r18, %[top_lsb] ;1 \n\t" //image - (HEIGHT / 8) * ((WIDTH / 8) - 1) + 2
" sbci r19, %[top_msb] ;1 \n\t"
" subi r21, 1 ;1 \n\t"
" out %[spdr], r20 ;1 \n\t"
" brne .lshiftothers ;1/2 \n\t"
" \n\t"
" nop ;1 \n\t"
" subi %A[ptr], %[width] ;1 \n\t" //image + width (negated addition)
" sbci %B[ptr], -1 ;1 \n\t"
" subi r24, 1 ;1 \n\t"
" brne .lrow ;1/2 \n\t"
" \n\t"
" movw %A[ptr], r18 ;1 \n\t"
" subi r25, 1 ;1 \n\t"
" brne .lcolumn ;1/2 \n\t"
:
: [ptr] "z" (image),
[spdr] "I" (_SFR_IO_ADDR(SPDR)),
[row] "M" (HEIGHT / 8),
[col] "M" (WIDTH / 2),
[width] "M" (256 - WIDTH),
[top_lsb] "M" ((WIDTH * ((HEIGHT / 8) - 1) - 2) & 0xFF),
[top_msb] "M" ((WIDTH * ((HEIGHT / 8) - 1) - 2) >> 8),
[clear] "a" (clear)
: "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25"
);
#else
//OLED SSD1306 and compatibles
//data only transfer with clear support at 18 cycles per transfer
asm volatile (
" ldi r24,%[len_lsb] \n\t"
" ldi r25,%[len_msb] \n\t"
".l1: ld r20, Z ;2 \n\t"
" out %[spdr], r20 ;1 \n\t"
" cp %[clear], __zero_reg__ ;1 \n\t" //if (clear) *(image++) = 0
" breq .l2 ;1/2 : 5/6 \n\t"
" st Z+, __zero_reg__ ;2 \n\t"
" rjmp .l3 ;2 \n\t"
".l2: \n\t"
" adiw r30, 1 ;2 \n\t" // else *(image++)
" nop ;1 \n\t"
".l3: \n\t"
" rjmp .+0 ;2 \n\t"
" rjmp .+0 ;2 \n\t"
" rjmp .+0 ;2 \n\t"
" sbiw r24, 1 ;1 \n\t"
" brne .l1 ;1/2 : 18 \n\t"
:
: [ptr] "z" (image),
[spdr] "I" (_SFR_IO_ADDR(SPDR)),
[len_msb] "M" (WIDTH * (HEIGHT / 8) >> 8),
[len_lsb] "M" (WIDTH * (HEIGHT / 8) & 0xFF),
[clear] "a" (clear)
: "r20", "r24", "r25"
);
#endif
while (!(SPSR & _BV(SPIF))); // wait for the last transfer to finish
}
void Arduboy2Core::blank()
{
#ifdef OLED_SH1106
for (int i = 0; i < (HEIGHT*132)/8; i++)
SPItransfer(0x00);
#elif defined(OLED_96X96)
for (int i = 0; i < (HEIGHT*WIDTH)/2; i++)
SPItransfer(0x00);
#else //OLED SSD1306 and compatibles
for (int i = 0; i < (HEIGHT*WIDTH)/8; i++)
SPItransfer(0x00);
#endif
}
void Arduboy2Core::sendLCDCommand(uint8_t command)
{
LCDCommandMode();
SPItransfer(command);
LCDDataMode();
}
// invert the display or set to normal
// when inverted, a pixel set to 0 will be on
void Arduboy2Core::invert(bool inverse)
{
sendLCDCommand(inverse ? OLED_PIXELS_INVERTED : OLED_PIXELS_NORMAL);
}
// turn all display pixels on, ignoring buffer contents
// or set to normal buffer display
void Arduboy2Core::allPixelsOn(bool on)
{
sendLCDCommand(on ? OLED_ALL_PIXELS_ON : OLED_PIXELS_FROM_RAM);
}
// flip the display vertically or set to normal
void Arduboy2Core::flipVertical(bool flipped)
{
sendLCDCommand(flipped ? OLED_VERTICAL_FLIPPED : OLED_VERTICAL_NORMAL);
}
// flip the display horizontally or set to normal
void Arduboy2Core::flipHorizontal(bool flipped)
{
sendLCDCommand(flipped ? OLED_HORIZ_FLIPPED : OLED_HORIZ_NORMAL);
}
/* RGB LED */
void Arduboy2Core::setRGBled(uint8_t red, uint8_t green, uint8_t blue)
{
#ifdef ARDUBOY_10 // RGB, all the pretty colors
// inversion is necessary because these are common annode LEDs
analogWrite(RED_LED, 255 - red);
analogWrite(GREEN_LED, 255 - green);
analogWrite(BLUE_LED, 255 - blue);
#elif defined(AB_DEVKIT)
// only blue on DevKit, which is not PWM capable
(void)red; // parameter unused
(void)green; // parameter unused
bitWrite(BLUE_LED_PORT, BLUE_LED_BIT, blue ? RGB_ON : RGB_OFF);
#endif
}
void Arduboy2Core::digitalWriteRGB(uint8_t red, uint8_t green, uint8_t blue)
{
#ifdef ARDUBOY_10
bitWrite(RED_LED_PORT, RED_LED_BIT, red);
bitWrite(GREEN_LED_PORT, GREEN_LED_BIT, green);
bitWrite(BLUE_LED_PORT, BLUE_LED_BIT, blue);
#elif defined(AB_DEVKIT)
// only blue on DevKit
(void)red; // parameter unused
(void)green; // parameter unused
bitWrite(BLUE_LED_PORT, BLUE_LED_BIT, blue);
#endif
}
void Arduboy2Core::digitalWriteRGB(uint8_t color, uint8_t val)
{
#ifdef ARDUBOY_10
if (color == RED_LED)
{
bitWrite(RED_LED_PORT, RED_LED_BIT, val);
}
else if (color == GREEN_LED)
{
bitWrite(GREEN_LED_PORT, GREEN_LED_BIT, val);
}
else if (color == BLUE_LED)
{
bitWrite(BLUE_LED_PORT, BLUE_LED_BIT, val);
}
#elif defined(AB_DEVKIT)
// only blue on DevKit
if (color == BLUE_LED)
{
bitWrite(BLUE_LED_PORT, BLUE_LED_BIT, val);
}
#endif
}
/* Buttons */
uint8_t Arduboy2Core::buttonsState()
{
uint8_t buttons;
// using ports here is ~100 bytes smaller than digitalRead()
#ifdef AB_DEVKIT
// down, left, up
buttons = ((~PINB) & B01110000);
// right button
if ((PINC & _BV(6)) == 0) buttons |= RIGHT_BUTTON; //compiles to shorter and faster code
// A and B
if ((PINF & _BV(7)) == 0) buttons |= A_BUTTON;
if ((PINF & _BV(6)) == 0) buttons |= B_BUTTON;
#elif defined(ARDUBOY_10)
// down, up, left right
buttons = ((~PINF) & B11110000);
// A (left)
if ((PINE & _BV(6)) == 0) {buttons |= A_BUTTON;}
// B (right)
if ((PINB & _BV(4)) == 0) {buttons |= B_BUTTON;}
#endif
#ifdef ENABLE_BOOTLOADER_KEYS
//bootloader button combo
if (buttons == (LEFT_BUTTON | UP_BUTTON | A_BUTTON | B_BUTTON))
{ cli();
//set magic boot key
*(uint8_t *)0x0800 = 0x77;//using uint8_t saves an instruction
*(uint8_t *)0x0801 = 0x77;
//enable and trigger watchdog by timeout
wdt_enable(WDTO_15MS);
while (true);
}
#endif
return buttons;
}
// delay in ms with 16 bit duration
void Arduboy2Core::delayShort(uint16_t ms)
{
delay((unsigned long) ms);
}

735
Arduboy2Core.h Normal file
View File

@ -0,0 +1,735 @@
/**
* @file Arduboy2Core.h
* \brief
* The Arduboy2Core class for Arduboy hardware initilization and control.
*/
#ifndef ARDUBOY2_CORE_H
#define ARDUBOY2_CORE_H
#include <Arduino.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <limits.h>
// main hardware compile flags
#if !defined(ARDUBOY_10) && !defined(AB_DEVKIT)
/* defaults to Arduboy Release 1.0 if not using a boards.txt file
*
* we default to Arduboy Release 1.0 if a compile flag has not been
* passed to us from a boards.txt file
*
* if you wish to compile for the devkit without using a boards.txt
* file simply comment out the ARDUBOY_10 define and uncomment
* the AB_DEVKIT define like this:
*
* // #define ARDUBOY_10
* #define AB_DEVKIT
*/
#define ARDUBOY_10 //< compile for the production Arduboy v1.0
// #define AB_DEVKIT //< compile for the official dev kit
#endif
#define RGB_ON LOW /**< For digitially setting an RGB LED on using digitalWriteRGB() */
#define RGB_OFF HIGH /**< For digitially setting an RGB LED off using digitalWriteRGB() */
// ----- Arduboy pins -----
#ifdef ARDUBOY_10
#ifdef ARDUINO_AVR_PROMICRO
#define PIN_CS 3 // Pro Micro alternative display CS pin
#define CS_PORT PORTD
#define CS_BIT PORTD3
#else
#define PIN_CS 12 // Display CS Arduino pin number
#define CS_PORT PORTD // Display CS port
#define CS_BIT PORTD6 // Display CS 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_RST 6 // Display reset Arduino pin number
#define RST_PORT PORTD // Display reset port
#define RST_BIT PORTD7 // Display reset physical bit number
#define SPI_MOSI_PORT PORTB
#define SPI_MOSI_BIT PORTB2
#define SPI_SCK_PORT PORTB
#define SPI_SCK_BIT PORTB1
#define RED_LED 10 /**< The pin number for the red color in the RGB LED. */
#ifdef ARDUINO_AVR_PROMICRO
#define GREEN_LED 3 // Pro Micro alternative green LED pin
#else
#define GREEN_LED 11 /**< The pin number for the green color in the RGB LED. */
#endif
#define BLUE_LED 9 /**< The pin number for the blue color in the RGB LED. */
#define RED_LED_PORT PORTB
#define RED_LED_BIT PORTB6
#ifdef ARDUINO_AVR_PROMICRO
#define GREEN_LED_PORT PORTD // Pro Micro alternative green LED port
#define GREEN_LED_BIT PORTD0
#else
#define GREEN_LED_PORT PORTB
#define GREEN_LED_BIT PORTB7
#endif
#define BLUE_LED_PORT PORTB
#define BLUE_LED_BIT PORTB5
// bit values for button states
// these are determined by the buttonsState() function
#define LEFT_BUTTON _BV(5) /**< The Left button value for functions requiring a bitmask */
#define RIGHT_BUTTON _BV(6) /**< The Right button value for functions requiring a bitmask */
#define UP_BUTTON _BV(7) /**< The Up button value for functions requiring a bitmask */
#define DOWN_BUTTON _BV(4) /**< The Down button value for functions requiring a bitmask */
#define A_BUTTON _BV(3) /**< The A button value for functions requiring a bitmask */
#define B_BUTTON _BV(2) /**< The B button value for functions requiring a bitmask */
#define PIN_LEFT_BUTTON A2
#define LEFT_BUTTON_PORT PORTF
#define LEFT_BUTTON_BIT PORTF5
#define PIN_RIGHT_BUTTON A1
#define RIGHT_BUTTON_PORT PORTF
#define RIGHT_BUTTON_BIT PORTF6
#define PIN_UP_BUTTON A0
#define UP_BUTTON_PORT PORTF
#define UP_BUTTON_BIT PORTF7
#define PIN_DOWN_BUTTON A3
#define DOWN_BUTTON_PORT PORTF
#define DOWN_BUTTON_BIT PORTF4
#define PIN_A_BUTTON 7
#define A_BUTTON_PORT PORTE
#define A_BUTTON_BIT PORTE6
#define PIN_B_BUTTON 8
#define B_BUTTON_PORT PORTB
#define B_BUTTON_BIT PORTB4
#define PIN_SPEAKER_1 5 /**< The pin number of the first lead of the speaker */
#define SPEAKER_1_PORT PORTC
#define SPEAKER_1_DDR DDRC
#define SPEAKER_1_BIT PORTC6
#ifdef ARDUINO_AVR_PROMICRO
#define PIN_SPEAKER_2 2 //Pro Micro alternative for 2nd speaker pin
#define SPEAKER_2_PORT PORTD
#define SPEAKER_2_DDR DDRD
#define SPEAKER_2_BIT PORTD1
#else
#define PIN_SPEAKER_2 13 /**< The pin number of the second lead of the speaker */
#define SPEAKER_2_PORT PORTC
#define SPEAKER_2_DDR DDRC
#define SPEAKER_2_BIT PORTC7
#endif
#define RAND_SEED_IN A4 // Open analog input used for noise by initRandomSeed()
#define RAND_SEED_IN_PORTF
#define RAND_SEED_IN_BIT PORTF1
// Value for ADMUX to read the random seed pin: 2.56V reference, ADC1
#define RAND_SEED_IN_ADMUX (_BV(REFS0) | _BV(REFS1) | _BV(MUX0))
// -----------------------
// ----- DevKit pins -----
#elif defined(AB_DEVKIT)
#define PIN_CS 6 // Display CS Arduino pin number
#define CS_PORT PORTD // Display CS port
#define CS_BIT PORTD7 // Display CS 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_RST 12 // Display reset Arduino pin number
#define RST_PORT PORTD // Display reset port
#define RST_BIT PORTD6 // Display reset physical bit number
#define SPI_MOSI_PORT PORTB
#define SPI_MOSI_BIT PORTB2
#define SPI_SCK_PORT PORTB
#define SPI_SCK_BIT PORTB1
// map all LEDs to the single TX LED on DEVKIT
#define RED_LED 17
#define GREEN_LED 17
#define BLUE_LED 17
#define BLUE_LED_PORT PORTB
#define BLUE_LED_BIT PORTB0
// bit values for button states
// these are determined by the buttonsState() function
#define LEFT_BUTTON _BV(5)
#define RIGHT_BUTTON _BV(2)
#define UP_BUTTON _BV(4)
#define DOWN_BUTTON _BV(6)
#define A_BUTTON _BV(1)
#define B_BUTTON _BV(0)
// pin values for buttons, probably shouldn't use these
#define PIN_LEFT_BUTTON 9
#define LEFT_BUTTON_PORT PORTB
#define LEFT_BUTTON_BIT PORTB5
#define PIN_RIGHT_BUTTON 5
#define RIGHT_BUTTON_PORT PORTC
#define RIGHT_BUTTON_BIT PORTC6
#define PIN_UP_BUTTON 8
#define UP_BUTTON_PORT PORTB
#define UP_BUTTON_BIT PORTB4
#define PIN_DOWN_BUTTON 10
#define DOWN_BUTTON_PORT PORTB
#define DOWN_BUTTON_BIT PORTB6
#define PIN_A_BUTTON A0
#define A_BUTTON_PORT PORTF
#define A_BUTTON_BIT PORTF7
#define PIN_B_BUTTON A1
#define B_BUTTON_PORT PORTF
#define B_BUTTON_BIT PORTF6
#define PIN_SPEAKER_1 A2
#define SPEAKER_1_PORT PORTF
#define SPEAKER_1_DDR DDRF
#define SPEAKER_1_BIT PORTF5
// SPEAKER_2 is purposely not defined for DEVKIT as it could potentially
// be dangerous and fry your hardware (because of the devkit wiring).
//
// Reference: https://github.com/Arduboy/Arduboy/issues/108
#define RAND_SEED_IN A4 // Open analog input used for noise by initRandomSeed()
#define RAND_SEED_IN_PORTF
#define RAND_SEED_IN_BIT PORTF1
// Value for ADMUX to read the random seed pin: 2.56V reference, ADC1
#define RAND_SEED_IN_ADMUX (_BV(REFS0) | _BV(REFS1) | _BV(MUX0))
#endif
// --------------------
// OLED hardware (SSD1306,SSD1309,SH1106,OLED_64X64)
#define OLED_PIXELS_INVERTED 0xA7 // All pixels inverted
#define OLED_PIXELS_NORMAL 0xA6 // All pixels normal
#define OLED_ALL_PIXELS_ON 0xA5 // all pixels on
#define OLED_PIXELS_FROM_RAM 0xA4 // pixels mapped to display RAM contents
#define OLED_VERTICAL_FLIPPED 0xC0 // reversed COM scan direction
#define OLED_VERTICAL_NORMAL 0xC8 // normal COM scan direction
#define OLED_HORIZ_FLIPPED 0xA0 // reversed segment re-map
#define OLED_HORIZ_NORMAL 0xA1 // normal segment re-map
#define OLED_SET_PAGE_ADDRESS 0xB0
#ifdef OLED_SH1106
#define OLED_SET_COLUMN_ADDRESS_LO 0x02 //SH1106 only: 1st pixel starts on column 2
#else
#define OLED_SET_COLUMN_ADDRESS_LO 0x00
#endif
#define OLED_SET_COLUMN_ADDRESS_HI 0x10
// -----
#ifdef OLED_96X96
#define WIDTH 96
#define HEIGHT 96
#elif defined(OLED_128X64)
#define WIDTH 128
#define HEIGHT 96
#else
#define WIDTH 128 /**< The width of the display in pixels */
#define HEIGHT 64 /**< The height of the display in pixels */
#endif
#define COLUMN_ADDRESS_END (WIDTH - 1) & 127 // 128 pixels wide
#define PAGE_ADDRESS_END ((HEIGHT/8)-1) & 7 // 8 pages high
/** \brief
* Lower level functions generally dealing directly with the hardware.
*
* \details
* This class is inherited by Arduboy2Base and thus also Arduboy2, so wouldn't
* normally be used directly by a sketch.
*
* \note
* A friend class named _Arduboy2Ex_ is declared by this class. The intention
* is to allow a sketch to create an _Arduboy2Ex_ class which would have access
* to the private and protected members of the Arduboy2Core class. It is hoped
* that this may eliminate the need to create an entire local copy of the
* library, in order to extend the functionality, in most circumstances.
*/
class Arduboy2Core
{
friend class Arduboy2Ex;
public:
Arduboy2Core();
/** \brief
* Idle the CPU to save power.
*
* \details
* This puts the CPU in _idle_ sleep mode. You should call this as often
* as you can for the best power savings. The timer 0 overflow interrupt
* will wake up the chip every 1ms, so even at 60 FPS a well written
* app should be able to sleep maybe half the time in between rendering
* it's own frames.
*/
void static idle();
/** \brief
* Put the display into data mode.
*
* \details
* When placed in data mode, data that is sent to the display will be
* considered as data to be displayed.
*
* \note
* This is a low level function that is not intended for general use in a
* sketch. It has been made public and documented for use by derived
* classes.
*
* \see LCDCommandMode() SPItransfer()
*/
inline void static LCDDataMode() __attribute__((always_inline));
/** \brief
* Put the display into command mode.
*
* \details
* When placed in command mode, data that is sent to the display will be
* treated as commands.
*
* See the SSD1306 controller and OLED display documents for available
* commands and command sequences.
*
* Links:
*
* - https://www.adafruit.com/datasheets/SSD1306.pdf
* - http://www.buydisplay.com/download/manual/ER-OLED013-1_Series_Datasheet.pdf
*
* \note
* This is a low level function that is not intended for general use in a
* sketch. It has been made public and documented for use by derived
* classes.
*
* \see LCDDataMode() sendLCDCommand() SPItransfer()
*/
inline void static LCDCommandMode() __attribute__((always_inline));
/** \brief
* Transfer a byte to the display.
*
* \param data The byte to be sent to the display.
*
* \details
* Transfer one byte to the display over the SPI port and wait for the
* transfer to complete. The byte will either be interpreted as a command
* or as data to be placed on the screen, depending on the command/data
* mode.
*
* \see LCDDataMode() LCDCommandMode() sendLCDCommand()
*/
void static SPItransfer(uint8_t data);
/** \brief
* Turn the display off.
*
* \details
* The display will clear and be put into a low power mode. This can be
* used to extend battery life when a game is paused or when a sketch
* doesn't require anything to be displayed for a relatively long period
* of time.
*
* \see displayOn()
*/
void static displayOff();
/** \brief
* Turn the display on.
*
* \details
* Used to power up and reinitialize the display after calling
* `displayOff()`.
*
* \note
* The previous call to `displayOff()` will have caused the display's
* buffer contents to be lost. The display will have to be re-painted,
* which is usually done by calling `display()`.
*
* \see displayOff()
*/
void static displayOn();
/** \brief
* Get the width of the display in pixels.
*
* \return The width of the display in pixels.
*
* \note
* In most cases, the defined value `WIDTH` would be better to use instead
* of this function.
*/
uint8_t static width();
/** \brief
* Get the height of the display in pixels.
*
* \return The height of the display in pixels.
*
* \note
* In most cases, the defined value `HEIGHT` would be better to use instead
* of this function.
*/
uint8_t static height();
/** \brief
* Get the current state of all buttons as a bitmask.
*
* \return A bitmask of the state of all the buttons.
*
* \details
* The returned mask contains a bit for each button. For any pressed button,
* its bit will be 1. For released buttons their associated bits will be 0.
*
* The following defined mask values should be used for the buttons:
*
* LEFT_BUTTON, RIGHT_BUTTON, UP_BUTTON, DOWN_BUTTON, A_BUTTON, B_BUTTON
*/
uint8_t static buttonsState();
/** \brief
* Paint 8 pixels vertically to the display.
*
* \param pixels A byte whose bits specify a vertical column of 8 pixels.
*
* \details
* A byte representing a vertical column of 8 pixels is written to the
* display at the current page and column address. The address is then
* incremented. The page/column address will wrap to the start of the
* display (the top left) when it increments past the end (lower right).
*
* The least significant bit represents the top pixel in the column.
* A bit set to 1 is lit, 0 is unlit.
*
* Example:
*
* X = lit pixels, . = unlit pixels
*
* blank() paint8Pixels() 0xFF, 0, 0xF0, 0, 0x0F
* v TOP LEFT corner (8x9) v TOP LEFT corner
* . . . . . . . . (page 1) X . . . X . . . (page 1)
* . . . . . . . . X . . . X . . .
* . . . . . . . . X . . . X . . .
* . . . . . . . . X . . . X . . .
* . . . . . . . . X . X . . . . .
* . . . . . . . . X . X . . . . .
* . . . . . . . . X . X . . . . .
* . . . . . . . . (end of page 1) X . X . . . . . (end of page 1)
* . . . . . . . . (page 2) . . . . . . . . (page 2)
*/
void static paint8Pixels(uint8_t pixels);
/** \brief
* Paints an entire image directly to the display from program memory.
*
* \param image A byte array in program memory representing the entire
* contents of the display.
*
* \details
* The contents of the specified array in program memory is written to the
* display. Each byte in the array represents a vertical column of 8 pixels
* with the least significant bit at the top. The bytes are written starting
* at the top left, progressing horizontally and wrapping at the end of each
* row, to the bottom right. The size of the array must exactly match the
* number of pixels in the entire display.
*
* \see paint8Pixels()
*/
void static paintScreen(const uint8_t *image);
/** \brief
* Paints an entire image directly to the display from an array in RAM.
*
* \param image A byte array in RAM representing the entire contents of
* the display.
* \param clear If `true` the array in RAM will be cleared to zeros upon
* return from this function. If `false` the RAM buffer will remain
* unchanged. (optional; defaults to `false`)
*
* \details
* The contents of the specified array in RAM is written to the display.
* Each byte in the array represents a vertical column of 8 pixels with
* the least significant bit at the top. The bytes are written starting
* at the top left, progressing horizontally and wrapping at the end of
* each row, to the bottom right. The size of the array must exactly
* match the number of pixels in the entire display.
*
* If parameter `clear` is set to `true` the RAM array will be cleared to
* zeros after its contents are written to the display.
*
* \see paint8Pixels()
*/
void static paintScreen(uint8_t image[], bool clear = false);
/** \brief
* Blank the display screen by setting all pixels off.
*
* \details
* All pixels on the screen will be written with a value of 0 to turn
* them off.
*/
void static blank();
/** \brief
* Invert the entire display or set it back to normal.
*
* \param inverse `true` will invert the display. `false` will set the
* display to no-inverted.
*
* \details
* Calling this function with a value of `true` will set the display to
* inverted mode. A pixel with a value of 0 will be on and a pixel set to 1
* will be off.
*
* Once in inverted mode, the display will remain this way
* until it is set back to non-inverted mode by calling this function with
* `false`.
*/
void static invert(bool inverse);
/** \brief
* Turn all display pixels on or display the buffer contents.
*
* \param on `true` turns all pixels on. `false` displays the contents
* of the hardware display buffer.
*
* \details
* Calling this function with a value of `true` will override the contents
* of the hardware display buffer and turn all pixels on. The contents of
* the hardware buffer will remain unchanged.
*
* Calling this function with a value of `false` will set the normal state
* of displaying the contents of the hardware display buffer.
*
* \note
* All pixels will be lit even if the display is in inverted mode.
*
* \see invert()
*/
void static allPixelsOn(bool on);
/** \brief
* Flip the display vertically or set it back to normal.
*
* \param flipped `true` will set vertical flip mode. `false` will set
* normal vertical orientation.
*
* \details
* Calling this function with a value of `true` will cause the Y coordinate
* to start at the bottom edge of the display instead of the top,
* effectively flipping the display vertically.
*
* Once in vertical flip mode, it will remain this way until normal
* vertical mode is set by calling this function with a value of `false`.
*
* \see flipHorizontal()
*/
void static flipVertical(bool flipped);
/** \brief
* Flip the display horizontally or set it back to normal.
*
* \param flipped `true` will set horizontal flip mode. `false` will set
* normal horizontal orientation.
*
* \details
* Calling this function with a value of `true` will cause the X coordinate
* to start at the left edge of the display instead of the right,
* effectively flipping the display horizontally.
*
* Once in horizontal flip mode, it will remain this way until normal
* horizontal mode is set by calling this function with a value of `false`.
*
* \see flipVertical()
*/
void static flipHorizontal(bool flipped);
/** \brief
* Send a single command byte to the display.
*
* \param command The command byte to send to the display.
*
* \details
* The display will be set to command mode then the specified command
* byte will be sent. The display will then be set to data mode.
* Multi-byte commands can be sent by calling this function multiple times.
*
* \note
* Sending improper commands to the display can place it into invalid or
* unexpected states, possibly even causing physical damage.
*/
void static sendLCDCommand(uint8_t command);
/** \brief
* Set the light output of the RGB LED.
*
* \param red,green,blue The brightness value for each LED.
*
* \details
* The RGB LED is actually individual red, green and blue LEDs placed
* very close together in a single package. By setting the brightness of
* each LED, the RGB LED can show various colors and intensities.
* The brightness of each LED can be set to a value from 0 (fully off)
* to 255 (fully on).
*
* \note
* \parblock
* Certain libraries that take control of the hardware timers may interfere
* with the ability of this function to properly control the RGB LED.
*_ArduboyPlaytune_ is one such library known to do this.
* The digitalWriteRGB() function will still work properly in this case.
* \endparblock
*
* \note
* \parblock
* Many of the Kickstarter Arduboys were accidentally shipped with the
* RGB LED installed incorrectly. For these units, the green LED cannot be
* lit. As long as the green led is set to off, setting the red LED will
* actually control the blue LED and setting the blue LED will actually
* control the red LED. If the green LED is turned fully on, none of the
* LEDs will light.
* \endparblock
*
* \see digitalWriteRGB()
*/
void static setRGBled(uint8_t red, uint8_t green, uint8_t blue);
/** \brief
* Set the RGB LEDs digitally, to either fully on or fully off.
*
* \param red,green,blue Use value RGB_ON or RGB_OFF to set each LED.
*
* \details
* The RGB LED is actually individual red, green and blue LEDs placed
* very close together in a single package. This 3 parameter version of the
* function will set each LED either on or off, to set the RGB LED to
* 7 different colors at their highest brightness or turn it off.
*
* The colors are as follows:
*
* RED LED GREEN_LED BLUE_LED COLOR
* ------- --------- -------- -----
* RGB_OFF RGB_OFF RGB_OFF OFF
* RGB_OFF RGB_OFF RGB_ON Blue
* RGB_OFF RGB_ON RGB_OFF Green
* RGB_OFF RGB_ON RGB_ON Cyan
* RGB_ON RGB_OFF RGB_OFF Red
* RGB_ON RGB_OFF RGB_ON Magenta
* RGB_ON RGB_ON RGB_OFF Yellow
* RGB_ON RGB_ON RGB_ON White
*
* \note
* Many of the Kickstarter Arduboys were accidentally shipped with the
* RGB LED installed incorrectly. For these units, the green LED cannot be
* lit. As long as the green led is set to off, turning on the red LED will
* actually light the blue LED and turning on the blue LED will actually
* light the red LED. If the green LED is turned on, none of the LEDs
* will light.
*
* \see digitalWriteRGB(uint8_t, uint8_t) setRGBled()
*/
void static digitalWriteRGB(uint8_t red, uint8_t green, uint8_t blue);
/** \brief
* Set one of the RGB LEDs digitally, to either fully on or fully off.
*
* \param color The name of the LED to set. The value given should be one
* of RED_LED, GREEN_LED or BLUE_LED.
*
* \param val Indicates whether to turn the specified LED on or off.
* The value given should be RGB_ON or RGB_OFF.
*
* \details
* This 2 parameter version of the function will set a single LED within
* the RGB LED either fully on or fully off. See the description of the
* 3 parameter version of this function for more details on the RGB LED.
*
* \see digitalWriteRGB(uint8_t, uint8_t, uint8_t) setRGBled()
*/
void static digitalWriteRGB(uint8_t color, uint8_t val);
/** \brief
* Initialize the Arduboy's hardware.
*
* \details
* This function initializes the display, buttons, etc.
*
* This function is called by begin() so isn't normally called within a
* sketch. However, in order to free up some code space, by eliminating
* some of the start up features, it can be called in place of begin().
* The functions that begin() would call after boot() can then be called
* to add back in some of the start up features, if desired.
* See the README file or documentation on the main page for more details.
*
* \see Arduboy2Base::begin()
*/
void static boot();
/** \brief
* Allow upload when the bootloader "magic number" could be corrupted.
*
* \details
* If the UP button is held when this function is entered, the RGB LED
* will be lit and timer 0 will be disabled, then the sketch will remain
* in a tight loop. This is to address a problem with uploading a new
* sketch, for sketches that interfere with the bootloader "magic number".
* The problem occurs with certain sketches that use large amounts of RAM.
*
* This function should be called after `boot()` in sketches that
* potentially could cause the problem.
*
* It is intended to replace the `flashlight()` function when more
* program space is required. If possible, it is more desirable to use
* `flashlight()`, so that the actual flashlight feature isn't lost.
*
* \see Arduboy2Base::flashlight() boot()
*/
void static safeMode();
/** \brief
* Delay for the number of milliseconds, specified as a 16 bit value.
*
* \param ms The delay in milliseconds.
*
* \details
* This function works the same as the Arduino `delay()` function except
* the provided value is 16 bits long, so the maximum delay allowed is
* 65535 milliseconds (about 65.5 seconds). Using this function instead
* of Arduino `delay()` will save a few bytes of code.
*/
void static delayShort(uint16_t ms) __attribute__ ((noinline));
protected:
// internals
void static setCPUSpeed8MHz();
void static bootSPI();
void static bootOLED();
void static bootPins();
void static bootPowerSaving();
};
#endif

362
Sprites.cpp Normal file
View File

@ -0,0 +1,362 @@
/**
* @file Sprites.cpp
* \brief
* A class for drawing animated sprites from image and mask bitmaps.
*/
#include "Sprites.h"
void Sprites::drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap,
const uint8_t *mask, uint8_t frame, uint8_t mask_frame)
{
draw(x, y, bitmap, frame, mask, mask_frame, SPRITE_MASKED);
}
void Sprites::drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_OVERWRITE);
}
void Sprites::drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK_ERASE);
}
void Sprites::drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK);
}
void Sprites::drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
{
draw(x, y, bitmap, frame, NULL, 0, SPRITE_PLUS_MASK);
}
//common functions
void Sprites::draw(int16_t x, int16_t y,
const uint8_t *bitmap, uint8_t frame,
const uint8_t *mask, uint8_t sprite_frame,
uint8_t drawMode)
{
unsigned int frame_offset;
if (bitmap == NULL)
return;
uint8_t width = pgm_read_byte(bitmap);
uint8_t height = pgm_read_byte(++bitmap);
bitmap++;
if (frame > 0 || sprite_frame > 0) {
frame_offset = (width * ( height / 8 + ( height % 8 == 0 ? 0 : 1)));
// sprite plus mask uses twice as much space for each frame
if (drawMode == SPRITE_PLUS_MASK) {
frame_offset *= 2;
} else if (mask != NULL) {
mask += sprite_frame * frame_offset;
}
bitmap += frame * frame_offset;
}
// if we're detecting the draw mode then base it on whether a mask
// was passed as a separate object
if (drawMode == SPRITE_AUTO_MODE) {
drawMode = mask == NULL ? SPRITE_UNMASKED : SPRITE_MASKED;
}
drawBitmap(x, y, bitmap, mask, width, height, drawMode);
}
void Sprites::drawBitmap(int16_t x, int16_t y,
const uint8_t *bitmap, const uint8_t *mask,
uint8_t w, uint8_t h, uint8_t draw_mode)
{
// no need to draw at all of we're offscreen
if (x + w <= 0 || x > WIDTH - 1 || y + h <= 0 || y > HEIGHT - 1)
return;
if (bitmap == NULL)
return;
// xOffset technically doesn't need to be 16 bit but the math operations
// are measurably faster if it is
uint16_t xOffset, ofs;
int8_t yOffset = abs(y) % 8;
int8_t sRow = y / 8;
uint8_t loop_h, start_h, rendered_width;
if (y < 0 && yOffset > 0) {
sRow--;
yOffset = 8 - yOffset;
}
// if the left side of the render is offscreen skip those loops
if (x < 0) {
xOffset = abs(x);
} else {
xOffset = 0;
}
// if the right side of the render is offscreen skip those loops
if (x + w > WIDTH - 1) {
rendered_width = ((WIDTH - x) - xOffset);
} else {
rendered_width = (w - xOffset);
}
// if the top side of the render is offscreen skip those loops
if (sRow < -1) {
start_h = abs(sRow) - 1;
} else {
start_h = 0;
}
loop_h = h / 8 + (h % 8 > 0 ? 1 : 0); // divide, then round up
// if (sRow + loop_h - 1 > (HEIGHT/8)-1)
if (sRow + loop_h > (HEIGHT / 8)) {
loop_h = (HEIGHT / 8) - sRow;
}
// prepare variables for loops later so we can compare with 0
// instead of comparing two variables
loop_h -= start_h;
sRow += start_h;
ofs = (sRow * WIDTH) + x + xOffset;
uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset;
uint8_t data;
uint8_t mul_amt = 1 << yOffset;
uint16_t mask_data;
uint16_t bitmap_data;
switch (draw_mode) {
case SPRITE_UNMASKED:
// we only want to mask the 8 bits of our own sprite, so we can
// calculate the mask before the start of the loop
mask_data = ~(0xFF * mul_amt);
// really if yOffset = 0 you have a faster case here that could be
// optimized
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
data = Arduboy2Base::sBuffer[ofs];
data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
Arduboy2Base::sBuffer[ofs] = data;
}
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;
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_IS_MASK:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
Arduboy2Base::sBuffer[ofs] |= (uint8_t)(bitmap_data);
}
if (yOffset != 0 && sRow < ((HEIGHT / 8) - 1)) {
Arduboy2Base::sBuffer[ofs + WIDTH] |= (*((unsigned char *) (&bitmap_data) + 1));
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_IS_MASK_ERASE:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
Arduboy2Base::sBuffer[ofs] &= ~(uint8_t)(bitmap_data);
}
if (yOffset != 0 && sRow < ((HEIGHT / 8) - 1)) {
Arduboy2Base::sBuffer[ofs + WIDTH] &= ~(*((unsigned char *) (&bitmap_data) + 1));
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
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 < ((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;
}
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
"subi r28, %[neg_width]\n" // buffer_ofs_2 = buffer_ofs + WIDTH
"sbci r29, -1\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 < ((HEIGHT / 8) - 1)
"cpi %[sRow], %[row_height]\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
[neg_width] "M" (256 - WIDTH),
[row_height] "M" ((HEIGHT / 8) - 1)
// 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;
}
}

235
Sprites.h Normal file
View File

@ -0,0 +1,235 @@
/**
* @file Sprites.h
* \brief
* A class for drawing animated sprites from image and mask bitmaps.
*/
#ifndef Sprites_h
#define Sprites_h
#include "Arduboy2.h"
#define SPRITE_MASKED 1
#define SPRITE_UNMASKED 2
#define SPRITE_OVERWRITE 2
#define SPRITE_PLUS_MASK 3
#define SPRITE_IS_MASK 250
#define SPRITE_IS_MASK_ERASE 251
#define SPRITE_AUTO_MODE 255
/** \brief
* A class for drawing animated sprites from image and mask bitmaps.
*
* \details
* The functions in this class will draw to the screen buffer an image
* contained in an array located in program memory. A mask can also be
* specified or implied, which dictates how existing pixels in the buffer,
* within the image boundaries, will be affected.
*
* A sprite or mask array contains one or more "frames". Each frame is intended
* to show whatever the sprite represents in a different position, such as the
* various poses for a running or jumping character. By specifying a different
* frame each time the sprite is drawn, it can be animated.
*
* Each image array begins with values for the width and height of the sprite,
* in pixels. The width can be any value. The height must be a multiple of
* 8 pixels, but with proper masking, a sprite of any height can be created.
*
* For a separate mask array, as is used with `drawExternalMask()`, the width
* and height are not included but must contain data of the same dimensions
* as the corresponding image array.
*
* Following the width and height values for an image array, or the from the
* beginning of a separate mask array, the array contains the image and/or
* mask data for each frame. Each byte represents a vertical column of 8 pixels
* with the least significant bit (bit 0) at the top. The bytes are drawn as
* 8 pixel high rows from left to right, top to bottom. When the end of a row
* is reached, as specified by the width value, the next byte in the array will
* be the start of the next row.
*
* Data for each frame after the first one immediately follows the previous
* frame. Frame numbers start at 0.
*/
class Sprites
{
public:
/** \brief
* Draw a sprite using a separate image and mask array.
*
* \param x,y The coordinates of the top left pixel location.
* \param bitmap A pointer to the array containing the image frames.
* \param mask A pointer to the array containing the mask frames.
* \param frame The frame number of the image to draw.
* \param mask_frame The frame number for the mask to use (can be different
* from the image frame number).
*
* \details
* An array containing the image frames, and another array containing
* corresponding mask frames, are used to draw a sprite.
*
* 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.
*
* image mask before after
*
* ..... .OOO. ..... .....
* ..O.. OOOOO ..... ..O..
* OO.OO OO.OO ..... OO.OO
* ..O.. OOOOO ..... ..O..
* ..... .OOO. ..... .....
*
* image mask before after
*
* ..... .OOO. OOOOO O...O
* ..O.. OOOOO OOOOO ..O..
* OO.OO OOOOO OOOOO OO.OO
* ..O.. OOOOO OOOOO ..O..
* ..... .OOO. OOOOO O...O
*/
static void drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap,
const uint8_t *mask, uint8_t frame, uint8_t mask_frame);
/** \brief
* Draw a sprite using an array containing both image and mask values.
*
* \param x,y The coordinates of the top left pixel location.
* \param bitmap A pointer to the array containing the image/mask frames.
* \param frame The frame number of the image to draw.
*
* \details
* An array containing combined image and mask data is used to draw a
* sprite. Bytes are given in pairs with the first byte representing the
* image pixels and the second byte specifying the corresponding mask.
* The width given in the array still specifies the image width, so each
* row of image and mask bytes will be twice the width value.
*
* 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.
*
* image mask before after
*
* ..... .OOO. ..... .....
* ..O.. OOOOO ..... ..O..
* OO.OO OO.OO ..... OO.OO
* ..O.. OOOOO ..... ..O..
* ..... .OOO. ..... .....
*
* image mask before after
*
* ..... .OOO. OOOOO O...O
* ..O.. OOOOO OOOOO ..O..
* OO.OO OOOOO OOOOO OO.OO
* ..O.. OOOOO OOOOO ..O..
* ..... .OOO. OOOOO O...O
*/
static void drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/** \brief
* Draw a sprite by replacing the existing content completely.
*
* \param x,y The coordinates of the top left pixel location.
* \param bitmap A pointer to the array containing the image frames.
* \param frame The frame number of the image to draw.
*
* \details
* A sprite is drawn by overwriting the pixels in the buffer with the data
* from the specified frame in the array. No masking is done. A bit set
* to 1 in the frame will set the pixel to 1 in the buffer, and a 0 in the
* array will set a 0 in the buffer.
*
* image before after
*
* ..... ..... .....
* ..O.. ..... ..O..
* OO.OO ..... OO.OO
* ..O.. ..... ..O..
* ..... ..... .....
*
* image before after
*
* ..... OOOOO .....
* ..O.. OOOOO ..O..
* OO.OO OOOOO OO.OO
* ..O.. OOOOO ..O..
* ..... OOOOO .....
*/
static void drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/** \brief
* "Erase" a sprite.
*
* \param x,y The coordinates of the top left pixel location.
* \param bitmap A pointer to the array containing the image frames.
* \param frame The frame number of the image to erase.
*
* \details
* The data from the specified frame in the array is used to erase a
* sprite. To "erase" a sprite, bits set to 1 in the frame will set the
* corresponding pixel in the buffer to 0. Frame bits set to 0 will remain
* unchanged in the buffer.
*
* image before after
*
* ..... ..... .....
* ..O.. ..... .....
* OO.OO ..... .....
* ..O.. ..... .....
* ..... ..... .....
*
* image before after
*
* ..... OOOOO OOOOO
* ..O.. OOOOO OO.OO
* OO.OO OOOOO ..O..
* ..O.. OOOOO OO.OO
* ..... OOOOO OOOOO
*/
static void drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
/** \brief
* Draw a sprite using only the bits set to 1.
*
* \param x,y The coordinates of the top left pixel location.
* \param bitmap A pointer to the array containing the image frames.
* \param frame The frame number of the image to draw.
*
* \details
* Bits set to 1 in the frame will be used to draw the sprite by setting
* the corresponding pixel in the buffer to 1. Bits set to 0 in the frame
* will remain unchanged in the buffer.
*
* image before after
*
* ..... ..... .....
* ..O.. ..... ..O..
* OO.OO ..... OO.OO
* ..O.. ..... ..O..
* ..... ..... .....
*
* image before after
*
* ..... OOOOO OOOOO (no change because all pixels were
* ..O.. OOOOO OOOOO already white)
* OO.OO OOOOO OOOOO
* ..O.. OOOOO OOOOO
* ..... OOOOO OOOOO
*/
static void drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
// Master function. Needs to be abstracted into separate function for
// every render type.
// (Not officially part of the API)
static void draw(int16_t x, int16_t y,
const uint8_t *bitmap, uint8_t frame,
const uint8_t *mask, uint8_t sprite_frame,
uint8_t drawMode);
// (Not officially part of the API)
static void drawBitmap(int16_t x, int16_t y,
const uint8_t *bitmap, const uint8_t *mask,
uint8_t w, uint8_t h, uint8_t draw_mode);
};
#endif