2015-05-01 05:26:58 +00:00
|
|
|
#include "core.h"
|
|
|
|
|
|
|
|
// need to redeclare these here since we declare them static in .h
|
2016-02-21 06:39:41 +00:00
|
|
|
volatile uint8_t *ArduboyCore::csport, *ArduboyCore::dcport;
|
|
|
|
uint8_t ArduboyCore::cspinmask, ArduboyCore::dcpinmask;
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
const uint8_t PROGMEM pinBootProgram[] = {
|
|
|
|
// buttons
|
|
|
|
PIN_LEFT_BUTTON, INPUT_PULLUP,
|
|
|
|
PIN_RIGHT_BUTTON, INPUT_PULLUP,
|
|
|
|
PIN_UP_BUTTON, INPUT_PULLUP,
|
|
|
|
PIN_DOWN_BUTTON, INPUT_PULLUP,
|
|
|
|
PIN_A_BUTTON, INPUT_PULLUP,
|
|
|
|
PIN_B_BUTTON, INPUT_PULLUP,
|
|
|
|
|
2016-02-24 01:57:35 +00:00
|
|
|
// audio is specifically not included here as those pins are handled
|
|
|
|
// separately by `audio.begin()`, `audio.on()` and `audio.off()` in order
|
|
|
|
// to respect the EEPROM audio settings
|
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
// OLED SPI
|
|
|
|
DC, OUTPUT,
|
|
|
|
CS, OUTPUT,
|
|
|
|
RST, OUTPUT,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
const uint8_t PROGMEM lcdBootProgram[] = {
|
|
|
|
// boot defaults are commented out but left here incase they
|
|
|
|
// might prove useful for reference
|
|
|
|
//
|
|
|
|
// Further reading: https://www.adafruit.com/datasheets/SSD1306.pdf
|
|
|
|
//
|
|
|
|
// Display Off
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xAE,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set Display Clock Divisor v = 0xF0
|
|
|
|
// default is 0x80
|
|
|
|
0xD5, 0xF0,
|
|
|
|
|
|
|
|
// Set Multiplex Ratio v = 0x3F
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xA8, 0x3F,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set Display Offset v = 0
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xD3, 0x00,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set Start Line (0)
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0x40,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Charge Pump Setting v = enable (0x14)
|
|
|
|
// default is disabled
|
2016-02-21 06:39:41 +00:00
|
|
|
0x8D, 0x14,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set Segment Re-map (A0) | (b0001)
|
|
|
|
// default is (b0000)
|
2016-02-21 06:39:41 +00:00
|
|
|
0xA1,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set COM Output Scan Direction
|
2016-02-21 06:39:41 +00:00
|
|
|
0xC8,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set COM Pins v
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xDA, 0x12,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set Contrast v = 0xCF
|
2016-02-21 06:39:41 +00:00
|
|
|
0x81, 0xCF,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set Precharge = 0xF1
|
|
|
|
0xD9, 0xF1,
|
2016-02-21 06:39:41 +00:00
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
// Set VCom Detect
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xDB, 0x40,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Entire Display ON
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xA4,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Set normal/inverse display
|
2016-02-21 06:39:41 +00:00
|
|
|
// 0xA6,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// Display On
|
2016-02-21 06:39:41 +00:00
|
|
|
0xAF,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
// set display mode = horizontal addressing mode (0x00)
|
2016-02-21 06:39:41 +00:00
|
|
|
0x20, 0x00,
|
2015-05-01 05:26:58 +00:00
|
|
|
|
2016-02-21 06:39:41 +00:00
|
|
|
// set col address range
|
2015-05-01 05:26:58 +00:00
|
|
|
// 0x21, 0x00, COLUMN_ADDRESS_END,
|
|
|
|
|
|
|
|
// set page address range
|
|
|
|
// 0x22, 0x00, PAGE_ADDRESS_END
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ArduboyCore::ArduboyCore() {}
|
|
|
|
|
|
|
|
void ArduboyCore::boot()
|
|
|
|
{
|
2016-04-01 20:27:38 +00:00
|
|
|
#ifdef ARDUBOY_SET_CPU_8MHZ
|
|
|
|
// ARDUBOY_SET_CPU_8MHZ will be set by the IDE using boards.txt
|
|
|
|
setCPUSpeed8MHz();
|
|
|
|
#endif
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
SPI.begin();
|
|
|
|
bootPins();
|
2016-02-21 06:46:03 +00:00
|
|
|
bootOLED();
|
2015-05-01 05:26:58 +00:00
|
|
|
|
|
|
|
#ifdef SAFE_MODE
|
|
|
|
if (buttonsState() == (LEFT_BUTTON | UP_BUTTON))
|
|
|
|
safeMode();
|
|
|
|
#endif
|
|
|
|
|
2016-02-21 07:06:22 +00:00
|
|
|
bootPowerSaving();
|
2015-05-01 05:26:58 +00:00
|
|
|
}
|
|
|
|
|
2016-04-01 20:27:38 +00:00
|
|
|
#ifdef ARDUBOY_SET_CPU_8MHZ
|
|
|
|
// If we're compiling for 8MHz we need to slow the CPU down because the
|
|
|
|
// hardware clock on the Arduboy is 16MHz.
|
|
|
|
// We also need to readjust the PLL prescaler because the Arduino USB code
|
|
|
|
// likely will have incorrectly set it for an 8MHz hardware clock.
|
|
|
|
void ArduboyCore::setCPUSpeed8MHz()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
uint8_t oldSREG = SREG;
|
|
|
|
cli(); // suspend interrupts
|
2016-04-01 20:27:38 +00:00
|
|
|
PLLCSR = _BV(PINDIV); // dissable the PLL and set prescale for 16MHz)
|
2015-05-01 05:26:58 +00:00
|
|
|
CLKPR = _BV(CLKPCE); // allow reprogramming clock
|
|
|
|
CLKPR = 1; // set clock divisor to 2 (0b0001)
|
2016-04-01 20:27:38 +00:00
|
|
|
PLLCSR = _BV(PLLE) | _BV(PINDIV); // enable the PLL (with 16MHz prescale)
|
2015-05-01 05:26:58 +00:00
|
|
|
SREG = oldSREG; // restore interrupts
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void ArduboyCore::bootPins()
|
|
|
|
{
|
|
|
|
uint8_t pin, mode;
|
|
|
|
const uint8_t *i = pinBootProgram;
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
pin = pgm_read_byte(i++);
|
|
|
|
mode = pgm_read_byte(i++);
|
|
|
|
if (pin==0) break;
|
|
|
|
pinMode(pin, mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-02-21 06:46:03 +00:00
|
|
|
void ArduboyCore::bootOLED()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
// setup the ports we need to talk to the OLED
|
|
|
|
csport = portOutputRegister(digitalPinToPort(CS));
|
|
|
|
cspinmask = digitalPinToBitMask(CS);
|
|
|
|
dcport = portOutputRegister(digitalPinToPort(DC));
|
|
|
|
dcpinmask = digitalPinToBitMask(DC);
|
|
|
|
|
|
|
|
SPI.setClockDivider(SPI_CLOCK_DIV2);
|
|
|
|
|
|
|
|
LCDCommandMode();
|
|
|
|
// run our customized boot-up command sequence against the
|
|
|
|
// OLED to initialize it properly for Arduboy
|
|
|
|
for (int8_t i=0; i < sizeof(lcdBootProgram); i++) {
|
|
|
|
SPI.transfer(pgm_read_byte(lcdBootProgram + i));
|
|
|
|
}
|
|
|
|
LCDDataMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArduboyCore::LCDDataMode()
|
|
|
|
{
|
|
|
|
*dcport |= dcpinmask;
|
|
|
|
*csport &= ~cspinmask;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArduboyCore::LCDCommandMode()
|
|
|
|
{
|
|
|
|
*csport |= cspinmask;
|
|
|
|
*dcport &= ~dcpinmask;
|
|
|
|
*csport &= ~cspinmask;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void ArduboyCore::safeMode()
|
|
|
|
{
|
|
|
|
blank(); // too avoid random gibberish
|
|
|
|
while (true) {
|
|
|
|
asm volatile("nop \n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Power Management */
|
|
|
|
|
|
|
|
void ArduboyCore::idle()
|
|
|
|
{
|
|
|
|
set_sleep_mode(SLEEP_MODE_IDLE);
|
|
|
|
sleep_mode();
|
|
|
|
}
|
|
|
|
|
2016-02-21 07:06:22 +00:00
|
|
|
void ArduboyCore::bootPowerSaving()
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
power_adc_disable();
|
|
|
|
power_usart0_disable();
|
|
|
|
power_twi_disable();
|
|
|
|
// timer 0 is for millis()
|
|
|
|
// timers 1 and 3 are for music and sounds
|
|
|
|
power_timer2_disable();
|
|
|
|
power_usart1_disable();
|
|
|
|
// we need USB, for now (to allow triggered reboots to reprogram)
|
|
|
|
// power_usb_disable()
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t ArduboyCore::width() { return WIDTH; }
|
|
|
|
|
|
|
|
uint8_t ArduboyCore::height() { return HEIGHT; }
|
|
|
|
|
|
|
|
|
|
|
|
/* Drawing */
|
|
|
|
|
|
|
|
void ArduboyCore::paint8Pixels(uint8_t pixels)
|
|
|
|
{
|
|
|
|
SPI.transfer(pixels);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArduboyCore::paintScreen(const unsigned char *image)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < (HEIGHT*WIDTH)/8; i++)
|
|
|
|
{
|
|
|
|
SPI.transfer(pgm_read_byte(image + i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// paint from a memory buffer, this should be FAST as it's likely what
|
|
|
|
// will be used by any buffer based subclass
|
|
|
|
void ArduboyCore::paintScreen(unsigned char image[])
|
|
|
|
{
|
|
|
|
for (int i = 0; i < (HEIGHT*WIDTH)/8; i++)
|
|
|
|
{
|
|
|
|
// SPI.transfer(image[i]);
|
|
|
|
|
|
|
|
// we need to burn 18 cycles between sets of SPDR
|
|
|
|
// 4 clock cycles
|
|
|
|
SPDR = image[i];
|
|
|
|
// 7 clock cycles
|
|
|
|
asm volatile(
|
|
|
|
"mul __zero_reg__, __zero_reg__ \n" // 2 cycles
|
|
|
|
"mul __zero_reg__, __zero_reg__ \n" // 2 cycles
|
|
|
|
"mul __zero_reg__, __zero_reg__ \n" // 2 cycles
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArduboyCore::blank()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < (HEIGHT*WIDTH)/8; i++)
|
|
|
|
SPI.transfer(0x00);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArduboyCore::sendLCDCommand(uint8_t command)
|
|
|
|
{
|
|
|
|
LCDCommandMode();
|
|
|
|
SPI.transfer(command);
|
|
|
|
LCDDataMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
// invert the display or set to normal
|
|
|
|
// when inverted, a pixel set to 0 will be on
|
2016-03-30 22:59:17 +00:00
|
|
|
void ArduboyCore::invert(bool inverse)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
sendLCDCommand(inverse ? OLED_PIXELS_INVERTED : OLED_PIXELS_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// turn all display pixels on, ignoring buffer contents
|
|
|
|
// or set to normal buffer display
|
2016-03-30 22:59:17 +00:00
|
|
|
void ArduboyCore::allPixelsOn(bool on)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
sendLCDCommand(on ? OLED_ALL_PIXELS_ON : OLED_PIXELS_FROM_RAM);
|
|
|
|
}
|
|
|
|
|
|
|
|
// flip the display vertically or set to normal
|
2016-03-30 22:59:17 +00:00
|
|
|
void ArduboyCore::flipVertical(bool flipped)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
sendLCDCommand(flipped ? OLED_VERTICAL_FLIPPED : OLED_VERTICAL_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// flip the display horizontally or set to normal
|
2016-03-30 22:59:17 +00:00
|
|
|
void ArduboyCore::flipHorizontal(bool flipped)
|
2015-05-01 05:26:58 +00:00
|
|
|
{
|
|
|
|
sendLCDCommand(flipped ? OLED_HORIZ_FLIPPED : OLED_HORIZ_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* RGB LED */
|
|
|
|
|
|
|
|
void ArduboyCore::setRGBled(uint8_t red, uint8_t green, uint8_t blue)
|
|
|
|
{
|
|
|
|
#ifdef ARDUBOY_10 // RGB, all the pretty colors
|
|
|
|
// inversion is necessary because these are common annode LEDs
|
|
|
|
analogWrite(RED_LED, 255 - red);
|
|
|
|
analogWrite(GREEN_LED, 255 - green);
|
|
|
|
analogWrite(BLUE_LED, 255 - blue);
|
|
|
|
#elif defined(AB_DEVKIT)
|
|
|
|
// only blue on devkit
|
|
|
|
digitalWrite(BLUE_LED, ~blue);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Buttons */
|
|
|
|
|
|
|
|
uint8_t ArduboyCore::getInput()
|
|
|
|
{
|
|
|
|
return buttonsState();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t ArduboyCore::buttonsState()
|
|
|
|
{
|
|
|
|
uint8_t buttons;
|
2016-02-21 06:39:41 +00:00
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
// using ports here is ~100 bytes smaller than digitalRead()
|
|
|
|
#ifdef AB_DEVKIT
|
|
|
|
// down, left, up
|
|
|
|
buttons = ((~PINB) & B01110000);
|
|
|
|
// right button
|
|
|
|
buttons = buttons | (((~PINC) & B01000000) >> 4);
|
|
|
|
// A and B
|
|
|
|
buttons = buttons | (((~PINF) & B11000000) >> 6);
|
|
|
|
#elif defined(ARDUBOY_10)
|
|
|
|
// down, up, left right
|
|
|
|
buttons = ((~PINF) & B11110000);
|
|
|
|
// A (left)
|
|
|
|
buttons = buttons | (((~PINE) & B01000000) >> 3);
|
|
|
|
// B (right)
|
|
|
|
buttons = buttons | (((~PINB) & B00010000) >> 2);
|
|
|
|
#endif
|
2016-02-21 06:39:41 +00:00
|
|
|
|
2015-05-01 05:26:58 +00:00
|
|
|
return buttons;
|
|
|
|
}
|