Runs a series of diagnostic tests, and on successful completion displays the screen above. Included tests are:
- SPI traffic stress test
- Onboard 32K RAM test
- Hardware collision detection test
selftest also displays color bars and a 128x128 256-color test image, drawn as sprites.
#include <SPI.h>
#include <GD.h>
int atxy(int x, int y)
{
return (y << 6) + x;
}
void readn(byte *dst, unsigned int addr, int c)
{
GD.__start(addr);
while (c--)
*dst++ = SPI.transfer(0);
GD.__end();
}
static byte coll[256];
static void debug_coll()
{
while (GD.rd(VBLANK) == 0) // Wait until vblank
;
while (GD.rd(VBLANK) == 1) // Wait until display
;
while (GD.rd(VBLANK) == 0) // Wait until vblank
;
readn(coll, COLLISION, 256);
}
#define FAIL do { Serial.print("Fail at line: "); Serial.println(__LINE__, DEC); return 0; } while (0)
int test_collision()
{
int i, j;
#define NOCOLL 0xff
GD.wr16(RAM_SPRPAL, 0x8000); // color 0 transparent, 1-255 0x5555 (pinkish)
GD.fill(RAM_SPRPAL + 2, 0x55, 510);
GD.fill(RAM_SPRIMG, 1, 256);
for (i = 0; i < 256; i++)
GD.sprite(i, 400, 400, 0, 0, 0);
debug_coll();
for (i = 0; i < 256; i++)
if (coll[i] != NOCOLL)
FAIL;
GD.sprite(7, 200, 100, 0, 0, 0);
GD.sprite(117, 200, 200, 0, 0, 0);
byte jkmode, jk;
for (jkmode = 0; jkmode < 2; jkmode++) {
for (jk = 0; jk < 2; jk++) {
GD.wr(JK_MODE, jkmode);
for (i = -20; i < 20; i++) {
GD.sprite(8, 200, 100 + i, 0, 0, 0, jk);
GD.sprite(200, 200 + i, 200, 0, 0, 0, jk);
debug_coll();
byte expected = ((!jkmode || jk) && (abs(i) < 16)) ? 7 : NOCOLL;
if (coll[8] != expected)
FAIL;
expected = ((!jkmode || jk) && (abs(i) < 16)) ? 117 : NOCOLL;
if (coll[200] != expected)
FAIL;
}
}
}
randomSeed(1);
for (j = 100; j; j--) {
for (i = 0; i < 256; i++) {
GD.sprite(i, random(512), random(512), 0, 0, 0);
}
debug_coll();
for (i = 0; i < 256; i++) {
if (coll[i] != 0xff && (coll[i] >= i))
FAIL;
}
}
for (i = 0; i < 256; i++)
GD.sprite(i, 400, 400, 0, 0, 0);
return 1;
}
int test_ident()
{
byte id = GD.rd(IDENT);
if (id != 0x6d) {
Serial.println(id, HEX);
FAIL;
}
return 1;
}
// low-level SPI test. Write a random pattern to the 16K image RAM,
// then read it back, verifying the same random values. Meant to
// catch SPI transmission errors.
int test_spi()
{
int i;
randomSeed(947);
GD.__wstart(RAM_SPRIMG);
for (i = 0; i < 16384; i++)
SPI.transfer(random(256));
GD.__end();
randomSeed(947);
GD.__start(RAM_SPRIMG);
for (i = 0; i < 16384; i++)
if (SPI.transfer(0) != random(256))
FAIL;
GD.__end();
return 1;
}
// Test a RAM area (addr, c)
int test_a_ram(unsigned int addr, int c)
{
while (c--) {
byte prev = GD.rd(addr);
GD.wr(addr, 0xff); if (GD.rd(addr) != 0xff) FAIL;
GD.wr(addr, 0x00); if (GD.rd(addr) != 0x00) FAIL;
GD.wr(addr, 0x47); if (GD.rd(addr) != 0x47) FAIL;
GD.wr(addr, prev); if (GD.rd(addr) != prev) FAIL;
addr++;
}
return 1;
}
// Write/read a simple pattern to each RAM byte.
// (Restores RAM values so display is preserved.)
int test_rams()
{
test_a_ram(0, (4 + 4 + 2) * 1024); /* Pic, chr and pal */
test_a_ram(RAM_SPR, 0x5000); /* Sprites */
test_a_ram(PALETTE16A, 64);
test_a_ram(PALETTE4A, 16);
test_a_ram(VOICES, 64 * 4);
GD.wr(J1_RESET, 1);
test_a_ram(J1_CODE, 256);
return 1;
}
int test_audio_l()
{
GD.fill(VOICES, 0, 64 * 4);
GD.voice(0, 0, 4 * 440, 255, 0);
delay(1000);
return 1;
}
int test_audio_r()
{
GD.fill(VOICES, 0, 64 * 4);
GD.voice(0, 0, 4 * 440, 0, 255);
delay(1000);
GD.fill(VOICES, 0, 64 * 4);
return 1;
}
int test_speed()
{
long t0 = millis();
int i, j;
for (i = 0; i < 1000; i++) {
GD.fill(RAM_SPRIMG, 0x55, 1000);
}
Serial.print("(Took ");
Serial.print(millis() - t0);
Serial.print(")");
return 1;
}
#include "lena.h"
static void show_lena()
{
GD.copy(RAM_SPRPAL, lenapal, sizeof(lenapal));
int i;
for (i = 0; i < 64; i++)
GD.sprite(i, 256 + ((i & 7) << 4), 64 + 2 * (i & 070), i, 0, 0);
for (i = 64; i < 512; i++)
GD.sprite(i, 400, 400, 0, 0, 0);
GD.uncompress(RAM_SPRIMG, lenaimg);
}
void show_stripes()
{
int i;
for (i = 0; i < 32; i++) {
GD.wr16(RAM_PAL + (0x80 + i) * 8, RGB(8 * i, 0, 0));
GD.wr16(RAM_PAL + (0xa0 + i) * 8, RGB(0, 8 * i, 0));
GD.wr16(RAM_PAL + (0xc0 + i) * 8, RGB(0, 0, 8 * i));
GD.wr(atxy(i, 24), 0x80 + i);
GD.wr(atxy(i, 25), 0xa0 + i);
GD.wr(atxy(i, 26), 0xc0 + i);
}
GD.putstr(0, 28, "R");
GD.putstr(0, 29, "G");
GD.putstr(0, 30, "B");
GD.putstr(4, 31, "0");
GD.putstr(8, 31, "1");
GD.putstr(16, 31, "2");
GD.wr(atxy(4, 28), 0x80 + 4);
GD.wr(atxy(8, 28), 0x80 + 8);
GD.wr(atxy(16, 28), 0x80 + 16);
GD.wr(atxy(4, 29), 0xa0 + 4);
GD.wr(atxy(8, 29), 0xa0 + 8);
GD.wr(atxy(16, 29), 0xa0 + 16);
GD.wr(atxy(4, 30), 0xc0 + 4);
GD.wr(atxy(8, 30), 0xc0 + 8);
GD.wr(atxy(16, 30), 0xc0 + 16);
}
byte y;
static void logn(const char*s)
{
Serial.print(s);
GD.putstr(0, y, s);
}
static void log(const char*s)
{
Serial.println(s);
GD.putstr(16, y++, s);
}
#define RUNTEST(NAME) \
do { \
logn(#NAME ": "); \
r = NAME(); \
log(r ? "pass" : "FAIL"); \
pass &= r; \
} while (0)
#include "selftest1.h"
static unsigned long rd32()
{
return GD.rd16(COMM+0) + ((unsigned long)GD.rd16(COMM+2) << 16);
}
int test_coproc()
{
GD.microcode(selftest1_code, sizeof(selftest1_code));
GD.wr(COMM+15, 0); // stop
GD.wr16(COMM+0, 0);
GD.wr16(COMM+2, 0);
unsigned long started;
unsigned long cycles0, cycles1;
byte regime;
int jj;
for (regime = 0; regime < 6; regime++) {
cycles0 = rd32();
started = micros();
GD.wr(COMM+15, 1); // go
switch (regime) {
case 0:
delay(1000);
break;
case 1:
GD.__start(0);
delay(1000);
GD.__end();
break;
case 2:
GD.__start(0);
SPI.transfer(0);
delay(1000);
GD.__end();
break;
case 3:
GD.__start(0);
for (jj = 0; jj < 1000; jj++) {
SPI.transfer(0);
delay(1);
}
GD.__end();
break;
case 4:
for (jj = 0; jj < 1000; jj++) {
GD.rd(0);
delay(1);
}
break;
case 5:
while ((micros() - started) < 1000000) {
GD.__start(0);
for (jj = 0; jj < 1000; jj++)
SPI.transfer(0);
GD.__end();
}
break;
}
GD.wr(COMM+15, 0); // stop
delay(1);
cycles1 = rd32();
long cps = long(1e6 * (cycles1 - cycles0) / (micros() - started));
if (cps < 1000000)
FAIL;
// Serial.println(micros() - started, DEC);
// Serial.print(regime, DEC);
// Serial.print(' ');
// Serial.println(cps, DEC);
}
return 1;
}
// See Atmel AT45DB021D datasheet:
// http://www.atmel.com/dyn/resources/prod_documents/doc3638.pdf
static int test_flash()
{
GD.wr(IOMODE, 'F');
pinMode(2, OUTPUT);
digitalWrite(2, HIGH);
delay(1);
digitalWrite(2, LOW);
SPI.transfer(0xd7); // read SPI flash status
byte status = SPI.transfer(0);
digitalWrite(2, HIGH);
if (status != 0x94) // 0x94 means "idle; all is well"
FAIL;
GD.wr(IOMODE, 0);
return 1;
}
static void runtests()
{
char msg[50];
GD.begin();
GD.ascii();
GD.fill(0, ' ', 4096);
GD.putstr(0, 0,"<------------------- TOP LINE ------------------->");
GD.putstr(0,36,"<----------------- BOTTOM LINE ------------------>");
show_stripes();
y = 3;
byte r, pass = 1;
log("Starting self-test");
RUNTEST(test_ident);
RUNTEST(test_flash);
RUNTEST(test_audio_l);
RUNTEST(test_audio_r);
RUNTEST(test_coproc);
RUNTEST(test_speed);
RUNTEST(test_spi);
RUNTEST(test_rams);
RUNTEST(test_collision);
if (pass) {
log("All tests passed");
show_lena();
long seconds = millis() / 1000;
long minutes = seconds / 60;
sprintf(msg, "%d minutes", minutes);
log(msg);
// GD.screenshot(0);
} else {
for (;;) {
GD.wr16(BG_COLOR, RGB(255,0,0));
delay(100);
GD.wr16(BG_COLOR, RGB(0,0,0));
delay(100);
}
}
byte i;
for (i = 9; i; i--) {
sprintf(msg, "Restarting in %d", i);
GD.putstr(0, y, msg);
delay(1000);
}
}
void setup()
{
Serial.begin(1000000);
runtests();
}
void loop()
{
runtests();
}