update Arduboy and Arduboy2 libraries

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

View File

@ -14,11 +14,17 @@ const uint8_t PROGMEM pinBootProgram[] = {
PIN_DOWN_BUTTON, INPUT_PULLUP,
PIN_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

View File

@ -43,6 +43,38 @@
#define RST 6
#endif
#define DC 4
#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
//bitbanged I2C pins
#define I2C_PORT PORTD
#define I2C_DDR DDRD
#define I2C_PIN PIND
#ifdef AB_ALTERNATE_WIRING
#define SCL 1
#define I2C_SCL PORTD3
#else
#define SCL 6
#define I2C_SCL PORTD7
#endif
#define SDA 4
#define I2C_SDA PORTD4
//port states
#define I2C_SDA_HIGH() I2C_PORT |= (1 << I2C_SDA)
#define I2C_SCL_HIGH() I2C_PORT |= (1 << I2C_SCL)
#define I2C_SDA_LOW() I2C_PORT &= ~(1 << I2C_SDA)
#define I2C_SCL_LOW() I2C_PORT &= ~(1 << I2C_SCL)
//port directions
#define I2C_SDA_AS_INPUT() I2C_DDR &= ~(1 << I2C_SDA)
#define I2C_SCL_AS_INPUT() I2C_DDR &= ~(1 << I2C_SCL)
#define I2C_SDA_AS_OUTPUT() I2C_DDR |= (1 << I2C_SDA)
#define I2C_SCL_AS_OUTPUT() I2C_DDR |= (1 << I2C_SCL)
// display address, commands
#define SSD1306_I2C_ADDR 0x3c //0x3c:default, 0x3d: alternative)
#define SSD1306_I2C_CMD 0x00
#define SSD1306_I2C_DATA 0x40
#endif
#define RED_LED 10
#if defined AB_ALTERNATE_WIRING //Pro Micro Alternative GREEN LED pin
@ -193,9 +225,22 @@ public:
*/
void static LCDCommandMode();
#if defined (OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
void static i2c_start(uint8_t mode);
void static inline i2c_stop() __attribute__((always_inline))
{
// SDA and SCL both are already low, from writing ACK bit no need to change state
I2C_SDA_AS_INPUT(); // switch to input so SDA is pulled up externally first for stop condition
I2C_SCL_AS_INPUT(); // pull up SCL externally
}
void static i2c_sendByte(uint8_t byte);
#endif
uint8_t static width(); //< return display width
uint8_t static height(); // < return display height
/// get current state of all buttons (bitmask)
/**
* Bit mask that is returned:

View File

@ -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

View File

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

View File

@ -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.

View File

@ -8,12 +8,32 @@
#include "ab_logo.c"
#include "glcdfont.c"
//================================
//========== class Rect ==========
//================================
Rect::Rect(int16_t x, int16_t y, uint8_t width, uint8_t height)
: x(x), y(y), width(width), height(height)
{
}
//=================================
//========== class Point ==========
//=================================
Point::Point(int16_t x, int16_t y)
: x(x), y(y)
{
}
//========================================
//========== class Arduboy2Base ==========
//========================================
uint8_t Arduboy2Base::sBuffer[];
uint8_t Arduboy2Base::batteryLow = EEPROM.read(EEPROM_BATTERY_LOW); //Low battery bandgap value - 192
Arduboy2Base::Arduboy2Base()
{
currentButtonState = 0;
@ -31,8 +51,9 @@ void Arduboy2Base::begin()
{
boot(); // raw hardware
display(CLEAR_BUFFER); //sBuffer is global, so cleared automatically)
//using CLEAR_BUFFER so a sketch can be optimized when using CLEAR_BUFFER exclusivly
display(CLEAR_BUFFER); //sBuffer is global, so cleared automatically.
flashlight(); // light the RGB LED and screen if UP button is being held.
// check for and handle buttons held during start up for system control
@ -323,14 +344,6 @@ void Arduboy2Base::clear()
fillScreen(BLACK);
}
// Used by drawPixel to help with left bitshifting since AVR has no
// multiple bit shift instruction. We can bit shift from a lookup table
// in flash faster than we can calculate the bit shifts on the CPU.
const uint8_t bitshift_left[] PROGMEM = {
_BV(0), _BV(1), _BV(2), _BV(3), _BV(4), _BV(5), _BV(6), _BV(7)
};
void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color)
{
#ifdef PIXEL_SAFE_MODE
@ -343,58 +356,57 @@ void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color)
uint16_t row_offset;
uint8_t bit;
// uint8_t row = (uint8_t)y / 8;
// row_offset = (row*WIDTH) + (uint8_t)x;
// bit = _BV((uint8_t)y % 8);
// the above math can also be rewritten more simply as;
// row_offset = (y * WIDTH/8) & ~0b01111111 + (uint8_t)x;
// which is what the below assembler does
// local variable for the bitshift_left array pointer,
// which can be declared a read-write operand
const uint8_t* bsl = bitshift_left;
asm volatile
(
#if WIDTH == 128
"mul %[width_offset], %A[y]\n"
"movw %[row_offset], r0\n"
"andi %A[row_offset], 0x80\n" // row_offset &= (~0b01111111);
"clr __zero_reg__\n"
"add %A[row_offset], %[x]\n"
// mask for only 0-7
"andi %A[y], 0x07\n"
#else
"mov r0, %A[y] \n"
"andi %A[y], 0x07 \n" // mask for only 0-7
"eor r0, %A[y] \n" // == and 0xF8
"mul %[width_offset], r0 \n"
// bit = 1 << (y & 7)
"ldi %[bit], 1 \n" //bit = 1;
"sbrc %[y], 1 \n" //if (y & _BV(1)) bit = 4;
"ldi %[bit], 4 \n"
"sbrc %[y], 0 \n" //if (y & _BV(0)) bit = bit << 1;
"lsl %[bit] \n"
"sbrc %[y], 2 \n" //if (y & _BV(2)) bit = (bit << 4) | (bit >> 4);
"swap %[bit] \n"
//row_offset = y / 8 * WIDTH + x;
"andi %A[y], 0xf8 \n" //row_offset = (y & 0xF8) * WIDTH / 8
"mul %[width_offset], %A[y] \n"
"movw %[row_offset], r0 \n"
"clr __zero_reg__ \n"
"add %A[row_offset], %[x] \n"
"adc %B[row_offset], __zero_reg__ \n"
"add %A[row_offset], %[x] \n" //row_offset += x
#if WIDTH != 128
"adc %B[row_offset], __zero_reg__ \n" // only non 128 width can overflow
#endif
// Z += y
"add r30, %A[y] \n"
"adc r31, __zero_reg__ \n"
// load correct bitshift from program RAM
"lpm %[bit], Z \n"
: [row_offset] "=&x" (row_offset), // upper register (ANDI)
[bit] "=r" (bit),
[y] "+d" (y), // upper register (ANDI), must be writable
"+z" (bsl) // is modified to point to the proper shift array element
: [width_offset] "r" ((uint8_t)(WIDTH/8)),
[x] "r" ((uint8_t)x)
: [row_offset] "=&x" (row_offset), // upper register (ANDI)
[bit] "=&d" (bit), // upper register (LDI)
[y] "+d" (y) // upper register (ANDI), must be writable
: [width_offset] "r" ((uint8_t)(WIDTH/8)),
[x] "r" ((uint8_t)x)
:
);
if (color) {
sBuffer[row_offset] |= bit;
} else {
sBuffer[row_offset] &= ~ bit;
}
uint8_t data = sBuffer[row_offset] | bit;
if (!(color & _BV(0))) data ^= bit;
sBuffer[row_offset] = data;
}
#if 0
// For reference, this is the C++ equivalent
void Arduboy2Base::drawPixel(int16_t x, int16_t y, uint8_t color)
{
#ifdef PIXEL_SAFE_MODE
if (x < 0 || x > (WIDTH-1) || y < 0 || y > (HEIGHT-1))
{
return;
}
#endif
uint16_t row_offset;
uint8_t bit;
bit = 1 << (y & 7);
row_offset = (y & 0xF8) * WIDTH / 8 + x;
uint8_t data = sBuffer[row_offset] | bit;
if (!color) data ^= bit;
sBuffer[row_offset] = data;
}
#endif
uint8_t Arduboy2Base::getPixel(uint8_t x, uint8_t y)
{
@ -685,7 +697,7 @@ void Arduboy2Base::fillScreen(uint8_t color)
"ldi %[color], 0xFF\n"
// counter = WIDTH * HEIGHT / 8 / 8
"ldi r24, %[cnt]\n"
"loopto:\n"
"1:\n"
// (4x/8x) store color into screen buffer,
// then increment buffer position
"st Z+, %[color]\n"
@ -701,7 +713,7 @@ void Arduboy2Base::fillScreen(uint8_t color)
// decrease counter
"subi r24, 1\n"
// repeat for 256, 144 or 192 loops depending on screen resolution
"brcc loopto\n"
"brcc 1b\n"
: [color] "+d" (color),
"+z" (bPtr)
#if defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128) || defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128)
@ -858,14 +870,9 @@ void Arduboy2Base::drawBitmap
if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1)
return;
int yOffset = abs(y) % 8;
int sRow = y / 8;
if (y < 0) {
sRow--;
yOffset = 8 - yOffset;
}
int rows = h/8;
if (h%8!=0) rows++;
int8_t yOffset = y & 7;
int8_t sRow = y;
uint8_t rows = h >> 3;
for (int a = 0; a < rows; a++) {
int bRow = sRow + a;
if (bRow > (HEIGHT/8)-1) break;
@ -873,21 +880,22 @@ void Arduboy2Base::drawBitmap
for (int iCol = 0; iCol<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 ==========

View File

@ -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.

View File

@ -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
);
#if defined(AB_ALTERNATE_WIRING)
_BV(SPEAKER_2_BIT) |
#endif
#if defined(LCD_ST7565)
_BV(POWER_LED_BIT) |
#endif
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
_BV(I2C_SCL) |
_BV(I2C_SDA) |
#endif
0);
// Port D outputs
DDRD = _BV(RST_BIT) | _BV(CS_BIT) | _BV(DC_BIT) |
#ifdef AB_ALTERNATE_WIRING
DDRD = (
#if !(defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX))
_BV(DC_BIT) |
#endif
#if !(defined(AB_ALTERNATE_WIRING) && (CART_CS_SDA))
_BV(RST_BIT) |
_BV(CS_BIT) |
#endif
#if defined(AB_ALTERNATE_WIRING)
_BV(GREEN_LED_BIT) |
#endif
#ifdef LCD_ST7565
_BV(POWER_LED_BIT) |
#if defined(LCD_ST7565)
_BV(POWER_LED_BIT) |
#endif
_BV(CART_BIT) | _BV(TX_LED_BIT);
// Port D inputs (none)
_BV(CART_BIT) |
_BV(TX_LED_BIT) |
0) & ~(// Port D inputs
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
_BV(I2C_SCL) | // SDA and SCL as inputs without pullups
_BV(I2C_SDA) | // (both externally pulled up)
#endif
0);
// Port E INPUT_PULLUP or HIGH
PORTE |= _BV(A_BUTTON_BIT);
@ -294,6 +322,12 @@ void Arduboy2Core::bootPins()
void Arduboy2Core::bootOLED()
{
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
i2c_start(SSD1306_I2C_CMD);
for (uint8_t i = 0; i < sizeof(lcdBootProgram); i++)
i2c_sendByte(pgm_read_byte(lcdBootProgram + i));
i2c_stop();
#else
// reset the display
uint8_t cmd;
const void* ptr = lcdBootProgram;
@ -301,6 +335,7 @@ void Arduboy2Core::bootOLED()
"1: \n\t" //assembly loop for 2nd delayShort(5)
);
delayShort(5); //for a short active low reset pulse
#if !(defined(AB_ALTERNATE_WIRING) && defined(CART_CS_SDA))
asm volatile(
" sbic %[rst_port], %[rst_bit] \n\t" //continue if reset is active
" rjmp 2f \n\t" //else break
@ -311,7 +346,8 @@ void Arduboy2Core::bootOLED()
: [rst_port] "I" (_SFR_IO_ADDR(RST_PORT)),
[rst_bit] "I" (RST_BIT)
:
);
);
#endif
#if defined(OLED_128X64_ON_96X96) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128)
for (uint16_t i = 0; i < 8192; i++) SPItransfer(0); //make sure all display ram is cleared
#endif
@ -335,16 +371,7 @@ void Arduboy2Core::bootOLED()
: "r25"
);
LCDDataMode();
}
void Arduboy2Core::LCDDataMode()
{
bitSet(DC_PORT, DC_BIT);
}
void Arduboy2Core::LCDCommandMode()
{
bitClear(DC_PORT, DC_BIT);
#endif
}
// Initialize the SPI interface for the display
@ -370,6 +397,50 @@ uint8_t Arduboy2Core::SPItransfer(uint8_t data)
return SPDR;
}
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
void Arduboy2Core::i2c_start(uint8_t mode)
{
I2C_SDA_LOW(); // disable posible internal pullup, ensure SDA low on enabling output
I2C_SDA_AS_OUTPUT(); // SDA low before SCL for start condition
I2C_SCL_LOW();
I2C_SCL_AS_OUTPUT();
i2c_sendByte(SSD1306_I2C_ADDR << 1);
i2c_sendByte(mode);
}
void Arduboy2Core::i2c_sendByte(uint8_t byte)
{
uint8_t sda_clr = I2C_PORT & ~((1 << I2C_SDA) | (1 << I2C_SCL));
uint8_t scl = 1 << I2C_SCL;
uint8_t sda = 1 << I2C_SDA;
uint8_t scl_bit = I2C_SCL;
asm volatile (
" sec \n" // set carry for 8 shift counts
" rol %[byte] \n" // shift a bit out and count at the same time
"1: \n"
" out %[port], %[sda0] \n" // preemtively clear SDA
" brcc 2f \n" // skip if dealing with 0 bit
" out %[pin], %[sda] \n"
"2: \n"
" out %[pin], %[scl] \n" // toggle SCL on
" lsl %[byte] \n" // next bit to carry (moved here for 1 extra cycle delay)
" out %[pin], %[scl] \n" // toggle SCL off
" brne 1b \n" // initial set carry will be shifted out after 8 loops setting Z flag
" \n"
" out %[port], %[sda0] \n" // clear SDA for ACK
" sbi %[port], %[sclb] \n" // set SCL (extends ACK bit by 1 cycle)
" cbi %[port], %[sclb] \n" // clear SCL (extends SCL high by 1 cycle)
:[byte] "+r" (byte)
:[port] "i" (_SFR_IO_ADDR(I2C_PORT)),
[pin] "i" (_SFR_IO_ADDR(I2C_PIN)),
[sda0] "r" (sda_clr),
[scl] "r" (scl),
[sda] "r" (sda),
[sclb] "i" (scl_bit)
);
}
#endif
void Arduboy2Core::safeMode()
{
if (buttonsState() == UP_BUTTON)
@ -403,18 +474,23 @@ void Arduboy2Core::bootPowerSaving()
PRR0 = _BV(PRTWI) | _BV(PRADC);
// disable USART1
PRR1 = _BV(PRUSART1);
// All other bits will be written with 0 so will be enabled
}
// Shut down the display
void Arduboy2Core::displayOff()
{
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
i2c_start(SSD1306_I2C_CMD);
i2c_sendByte(0xAE); // display off
i2c_sendByte(0x8D); // charge pump:
i2c_sendByte(0x10); // disable
i2c_stop();
#else
LCDCommandMode();
SPItransfer(0xAE); // display off
SPItransfer(0x8D); // charge pump:
SPItransfer(0x10); // disable
delayShort(250);
bitClear(RST_PORT, RST_BIT); // set display reset pin low (reset state)
#endif
}
// Restart the display after a displayOff()
@ -432,12 +508,23 @@ uint8_t Arduboy2Core::height() { return HEIGHT; }
void Arduboy2Core::paint8Pixels(uint8_t pixels)
{
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
i2c_start(SSD1306_I2C_DATA);
i2c_sendByte(pixels);
i2c_stop();
#else
SPItransfer(pixels);
#endif
}
void Arduboy2Core::paintScreen(const uint8_t *image)
{
#if defined(OLED_SH1106) || defined(LCD_ST7565)
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
i2c_start(SSD1306_I2C_DATA);
for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++)
i2c_sendByte(pgm_read_byte(image+i));
i2c_stop();
#elif defined(OLED_SH1106) || defined(LCD_ST7565)
for (uint8_t i = 0; i < HEIGHT / 8; i++)
{
LCDCommandMode();
@ -486,7 +573,6 @@ void Arduboy2Core::paintScreen(const uint8_t *image)
for (uint8_t row = 0; row < HEIGHT / 8; row++)
{
uint8_t b = pgm_read_byte(image + i);
if (clear) *(image + i) = 0;
for (uint8_t shift = 0; shift < 4; shift++)
{
uint8_t c = 0xFF;
@ -513,7 +599,129 @@ void Arduboy2Core::paintScreen(const uint8_t *image)
// will be used by any buffer based subclass
void Arduboy2Core::paintScreen(uint8_t image[], bool clear)
{
#if defined(OLED_SH1106) || defined(LCD_ST7565)
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
uint16_t length = WIDTH * HEIGHT / 8;
uint8_t sda_clr = I2C_PORT & ~((1 << I2C_SDA) | (1 << I2C_SCL));
uint8_t scl = 1 << I2C_SCL;
uint8_t sda = 1 << I2C_SDA;
uint8_t scl_bit = I2C_SCL;
i2c_start(SSD1306_I2C_DATA);
#if defined (OLED_SSD1306_I2C)
//bitbanging I2C ~2Mbps (8 cycles per bit / 78 cycles per byte)
asm volatile (
" dec %[clear] \n" // get clear mask 0:0xFF, 1:0x00
"1: \n"
" ld r24, %a[ptr] \n" // fetch display byte from buffer
" mov r0, r24 \n" // move to shift register
" and r24, %[clear] \n" // apply clear mask
" st %a[ptr]+, r24 \n" // update buffer
" \n"
" sec \n" // set carry for 8 shift counts
" rol r0 \n" // shift a bit out and count at the same time
"2: \n"
" out %[port], %[sda0] \n" // preemtively clear SDA
" brcc 3f \n" // skip if dealing with 0 bit
" out %[pin], %[sda] \n"
"3: \n"
" out %[pin], %[scl] \n" // toggle SCL on
" lsl r0 \n" // next bit to carry (moved here for 1 extra cycle delay)
" out %[pin], %[scl] \n" // toggle SCL off
" brne 2b \n" // initial set carry will be shifted out after 8 loops setting Z flag
" \n"
" out %[port], %[sda0] \n" // clear SDA for ACK
" subi %A[len], 1 \n" // len-- part1 (moved here for 1 cycle delay)
" out %[pin], %[scl] \n" // set SCL (2 cycles required)
" sbci %B[len], 0 \n" // len-- part2 (moved here for 1 cycle delay)
" out %[pin], %[scl] \n" // clear SCL (2 cycles required)
" brne 1b \n"
:[ptr] "+e" (image),
[len] "+d" (length),
[clear] "+r" (clear)
:[port] "i" (_SFR_IO_ADDR(I2C_PORT)),
[pin] "i" (_SFR_IO_ADDR(I2C_PIN)),
[sda0] "r" (sda_clr),
[scl] "r" (scl),
[sda] "r" (sda)
:"r24"
);
#else
//bitbanging I2C @ 2.66Mbps (6 cycles per bit / 56 cycles per byte)
asm volatile (
" dec %[clear] \n" // get clear mask 0:0xFF, 1:0x00
" ld r0, %a[ptr] \n" // fetch display byte from buffer
"1: \n"
" sbrc r0, 7 \n" // MSB first comes first
" out %[pin], %[sda] \n" // toggle SDA on for 1-bit
" out %[pin], %[scl] \n" // toggle SCL high
" mov r24, r0 \n" // duplicate byte (also serves as extra clock cycle delay)
" out %[pin], %[scl] \n" // toggle SCL low
" out %[port], %[sda0] \n" // preemptively clear SDA for next bit
" \n"
" sbrc r0, 6 \n" // repeat of above but for bit 6
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" and r24, %[clear] \n" // apply clear mask (also serves as extra clock cycle delay)
" out %[pin], %[scl] \n" //
" out %[port], %[sda0] \n" //
" sbrc r0, 5 \n" //
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" st %a[ptr]+, r24 \n" // new buffer contents (also serves as extra clock cycle delay)
" out %[pin], %[scl] \n" //
" out %[port], %[sda0] \n" //
" sbrc r0, 4 \n" //
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay
" out %[port], %[sda0] \n" //
" sbrc r0, 3 \n" //
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay
" out %[port], %[sda0] \n" //
" sbrc r0, 2 \n" //
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay
" out %[port], %[sda0] \n" //
" sbrc r0, 1 \n" //
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" cbi %[port], %[sclb] \n" // using cbi for extra extra clock cycle delay
" out %[port], %[sda0] \n" //
" sbrc r0, 0 \n" //
" out %[pin], %[sda] \n" //
" out %[pin], %[scl] \n" //
" subi %A[len], 1 \n" // length-- part 1 (also serves as extra clock cycle delay)
" out %[pin], %[scl] \n" //
" out %[port], %[sda0] \n" // SDA low for ACK
" sbci %B[len], 0 \n" // length-- part 2 (also serves as extra clock cycle delay)
" out %[pin], %[scl] \n" // // clock ACK bit
" ld r0, %a[ptr] \n" // fetch next buffer byte (also serves as clock delay)
" out %[pin], %[scl] \n" //
" brne 1b \n" // length != 0 do next byte
:[ptr] "+e" (image),
[len] "+d" (length),
[clear] "+r" (clear)
:[port] "i" (_SFR_IO_ADDR(I2C_PORT)),
[pin] "i" (_SFR_IO_ADDR(I2C_PIN)),
[sda0] "r" (sda_clr),
[scl] "r" (scl),
[sda] "r" (sda),
[sclb] "i" (scl_bit)
:"r24"
);
#endif
i2c_stop();
#elif defined(OLED_SH1106) || defined(LCD_ST7565)
//Assembly optimized page mode display code with clear support.
//Each byte transfer takes 18 cycles
asm volatile (
@ -682,24 +890,77 @@ void Arduboy2Core::paintScreen(uint8_t image[], bool clear)
);
#endif
}
#if 0
// For reference, this is the "closed loop" C++ version of paintScreen()
// used prior to the above version.
void Arduboy2Core::paintScreen(uint8_t image[], bool clear)
{
uint8_t c;
int i = 0;
if (clear)
{
SPDR = image[i]; // set the first SPI data byte to get things started
image[i++] = 0; // clear the first image byte
}
else
SPDR = image[i++];
// the code to iterate the loop and get the next byte from the buffer is
// executed while the previous byte is being sent out by the SPI controller
while (i < (HEIGHT * WIDTH) / 8)
{
// get the next byte. It's put in a local variable so it can be sent as
// as soon as possible after the sending of the previous byte has completed
if (clear)
{
c = image[i];
// clear the byte in the image buffer
image[i++] = 0;
}
else
c = image[i++];
while (!(SPSR & _BV(SPIF))) { } // wait for the previous byte to be sent
// put the next byte in the SPI data register. The SPI controller will
// clock it out while the loop continues and gets the next byte ready
SPDR = c;
}
while (!(SPSR & _BV(SPIF))) { } // wait for the last byte to be sent
}
#endif
void Arduboy2Core::blank()
{
#ifdef OLED_SH1106
for (int i = 0; i < (HEIGHT * 132) / 8; i++)
#elif defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128)|| defined(OLED_128X64_ON_96X96) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128)
for (int i = 0; i < (HEIGHT * WIDTH) / 2; i++)
#else //OLED SSD1306 and compatibles
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
i2c_start(SSD1306_I2C_DATA);
for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++)
#endif
i2c_sendByte(0);
i2c_stop();
#else
#if defined (OLED_SH1106)
for (int i = 0; i < (HEIGHT * 132) / 8; i++)
#elif defined(OLED_96X96) || defined(OLED_128X96) || defined(OLED_128X128)|| defined(OLED_128X64_ON_96X96) || defined(OLED_128X64_ON_128X96) || defined(OLED_128X64_ON_128X128)|| defined(OLED_128X96_ON_128X128) || defined(OLED_96X96_ON_128X128) || defined(OLED_64X128_ON_128X128)
for (int i = 0; i < (HEIGHT * WIDTH) / 2; i++)
#else //OLED SSD1306 and compatibles
for (int i = 0; i < (HEIGHT * WIDTH) / 8; i++)
#endif
SPItransfer(0x00);
#endif
}
void Arduboy2Core::sendLCDCommand(uint8_t command)
{
#if defined(OLED_SSD1306_I2C) || (OLED_SSD1306_I2CX)
i2c_start(SSD1306_I2C_CMD);
i2c_sendByte(command);
i2c_stop();
#else
LCDCommandMode();
SPItransfer(command);
LCDDataMode();
#endif
}
// invert the display or set to normal
@ -994,6 +1255,7 @@ void Arduboy2Core::exitToBootloader()
while (true) { }
#else
bootloader_timer = 120; //ms
while (true) { }
#endif
}

View File

@ -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.
*

View File

@ -180,53 +180,238 @@ void Sprites::drawBitmap(int16_t x, int16_t y,
sRow += start_h;
ofs = (sRow * WIDTH) + x + xOffset;
uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset;
uint8_t data;
uint8_t mul_amt = 1 << yOffset;
uint16_t mask_data;
uint16_t bitmap_data;
const uint8_t ofs_step = draw_mode == SPRITE_PLUS_MASK ? 2 : 1;
const uint8_t ofs_stride = (w - rendered_width)*ofs_step;
const uint16_t initial_bofs = ((start_h * w) + xOffset)*ofs_step;
switch (draw_mode) {
case SPRITE_UNMASKED:
// we only want to mask the 8 bits of our own sprite, so we can
// calculate the mask before the start of the loop
mask_data = ~(0xFF * mul_amt);
// really if yOffset = 0 you have a faster case here that could be
// optimized
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
const uint8_t *bofs = bitmap + initial_bofs;
const uint8_t *mask_ofs = !mask ? bitmap : mask;
mask_ofs += initial_bofs + ofs_step - 1;
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
uint8_t data;
bitmap_data = pgm_read_byte(bofs) * mul_amt;
mask_data = ~bitmap_data;
if (draw_mode == SPRITE_UNMASKED) {
mask_data = ~(0xFF * mul_amt);
} else if (draw_mode == SPRITE_IS_MASK_ERASE) {
bitmap_data = 0;
} else {
mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt);
if (sRow >= 0) {
data = Arduboy2Base::sBuffer[ofs];
data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
Arduboy2Base::sBuffer[ofs] = data;
}
if (yOffset != 0 && sRow < 7) {
data = Arduboy2Base::sBuffer[ofs + WIDTH];
data &= (*((unsigned char *) (&mask_data) + 1));
data |= (*((unsigned char *) (&bitmap_data) + 1));
Arduboy2Base::sBuffer[ofs + WIDTH] = data;
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
if (sRow >= 0) {
data = Arduboy2Base::sBuffer[ofs];
data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
Arduboy2Base::sBuffer[ofs] = data;
case SPRITE_IS_MASK:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
Arduboy2Base::sBuffer[ofs] |= (uint8_t)(bitmap_data);
}
if (yOffset != 0 && sRow < 7) {
Arduboy2Base::sBuffer[ofs + WIDTH] |= (*((unsigned char *) (&bitmap_data) + 1));
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
if (yOffset != 0 && sRow < (HEIGHT / 8 - 1)) {
data = Arduboy2Base::sBuffer[ofs + WIDTH];
data &= (*((unsigned char *) (&mask_data) + 1));
data |= (*((unsigned char *) (&bitmap_data) + 1));
Arduboy2Base::sBuffer[ofs + WIDTH] = data;
break;
case SPRITE_IS_MASK_ERASE:
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
Arduboy2Base::sBuffer[ofs] &= ~(uint8_t)(bitmap_data);
}
if (yOffset != 0 && sRow < 7) {
Arduboy2Base::sBuffer[ofs + WIDTH] &= ~(*((unsigned char *) (&bitmap_data) + 1));
}
ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
ofs++;
mask_ofs += ofs_step;
bofs += ofs_step;
}
sRow++;
bofs += ofs_stride;
mask_ofs += ofs_stride;
ofs += WIDTH - rendered_width;
break;
case SPRITE_MASKED:
uint8_t *mask_ofs;
mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset;
for (uint8_t a = 0; a < loop_h; a++) {
for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
// NOTE: you might think in the yOffset==0 case that this results
// in more effort, but in all my testing the compiler was forcing
// 16-bit math to happen here anyways, so this isn't actually
// compiling to more code than it otherwise would. If the offset
// is 0 the high part of the word will just never be used.
// load data and bit shift
// mask needs to be bit flipped
mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt);
bitmap_data = pgm_read_byte(bofs) * mul_amt;
if (sRow >= 0) {
data = Arduboy2Base::sBuffer[ofs];
data &= (uint8_t)(mask_data);
data |= (uint8_t)(bitmap_data);
Arduboy2Base::sBuffer[ofs] = data;
}
if (yOffset != 0 && sRow < 7) {
data = Arduboy2Base::sBuffer[ofs + WIDTH];
data &= (*((unsigned char *) (&mask_data) + 1));
data |= (*((unsigned char *) (&bitmap_data) + 1));
Arduboy2Base::sBuffer[ofs + WIDTH] = data;
}
ofs++;
mask_ofs++;
bofs++;
}
sRow++;
bofs += w - rendered_width;
mask_ofs += w - rendered_width;
ofs += WIDTH - rendered_width;
}
break;
case SPRITE_PLUS_MASK:
// *2 because we use double the bits (mask + bitmap)
bofs = (uint8_t *)(bitmap + ((start_h * w) + xOffset) * 2);
uint8_t xi = rendered_width; // counter for x loop below
asm volatile(
"push r28\n" // save Y
"push r29\n"
"movw r28, %[buffer_ofs]\n" // Y = buffer_ofs_2
"adiw r28, 63\n" // buffer_ofs_2 = buffer_ofs + 128
"adiw r28, 63\n"
"adiw r28, 2\n"
"loop_y:\n"
"loop_x:\n"
// load bitmap and mask data
"lpm %A[bitmap_data], Z+\n"
"lpm %A[mask_data], Z+\n"
// shift mask and buffer data
"tst %[yOffset]\n"
"breq skip_shifting\n"
"mul %A[bitmap_data], %[mul_amt]\n"
"movw %[bitmap_data], r0\n"
"mul %A[mask_data], %[mul_amt]\n"
"movw %[mask_data], r0\n"
// SECOND PAGE
// if yOffset != 0 && sRow < 7
"cpi %[sRow], 7\n"
"brge end_second_page\n"
// then
"ld %[data], Y\n"
"com %B[mask_data]\n" // invert high byte of mask
"and %[data], %B[mask_data]\n"
"or %[data], %B[bitmap_data]\n"
// update buffer, increment
"st Y+, %[data]\n"
"end_second_page:\n"
"skip_shifting:\n"
// FIRST PAGE
// if sRow >= 0
"tst %[sRow]\n"
"brmi skip_first_page\n"
"ld %[data], %a[buffer_ofs]\n"
// then
"com %A[mask_data]\n"
"and %[data], %A[mask_data]\n"
"or %[data], %A[bitmap_data]\n"
// update buffer, increment
"st %a[buffer_ofs]+, %[data]\n"
"jmp end_first_page\n"
"skip_first_page:\n"
// since no ST Z+ when skipped we need to do this manually
"adiw %[buffer_ofs], 1\n"
"end_first_page:\n"
// "x_loop_next:\n"
"dec %[xi]\n"
"brne loop_x\n"
// increment y
"next_loop_y:\n"
"dec %[yi]\n"
"breq finished\n"
"mov %[xi], %[x_count]\n" // reset x counter
// sRow++;
"inc %[sRow]\n"
"clr __zero_reg__\n"
// sprite_ofs += (w - rendered_width) * 2;
"add %A[sprite_ofs], %A[sprite_ofs_jump]\n"
"adc %B[sprite_ofs], __zero_reg__\n"
// buffer_ofs += WIDTH - rendered_width;
"add %A[buffer_ofs], %A[buffer_ofs_jump]\n"
"adc %B[buffer_ofs], __zero_reg__\n"
// buffer_ofs_page_2 += WIDTH - rendered_width;
"add r28, %A[buffer_ofs_jump]\n"
"adc r29, __zero_reg__\n"
"rjmp loop_y\n"
"finished:\n"
// put the Y register back in place
"pop r29\n"
"pop r28\n"
"clr __zero_reg__\n" // just in case
: [xi] "+&a" (xi),
[yi] "+&a" (loop_h),
[sRow] "+&a" (sRow), // CPI requires an upper register (r16-r23)
[data] "=&l" (data),
[mask_data] "=&l" (mask_data),
[bitmap_data] "=&l" (bitmap_data)
:
[screen_width] "M" (WIDTH),
[x_count] "l" (rendered_width), // lower register
[sprite_ofs] "z" (bofs),
[buffer_ofs] "x" (Arduboy2Base::sBuffer+ofs),
[buffer_ofs_jump] "a" (WIDTH-rendered_width), // upper reg (r16-r23)
[sprite_ofs_jump] "a" ((w-rendered_width)*2), // upper reg (r16-r23)
// [sprite_ofs_jump] "r" (0),
[yOffset] "l" (yOffset), // lower register
[mul_amt] "l" (mul_amt) // lower register
// NOTE: We also clobber r28 and r29 (y) but sometimes the compiler
// won't allow us, so in order to make this work we don't tell it
// that we clobber them. Instead, we push/pop to preserve them.
// Then we need to guarantee that the the compiler doesn't put one of
// our own variables into r28/r29.
// We do that by specifying all the inputs and outputs use either
// lower registers (l) or simple (r16-r23) upper registers (a).
: // pushes/clobbers/pops r28 and r29 (y)
);
break;
}
}

View File

@ -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.