Initial commit
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "hw_config.h"
|
||||
|
||||
/* controller is GPIO */
|
||||
#if defined(HW_CONTROLLER_GPIO)
|
||||
|
||||
extern "C" void controller_init()
|
||||
{
|
||||
#if defined(HW_CONTROLLER_GPIO_ANALOG_JOYSTICK)
|
||||
pinMode(HW_CONTROLLER_GPIO_UP_DOWN, INPUT);
|
||||
pinMode(HW_CONTROLLER_GPIO_LEFT_RIGHT, INPUT);
|
||||
#else /* !defined(HW_CONTROLLER_GPIO_ANALOG_JOYSTICK) */
|
||||
pinMode(HW_CONTROLLER_GPIO_UP, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_DOWN, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_LEFT, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_RIGHT, INPUT_PULLUP);
|
||||
#endif /* !defined(HW_CONTROLLER_GPIO_ANALOG_JOYSTICK) */
|
||||
pinMode(HW_CONTROLLER_GPIO_SELECT, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_START, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_A, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_B, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_X, INPUT_PULLUP);
|
||||
pinMode(HW_CONTROLLER_GPIO_Y, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
extern "C" uint32_t controller_read_input()
|
||||
{
|
||||
uint32_t u, d, l, r, s, t, a, b, x, y;
|
||||
|
||||
#if defined(HW_CONTROLLER_GPIO_ANALOG_JOYSTICK)
|
||||
|
||||
#if defined(HW_CONTROLLER_GPIO_REVERSE_UP)
|
||||
int joyY = 4095 - analogRead(HW_CONTROLLER_GPIO_UP_DOWN);
|
||||
#else /* !defined(HW_CONTROLLER_GPIO_REVERSE_UD) */
|
||||
int joyY = analogRead(HW_CONTROLLER_GPIO_UP_DOWN);
|
||||
#endif /* !defined(HW_CONTROLLER_GPIO_REVERSE_UD) */
|
||||
|
||||
#if defined(HW_CONTROLLER_GPIO_REVERSE_LF)
|
||||
int joyX = 4095 - analogRead(HW_CONTROLLER_GPIO_LEFT_RIGHT);
|
||||
#else /* !defined(HW_CONTROLLER_GPIO_REVERSE_LF) */
|
||||
int joyX = analogRead(HW_CONTROLLER_GPIO_LEFT_RIGHT);
|
||||
#endif /* !defined(HW_CONTROLLER_GPIO_REVERSE_LF) */
|
||||
|
||||
// Serial.printf("joyX: %d, joyY: %d\n", joyX, joyY);
|
||||
#if defined(ARDUINO_ODROID_ESP32)
|
||||
|
||||
if (joyY > 2048 + 1024)
|
||||
{
|
||||
u = 0;
|
||||
d = 1;
|
||||
}
|
||||
else if (joyY > 1024)
|
||||
{
|
||||
u = 1;
|
||||
d = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
u = 1;
|
||||
d = 1;
|
||||
}
|
||||
if (joyX > 2048 + 1024)
|
||||
{
|
||||
l = 0;
|
||||
r = 1;
|
||||
}
|
||||
else if (joyX > 1024)
|
||||
{
|
||||
l = 1;
|
||||
r = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
l = 1;
|
||||
r = 1;
|
||||
}
|
||||
|
||||
#else /* !defined(ARDUINO_ODROID_ESP32) */
|
||||
|
||||
if (joyY > 2048 + 1024)
|
||||
{
|
||||
u = 1;
|
||||
d = 0;
|
||||
}
|
||||
else if (joyY < 1024)
|
||||
{
|
||||
u = 0;
|
||||
d = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
u = 1;
|
||||
d = 1;
|
||||
}
|
||||
|
||||
if (joyX > 2048 + 1024)
|
||||
{
|
||||
l = 1;
|
||||
r = 0;
|
||||
}
|
||||
else if (joyX < 1024)
|
||||
{
|
||||
l = 0;
|
||||
r = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
l = 1;
|
||||
r = 1;
|
||||
}
|
||||
|
||||
#endif /* !defined(ARDUINO_ODROID_ESP32) */
|
||||
#else /* !defined(HW_CONTROLLER_GPIO_ANALOG_JOYSTICK) */
|
||||
u = digitalRead(HW_CONTROLLER_GPIO_UP);
|
||||
d = digitalRead(HW_CONTROLLER_GPIO_DOWN);
|
||||
l = digitalRead(HW_CONTROLLER_GPIO_LEFT);
|
||||
r = digitalRead(HW_CONTROLLER_GPIO_RIGHT);
|
||||
#endif /* !defined(HW_CONTROLLER_GPIO_ANALOG_JOYSTICK) */
|
||||
|
||||
s = digitalRead(HW_CONTROLLER_GPIO_SELECT);
|
||||
t = digitalRead(HW_CONTROLLER_GPIO_START);
|
||||
a = digitalRead(HW_CONTROLLER_GPIO_A);
|
||||
b = digitalRead(HW_CONTROLLER_GPIO_B);
|
||||
x = digitalRead(HW_CONTROLLER_GPIO_X);
|
||||
y = digitalRead(HW_CONTROLLER_GPIO_Y);
|
||||
|
||||
return 0xFFFFFFFF ^ ((!u << 0) | (!d << 1) | (!l << 2) | (!r << 3) | (!s << 4) | (!t << 5) | (!a << 6) | (!b << 7) | (!x << 8) | (!y << 9));
|
||||
}
|
||||
|
||||
/* controller is I2C M5Stack CardKB */
|
||||
#elif defined(HW_CONTROLLER_I2C_M5CARDKB)
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
#define I2C_M5CARDKB_ADDR 0x5f
|
||||
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */
|
||||
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave */
|
||||
#define NACK_VAL 0x1 /*!< I2C nack value */
|
||||
|
||||
extern "C" void controller_init()
|
||||
{
|
||||
Wire.begin();
|
||||
}
|
||||
|
||||
extern "C" uint32_t controller_read_input()
|
||||
{
|
||||
uint32_t value = 0xFFFFFFFF;
|
||||
|
||||
Wire.requestFrom(I2C_M5CARDKB_ADDR, 1);
|
||||
while (Wire.available())
|
||||
{
|
||||
char c = Wire.read(); // receive a byte as characterif
|
||||
if (c != 0)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case 181: // up
|
||||
value ^= (1 << 0);
|
||||
break;
|
||||
case 182: // down
|
||||
value ^= (1 << 1);
|
||||
break;
|
||||
case 180: // left
|
||||
value ^= (1 << 2);
|
||||
break;
|
||||
case 183: // right
|
||||
value ^= (1 << 3);
|
||||
break;
|
||||
case ' ': // select
|
||||
value ^= (1 << 4);
|
||||
break;
|
||||
case 13: // enter -> start
|
||||
value ^= (1 << 5);
|
||||
break;
|
||||
case 'k': // A
|
||||
value ^= (1 << 6);
|
||||
break;
|
||||
case 'l': // B
|
||||
value ^= (1 << 7);
|
||||
break;
|
||||
case 'o': // X
|
||||
value ^= (1 << 8);
|
||||
break;
|
||||
case 'p': // Y
|
||||
value ^= (1 << 9);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* controller is I2C BBQ10Keyboard */
|
||||
#elif defined(HW_CONTROLLER_I2C_BBQ10KB)
|
||||
|
||||
#include <Wire.h>
|
||||
#include <BBQ10Keyboard.h>
|
||||
BBQ10Keyboard keyboard;
|
||||
static uint32_t value = 0xFFFFFFFF;
|
||||
|
||||
extern "C" void controller_init()
|
||||
{
|
||||
Wire.begin();
|
||||
keyboard.begin();
|
||||
keyboard.setBacklight(0.2f);
|
||||
}
|
||||
|
||||
extern "C" uint32_t controller_read_input()
|
||||
{
|
||||
|
||||
int keyCount = keyboard.keyCount();
|
||||
while (keyCount--)
|
||||
{
|
||||
const BBQ10Keyboard::KeyEvent key = keyboard.keyEvent();
|
||||
String state = "pressed";
|
||||
if (key.state == BBQ10Keyboard::StateLongPress)
|
||||
state = "held down";
|
||||
else if (key.state == BBQ10Keyboard::StateRelease)
|
||||
state = "released";
|
||||
|
||||
// Serial.printf("key: '%c' (dec %d, hex %02x) %s\r\n", key.key, key.key, key.key, state.c_str());
|
||||
|
||||
uint32_t bit = 0;
|
||||
if (key.key != 0)
|
||||
{
|
||||
switch (key.key)
|
||||
{
|
||||
case 'w': // up
|
||||
bit = (1 << 0);
|
||||
break;
|
||||
case 'z': // down
|
||||
bit = (1 << 1);
|
||||
break;
|
||||
case 'a': // left
|
||||
bit = (1 << 2);
|
||||
break;
|
||||
case 'd': // right
|
||||
bit = (1 << 3);
|
||||
break;
|
||||
case ' ': // select
|
||||
bit = (1 << 4);
|
||||
break;
|
||||
case 10: // enter -> start
|
||||
bit = (1 << 5);
|
||||
break;
|
||||
case 'k': // A
|
||||
bit = (1 << 6);
|
||||
break;
|
||||
case 'l': // B
|
||||
bit = (1 << 7);
|
||||
break;
|
||||
case 'o': // X
|
||||
bit = (1 << 8);
|
||||
break;
|
||||
case 'p': // Y
|
||||
bit = (1 << 9);
|
||||
break;
|
||||
}
|
||||
if (key.state == BBQ10Keyboard::StatePress)
|
||||
{
|
||||
value ^= bit;
|
||||
}
|
||||
else if (key.state == BBQ10Keyboard::StateRelease)
|
||||
{
|
||||
value |= bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#else /* no controller defined */
|
||||
|
||||
extern "C" void controller_init()
|
||||
{
|
||||
Serial.printf("GPIO controller disabled in menuconfig; no input enabled.\n");
|
||||
}
|
||||
|
||||
extern "C" uint32_t controller_read_input()
|
||||
{
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
#endif /* no controller defined */
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
This is an example game that is developed for my article Programming NES games in C. The article itself is located at my website in the Articles section, to make things simpler in case of possible updates.
|
||||
|
||||
http://shiru.untergrund.net
|
||||
mailto:shiru@mail.ru
|
||||
@@ -0,0 +1,174 @@
|
||||
extern "C"
|
||||
{
|
||||
#include <nes/nes.h>
|
||||
}
|
||||
|
||||
#include "hw_config.h"
|
||||
|
||||
#include <Arduino_GFX_Library.h>
|
||||
#define TFT_BRIGHTNESS 128 /* 0 - 255 */
|
||||
|
||||
/* M5Stack */
|
||||
#if defined(ARDUINO_M5Stack_Core_ESP32) || defined(ARDUINO_M5STACK_FIRE)
|
||||
|
||||
#define TFT_BL 32
|
||||
Arduino_ESP32SPI_DMA *bus = new Arduino_ESP32SPI_DMA(27 /* DC */, 14 /* CS */, SCK, MOSI, MISO);
|
||||
Arduino_ILI9341_M5STACK *gfx = new Arduino_ILI9341_M5STACK(bus, 33 /* RST */, 1 /* rotation */);
|
||||
|
||||
/* Odroid-Go */
|
||||
#elif defined(ARDUINO_ODROID_ESP32)
|
||||
|
||||
#define TFT_BL 14
|
||||
Arduino_ESP32SPI_DMA *bus = new Arduino_ESP32SPI_DMA(21 /* DC */, 5 /* CS */, SCK, MOSI, MISO);
|
||||
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, -1 /* RST */, 3 /* rotation */);
|
||||
|
||||
/* TTGO T-Watch */
|
||||
#elif defined(ARDUINO_T) || defined(ARDUINO_TWATCH_BASE) || defined(ARDUINO_TWATCH_2020_V1) || defined(ARDUINO_TWATCH_2020_V2) // TTGO T-Watch
|
||||
|
||||
#define TFT_BL 12
|
||||
Arduino_DataBus *bus = new Arduino_ESP32SPI_DMA(27 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
|
||||
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, -1 /* RST */, 1 /* rotation */, true /* IPS */, 240, 240, 0, 80);
|
||||
|
||||
/* custom hardware */
|
||||
#else
|
||||
|
||||
/* ST7789 ODROID Compatible pin connection */
|
||||
// #define TFT_BL 14
|
||||
// Arduino_ESP32SPI_DMA *bus = new Arduino_ESP32SPI_DMA(21 /* DC */, 5 /* CS */, SCK, MOSI, MISO);
|
||||
// Arduino_ST7789 *gfx = new Arduino_ST7789(bus, -1 /* RST */, 1 /* rotation */, true /* IPS */);
|
||||
|
||||
/* ST7796 on breadboard */
|
||||
// #define TFT_BL 32
|
||||
Arduino_DataBus *bus = new Arduino_ESP32SPI_DMA(32 /* DC */, -1 /* CS */, 25 /* SCK */, 33 /* MOSI */, -1 /* MISO */);
|
||||
Arduino_TFT *gfx = new Arduino_ST7796(bus, -1 /* RST */, 1 /* rotation */);
|
||||
|
||||
/* ST7796 on LCDKit */
|
||||
// #define TFT_BL 23
|
||||
// Arduino_ESP32SPI_DMA *bus = new Arduino_ESP32SPI_DMA(19 /* DC */, 5 /* CS */, 22 /* SCK */, 21 /* MOSI */, -1 /* MISO */);
|
||||
// Arduino_ST7796 *gfx = new Arduino_ST7796(bus, 18, 1 /* rotation */);
|
||||
|
||||
#endif /* custom hardware */
|
||||
|
||||
static int16_t w, h, frame_x, frame_y, frame_x_offset, frame_width, frame_height, frame_line_pixels;
|
||||
extern int16_t bg_color;
|
||||
extern uint16_t myPalette[];
|
||||
|
||||
extern "C" void display_init()
|
||||
{
|
||||
w = gfx->width();
|
||||
h = gfx->height();
|
||||
if (w < 480) // assume only 240x240 or 320x240
|
||||
{
|
||||
if (w > NES_SCREEN_WIDTH)
|
||||
{
|
||||
frame_x = (w - NES_SCREEN_WIDTH) / 2;
|
||||
frame_x_offset = 0;
|
||||
frame_width = NES_SCREEN_WIDTH;
|
||||
frame_height = NES_SCREEN_HEIGHT;
|
||||
frame_line_pixels = frame_width;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame_x = 0;
|
||||
frame_x_offset = (NES_SCREEN_WIDTH - w) / 2;
|
||||
frame_width = w;
|
||||
frame_height = NES_SCREEN_HEIGHT;
|
||||
frame_line_pixels = frame_width;
|
||||
}
|
||||
frame_y = (gfx->height() - NES_SCREEN_HEIGHT) / 2;
|
||||
}
|
||||
else // assume 480x320
|
||||
{
|
||||
frame_x = 0;
|
||||
frame_y = 0;
|
||||
frame_x_offset = 8;
|
||||
frame_width = w;
|
||||
frame_height = h;
|
||||
frame_line_pixels = frame_width / 2;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void display_write_frame(const uint8_t *data[])
|
||||
{
|
||||
gfx->startWrite();
|
||||
if (w < 480)
|
||||
{
|
||||
gfx->writeAddrWindow(frame_x, frame_y, frame_width, frame_height);
|
||||
for (int32_t i = 0; i < NES_SCREEN_HEIGHT; i++)
|
||||
{
|
||||
gfx->writeIndexedPixels((uint8_t *)(data[i] + frame_x_offset), myPalette, frame_line_pixels);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* ST7796 480 x 320 resolution */
|
||||
|
||||
/* Option 1:
|
||||
* crop 256 x 240 to 240 x 214
|
||||
* then scale up width x 2 and scale up height x 1.5
|
||||
* repeat a line for every 2 lines
|
||||
*/
|
||||
// gfx->writeAddrWindow(frame_x, frame_y, frame_width, frame_height);
|
||||
// for (int16_t i = 10; i < (10 + 214); i++)
|
||||
// {
|
||||
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
|
||||
// if ((i % 2) == 1)
|
||||
// {
|
||||
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
|
||||
// }
|
||||
// }
|
||||
|
||||
/* Option 2:
|
||||
* crop 256 x 240 to 240 x 214
|
||||
* then scale up width x 2 and scale up height x 1.5
|
||||
* simply blank a line for every 2 lines
|
||||
*/
|
||||
int16_t y = 0;
|
||||
for (int16_t i = 10; i < (10 + 214); i++)
|
||||
{
|
||||
gfx->writeAddrWindow(frame_x, y++, frame_width, 1);
|
||||
gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
|
||||
if ((i % 2) == 1)
|
||||
{
|
||||
y++; // blank line
|
||||
}
|
||||
}
|
||||
|
||||
/* Option 3:
|
||||
* crop 256 x 240 to 240 x 240
|
||||
* then scale up width x 2 and scale up height x 1.33
|
||||
* repeat a line for every 3 lines
|
||||
*/
|
||||
// gfx->writeAddrWindow(frame_x, frame_y, frame_width, frame_height);
|
||||
// for (int16_t i = 0; i < 240; i++)
|
||||
// {
|
||||
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
|
||||
// if ((i % 3) == 1)
|
||||
// {
|
||||
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
|
||||
// }
|
||||
// }
|
||||
|
||||
/* Option 4:
|
||||
* crop 256 x 240 to 240 x 240
|
||||
* then scale up width x 2 and scale up height x 1.33
|
||||
* simply blank a line for every 3 lines
|
||||
*/
|
||||
// int16_t y = 0;
|
||||
// for (int16_t i = 0; i < 240; i++)
|
||||
// {
|
||||
// gfx->writeAddrWindow(frame_x, y++, frame_width, 1);
|
||||
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
|
||||
// if ((i % 3) == 1)
|
||||
// {
|
||||
// y++; // blank line
|
||||
// }
|
||||
// }
|
||||
}
|
||||
gfx->endWrite();
|
||||
}
|
||||
|
||||
extern "C" void display_clear()
|
||||
{
|
||||
gfx->fillScreen(bg_color);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/* Arduino Nofrendo
|
||||
* Please check hw_config.h and display.cpp for configuration details
|
||||
*/
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <SD.h>
|
||||
#include <SD_MMC.h>
|
||||
#include <SPIFFS.h>
|
||||
|
||||
#include <Arduino_GFX_Library.h>
|
||||
|
||||
#include "hw_config.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <nofrendo.h>
|
||||
}
|
||||
|
||||
int16_t bg_color;
|
||||
extern Arduino_TFT *gfx;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
// turn off WiFi
|
||||
esp_wifi_deinit();
|
||||
|
||||
// disable Core 0 WDT
|
||||
TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
|
||||
esp_task_wdt_delete(idle_0);
|
||||
|
||||
// init display
|
||||
gfx->begin();
|
||||
bg_color = gfx->color565(24, 28, 24); // DARK DARK GREY
|
||||
gfx->fillScreen(bg_color);
|
||||
|
||||
#ifdef TFT_BL
|
||||
// turn display backlight on
|
||||
ledcAttachPin(TFT_BL, 1); // assign TFT_BL pin to channel 1
|
||||
ledcSetup(1, 12000, 8); // 12 kHz PWM, 8-bit resolution
|
||||
ledcWrite(1, TFT_BRIGHTNESS); // brightness 0 - 255
|
||||
#endif
|
||||
|
||||
// filesystem defined in hw_config.h
|
||||
FILESYSTEM_BEGIN
|
||||
|
||||
// find first rom file (*.nes)
|
||||
File root = filesystem.open("/");
|
||||
char *argv[1];
|
||||
if (!root)
|
||||
{
|
||||
Serial.println("Filesystem mount failed! Please check hw_config.h settings.");
|
||||
gfx->println("Filesystem mount failed! Please check hw_config.h settings.");
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundRom = false;
|
||||
|
||||
File file = root.openNextFile();
|
||||
while (file)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
// skip
|
||||
}
|
||||
else
|
||||
{
|
||||
char *filename = (char *)file.name();
|
||||
int8_t len = strlen(filename);
|
||||
if (strstr(strlwr(filename + (len - 4)), ".nes"))
|
||||
{
|
||||
foundRom = true;
|
||||
char fullFilename[256];
|
||||
sprintf(fullFilename, "%s%s", FSROOT, filename);
|
||||
Serial.println(fullFilename);
|
||||
argv[0] = fullFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
file = root.openNextFile();
|
||||
}
|
||||
|
||||
if (!foundRom)
|
||||
{
|
||||
Serial.println("Failed to find rom file, please copy rom file to data folder and upload with \"ESP32 Sketch Data Upload\"");
|
||||
argv[0] = "/";
|
||||
}
|
||||
|
||||
Serial.println("NoFrendo start!\n");
|
||||
nofrendo_main(1, argv);
|
||||
Serial.println("NoFrendo end!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef _HW_CONFIG_H_
|
||||
#define _HW_CONFIG_H_
|
||||
|
||||
#define FSROOT "/fs"
|
||||
|
||||
/* M5Stack */
|
||||
#if defined(ARDUINO_M5Stack_Core_ESP32) || defined(ARDUINO_M5STACK_FIRE)
|
||||
|
||||
// Uncomment one of below, M5Stack support SPIFFS and SD
|
||||
// #define FILESYSTEM_BEGIN SPIFFS.begin(false, FSROOT); FS filesystem = SPIFFS;
|
||||
#define FILESYSTEM_BEGIN SD.begin(4, SPI, 40000000, FSROOT); FS filesystem = SD;
|
||||
|
||||
/* enable audio */
|
||||
#define HW_AUDIO
|
||||
|
||||
/* controller is I2C M5Stack CardKB */
|
||||
#define HW_CONTROLLER_I2C_M5CARDKB
|
||||
|
||||
/* Odroid-Go */
|
||||
#elif defined(ARDUINO_ODROID_ESP32)
|
||||
|
||||
// Uncomment one of below, ODROID support SPIFFS and SD
|
||||
// #define FILESYSTEM_BEGIN SPIFFS.begin(false, FSROOT); FS filesystem = SPIFFS;
|
||||
#define FILESYSTEM_BEGIN SD.begin(SS, SPI, 40000000, FSROOT); FS filesystem = SD;
|
||||
|
||||
/* enable audio */
|
||||
#define HW_AUDIO
|
||||
|
||||
/* controller is GPIO */
|
||||
#define HW_CONTROLLER_GPIO
|
||||
#define HW_CONTROLLER_GPIO_ANALOG_JOYSTICK
|
||||
#define HW_CONTROLLER_GPIO_UP_DOWN 35
|
||||
#define HW_CONTROLLER_GPIO_LEFT_RIGHT 34
|
||||
#define HW_CONTROLLER_GPIO_SELECT 27
|
||||
#define HW_CONTROLLER_GPIO_START 39
|
||||
#define HW_CONTROLLER_GPIO_A 32
|
||||
#define HW_CONTROLLER_GPIO_B 33
|
||||
#define HW_CONTROLLER_GPIO_X 13
|
||||
#define HW_CONTROLLER_GPIO_Y 0
|
||||
|
||||
/* TTGO T-Watch */
|
||||
#elif defined(ARDUINO_T) || defined(ARDUINO_TWATCH_BASE) || defined(ARDUINO_TWATCH_2020_V1) || defined(ARDUINO_TWATCH_2020_V2) // TTGO T-Watch
|
||||
|
||||
// TTGO T-watch with game module only support SPIFFS
|
||||
#define FILESYSTEM_BEGIN SPIFFS.begin(false, FSROOT); FS filesystem = SPIFFS;
|
||||
|
||||
/* no audio */
|
||||
|
||||
/* controller is GPIO */
|
||||
#define HW_CONTROLLER_GPIO
|
||||
#define HW_CONTROLLER_GPIO_ANALOG_JOYSTICK
|
||||
#define HW_CONTROLLER_GPIO_UP_DOWN 34
|
||||
#define HW_CONTROLLER_GPIO_LEFT_RIGHT 33
|
||||
#define HW_CONTROLLER_GPIO_SELECT 15
|
||||
#define HW_CONTROLLER_GPIO_START 36
|
||||
#define HW_CONTROLLER_GPIO_A 13
|
||||
#define HW_CONTROLLER_GPIO_B 25
|
||||
#define HW_CONTROLLER_GPIO_X 14
|
||||
#define HW_CONTROLLER_GPIO_Y 26
|
||||
|
||||
/* custom hardware */
|
||||
#else
|
||||
|
||||
// Uncomment one of below, ESP32 support SPIFFS SD_MMC and SD
|
||||
/* SPIFFS */
|
||||
// #define FILESYSTEM_BEGIN SPIFFS.begin(false, FSROOT); FS filesystem = SPIFFS;
|
||||
/* 1-bit SD mode SD_MMC, always retry once for begin() failed */
|
||||
// #define FILESYSTEM_BEGIN (!SD_MMC.begin(FSROOT, true)) && (!SD_MMC.begin(FSROOT, true)); FS filesystem = SD_MMC;
|
||||
/* 4-bit SD mode SD_MMC, always retry once for begin() failed */
|
||||
// #define FILESYSTEM_BEGIN (!SD_MMC.begin(FSROOT, false)) && (!SD_MMC.begin(FSROOT, false)); FS filesystem = SD_MMC;
|
||||
/* SD using default SPI settings */
|
||||
// #define FILESYSTEM_BEGIN SD.begin(22 /* SS */, SPI, 8000000, FSROOT); FS filesystem = SD;
|
||||
/* SD using custom SPI settings */
|
||||
#define FILESYSTEM_BEGIN SPIClass spi = SPIClass(HSPI); spi.begin(14, 2, 15, 13); SD.begin(13, spi, 8000000, FSROOT); FS filesystem = SD;
|
||||
|
||||
// enable audio
|
||||
#define HW_AUDIO
|
||||
#define HW_AUDIO_EXTDAC
|
||||
#define HW_AUDIO_EXTDAC_WCLK 21
|
||||
#define HW_AUDIO_EXTDAC_BCLK 22
|
||||
#define HW_AUDIO_EXTDAC_DOUT 19
|
||||
|
||||
/* controller is GPIO */
|
||||
#define HW_CONTROLLER_GPIO
|
||||
#define HW_CONTROLLER_GPIO_ANALOG_JOYSTICK
|
||||
// #define HW_CONTROLLER_GPIO_REVERSE_UD
|
||||
#define HW_CONTROLLER_GPIO_UP_DOWN 34
|
||||
#define HW_CONTROLLER_GPIO_REVERSE_LF
|
||||
#define HW_CONTROLLER_GPIO_LEFT_RIGHT 35
|
||||
#define HW_CONTROLLER_GPIO_SELECT 27
|
||||
#define HW_CONTROLLER_GPIO_START 26
|
||||
#define HW_CONTROLLER_GPIO_A 5
|
||||
#define HW_CONTROLLER_GPIO_B 4
|
||||
#define HW_CONTROLLER_GPIO_X 23
|
||||
#define HW_CONTROLLER_GPIO_Y 18
|
||||
|
||||
/* controller is I2C M5Stack CardKB */
|
||||
// #define HW_CONTROLLER_I2C_M5CARDKB
|
||||
|
||||
/* controller is I2C BBQ10Keyboard */
|
||||
// #define HW_CONTROLLER_I2C_BBQ10KB
|
||||
|
||||
#endif /* custom hardware */
|
||||
|
||||
#endif /* _HW_CONFIG_H_ */
|
||||
@@ -0,0 +1,378 @@
|
||||
/* start rewrite from: https://github.com/espressif/esp32-nesemu.git */
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/timers.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#include <driver/i2s.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
#include <noftypes.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <gui.h>
|
||||
#include <log.h>
|
||||
#include <nes/nes.h>
|
||||
#include <nes/nes_pal.h>
|
||||
#include <nes/nesinput.h>
|
||||
#include <nofconfig.h>
|
||||
#include <osd.h>
|
||||
|
||||
#include "hw_config.h"
|
||||
|
||||
TimerHandle_t timer;
|
||||
|
||||
/* memory allocation */
|
||||
extern void *mem_alloc(int size, bool prefer_fast_memory)
|
||||
{
|
||||
if (prefer_fast_memory)
|
||||
{
|
||||
return heap_caps_malloc(size, MALLOC_CAP_8BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
return heap_caps_malloc_prefer(size, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
/* audio */
|
||||
#define DEFAULT_SAMPLERATE 22050
|
||||
|
||||
#if defined(HW_AUDIO)
|
||||
|
||||
#define DEFAULT_FRAGSIZE 1024
|
||||
static void (*audio_callback)(void *buffer, int length) = NULL;
|
||||
QueueHandle_t queue;
|
||||
static int16_t *audio_frame;
|
||||
|
||||
static int osd_init_sound(void)
|
||||
{
|
||||
audio_frame = NOFRENDO_MALLOC(4 * DEFAULT_FRAGSIZE);
|
||||
|
||||
i2s_config_t cfg = {
|
||||
#if defined(HW_AUDIO_EXTDAC)
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_TX,
|
||||
#else /* !defined(HW_AUDIO_EXTDAC) */
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
|
||||
#endif /* !defined(HW_AUDIO_EXTDAC) */
|
||||
.sample_rate = DEFAULT_SAMPLERATE,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
#if defined(HW_AUDIO_EXTDAC)
|
||||
.communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
|
||||
#else /* !defined(HW_AUDIO_EXTDAC) */
|
||||
.communication_format = I2S_COMM_FORMAT_PCM | I2S_COMM_FORMAT_I2S_MSB,
|
||||
#endif /* !defined(HW_AUDIO_EXTDAC) */
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 6,
|
||||
.dma_buf_len = 512,
|
||||
.use_apll = false,
|
||||
};
|
||||
i2s_driver_install(I2S_NUM_0, &cfg, 2, &queue);
|
||||
#if defined(HW_AUDIO_EXTDAC)
|
||||
i2s_pin_config_t pins = {
|
||||
.bck_io_num = HW_AUDIO_EXTDAC_BCLK,
|
||||
.ws_io_num = HW_AUDIO_EXTDAC_WCLK,
|
||||
.data_out_num = HW_AUDIO_EXTDAC_DOUT,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE,
|
||||
};
|
||||
i2s_set_pin(I2S_NUM_0, &pins);
|
||||
#else /* !defined(HW_AUDIO_EXTDAC) */
|
||||
i2s_set_pin(I2S_NUM_0, NULL);
|
||||
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
|
||||
#endif /* !defined(HW_AUDIO_EXTDAC) */
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
|
||||
audio_callback = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void osd_stopsound(void)
|
||||
{
|
||||
audio_callback = NULL;
|
||||
}
|
||||
|
||||
static void do_audio_frame()
|
||||
{
|
||||
int left = DEFAULT_SAMPLERATE / NES_REFRESH_RATE;
|
||||
while (left)
|
||||
{
|
||||
int n = DEFAULT_FRAGSIZE;
|
||||
if (n > left)
|
||||
n = left;
|
||||
audio_callback(audio_frame, n); //get more data
|
||||
|
||||
//16 bit mono -> 32-bit (16 bit r+l)
|
||||
int16_t *mono_ptr = audio_frame + n;
|
||||
int16_t *stereo_ptr = audio_frame + n + n;
|
||||
int i = n;
|
||||
while (i--)
|
||||
{
|
||||
#if defined(HW_AUDIO_EXTDAC)
|
||||
int16_t a = (*(--mono_ptr) >> 2);
|
||||
*(--stereo_ptr) = a;
|
||||
*(--stereo_ptr) = a;
|
||||
#else /* !defined(HW_AUDIO_EXTDAC) */
|
||||
int16_t a = (*(--mono_ptr) >> 3);
|
||||
*(--stereo_ptr) = 0x8000 + a;
|
||||
*(--stereo_ptr) = 0x8000 - a;
|
||||
#endif /* !defined(HW_AUDIO_EXTDAC) */
|
||||
}
|
||||
|
||||
size_t i2s_bytes_write;
|
||||
i2s_write(I2S_NUM_0, (const char *)audio_frame, 4 * n, &i2s_bytes_write, portMAX_DELAY);
|
||||
left -= i2s_bytes_write / 4;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_setsound(void (*playfunc)(void *buffer, int length))
|
||||
{
|
||||
//Indicates we should call playfunc() to get more data.
|
||||
audio_callback = playfunc;
|
||||
}
|
||||
|
||||
#else /* !defined(HW_AUDIO) */
|
||||
|
||||
static int osd_init_sound(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void osd_stopsound(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void do_audio_frame()
|
||||
{
|
||||
}
|
||||
|
||||
void osd_setsound(void (*playfunc)(void *buffer, int length))
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* !defined(HW_AUDIO) */
|
||||
|
||||
/* video */
|
||||
extern void display_init();
|
||||
extern void display_write_frame(const uint8_t *data[]);
|
||||
extern void display_clear();
|
||||
|
||||
//This runs on core 0.
|
||||
QueueHandle_t vidQueue;
|
||||
static void videoTask(void *arg)
|
||||
{
|
||||
bitmap_t *bmp = NULL;
|
||||
while (1)
|
||||
{
|
||||
// xQueueReceive(vidQueue, &bmp, portMAX_DELAY); //skip one frame to drop to 30
|
||||
xQueueReceive(vidQueue, &bmp, portMAX_DELAY);
|
||||
display_write_frame((const uint8_t **)bmp->line);
|
||||
}
|
||||
}
|
||||
|
||||
/* get info */
|
||||
static char fb[1]; //dummy
|
||||
bitmap_t *myBitmap;
|
||||
|
||||
/* initialise video */
|
||||
static int init(int width, int height)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void shutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* set a video mode */
|
||||
static int set_mode(int width, int height)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* copy nes palette over to hardware */
|
||||
uint16 myPalette[256];
|
||||
static void set_palette(rgb_t *pal)
|
||||
{
|
||||
uint16 c;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
{
|
||||
c = (pal[i].b >> 3) + ((pal[i].g >> 2) << 5) + ((pal[i].r >> 3) << 11);
|
||||
//myPalette[i]=(c>>8)|((c&0xff)<<8);
|
||||
myPalette[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/* clear all frames to a particular color */
|
||||
static void clear(uint8 color)
|
||||
{
|
||||
// SDL_FillRect(mySurface, 0, color);
|
||||
display_clear();
|
||||
}
|
||||
|
||||
/* acquire the directbuffer for writing */
|
||||
static bitmap_t *lock_write(void)
|
||||
{
|
||||
// SDL_LockSurface(mySurface);
|
||||
myBitmap = bmp_createhw((uint8 *)fb, NES_SCREEN_WIDTH, NES_SCREEN_HEIGHT, NES_SCREEN_WIDTH * 2);
|
||||
return myBitmap;
|
||||
}
|
||||
|
||||
/* release the resource */
|
||||
static void free_write(int num_dirties, rect_t *dirty_rects)
|
||||
{
|
||||
bmp_destroy(&myBitmap);
|
||||
}
|
||||
|
||||
static void custom_blit(bitmap_t *bmp, int num_dirties, rect_t *dirty_rects)
|
||||
{
|
||||
xQueueSend(vidQueue, &bmp, 0);
|
||||
do_audio_frame();
|
||||
}
|
||||
|
||||
viddriver_t sdlDriver =
|
||||
{
|
||||
"Simple DirectMedia Layer", /* name */
|
||||
init, /* init */
|
||||
shutdown, /* shutdown */
|
||||
set_mode, /* set_mode */
|
||||
set_palette, /* set_palette */
|
||||
clear, /* clear */
|
||||
lock_write, /* lock_write */
|
||||
free_write, /* free_write */
|
||||
custom_blit, /* custom_blit */
|
||||
false /* invalidate flag */
|
||||
};
|
||||
|
||||
void osd_getvideoinfo(vidinfo_t *info)
|
||||
{
|
||||
info->default_width = NES_SCREEN_WIDTH;
|
||||
info->default_height = NES_SCREEN_HEIGHT;
|
||||
info->driver = &sdlDriver;
|
||||
}
|
||||
|
||||
void osd_getsoundinfo(sndinfo_t *info)
|
||||
{
|
||||
info->sample_rate = DEFAULT_SAMPLERATE;
|
||||
info->bps = 16;
|
||||
}
|
||||
|
||||
/* input */
|
||||
extern void controller_init();
|
||||
extern uint32_t controller_read_input();
|
||||
|
||||
static void osd_initinput()
|
||||
{
|
||||
controller_init();
|
||||
}
|
||||
|
||||
static void osd_freeinput(void)
|
||||
{
|
||||
}
|
||||
|
||||
void osd_getinput(void)
|
||||
{
|
||||
const int ev[32] = {
|
||||
event_joypad1_up, event_joypad1_down, event_joypad1_left, event_joypad1_right,
|
||||
event_joypad1_select, event_joypad1_start, event_joypad1_a, event_joypad1_b,
|
||||
event_state_save, event_state_load, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0};
|
||||
static int oldb = 0xffff;
|
||||
uint32_t b = controller_read_input();
|
||||
uint32_t chg = b ^ oldb;
|
||||
int x;
|
||||
oldb = b;
|
||||
event_t evh;
|
||||
// nofrendo_log_printf("Input: %x\n", b);
|
||||
for (x = 0; x < 16; x++)
|
||||
{
|
||||
if (chg & 1)
|
||||
{
|
||||
evh = event_get(ev[x]);
|
||||
if (evh)
|
||||
evh((b & 1) ? INP_STATE_BREAK : INP_STATE_MAKE);
|
||||
}
|
||||
chg >>= 1;
|
||||
b >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_getmouse(int *x, int *y, int *button)
|
||||
{
|
||||
}
|
||||
|
||||
/* init / shutdown */
|
||||
static int logprint(const char *string)
|
||||
{
|
||||
return printf("%s", string);
|
||||
}
|
||||
|
||||
int osd_init()
|
||||
{
|
||||
nofrendo_log_chain_logfunc(logprint);
|
||||
|
||||
if (osd_init_sound())
|
||||
return -1;
|
||||
|
||||
display_init();
|
||||
vidQueue = xQueueCreate(1, sizeof(bitmap_t *));
|
||||
// xTaskCreatePinnedToCore(&videoTask, "videoTask", 2048, NULL, 5, NULL, 1);
|
||||
xTaskCreatePinnedToCore(&videoTask, "videoTask", 2048, NULL, 0, NULL, 0);
|
||||
osd_initinput();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osd_shutdown()
|
||||
{
|
||||
osd_stopsound();
|
||||
osd_freeinput();
|
||||
}
|
||||
|
||||
char configfilename[] = "na";
|
||||
int osd_main(int argc, char *argv[])
|
||||
{
|
||||
config.filename = configfilename;
|
||||
|
||||
return main_loop(argv[0], system_autodetect);
|
||||
}
|
||||
|
||||
//Seemingly, this will be called only once. Should call func with a freq of frequency,
|
||||
int osd_installtimer(int frequency, void *func, int funcsize, void *counter, int countersize)
|
||||
{
|
||||
nofrendo_log_printf("Timer install, configTICK_RATE_HZ=%d, freq=%d\n", configTICK_RATE_HZ, frequency);
|
||||
timer = xTimerCreate("nes", configTICK_RATE_HZ / frequency, pdTRUE, NULL, func);
|
||||
xTimerStart(timer, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* filename manipulation */
|
||||
void osd_fullname(char *fullname, const char *shortname)
|
||||
{
|
||||
strncpy(fullname, shortname, PATH_MAX);
|
||||
}
|
||||
|
||||
/* This gives filenames for storage of saves */
|
||||
char *osd_newextension(char *string, char *ext)
|
||||
{
|
||||
// dirty: assume both extensions is 3 characters
|
||||
size_t l = strlen(string);
|
||||
string[l - 3] = ext[1];
|
||||
string[l - 2] = ext[2];
|
||||
string[l - 1] = ext[3];
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
/* This gives filenames for storage of PCX snapshots */
|
||||
int osd_makesnapname(char *filename, int len)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user