Skip to content

Commit

Permalink
div enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
retroelec committed Jun 9, 2024
1 parent d193947 commit d3f62a6
Show file tree
Hide file tree
Showing 52 changed files with 1,006 additions and 521 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
FQBN := esp32:esp32:esp32s3:CDCOnBoot=cdc,DFUOnBoot=dfu,FlashSize=16M,JTAGAdapter=builtin,PartitionScheme=huge_app,PSRAM=opi,LoopCore=0,DebugLevel=info
PORT := /dev/ttyACM0
FQBN := esp32:esp32:esp32s3:CDCOnBoot=cdc,DFUOnBoot=dfu,FlashSize=16M,JTAGAdapter=builtin,PartitionScheme=huge_app,PSRAM=opi,LoopCore=0,DebugLevel=info
SOURCEFILES=$(wildcard src/*.cpp)

default: T-HMI-C64.ino $(SOURCEFILES) src/loadactions.h
arduino-cli compile --fqbn $(FQBN) T-HMI-C64.ino
arduino-cli compile --fqbn $(FQBN) --build-path build T-HMI-C64.ino

src/loadactions.h: src/loadactions.asm
/opt/TMPx_v1.1.0-STYLE/linux-x86_64/tmpx src/loadactions.asm -o src/loadactions.prg
xxd -i src/loadactions.prg > src/loadactions.h

upload:
arduino-cli upload -p $(PORT) --fqbn $(FQBN) T-HMI-C64.ino
arduino-cli upload -p $(PORT) --fqbn $(FQBN) -i build/T-HMI-C64.ino.bin

# create file $HOME/.minirc.dfl
# content:
Expand Down
94 changes: 56 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
C64 emulator for the development board Lilygo T-HMI equipped with an ESP32-S3 chip, a 2.8 inch touch display LCD screen (ST7789V driver) and a SD card slot.
The keyboard for the emulator is simulated by an Android app, communication between the app and the emulator is realized using BLE (Bluetooth Low Energy).

<img src="doc/donkey_kong.png" alt="class diagram" width="600"/>
<img src="doc/donkey_kong.png" alt="class diagram" width="800"/>

## Hardware

From [Xinyuan-LilyGO/T-HMI](https://github.com/Xinyuan-LilyGO/T-HMI):

<img src="doc/T-HMI.jpg" alt="T-HMI" width="600"/>
<img src="doc/T-HMI.jpg" alt="T-HMI" width="800"/>

### ESP32-S3

The ESP32-S3 is dual core containing a Protocol CPU (known as CPU 0, core 0 or PRO_CPU) and an Application CPU (known as CPU 1, core 1 or APP_CPU).
The two cores are identical in practice and share the same memory.
The tasks responsible for handling wireless networking (Wi-Fi or Bluetooth) are pinned to CPU 0 by default
The tasks responsible for handling wireless networking (Wi-Fi or Bluetooth) are pinned to core 0 by default
(see [Espressif - Task Priorities](https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32s3/api-guides/performance/speed.html)).

Core 0 is used to copy the graphic bitmap to LCD.
For this project core 0 is used to copy the graphic bitmap to LCD.
Emulation of the CPU and the custom chips (VIC and CIAs) are done on core 1.

### Display
Expand All @@ -35,7 +35,7 @@ It has an analog 2-axis thumb joystick and several buttons.
As there are several games which use the space bar as a second fire button (e.g. Commando), another button of the Iduino joystick
can be used to simulate the pressing of the space bar.

<img src="doc/joystick.png" alt="joystick" width="400"/>
<img src="doc/joystick.png" alt="joystick" width="800"/>

Connections:

Expand All @@ -60,18 +60,29 @@ you may have to adapt the following constants in src/Config.h:

### Files

- build/*.bin : Binary files of the C64 emulator to be uploaded to the T-HMI
- THMIC64KB/thmic64kb.apk : Android APK file to be uploaded to your Android smartphone
- T-HMI-C64.ino : Arduino .ino file of the C64 emulator, use in the Arduino IDE to upload the emulator to the T-HMI
- src/* : C64 emulator source code
- THMIC64KB/thmic64kb.apk : Android APK file to be uploaded to your Android smartphone
- THMIC64KB/app/src/ : source code of Android app
- Makefile : used to install development environment and to compile + upload code

### Installation C64 Emulator
### Install C64 Emulator

I use arduino-cli to upload the provided binary files to the development board:

- Download arduino-cli for your platform (download section from https://arduino.github.io/arduino-cli/0.35/installation/),
unpack the binary and place it in a directory included in the search path of executables (e.g. /usr/local/bin on a linux system).
- You may have to install python3 and python3-serial if not already installed. On my linux system I had to install python3-serial:
sudo apt install python3-serial
- You may have to install esptool to flash the microcontroller if not already installed:
pip install esptool
- Execute arduino-cli with the correct parameters (you may have to replace "/dev/ttyACM0" with the name of the serial port on your system):
arduino-cli upload -p /dev/ttyACM0 --fqbn esp32:esp32:esp32s3:CDCOnBoot=cdc,DFUOnBoot=dfu,FlashSize=16M,JTAGAdapter=builtin,PartitionScheme=huge_app,PSRAM=opi,LoopCore=0,DebugLevel=info -i build/T-HMI-C64.ino.bin

You have two possibilities to install the emulator on the Lilygo T-HMI development board: Using the Makefile with arduino-cli or using the Arduino IDE.
Using the Makefile with arduino-cli is an automated process and is therefore usually preferable.
If you want to install the development environment, you can use the provided Makefile (which itself uses arduino-cli) or you can use the Arduino IDE.

#### Using Makefile with arduino-cli
#### Install development environment using Makefile (*not* necessary to run the emulator)

- Download arduino-cli for your platform (download section from https://arduino.github.io/arduino-cli/0.35/installation/),
unpack the binary and place it in a directory included in the search path of executables (e.g. /usr/local/bin on a linux system).
Expand All @@ -80,24 +91,26 @@ Using the Makefile with arduino-cli is an automated process and is therefore usu
make install
- You may have to install python3 and python3-serial if not already installed. On my linux system I had to install python3-serial:
sudo apt install python3-serial
- You may have to install the esptool to flash your microcontroller if not already installed:
pip install esptool
- You may have to adapt the file Makefile and change the name of the serial port (adapt variable PORT).
- On a linux system you may have to add group dialout to your serial port to be able to upload code as a normal user:
- On a linux system you may have to add the group dialout to your user to be able to upload code as a normal user:
sudo usermod -a -G dialout your-username
(You have to logout and login again to get the group get active.)
- Compile code:
make
- Upload code:
make upload

#### Using Arduino IDE
#### Install development environment using Arduino IDE (*not* necessary to run the emulator)

From [Xinyuan-LilyGO/T-HMI](https://github.com/Xinyuan-LilyGO/T-HMI):
In Arduino Preferences, on the Settings tab, enter the [Espressif Arduino ESP32 Package](https://espressif.github.io/arduino-esp32/package_esp32_index.json)
URL in the Additional boards manager URLs input box.
Click OK and the software will install.
Search for ESP32 in Tools → Board Manager and install ESP32-Arduino SDK (V 2.0.5 or above and below V3.0).

I used the following settings in the Tools menu of the Arduino IDE 2.3.2:
Use the following settings in the Tools menu of the Arduino IDE 2.3.2:

| Setting | Value |
|--------------------------------------|-----------------------------------|
Expand Down Expand Up @@ -130,36 +143,40 @@ and choose menu Sketch - Upload or press ctrl-u.

### Install Android App

I wrote a simple Android app which provides a C64 keyboard for the emulator.
I wrote an Android app which provides a C64 keyboard for the emulator.

<img src="doc/THMIC64KB.png" alt="THMIC64KB" width="600"/>
<img src="doc/THMIC64KB.png" alt="THMIC64KB" width="1000"/>

You may follow these steps to install the app on your Android device:
However, this app is not available in the Google Play Store - you have to download the APK file
and install it "manually".
You may follow these steps to install the app on your Android device (there may be slight variations depending on your smartphone).

1. Download the app directly on your Android device: Click [here](https://github.com/retroelec/T-HMI-C64/blob/main/THMIC64KB/thmic64kb.apk) to download the APK file of the app.
2. Allow installation from unknown sources:
1. Allow installation of APK files from unknown sources:
- Go to "Settings" on your Android device.
- Navigate to "Security" or "Privacy".
- Enable "Unknown sources" or "Install unknown apps". This allows you to install apps from sources other than the Google Play Store.
3. Install the App:
- Once the APK file is downloaded, open the file manager on your device.
- Navigate to the folder where the APK file is saved.
- Tap on the APK file to start the installation process.
- Follow the on-screen instructions to complete the installation.

Alternatively you can also install the app e.g. using Airdroid or using the Android IDE.
- Navigate to "Security and Privacy".
- Navigate to "Additional Security Settings".
- Navigate to "Install Unknown Apps". A list of installed apps appears. Allow Chrome to install unknown aps.
2. Download the APK file to your Android device: Click [here](https://github.com/retroelec/T-HMI-C64/blob/main/THMIC64KB/thmic64kb.apk)
3. After the app has been downloaded, a message appears which allows you to open the file.
Click on open and follow the on-screen instructions to complete the installation.

## Usage

### Android keyboard

The emulator starts a BLE (Bluetooth Low Energy) server to receive keystrokes from the Android client.

Once the app is installed and launched, you must accept the requested permissions
Once the app is installed and launched, you must accept the requested permissions once
(access to the precise location (*not* coarse location), permission to search for BLE devices).
If you start the emulator (i.e. power on the T-HMI) before starting the app, the app will automatically connect to the BLE server.
Otherwise you can move the "BLE connection" switch to the right to connect to the BLE server. You also have to do this manually
after reseting the development board.
after "hardware reseting" the development board.

As it is not possible to press two keys together on the Android keyboard, the keys Shift, Ctrl and Commodore are special keys
which usually are pressed first, followed by another key to simulate the corresponding key combination.
If it is necessary to send the raw key code of these special keys (e.g. some pinball games use the shift key), you have to
set the corresponding switch in the Android app (DIV screen).
The key combination Run/Stop + Restore has been replaced by first pressing the Commodore key and then pressing the Restore key.

Besides the normal C64 keys this virtual keyboard also provides red extra buttons to send "external commands".
Actually the LOAD, DIV and several JOYSTICK buttons are available:
Expand All @@ -171,13 +188,11 @@ Actually the LOAD, DIV and several JOYSTICK buttons are available:
- KBJOYSTICK 1: "virtual joystick" can be used as a joystick in port 1
- KBJOYSTICK 2: "virtual joystick" can be used as a joystick in port 2

<img src="doc/THMIC64KB_VirtJoystick.png" alt="Virtual Joystick" width="600"/>
<img src="doc/THMIC64KB_VirtJoystick.png" alt="Virtual Joystick" width="800"/>

The virtual joystick has some drawbacks in terms of responsiveness.
To play games, a hardware joystick is recommended.

Up to now the following keys are not implemented: Commodore key, CTRL key, RESTORE key

### Load and start a game

You first have to copy C64 games in prg format (only supported format!) to an SD card
Expand All @@ -191,6 +206,8 @@ You then press the LOAD button on your Android phone (cursor must be on the same
If the file is found the text "LOADED" appears on screen, otherwise the text "FILE NOT FOUND" appears.
Afterwards, as usual, you can start the game by typing "RUN" followed by pressing the button RETURN.

<img src="doc/loadprg.gif" alt="class diagram" width="800"/>

## Software

### Class diagram
Expand All @@ -205,6 +222,8 @@ Keyboard inputs are sent to the ESP32 via BLE. Three bytes must be sent for each
- Value for the $DC01 register
- Control code:
- Bit 0 is set when a shift key is pressed
- Bit 1 is set when the ctrl key is pressed
- Bit 2 is set when the commodore key is pressed
- Bit 7 is set when an "external command" is sent

### Emulation status
Expand All @@ -221,8 +240,7 @@ All hardware ports not explicitly mentioned including their corresponding regist
- some VIC registers are only partly implemented (yet): $d011 (bit 3+4)
- some CIA registers are not implemented (yet): $d[c|d]02, $d[c|d]03, $d[c|d]08, $d[c|d]09, $d[c|d]0a, $d[c|d]0b,
- some CIA registers are only partly implemented (yet): $dc0d (bit 2), $dc0e (bit 7), $dc0f (bit 7)
- not all "illegal" opcodes of the 6502 CPU are implemented yet
- Android app: implement Commodore key, CTRL key, RESTORE key
- not all "illegal" opcodes of the 6502 CPU are implemented (yet)
- some games have graphic errors
- some games are not working at all

Expand Down Expand Up @@ -260,11 +278,11 @@ Games that are playable:
- Hero
- Burger time 97
- Outrun (small graphic errors in the middle of screen)
- Q*bert

Games not working:

- terra cresta (cannot start game)
- q*bert (crashing after start)
- burger time (crashing)
- arkanoid (crashing)
- Terra cresta (loops endless)
- Burger time (crashing)
- Arkanoid (loops endless)

2 changes: 2 additions & 0 deletions T-HMI-C64.ino
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
For the complete text of the GNU General Public License see
http://www.gnu.org/licenses/.
*/

#include "src/Main.h"
#include <esp_log.h>

static const char *TAG = "T-HMI-C64";

void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "start setup...");
ESP_LOGI(TAG, "setup() running on core %d", xPortGetCoreID());
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ public class BLEManager {
private final String targetDeviceName;
private final Settings settings;
private final Type2Notification type2Notification;
private final Type3Notification type3Notification;

public BluetoothGattCharacteristic getCharacteristic() {
return characteristic;
}

public BLEManager(MainActivity mainActivity, String targetDeviceName, Settings settings, Type2Notification type2Notification) {
public BLEManager(MainActivity mainActivity, String targetDeviceName, Settings settings, Type2Notification type2Notification, Type3Notification type3Notification) {
this.mainActivity = mainActivity;
this.targetDeviceName = targetDeviceName;
this.settings = settings;
this.type2Notification = type2Notification;
this.type3Notification = type3Notification;
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
if (!bluetoothAdapter.isEnabled() || bluetoothLeScanner == null) {
Expand Down Expand Up @@ -90,11 +92,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris
settings.setJoymode(receivedData[1]);
settings.setRefreshframecolor(receivedData[2] != 0);
settings.setDeactivatecia2(receivedData[3] != 0);
settings.setJoyemulmode(receivedData[4]);
Log.d("THMIC64", "joymode = " + settings.getJoymode());
Log.d("THMIC64", "refreshframecolor = " + settings.isRefreshframecolor());
Log.d("THMIC64", "switchonoffcia2 = " + settings.isDeactivatecia2());
Log.d("THMIC64", "joyemulmode = " + settings.getJoyemulmode());
settings.setSendRawKeyCodes(receivedData[4] != 0);
settings.notifySettingsObserver();
break;
case 2:
Expand All @@ -112,8 +110,22 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris
type2Notification.setD019(receivedData[11]);
type2Notification.setD01a(receivedData[12]);
type2Notification.setRegister1(receivedData[13]);
type2Notification.setDc0d(receivedData[14]);
type2Notification.setDc0e(receivedData[15]);
type2Notification.setDc0f(receivedData[16]);
type2Notification.setDd0d(receivedData[17]);
type2Notification.setDd0e(receivedData[18]);
type2Notification.setDd0f(receivedData[19]);
type2Notification.notifyObserver();
break;
case 3:
short[] mem = new short[16];
for (byte i = 0; i < 16; i++) {
mem[i] = receivedData[i + 1];
}
type3Notification.setMem(mem);
type3Notification.notifyObserver();
break;
}
} else {
Log.e("THMIC64", "received notification: wrong length");
Expand Down
Loading

0 comments on commit d3f62a6

Please sign in to comment.