forked from rveilleux/ard-drivin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ArduboyCoreRem.cpp
400 lines (326 loc) · 9.45 KB
/
ArduboyCoreRem.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
#include "ArduboyCoreRem.h"
// need to redeclare these here since we declare them static in .h
volatile uint8_t *ArduboyCoreRem::csport, *ArduboyCoreRem::dcport;
uint8_t ArduboyCoreRem::cspinmask, ArduboyCoreRem::dcpinmask;
byte ArduboyCoreRem::flicker = 0;
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
// 0xAE,
// Set Display Clock Divisor v = 0xF0
// default is 0x80
0xD5, 0xF0,
// Set Multiplex Ratio v = 0x3F
// 0xA8, 0x3F,
// Set Display Offset v = 0
// 0xD3, 0x00,
// Set Start Line (0)
// 0x40,
// Charge Pump Setting v = enable (0x14)
// default is disabled
0x8D, 0x14,
// Set Segment Re-map (A0) | (b0001)
// default is (b0000)
0xA1,
// Set COM Output Scan Direction
0xC8,
// Set COM Pins v
// 0xDA, 0x12,
// Set Contrast v = 0xCF
0x81, 0xCF,
// Set Precharge = 0xF1
0xD9, 0xF1,
// Set VCom Detect
// 0xDB, 0x40,
// Entire Display ON
// 0xA4,
// Set normal/inverse display
// 0xA6,
// Display On
0xAF,
// set display mode = horizontal addressing mode (0x00)
0x20, 0x00,
// set col address range
// 0x21, 0x00, COLUMN_ADDRESS_END,
// set page address range
// 0x22, 0x00, PAGE_ADDRESS_END
};
ArduboyCoreRem::ArduboyCoreRem() {}
void ArduboyCoreRem::boot()
{
#ifdef ARDUBOY_SET_CPU_8MHZ
// ARDUBOY_SET_CPU_8MHZ will be set by the IDE using boards.txt
setCPUSpeed8MHz();
#endif
SPI.begin();
bootPins();
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 ArduboyCoreRem::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 ArduboyCoreRem::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
}
void ArduboyCoreRem::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 (uint8_t i = 0; i < sizeof(lcdBootProgram); i++) {
SPI.transfer(pgm_read_byte(lcdBootProgram + i));
}
LCDDataMode();
}
void ArduboyCoreRem::LCDDataMode()
{
*dcport |= dcpinmask;
*csport &= ~cspinmask;
}
void ArduboyCoreRem::LCDCommandMode()
{
*csport |= cspinmask;
*dcport &= ~dcpinmask;
*csport &= ~cspinmask;
}
void ArduboyCoreRem::safeMode()
{
blank(); // too avoid random gibberish
while (true) {
asm volatile("nop \n");
}
}
/* Power Management */
void ArduboyCoreRem::idle()
{
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_mode();
}
void ArduboyCoreRem::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 ArduboyCoreRem::width() { return WIDTH; }
uint8_t ArduboyCoreRem::height() { return HEIGHT; }
/* Drawing */
void ArduboyCoreRem::paint8Pixels(uint8_t pixels)
{
SPI.transfer(pixels);
}
void ArduboyCoreRem::paintScreen(const uint8_t *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
// PaintScreen normally takes about 1496 us, so about 23936 clock cycles, so approx 23 clocks per byte
// The fastest possible is 1300 us, about 20800 clocks, approx 20 clocks per byte
// The loop takes 7 clocks, so 13 free clocks per byte to do something for free
void ArduboyCoreRem::paintScreen(uint8_t image[])
{
//uint8_t c;
//int i = 0;
//SPDR = image[i++]; // set the first SPI data byte to get things started
//// 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
// 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
uint8_t* imageEnd = image + (HEIGHT*WIDTH) / 8;
uint8_t alternate = 0xAA;
if (flicker & 1) {
alternate = ~alternate;
}
do
{
//SPDR = *image++;
SPDR = *image;
// Clear screen with black:
// *image++ = 0;
// Clear screen with flicker-gray:
*image++ = alternate;
alternate = ~alternate;
if (image == imageEnd) break;
//while (!(SPSR & _BV(SPIF))) {}
// Spare cycles, we could use them to do something else for free:
asm volatile("\
nop \n\
nop \n\
nop \n\
nop \n\
nop \n\
nop \n\
nop \n\
nop \n\
nop \n\
nop \n\
");
// One additional 'nop' to fix the problematic byte at (1,0) which remains black for mysterious reason.
asm volatile("nop");
} while (true);
while (!(SPSR & _BV(SPIF))) {}
}
void ArduboyCoreRem::blank()
{
for (int i = 0; i < (HEIGHT*WIDTH)/8; i++)
SPI.transfer(0x00);
}
void ArduboyCoreRem::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
void ArduboyCoreRem::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
void ArduboyCoreRem::allPixelsOn(bool on)
{
sendLCDCommand(on ? OLED_ALL_PIXELS_ON : OLED_PIXELS_FROM_RAM);
}
// flip the display vertically or set to normal
void ArduboyCoreRem::flipVertical(bool flipped)
{
sendLCDCommand(flipped ? OLED_VERTICAL_FLIPPED : OLED_VERTICAL_NORMAL);
}
// flip the display horizontally or set to normal
void ArduboyCoreRem::flipHorizontal(bool flipped)
{
sendLCDCommand(flipped ? OLED_HORIZ_FLIPPED : OLED_HORIZ_NORMAL);
}
/* RGB LED */
void ArduboyCoreRem::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
}
void ArduboyCoreRem::digitalWriteRGB(uint8_t red, uint8_t green, uint8_t blue)
{
#ifdef ARDUBOY_10
digitalWrite(RED_LED, red);
digitalWrite(GREEN_LED, green);
digitalWrite(BLUE_LED, blue);
#elif defined(AB_DEVKIT)
digitalWrite(BLUE_LED, blue);
#endif
}
/* Buttons */
uint8_t ArduboyCoreRem::buttonsState()
{
uint8_t buttons;
// 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
return buttons;
}