2016-05-27 21:43:58 +00:00
|
|
|
#include "Arduboy2.h"
|
2015-05-01 05:26:58 +00:00
|
|
|
#include "ab_logo.c"
|
2016-05-27 21:43:58 +00:00
|
|
|
#include "glcdfont.c"
|
2015-05-01 05:26:58 +00:00
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
//========================================
|
|
|
|
//========== class Arduboy2Base ==========
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
Arduboy2Base::Arduboy2Base()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
// frame management
|
|
|
|
setFrameRate(60);
|
|
|
|
frameCount = 0;
|
|
|
|
nextFrameStart = 0;
|
|
|
|
post_render = false;
|
2016-02-11 13:57:06 +00:00
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
// init not necessary, will be reset after first use
|
|
|
|
// lastFrameStart
|
|
|
|
// lastFrameDurationMs
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::start() // deprecated
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
begin();
|
|
|
|
}
|
|
|
|
|
2016-02-21 04:18:02 +00:00
|
|
|
// functions called here should be public so users can create their
|
|
|
|
// own init functions if they need different behavior than `begin`
|
|
|
|
// provides by default
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::begin()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-02-20 19:54:06 +00:00
|
|
|
boot(); // raw hardware
|
2016-02-20 19:49:54 +00:00
|
|
|
|
2016-06-13 18:21:45 +00:00
|
|
|
blank(); // blank the display
|
|
|
|
|
2016-02-20 19:54:06 +00:00
|
|
|
// utils
|
2016-02-20 19:49:54 +00:00
|
|
|
if(pressed(UP_BUTTON)) {
|
|
|
|
flashlight();
|
|
|
|
}
|
|
|
|
|
2016-06-13 18:21:45 +00:00
|
|
|
// check for and handle buttons held during start up for system control
|
|
|
|
systemButtons();
|
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
bootLogo();
|
|
|
|
|
|
|
|
audio.begin();
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::flashlight()
|
2016-02-20 19:43:36 +00:00
|
|
|
{
|
|
|
|
// sendLCDCommand(OLED_ALL_PIXELS_ON); // smaller than allPixelsOn()
|
|
|
|
setRGBled(255,255,255);
|
|
|
|
while(!pressed(DOWN_BUTTON)) {
|
|
|
|
idle();
|
|
|
|
}
|
|
|
|
setRGBled(0,0,0);
|
|
|
|
}
|
|
|
|
|
2016-06-13 18:21:45 +00:00
|
|
|
void Arduboy2Base::systemButtons() {
|
|
|
|
while (pressed(B_BUTTON)) {
|
|
|
|
digitalWrite(BLUE_LED, LOW); // turn on blue LED
|
|
|
|
sysCtrlSound(UP_BUTTON + B_BUTTON, GREEN_LED, 0xff);
|
|
|
|
sysCtrlSound(DOWN_BUTTON + B_BUTTON, RED_LED, 0);
|
|
|
|
delay(200);
|
|
|
|
}
|
|
|
|
|
|
|
|
digitalWrite(BLUE_LED, HIGH); // turn off blue LED
|
|
|
|
}
|
|
|
|
|
|
|
|
void Arduboy2Base::sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal) {
|
|
|
|
if (pressed(buttons)) {
|
|
|
|
digitalWrite(BLUE_LED, HIGH); // turn off blue LED
|
|
|
|
delay(200);
|
|
|
|
digitalWrite(led, LOW); // turn on "acknowledge" LED
|
|
|
|
EEPROM.update(EEPROM_AUDIO_ON_OFF, eeVal);
|
|
|
|
delay(500);
|
|
|
|
digitalWrite(led, HIGH); // turn off "acknowledge" LED
|
|
|
|
|
|
|
|
while (pressed(buttons)) {} // Wait for button release
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::bootLogo()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
// setRGBled(10,0,0);
|
|
|
|
for(int8_t y = -18; y<=24; y++) {
|
|
|
|
setRGBled(24-y, 0, 0);
|
|
|
|
|
|
|
|
clear();
|
|
|
|
drawBitmap(20,y, arduboy_logo, 88, 16, WHITE);
|
|
|
|
display();
|
|
|
|
delay(27);
|
|
|
|
// longer delay post boot, we put it inside the loop to
|
|
|
|
// save the flash calling clear/delay again outside the loop
|
|
|
|
if (y==-16) {
|
|
|
|
delay(250);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delay(750);
|
|
|
|
setRGBled(0,0,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Frame management */
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::setFrameRate(uint8_t rate)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
frameRate = rate;
|
|
|
|
eachFrameMillis = 1000/rate;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
bool Arduboy2Base::everyXFrames(uint8_t frames)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
return frameCount % frames == 0;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
bool Arduboy2Base::nextFrame()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
long now = millis();
|
|
|
|
uint8_t remaining;
|
|
|
|
|
|
|
|
// post render
|
|
|
|
if (post_render) {
|
|
|
|
lastFrameDurationMs = now - lastFrameStart;
|
|
|
|
frameCount++;
|
|
|
|
post_render = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it's not time for the next frame yet
|
|
|
|
if (now < nextFrameStart) {
|
|
|
|
remaining = nextFrameStart - now;
|
|
|
|
// if we have more than 1ms to spare, lets sleep
|
|
|
|
// we should be woken up by timer0 every 1ms, so this should be ok
|
|
|
|
if (remaining > 1)
|
|
|
|
idle();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// pre-render
|
|
|
|
|
2016-02-28 07:52:42 +00:00
|
|
|
// next frame should start from last frame start + frame duration
|
|
|
|
nextFrameStart = lastFrameStart + eachFrameMillis;
|
|
|
|
// If we're running CPU at 100%+ (too slow to complete each loop within
|
|
|
|
// the frame duration) then it's possible that we get "behind"... Say we
|
|
|
|
// took 5ms too long, resulting in nextFrameStart being 5ms in the PAST.
|
|
|
|
// In that case we simply schedule the next frame to start immediately.
|
|
|
|
//
|
|
|
|
// If we were to let the nextFrameStart slide further and further into
|
|
|
|
// the past AND eventually the CPU usage dropped then frame management
|
|
|
|
// would try to "catch up" (by speeding up the game) to make up for all
|
|
|
|
// that lost time. That would not be good. We allow frames to take too
|
|
|
|
// long (what choice do we have?), but we do not allow super-fast frames
|
|
|
|
// to make up for slow frames in the past.
|
|
|
|
if (nextFrameStart < now) {
|
|
|
|
nextFrameStart = now;
|
|
|
|
}
|
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
lastFrameStart = now;
|
2016-02-28 07:52:42 +00:00
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
post_render = true;
|
|
|
|
return post_render;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
int Arduboy2Base::cpuLoad()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
return lastFrameDurationMs*100 / eachFrameMillis;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::initRandomSeed()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
power_adc_enable(); // ADC on
|
|
|
|
randomSeed(~rawADC(ADC_TEMP) * ~rawADC(ADC_VOLTAGE) * ~micros() + micros());
|
|
|
|
power_adc_disable(); // ADC off
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
uint16_t Arduboy2Base::rawADC(byte adc_bits)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
ADMUX = adc_bits;
|
|
|
|
// we also need MUX5 for temperature check
|
|
|
|
if (adc_bits == ADC_TEMP) {
|
|
|
|
ADCSRB = _BV(MUX5);
|
|
|
|
}
|
|
|
|
|
|
|
|
delay(2); // Wait for ADMUX setting to settle
|
|
|
|
ADCSRA |= _BV(ADSC); // Start conversion
|
|
|
|
while (bit_is_set(ADCSRA,ADSC)); // measuring
|
|
|
|
|
|
|
|
return ADC;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Graphics */
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::clearDisplay() // deprecated
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
clear();
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::clear()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
fillScreen(BLACK);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawPixel(int x, int y, uint8_t color)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
#ifdef PIXEL_SAFE_MODE
|
|
|
|
if (x < 0 || x > (WIDTH-1) || y < 0 || y > (HEIGHT-1))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uint8_t row = (uint8_t)y / 8;
|
|
|
|
if (color)
|
|
|
|
{
|
|
|
|
sBuffer[(row*WIDTH) + (uint8_t)x] |= _BV((uint8_t)y % 8);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sBuffer[(row*WIDTH) + (uint8_t)x] &= ~ _BV((uint8_t)y % 8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
uint8_t Arduboy2Base::getPixel(uint8_t x, uint8_t y)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
uint8_t row = y / 8;
|
|
|
|
uint8_t bit_position = y % 8;
|
|
|
|
return (sBuffer[(row*WIDTH) + x] & _BV(bit_position)) >> bit_position;
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawCircle(int16_t x0, int16_t y0, uint8_t r, uint8_t color)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
int16_t f = 1 - r;
|
|
|
|
int16_t ddF_x = 1;
|
|
|
|
int16_t ddF_y = -2 * r;
|
|
|
|
int16_t x = 0;
|
|
|
|
int16_t y = r;
|
|
|
|
|
|
|
|
drawPixel(x0, y0+r, color);
|
|
|
|
drawPixel(x0, y0-r, color);
|
|
|
|
drawPixel(x0+r, y0, color);
|
|
|
|
drawPixel(x0-r, y0, color);
|
|
|
|
|
|
|
|
while (x<y)
|
|
|
|
{
|
|
|
|
if (f >= 0)
|
|
|
|
{
|
|
|
|
y--;
|
|
|
|
ddF_y += 2;
|
|
|
|
f += ddF_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
x++;
|
|
|
|
ddF_x += 2;
|
|
|
|
f += ddF_x;
|
|
|
|
|
|
|
|
drawPixel(x0 + x, y0 + y, color);
|
|
|
|
drawPixel(x0 - x, y0 + y, color);
|
|
|
|
drawPixel(x0 + x, y0 - y, color);
|
|
|
|
drawPixel(x0 - x, y0 - y, color);
|
|
|
|
drawPixel(x0 + y, y0 + x, color);
|
|
|
|
drawPixel(x0 - y, y0 + x, color);
|
|
|
|
drawPixel(x0 + y, y0 - x, color);
|
|
|
|
drawPixel(x0 - y, y0 - x, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawCircleHelper
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x0, int16_t y0, uint8_t r, uint8_t cornername, uint8_t color)
|
|
|
|
{
|
|
|
|
int16_t f = 1 - r;
|
|
|
|
int16_t ddF_x = 1;
|
|
|
|
int16_t ddF_y = -2 * r;
|
|
|
|
int16_t x = 0;
|
|
|
|
int16_t y = r;
|
|
|
|
|
|
|
|
while (x<y)
|
|
|
|
{
|
|
|
|
if (f >= 0)
|
|
|
|
{
|
|
|
|
y--;
|
|
|
|
ddF_y += 2;
|
|
|
|
f += ddF_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
x++;
|
|
|
|
ddF_x += 2;
|
|
|
|
f += ddF_x;
|
|
|
|
|
|
|
|
if (cornername & 0x4)
|
|
|
|
{
|
|
|
|
drawPixel(x0 + x, y0 + y, color);
|
|
|
|
drawPixel(x0 + y, y0 + x, color);
|
|
|
|
}
|
|
|
|
if (cornername & 0x2)
|
|
|
|
{
|
|
|
|
drawPixel(x0 + x, y0 - y, color);
|
|
|
|
drawPixel(x0 + y, y0 - x, color);
|
|
|
|
}
|
|
|
|
if (cornername & 0x8)
|
|
|
|
{
|
|
|
|
drawPixel(x0 - y, y0 + x, color);
|
|
|
|
drawPixel(x0 - x, y0 + y, color);
|
|
|
|
}
|
|
|
|
if (cornername & 0x1)
|
|
|
|
{
|
|
|
|
drawPixel(x0 - y, y0 - x, color);
|
|
|
|
drawPixel(x0 - x, y0 - y, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::fillCircle(int16_t x0, int16_t y0, uint8_t r, uint8_t color)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
drawFastVLine(x0, y0-r, 2*r+1, color);
|
|
|
|
fillCircleHelper(x0, y0, r, 3, 0, color);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::fillCircleHelper
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x0, int16_t y0, uint8_t r, uint8_t cornername, int16_t delta,
|
|
|
|
uint8_t color)
|
|
|
|
{
|
|
|
|
// used to do circles and roundrects!
|
|
|
|
int16_t f = 1 - r;
|
|
|
|
int16_t ddF_x = 1;
|
|
|
|
int16_t ddF_y = -2 * r;
|
|
|
|
int16_t x = 0;
|
|
|
|
int16_t y = r;
|
|
|
|
|
|
|
|
while (x < y)
|
|
|
|
{
|
|
|
|
if (f >= 0)
|
|
|
|
{
|
|
|
|
y--;
|
|
|
|
ddF_y += 2;
|
|
|
|
f += ddF_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
x++;
|
|
|
|
ddF_x += 2;
|
|
|
|
f += ddF_x;
|
|
|
|
|
|
|
|
if (cornername & 0x1)
|
|
|
|
{
|
|
|
|
drawFastVLine(x0+x, y0-y, 2*y+1+delta, color);
|
|
|
|
drawFastVLine(x0+y, y0-x, 2*x+1+delta, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cornername & 0x2)
|
|
|
|
{
|
|
|
|
drawFastVLine(x0-x, y0-y, 2*y+1+delta, color);
|
|
|
|
drawFastVLine(x0-y, y0-x, 2*x+1+delta, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawLine
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color)
|
|
|
|
{
|
|
|
|
// bresenham's algorithm - thx wikpedia
|
|
|
|
boolean steep = abs(y1 - y0) > abs(x1 - x0);
|
|
|
|
if (steep) {
|
|
|
|
swap(x0, y0);
|
|
|
|
swap(x1, y1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x0 > x1) {
|
|
|
|
swap(x0, x1);
|
|
|
|
swap(y0, y1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t dx, dy;
|
|
|
|
dx = x1 - x0;
|
|
|
|
dy = abs(y1 - y0);
|
|
|
|
|
|
|
|
int16_t err = dx / 2;
|
|
|
|
int8_t ystep;
|
|
|
|
|
|
|
|
if (y0 < y1)
|
|
|
|
{
|
|
|
|
ystep = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ystep = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; x0 <= x1; x0++)
|
|
|
|
{
|
|
|
|
if (steep)
|
|
|
|
{
|
|
|
|
drawPixel(y0, x0, color);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
drawPixel(x0, y0, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
err -= dy;
|
|
|
|
if (err < 0)
|
|
|
|
{
|
|
|
|
y0 += ystep;
|
|
|
|
err += dx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawRect
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t color)
|
|
|
|
{
|
|
|
|
drawFastHLine(x, y, w, color);
|
|
|
|
drawFastHLine(x, y+h-1, w, color);
|
|
|
|
drawFastVLine(x, y, h, color);
|
|
|
|
drawFastVLine(x+w-1, y, h, color);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawFastVLine
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, uint8_t h, uint8_t color)
|
|
|
|
{
|
|
|
|
int end = y+h;
|
|
|
|
for (int a = max(0,y); a < min(end,HEIGHT); a++)
|
|
|
|
{
|
|
|
|
drawPixel(x,a,color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawFastHLine
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, uint8_t w, uint8_t color)
|
|
|
|
{
|
2016-02-23 05:51:54 +00:00
|
|
|
// Do bounds/limit checks
|
|
|
|
if (y < 0 || y >= HEIGHT) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we don't try to draw below 0
|
|
|
|
if (x < 0) {
|
|
|
|
w += x;
|
|
|
|
x = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we don't go off the edge of the display
|
|
|
|
if ((x + w) > WIDTH) {
|
|
|
|
w = (WIDTH - x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if our width is now negative, punt
|
|
|
|
if (w <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// buffer pointer plus row offset + x offset
|
|
|
|
register uint8_t *pBuf = sBuffer + ((y/8) * WIDTH) + x;
|
|
|
|
|
|
|
|
// pixel mask
|
|
|
|
register uint8_t mask = 1 << (y&7);
|
|
|
|
|
|
|
|
switch (color)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-02-23 05:51:54 +00:00
|
|
|
case WHITE:
|
|
|
|
while(w--) {
|
|
|
|
*pBuf++ |= mask;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BLACK:
|
|
|
|
mask = ~mask;
|
|
|
|
while(w--) {
|
|
|
|
*pBuf++ &= mask;
|
|
|
|
};
|
|
|
|
break;
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::fillRect
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t color)
|
|
|
|
{
|
|
|
|
// stupidest version - update in subclasses if desired!
|
|
|
|
for (int16_t i=x; i<x+w; i++)
|
|
|
|
{
|
|
|
|
drawFastVLine(i, y, h, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::fillScreen(uint8_t color)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-02-20 19:43:14 +00:00
|
|
|
// C version :
|
2015-05-01 05:26:58 +00:00
|
|
|
//
|
|
|
|
// if (color) color = 0xFF; //change any nonzero argument to b11111111 and insert into screen array.
|
2016-02-20 19:43:14 +00:00
|
|
|
// for(int16_t i=0; i<1024; i++) { sBuffer[i] = color; } //sBuffer = (128*64) = 8192/8 = 1024 bytes.
|
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
asm volatile
|
|
|
|
(
|
|
|
|
// load color value into r27
|
|
|
|
"mov r27, %1 \n\t"
|
|
|
|
// if value is zero, skip assigning to 0xff
|
|
|
|
"cpse r27, __zero_reg__ \n\t"
|
|
|
|
"ldi r27, 0xff \n\t"
|
|
|
|
// load sBuffer pointer into Z
|
|
|
|
"movw r30, %0\n\t"
|
|
|
|
// counter = 0
|
|
|
|
"clr __tmp_reg__ \n\t"
|
|
|
|
"loopto: \n\t"
|
|
|
|
// (4x) push zero into screen buffer,
|
|
|
|
// then increment buffer position
|
|
|
|
"st Z+, r27 \n\t"
|
|
|
|
"st Z+, r27 \n\t"
|
|
|
|
"st Z+, r27 \n\t"
|
|
|
|
"st Z+, r27 \n\t"
|
|
|
|
// increase counter
|
|
|
|
"inc __tmp_reg__ \n\t"
|
|
|
|
// repeat for 256 loops
|
|
|
|
// (until counter rolls over back to 0)
|
|
|
|
"brne loopto \n\t"
|
|
|
|
// input: sBuffer, color
|
|
|
|
// modified: Z (r30, r31), r27
|
|
|
|
:
|
|
|
|
: "r" (sBuffer), "r" (color)
|
|
|
|
: "r30", "r31", "r27"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawRoundRect
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t r, uint8_t color)
|
|
|
|
{
|
|
|
|
// smarter version
|
|
|
|
drawFastHLine(x+r, y, w-2*r, color); // Top
|
|
|
|
drawFastHLine(x+r, y+h-1, w-2*r, color); // Bottom
|
|
|
|
drawFastVLine(x, y+r, h-2*r, color); // Left
|
|
|
|
drawFastVLine(x+w-1, y+r, h-2*r, color); // Right
|
|
|
|
// draw four corners
|
|
|
|
drawCircleHelper(x+r, y+r, r, 1, color);
|
|
|
|
drawCircleHelper(x+w-r-1, y+r, r, 2, color);
|
|
|
|
drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color);
|
|
|
|
drawCircleHelper(x+r, y+h-r-1, r, 8, color);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::fillRoundRect
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t r, uint8_t color)
|
|
|
|
{
|
|
|
|
// smarter version
|
|
|
|
fillRect(x+r, y, w-2*r, h, color);
|
|
|
|
|
|
|
|
// draw four corners
|
|
|
|
fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color);
|
|
|
|
fillCircleHelper(x+r, y+r, r, 2, h-2*r-1, color);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawTriangle
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t color)
|
|
|
|
{
|
|
|
|
drawLine(x0, y0, x1, y1, color);
|
|
|
|
drawLine(x1, y1, x2, y2, color);
|
|
|
|
drawLine(x2, y2, x0, y0, color);
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::fillTriangle
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t color)
|
|
|
|
{
|
|
|
|
|
|
|
|
int16_t a, b, y, last;
|
|
|
|
// Sort coordinates by Y order (y2 >= y1 >= y0)
|
|
|
|
if (y0 > y1)
|
|
|
|
{
|
|
|
|
swap(y0, y1); swap(x0, x1);
|
|
|
|
}
|
|
|
|
if (y1 > y2)
|
|
|
|
{
|
|
|
|
swap(y2, y1); swap(x2, x1);
|
|
|
|
}
|
|
|
|
if (y0 > y1)
|
|
|
|
{
|
|
|
|
swap(y0, y1); swap(x0, x1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(y0 == y2)
|
|
|
|
{ // Handle awkward all-on-same-line case as its own thing
|
|
|
|
a = b = x0;
|
|
|
|
if(x1 < a)
|
|
|
|
{
|
|
|
|
a = x1;
|
|
|
|
}
|
|
|
|
else if(x1 > b)
|
|
|
|
{
|
|
|
|
b = x1;
|
|
|
|
}
|
|
|
|
if(x2 < a)
|
|
|
|
{
|
|
|
|
a = x2;
|
|
|
|
}
|
|
|
|
else if(x2 > b)
|
|
|
|
{
|
|
|
|
b = x2;
|
|
|
|
}
|
|
|
|
drawFastHLine(a, y0, b-a+1, color);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t dx01 = x1 - x0,
|
|
|
|
dy01 = y1 - y0,
|
|
|
|
dx02 = x2 - x0,
|
|
|
|
dy02 = y2 - y0,
|
|
|
|
dx12 = x2 - x1,
|
|
|
|
dy12 = y2 - y1,
|
|
|
|
sa = 0,
|
|
|
|
sb = 0;
|
|
|
|
|
|
|
|
// For upper part of triangle, find scanline crossings for segments
|
|
|
|
// 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
|
|
|
|
// is included here (and second loop will be skipped, avoiding a /0
|
|
|
|
// error there), otherwise scanline y1 is skipped here and handled
|
|
|
|
// in the second loop...which also avoids a /0 error here if y0=y1
|
|
|
|
// (flat-topped triangle).
|
|
|
|
if (y1 == y2)
|
|
|
|
{
|
|
|
|
last = y1; // Include y1 scanline
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
last = y1-1; // Skip it
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for(y = y0; y <= last; y++)
|
|
|
|
{
|
|
|
|
a = x0 + sa / dy01;
|
|
|
|
b = x0 + sb / dy02;
|
|
|
|
sa += dx01;
|
|
|
|
sb += dx02;
|
|
|
|
|
|
|
|
if(a > b)
|
|
|
|
{
|
|
|
|
swap(a,b);
|
|
|
|
}
|
|
|
|
|
|
|
|
drawFastHLine(a, y, b-a+1, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
// For lower part of triangle, find scanline crossings for segments
|
|
|
|
// 0-2 and 1-2. This loop is skipped if y1=y2.
|
|
|
|
sa = dx12 * (y - y1);
|
|
|
|
sb = dx02 * (y - y0);
|
|
|
|
|
|
|
|
for(; y <= y2; y++)
|
|
|
|
{
|
|
|
|
a = x1 + sa / dy12;
|
|
|
|
b = x0 + sb / dy02;
|
|
|
|
sa += dx12;
|
|
|
|
sb += dx02;
|
|
|
|
|
|
|
|
if(a > b)
|
|
|
|
{
|
|
|
|
swap(a,b);
|
|
|
|
}
|
|
|
|
|
|
|
|
drawFastHLine(a, y, b-a+1, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawBitmap
|
2016-02-20 19:43:14 +00:00
|
|
|
(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h,
|
2015-05-01 05:26:58 +00:00
|
|
|
uint8_t color)
|
|
|
|
{
|
|
|
|
// no need to dar at all of we're offscreen
|
|
|
|
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++;
|
|
|
|
for (int a = 0; a < rows; a++) {
|
|
|
|
int bRow = sRow + a;
|
|
|
|
if (bRow > (HEIGHT/8)-1) break;
|
|
|
|
if (bRow > -2) {
|
|
|
|
for (int iCol = 0; iCol<w; iCol++) {
|
|
|
|
if (iCol + x > (WIDTH-1)) break;
|
|
|
|
if (iCol + x >= 0) {
|
|
|
|
if (bRow >= 0) {
|
|
|
|
if (color == WHITE) this->sBuffer[ (bRow*WIDTH) + x + iCol ] |= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset;
|
|
|
|
else if (color == BLACK) this->sBuffer[ (bRow*WIDTH) + x + iCol ] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) << yOffset);
|
|
|
|
else this->sBuffer[ (bRow*WIDTH) + x + iCol ] ^= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset;
|
|
|
|
}
|
|
|
|
if (yOffset && bRow<(HEIGHT/8)-1 && bRow > -2) {
|
|
|
|
if (color == WHITE) this->sBuffer[ ((bRow+1)*WIDTH) + x + iCol ] |= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset);
|
|
|
|
else if (color == BLACK) this->sBuffer[ ((bRow+1)*WIDTH) + x + iCol ] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset));
|
|
|
|
else this->sBuffer[ ((bRow+1)*WIDTH) + x + iCol ] ^= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::drawSlowXYBitmap
|
2015-05-01 05:26:58 +00:00
|
|
|
(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, uint8_t color)
|
|
|
|
{
|
2016-05-27 21:43:58 +00:00
|
|
|
// no need to draw at all of we're offscreen
|
2015-05-01 05:26:58 +00:00
|
|
|
if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int16_t xi, yi, byteWidth = (w + 7) / 8;
|
|
|
|
for(yi = 0; yi < h; yi++) {
|
|
|
|
for(xi = 0; xi < w; xi++ ) {
|
|
|
|
if(pgm_read_byte(bitmap + yi * byteWidth + xi / 8) & (128 >> (xi & 7))) {
|
|
|
|
drawPixel(x + xi, y + yi, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2Base::display()
|
|
|
|
{
|
|
|
|
this->paintScreen(sBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char* Arduboy2Base::getBuffer()
|
|
|
|
{
|
|
|
|
return sBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean Arduboy2Base::pressed(uint8_t buttons)
|
|
|
|
{
|
|
|
|
return (buttonsState() & buttons) == buttons;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean Arduboy2Base::notPressed(uint8_t buttons)
|
|
|
|
{
|
|
|
|
return (buttonsState() & buttons) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Arduboy2Base::swap(int16_t& a, int16_t& b)
|
|
|
|
{
|
|
|
|
int temp = a;
|
|
|
|
a = b;
|
|
|
|
b = temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
Arduboy2::Arduboy2()
|
|
|
|
{
|
|
|
|
cursor_x = 0;
|
|
|
|
cursor_y = 0;
|
|
|
|
textColor = 1;
|
|
|
|
textBackground = 0;
|
|
|
|
textSize = 1;
|
|
|
|
textWrap = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//====================================
|
|
|
|
//========== class Arduboy2 ==========
|
|
|
|
//====================================
|
|
|
|
|
|
|
|
size_t Arduboy2::write(uint8_t c)
|
|
|
|
{
|
|
|
|
if (c == '\n')
|
|
|
|
{
|
|
|
|
cursor_y += textSize * 8;
|
|
|
|
cursor_x = 0;
|
|
|
|
}
|
|
|
|
else if (c == '\r')
|
|
|
|
{
|
|
|
|
// skip em
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
drawChar(cursor_x, cursor_y, c, textColor, textBackground, textSize);
|
|
|
|
cursor_x += textSize * 6;
|
|
|
|
if (textWrap && (cursor_x > (WIDTH - textSize * 6)))
|
|
|
|
{
|
|
|
|
// calling ourselves recursively for 'newline' is
|
|
|
|
// 12 bytes smaller than doing the same math here
|
|
|
|
write('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Arduboy2::drawChar
|
|
|
|
(int16_t x, int16_t y, unsigned char c, uint8_t color, uint8_t bg, uint8_t size)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
boolean draw_background = bg != color;
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
if ((x >= WIDTH) || // Clip right
|
|
|
|
(y >= HEIGHT) || // Clip bottom
|
|
|
|
((x + 5 * size - 1) < 0) || // Clip left
|
|
|
|
((y + 8 * size - 1) < 0) // Clip top
|
|
|
|
)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int8_t i=0; i<6; i++ )
|
|
|
|
{
|
|
|
|
uint8_t line;
|
|
|
|
if (i == 5)
|
|
|
|
{
|
|
|
|
line = 0x0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
line = pgm_read_byte(font+(c*5)+i);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int8_t j = 0; j<8; j++)
|
|
|
|
{
|
|
|
|
uint8_t draw_color = (line & 0x1) ? color : bg;
|
|
|
|
|
|
|
|
if (draw_color || draw_background) {
|
|
|
|
for (uint8_t a = 0; a < size; a++ ) {
|
|
|
|
for (uint8_t b = 0; b < size; b++ ) {
|
|
|
|
drawPixel(x + (i * size) + a, y + (j * size) + b, draw_color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
line >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2::setCursor(int16_t x, int16_t y)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-05-27 21:43:58 +00:00
|
|
|
cursor_x = x;
|
|
|
|
cursor_y = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t Arduboy2::getCursorX() {
|
|
|
|
return cursor_x;
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
uint16_t Arduboy2::getCursorY() {
|
|
|
|
return cursor_y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Arduboy2::setTextColor(uint8_t color)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-05-27 21:43:58 +00:00
|
|
|
textColor = color;
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2::setTextBackground(uint8_t bg)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-05-27 21:43:58 +00:00
|
|
|
textBackground = bg;
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2::setTextSize(uint8_t s)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-05-27 21:43:58 +00:00
|
|
|
// size must always be 1 or higher
|
|
|
|
textSize = max(1, s);
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 21:43:58 +00:00
|
|
|
void Arduboy2::setTextWrap(boolean w)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
2016-05-27 21:43:58 +00:00
|
|
|
textWrap = w;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Arduboy2::clear() {
|
|
|
|
Arduboy2Base::clear();
|
|
|
|
cursor_x = cursor_y = 0;
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
2016-02-20 19:43:14 +00:00
|
|
|
|