Arduboy2/src/core/core.cpp

348 lines
7.7 KiB
C++
Raw Normal View History

#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;
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,
// RGB LED (or single blue LED on the DevKit)
#ifdef ARDUBOY_10
RED_LED, INPUT_PULLUP, // set INPUT_PULLUP to make the pin high when
RED_LED, OUTPUT, // set to OUTPUT
GREEN_LED, INPUT_PULLUP,
GREEN_LED, OUTPUT,
#endif
BLUE_LED, INPUT_PULLUP,
BLUE_LED, OUTPUT,
// 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
// 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,
// 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,
// Set Display Offset v = 0
2016-02-21 06:39:41 +00:00
// 0xD3, 0x00,
// Set Start Line (0)
2016-02-21 06:39:41 +00:00
// 0x40,
// Charge Pump Setting v = enable (0x14)
// default is disabled
2016-02-21 06:39:41 +00:00
0x8D, 0x14,
// Set Segment Re-map (A0) | (b0001)
// default is (b0000)
2016-02-21 06:39:41 +00:00
0xA1,
// Set COM Output Scan Direction
2016-02-21 06:39:41 +00:00
0xC8,
// Set COM Pins v
2016-02-21 06:39:41 +00:00
// 0xDA, 0x12,
// Set Contrast v = 0xCF
2016-02-21 06:39:41 +00:00
0x81, 0xCF,
// Set Precharge = 0xF1
0xD9, 0xF1,
2016-02-21 06:39:41 +00:00
// Set VCom Detect
2016-02-21 06:39:41 +00:00
// 0xDB, 0x40,
// Entire Display ON
2016-02-21 06:39:41 +00:00
// 0xA4,
// Set normal/inverse display
2016-02-21 06:39:41 +00:00
// 0xA6,
// Display On
2016-02-21 06:39:41 +00:00
0xAF,
// set display mode = horizontal addressing mode (0x00)
2016-02-21 06:39:41 +00:00
0x20, 0x00,
2016-02-21 06:39:41 +00:00
// set col address range
// 0x21, 0x00, COLUMN_ADDRESS_END,
// set page address range
// 0x22, 0x00, PAGE_ADDRESS_END
};
ArduboyCore::ArduboyCore() {}
void ArduboyCore::boot()
{
2016-04-01 21:54:09 +00:00
#ifdef ARDUBOY_SET_CPU_8MHZ
// ARDUBOY_SET_CPU_8MHZ will be set by the IDE using boards.txt
setCPUSpeed8MHz();
2016-04-01 21:54:09 +00:00
#endif
SPI.begin();
bootPins();
2016-02-21 06:46:03 +00:00
bootOLED();
#ifdef SAFE_MODE
if (buttonsState() == (LEFT_BUTTON | UP_BUTTON))
safeMode();
#endif
bootPowerSaving();
}
#ifdef ARDUBOY_SET_CPU_8MHZ
// If we're compiling for 8MHz we need to slow the CPU down because the
// hardware clock on the Arduboy is 16MHz.
// We also need to readjust the PLL prescaler because the Arduino USB code
// likely will have incorrectly set it for an 8MHz hardware clock.
void ArduboyCore::setCPUSpeed8MHz()
{
uint8_t oldSREG = SREG;
cli(); // suspend interrupts
PLLCSR = _BV(PINDIV); // dissable the PLL and set prescale for 16MHz)
CLKPR = _BV(CLKPCE); // allow reprogramming clock
CLKPR = 1; // set clock divisor to 2 (0b0001)
PLLCSR = _BV(PLLE) | _BV(PINDIV); // enable the PLL (with 16MHz prescale)
SREG = oldSREG; // restore interrupts
}
#endif
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()
{
// 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();
}
void ArduboyCore::bootPowerSaving()
{
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)
{
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)
{
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)
{
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)
{
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
// 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
return buttons;
}