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