├── .DS_Store ├── .gitignore ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── design.txt ├── doc ├── IMG_0785.HEIC ├── IMG_0785.jpeg ├── IMG_0786.jpeg └── minicom.jpg ├── include ├── OneWire_direct_gpio.h ├── README ├── agon_audio.h ├── audio_channel.h ├── audio_sample.h ├── ay_3_8910.h ├── ay_3_8910_noise.h ├── buffer_stream.h ├── enhanced_samples_generator.h ├── envelopes │ ├── adsr.h │ ├── ay_3_8910_envelope.h │ ├── frequency.h │ ├── multiphase_adsr.h │ └── types.h ├── eos.h ├── globals.h ├── hal.h ├── mos.h ├── ppi-8255.h ├── sn76489an.h ├── tms9918.h ├── types.h ├── updater.h └── zdi.h ├── lib └── README ├── platformio.ini ├── src ├── .DS_Store ├── audio_channel.cpp ├── audio_driver.cpp ├── audio_sample.cpp ├── ay_3_8910.cpp ├── ay_3_8910_noise.cpp ├── buffer_stream.cpp ├── enhanced_samples_generator.cpp ├── envelopes │ ├── adsr.cpp │ ├── ay_3_8910_envelope.cpp │ ├── frequency.cpp │ └── multiphase_adsr.cpp ├── eos.cpp ├── hal.cpp ├── main.cpp ├── mos.cpp ├── ppi-8255.cpp ├── sn76489an.cpp ├── tms9918.cpp ├── updater.cpp └── zdi.cpp ├── test └── README └── vdu_updater_cmds.txt /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S0urceror/AgonElectronHAL/0fcc3b6029401ada0a33080173e402a5d1fbfa28/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false, 3 | "files.associations": { 4 | "cstdint": "cpp", 5 | "compare": "cpp", 6 | "array": "cpp", 7 | "string": "cpp", 8 | "string_view": "cpp", 9 | "span": "cpp", 10 | "unordered_map": "cpp" 11 | } 12 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "PlatformIO", 6 | "task": "Upload", 7 | "problemMatcher": [ 8 | "$platformio" 9 | ], 10 | "label": "PlatformIO: Upload", 11 | "group": "build" 12 | }, 13 | { 14 | "type": "PlatformIO", 15 | "task": "Upload and Monitor", 16 | "problemMatcher": [ 17 | "$platformio" 18 | ], 19 | "label": "PlatformIO: Upload and Monitor", 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | } 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mario Smit (S0urceror) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agon Electron HAL 2 | The AgonLight is an amazing device combining retro vibes with a modern programming environment. 3 | 4 | The Agon Electron Hardware Abstraction Layer will provide virtual hardware abstractions in the ESP32 'governor' that the EZ80 retro chip can talk to. 5 | 6 | Although there is an excellent and more complete alternative called Quark Agon-VDP by Dean Benfield, I created my own attempt to something similar called Electron HAL. Because Quarks and Electrons together make (things that) Matter. 7 | 8 | ## Features 9 | Currently this HAL has the following features: 10 | - Host<->ESP32 terminal interface, bridging to EZ80 11 | - ESP32<->EZ80 terminal interface connecting to Agon-MOS and other future EZ80 os-es like CP/M and MSX-DOS. 12 | - EZ80 ZDI remote debugging 13 | - EZ80 status 14 | - EZ80 register contents 15 | - 4 HW breakpoints 16 | - Break/Step/Continue 17 | - Jump to memory location 18 | - EZ80 ZDI memory access 19 | - read 20 | - write 21 | 22 | ## Hardware setup 23 | To be able to use the ZDI interface we need to connect two GPIO pins that come from the ESP32 and connect them with the corresponding pins on the EZ80. I don't know what is wrong with either the schematic or silk screen but I connected pins 4 and 6 on the ZDI connector. Of course I first probed around to see if they where correct and they are. 24 | 25 | To have the right setup you have to: 26 | - Connect GPIO26 (pin 9) to TCK (pin 4) 27 | - Connect GPIO27 (pin 8) to TDI (pin 6) 28 | 29 | Below some detailed shots of the setup. 30 | ![GPIO closeup](doc/IMG_0785.jpeg) 31 | ![ZDI closeup](doc/IMG_0786.jpeg) 32 | 33 | ### Large memory transfers 34 | One of the features of this special firmware is the ability to receive and send things to EZ80 memory by simple copying and pasting. This is done with the Intel HEX format which converts binary contents to a character stream. With tools like bin2hex and hex2bin you can convert from and to this format. You might want to automate this in your compilation setup. 35 | 36 | I got the best results with MiniCOM. I set the newline tx delay to 100ms. This way the ESP/EZ80 have some time to write thing to memory. Main reason is that the USB serial from host pc to ESP does not support hardware based overflow protection (RTS,CTS). Limitation of the current hw-design where RTS/CTS from the CH340 are used differently. 37 | ![minicom](doc/minicom.jpg) 38 | 39 | ## Software setup 40 | For the software setup you need the Arduino software or PlatformIO correctly configured. Check the Agon documentation how to do this. Then you can build, upload and flash the new ESP32 Agon Electron HAL to your board. 41 | 42 | ### Usage 43 | At boot the Agon behaves like normal. Only it sits in Terminal character-only mode (for now). In this mode it will echo the output of MOS to the terminal but also output the same via USB serial to the host PC. And vice-versa, keyboard input on the host pc travels to the ESP which sends it to the EZ80. 44 | 45 | ## Enter ZDI mode 46 | To enter ZDI mode you have to press CTRL-Z. You see the prompt changes from * to #. Also the version and revision of the EZ80 chip are shown. In this case 7.AA. 47 | 48 | Now you can enter the following commands: 49 | ``` 50 | ZDI mode EZ80: 7.AA 51 | #h 52 | h - this help message 53 | b - break the program 54 | b address - set breakpoint at hex address 55 | d nr - unset breakpoint 56 | c - continue the program 57 | s - step by step 58 | r - show registers and status 59 | j address - jump to address 60 | x address size - examine memory from address 61 | :0123456789ABCD - data in Intel Hex format 62 | ``` 63 | 64 | All parameters are assumed to be HEX values. You can prepend them with 0x for clarity but that is not necessary. 65 | 66 | So: 67 | ``` 68 | b 0xB0000 69 | ``` 70 | Is equal to: 71 | ``` 72 | b B0000 73 | ``` 74 | 75 | Addresses are assumed long-range ADL addresses using the 24-bit addressing range of the EZ80. Once I implement an ADL/Z80 mode switch I'll revisit this. 76 | 77 | ## Memory map in MOS 78 | The EZ80's memory map depends on the firmware that was flashed into it. If you run MOS RC2 it looks like this: 79 | ``` 80 | 512KB External RAM = 00-0000 - 08-0000 81 | 8KB internal RAM = 00-0000 - 00-2000 82 | 83 | FF-FFFF 84 | ... 85 | ... 86 | B7-FFFF Internal Data RAM 87 | B7-C000 Internal Data RAM 88 | ... 89 | ... 90 | 0C-0000 External RAM 91 | 0B-0000 SPL+SPS External RAM 92 | 0A-0000 SPL External RAM 93 | 09-0000 External RAM 94 | 08-0000 External RAM 95 | 07-0000 External RAM 96 | 06-0000 External RAM 97 | 05-0000 External RAM 98 | 04-0000 External RAM 99 | 03-0000 100 | 02-0000 Flash ROM 101 | 01-0000 Flash ROM 102 | 00-0000 Flash ROM 103 | ``` 104 | 105 | ## Memory map after RESET 106 | ``` 107 | FF-FFFF Internal Data RAM 108 | FF-C000 Internal Data RAM 109 | ... 110 | ... 111 | 08-0000 External RAM 112 | 07-0000 External RAM 113 | 06-0000 External RAM 114 | 05-0000 External RAM 115 | 04-0000 External RAM 116 | 03-0000 External RAM 117 | 02-0000 Flash ROM External RAM 118 | 01-0000 Flash ROM External RAM 119 | 00-0000 Flash ROM External RAM 120 | ``` -------------------------------------------------------------------------------- /design.txt: -------------------------------------------------------------------------------- 1 | MOS-VDP combo 2 | ============= 3 | WAKEUP 4 | - MOS sends GP packet for some time 5 | - VDP answers with echo GP packet 6 | VDP sends raw chars to MOS 7 | - only in Terminal mode after set by VDP packet 8 | VDP sends packets to MOS 9 | - packets have high-bit 7 + cmd nr 10 | - followed by length 11 | - followed by bytes 12 | MOS sends packets or chars to VDP 13 | - packets start with CTRL-W 14 | - chars are everything else 15 | special care for special chars 16 | case 0x20 ... 0x7E: 17 | case 0x80 ... 0xFF: 18 | plotCharacter(c); 19 | 20 | 21 | 22 | ELECTRON HAL - OS combo 23 | ======================== 24 | WAKEUP 25 | - HAL sends ESC to OS 26 | - OS responds with CTRL-Z 27 | HAL sends raw chars to OS 28 | -bit 7 is low + 7 bits ASCII 29 | HAL sends command to OS 30 | -bit 7 is high + 7 bits cmd-id 31 | -00 = VIRTUAL-KEYCODE + UP:0/DOWN:1 32 | OS sends raw chars to HAL 33 | OS sends commands to HAL 34 | -bit 7 is high + 7 bits cmd-id 35 | -OUTPUT/INPUT/FILL/BLOCKRD/BLOCKWR 36 | 37 | 38 | SOLVED DIFFERENCES: 39 | =================== 40 | 41 | MOS - hardware CTS via input pin, no hardware RTS 42 | ================================================= 43 | PORTD_DDR = 0000 1011 44 | PORTD_ALT1= 1111 0100 45 | PORTD_ALT2= 0000 0011 46 | RX/TX (pins 0,1) are alternative function => UART 47 | CTS (pin 3) is input 48 | write waits for CTS becoming Z 49 | MCTL = 0 (no and reset RTS) 50 | 51 | EOS - hardware CTS and RTS 52 | ================================================= 53 | PORTD_DDR = 0000 1111 54 | PORTD_ALT1= 1111 0000 55 | PORTD_ALT2= 0000 1111 56 | RX/TX/CTS/RTS (pins 0,1,2,3) are alternative function => UART 57 | write waits for bit4 MSR becoming NZ 58 | MCTL = 2 (use and set RTS) -------------------------------------------------------------------------------- /doc/IMG_0785.HEIC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S0urceror/AgonElectronHAL/0fcc3b6029401ada0a33080173e402a5d1fbfa28/doc/IMG_0785.HEIC -------------------------------------------------------------------------------- /doc/IMG_0785.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S0urceror/AgonElectronHAL/0fcc3b6029401ada0a33080173e402a5d1fbfa28/doc/IMG_0785.jpeg -------------------------------------------------------------------------------- /doc/IMG_0786.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S0urceror/AgonElectronHAL/0fcc3b6029401ada0a33080173e402a5d1fbfa28/doc/IMG_0786.jpeg -------------------------------------------------------------------------------- /doc/minicom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S0urceror/AgonElectronHAL/0fcc3b6029401ada0a33080173e402a5d1fbfa28/doc/minicom.jpg -------------------------------------------------------------------------------- /include/OneWire_direct_gpio.h: -------------------------------------------------------------------------------- 1 | #ifndef OneWire_Direct_GPIO_h 2 | #define OneWire_Direct_GPIO_h 3 | 4 | // This header should ONLY be included by OneWire.cpp. These defines are 5 | // meant to be private, used within OneWire.cpp, but not exposed to Arduino 6 | // sketches or other libraries which may include OneWire.h. 7 | 8 | #include 9 | 10 | // Platform specific I/O definitions 11 | 12 | #if defined(__AVR__) 13 | #define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) 14 | #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) 15 | #define IO_REG_TYPE uint8_t 16 | #define IO_REG_BASE_ATTR asm("r30") 17 | #define IO_REG_MASK_ATTR 18 | #if defined(__AVR_ATmega4809__) 19 | #define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) 20 | #define DIRECT_MODE_INPUT(base, mask) ((*((base)-8)) &= ~(mask)) 21 | #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)-8)) |= (mask)) 22 | #define DIRECT_WRITE_LOW(base, mask) ((*((base)-4)) &= ~(mask)) 23 | #define DIRECT_WRITE_HIGH(base, mask) ((*((base)-4)) |= (mask)) 24 | #else 25 | #define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) 26 | #define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) 27 | #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) 28 | #define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) 29 | #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) 30 | #endif 31 | 32 | #elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) 33 | #define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) 34 | #define PIN_TO_BITMASK(pin) (1) 35 | #define IO_REG_TYPE uint8_t 36 | #define IO_REG_BASE_ATTR 37 | #define IO_REG_MASK_ATTR __attribute__ ((unused)) 38 | #define DIRECT_READ(base, mask) (*((base)+512)) 39 | #define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) 40 | #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) 41 | #define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) 42 | #define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) 43 | 44 | #elif defined(__MKL26Z64__) 45 | #define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) 46 | #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) 47 | #define IO_REG_TYPE uint8_t 48 | #define IO_REG_BASE_ATTR 49 | #define IO_REG_MASK_ATTR 50 | #define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) 51 | #define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) 52 | #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) 53 | #define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) 54 | #define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) 55 | 56 | #elif defined(__IMXRT1052__) || defined(__IMXRT1062__) 57 | #define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) 58 | #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) 59 | #define IO_REG_TYPE uint32_t 60 | #define IO_REG_BASE_ATTR 61 | #define IO_REG_MASK_ATTR 62 | #define DIRECT_READ(base, mask) ((*((base)+2) & (mask)) ? 1 : 0) 63 | #define DIRECT_MODE_INPUT(base, mask) (*((base)+1) &= ~(mask)) 64 | #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+1) |= (mask)) 65 | #define DIRECT_WRITE_LOW(base, mask) (*((base)+34) = (mask)) 66 | #define DIRECT_WRITE_HIGH(base, mask) (*((base)+33) = (mask)) 67 | 68 | #elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) 69 | // Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. 70 | // http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 71 | // If you have trouble with OneWire on Arduino Due, please check the 72 | // status of delayMicroseconds() before reporting a bug in OneWire! 73 | #define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) 74 | #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) 75 | #define IO_REG_TYPE uint32_t 76 | #define IO_REG_BASE_ATTR 77 | #define IO_REG_MASK_ATTR 78 | #define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) 79 | #define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) 80 | #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) 81 | #define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) 82 | #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) 83 | #ifndef PROGMEM 84 | #define PROGMEM 85 | #endif 86 | #ifndef pgm_read_byte 87 | #define pgm_read_byte(addr) (*(const uint8_t *)(addr)) 88 | #endif 89 | 90 | #elif defined(__PIC32MX__) 91 | #define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) 92 | #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) 93 | #define IO_REG_TYPE uint32_t 94 | #define IO_REG_BASE_ATTR 95 | #define IO_REG_MASK_ATTR 96 | #define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 97 | #define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 98 | #define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 99 | #define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 100 | #define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 101 | 102 | #elif defined(ARDUINO_ARCH_ESP8266) 103 | // Special note: I depend on the ESP community to maintain these definitions and 104 | // submit good pull requests. I can not answer any ESP questions or help you 105 | // resolve any problems related to ESP chips. Please do not contact me and please 106 | // DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked 107 | // on ESP community forums. 108 | #define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) 109 | #define PIN_TO_BITMASK(pin) (1 << pin) 110 | #define IO_REG_TYPE uint32_t 111 | #define IO_REG_BASE_ATTR 112 | #define IO_REG_MASK_ATTR 113 | #define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS 114 | #define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS 115 | #define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS 116 | #define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS 117 | #define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS 118 | 119 | #elif defined(ARDUINO_ARCH_ESP32) 120 | #include 121 | #define PIN_TO_BASEREG(pin) (0) 122 | #define PIN_TO_BITMASK(pin) (pin) 123 | #define IO_REG_TYPE uint32_t 124 | #define IO_REG_BASE_ATTR 125 | #define IO_REG_MASK_ATTR 126 | 127 | static inline __attribute__((always_inline)) 128 | IO_REG_TYPE directRead(IO_REG_TYPE pin) 129 | { 130 | #if CONFIG_IDF_TARGET_ESP32C3 131 | return (GPIO.in.val >> pin) & 0x1; 132 | #else // plain ESP32 133 | if ( pin < 32 ) 134 | return (GPIO.in >> pin) & 0x1; 135 | else if ( pin < 46 ) 136 | return (GPIO.in1.val >> (pin - 32)) & 0x1; 137 | #endif 138 | 139 | return 0; 140 | } 141 | 142 | static inline __attribute__((always_inline)) 143 | void directWriteLow(IO_REG_TYPE pin) 144 | { 145 | #if CONFIG_IDF_TARGET_ESP32C3 146 | GPIO.out_w1tc.val = ((uint32_t)1 << pin); 147 | #else // plain ESP32 148 | if ( pin < 32 ) 149 | GPIO.out_w1tc = ((uint32_t)1 << pin); 150 | else if ( pin < 46 ) 151 | GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); 152 | #endif 153 | } 154 | 155 | static inline __attribute__((always_inline)) 156 | void directWriteHigh(IO_REG_TYPE pin) 157 | { 158 | #if CONFIG_IDF_TARGET_ESP32C3 159 | GPIO.out_w1ts.val = ((uint32_t)1 << pin); 160 | #else // plain ESP32 161 | if ( pin < 32 ) 162 | GPIO.out_w1ts = ((uint32_t)1 << pin); 163 | else if ( pin < 46 ) 164 | GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); 165 | #endif 166 | } 167 | 168 | static inline __attribute__((always_inline)) 169 | void directModeInput(IO_REG_TYPE pin) 170 | { 171 | #if CONFIG_IDF_TARGET_ESP32C3 172 | GPIO.enable_w1tc.val = ((uint32_t)1 << (pin)); 173 | #else 174 | if ( digitalPinIsValid(pin) ) 175 | { 176 | #if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4 177 | uint32_t rtc_reg(rtc_gpio_desc[pin].reg); 178 | 179 | if ( rtc_reg ) // RTC pins PULL settings 180 | { 181 | ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); 182 | ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); 183 | } 184 | #endif 185 | // Input 186 | if ( pin < 32 ) 187 | GPIO.enable_w1tc = ((uint32_t)1 << pin); 188 | else 189 | GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); 190 | } 191 | #endif 192 | } 193 | 194 | static inline __attribute__((always_inline)) 195 | void directModeOutput(IO_REG_TYPE pin) 196 | { 197 | #if CONFIG_IDF_TARGET_ESP32C3 198 | GPIO.enable_w1ts.val = ((uint32_t)1 << (pin)); 199 | #else 200 | if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs 201 | { 202 | #if ESP_IDF_VERSION_MAJOR < 4 // IDF 3.x ESP32/PICO-D4 203 | uint32_t rtc_reg(rtc_gpio_desc[pin].reg); 204 | 205 | if ( rtc_reg ) // RTC pins PULL settings 206 | { 207 | ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); 208 | ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); 209 | } 210 | #endif 211 | // Output 212 | if ( pin < 32 ) 213 | GPIO.enable_w1ts = ((uint32_t)1 << pin); 214 | else // already validated to pins <= 33 215 | GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); 216 | } 217 | #endif 218 | } 219 | 220 | #define DIRECT_READ(base, pin) directRead(pin) 221 | #define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) 222 | #define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) 223 | #define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) 224 | #define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) 225 | // https://github.com/PaulStoffregen/OneWire/pull/47 226 | // https://github.com/stickbreaker/OneWire/commit/6eb7fc1c11a15b6ac8c60e5671cf36eb6829f82c 227 | #ifdef interrupts 228 | #undef interrupts 229 | #endif 230 | #ifdef noInterrupts 231 | #undef noInterrupts 232 | #endif 233 | #define noInterrupts() {portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;portENTER_CRITICAL(&mux) 234 | #define interrupts() portEXIT_CRITICAL(&mux);} 235 | //#warning "ESP32 OneWire testing" 236 | 237 | #elif defined(ARDUINO_ARCH_STM32) 238 | #define PIN_TO_BASEREG(pin) (0) 239 | #define PIN_TO_BITMASK(pin) ((uint32_t)digitalPinToPinName(pin)) 240 | #define IO_REG_TYPE uint32_t 241 | #define IO_REG_BASE_ATTR 242 | #define IO_REG_MASK_ATTR 243 | #define DIRECT_READ(base, pin) digitalReadFast((PinName)pin) 244 | #define DIRECT_WRITE_LOW(base, pin) digitalWriteFast((PinName)pin, LOW) 245 | #define DIRECT_WRITE_HIGH(base, pin) digitalWriteFast((PinName)pin, HIGH) 246 | #define DIRECT_MODE_INPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0)) 247 | #define DIRECT_MODE_OUTPUT(base, pin) pin_function((PinName)pin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0)) 248 | 249 | #elif defined(__SAMD21G18A__) 250 | #define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) 251 | #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) 252 | #define IO_REG_TYPE uint32_t 253 | #define IO_REG_BASE_ATTR 254 | #define IO_REG_MASK_ATTR 255 | #define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) 256 | #define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) 257 | #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) 258 | #define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) 259 | #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) 260 | 261 | #elif defined(__ASR6501__) 262 | #define PIN_IN_PORT(pin) (pin % PIN_NUMBER_IN_PORT) 263 | #define PORT_FROM_PIN(pin) (pin / PIN_NUMBER_IN_PORT) 264 | #define PORT_OFFSET(port) (PORT_REG_SHFIT * port) 265 | #define PORT_ADDRESS(pin) (CYDEV_GPIO_BASE + PORT_OFFSET(PORT_FROM_PIN(pin))) 266 | 267 | #define PIN_TO_BASEREG(pin) (0) 268 | #define PIN_TO_BITMASK(pin) (pin) 269 | #define IO_REG_TYPE uint32_t 270 | #define IO_REG_BASE_ATTR 271 | #define IO_REG_MASK_ATTR 272 | #define DIRECT_READ(base, pin) CY_SYS_PINS_READ_PIN(PORT_ADDRESS(pin)+4, PIN_IN_PORT(pin)) 273 | #define DIRECT_WRITE_LOW(base, pin) CY_SYS_PINS_CLEAR_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin)) 274 | #define DIRECT_WRITE_HIGH(base, pin) CY_SYS_PINS_SET_PIN(PORT_ADDRESS(pin), PIN_IN_PORT(pin)) 275 | #define DIRECT_MODE_INPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_DIG_HIZ) 276 | #define DIRECT_MODE_OUTPUT(base, pin) CY_SYS_PINS_SET_DRIVE_MODE(PORT_ADDRESS(pin)+8, PIN_IN_PORT(pin), CY_SYS_PINS_DM_STRONG) 277 | 278 | #elif defined(RBL_NRF51822) 279 | #define PIN_TO_BASEREG(pin) (0) 280 | #define PIN_TO_BITMASK(pin) (pin) 281 | #define IO_REG_TYPE uint32_t 282 | #define IO_REG_BASE_ATTR 283 | #define IO_REG_MASK_ATTR 284 | #define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) 285 | #define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) 286 | #define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) 287 | #define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) 288 | #define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) 289 | 290 | #elif defined(__arc__) /* Arduino101/Genuino101 specifics */ 291 | 292 | #include "scss_registers.h" 293 | #include "portable.h" 294 | #include "avr/pgmspace.h" 295 | 296 | #define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) 297 | #define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) 298 | #define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) 299 | #define DIR_OFFSET_SS 0x01 300 | #define DIR_OFFSET_SOC 0x04 301 | #define EXT_PORT_OFFSET_SS 0x0A 302 | #define EXT_PORT_OFFSET_SOC 0x50 303 | 304 | /* GPIO registers base address */ 305 | #define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) 306 | #define PIN_TO_BITMASK(pin) pin 307 | #define IO_REG_TYPE uint32_t 308 | #define IO_REG_BASE_ATTR 309 | #define IO_REG_MASK_ATTR 310 | 311 | static inline __attribute__((always_inline)) 312 | IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) 313 | { 314 | IO_REG_TYPE ret; 315 | if (SS_GPIO == GPIO_TYPE(pin)) { 316 | ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); 317 | } else { 318 | ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); 319 | } 320 | return ((ret >> GPIO_ID(pin)) & 0x01); 321 | } 322 | 323 | static inline __attribute__((always_inline)) 324 | void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) 325 | { 326 | if (SS_GPIO == GPIO_TYPE(pin)) { 327 | WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), 328 | ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); 329 | } else { 330 | MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); 331 | } 332 | } 333 | 334 | static inline __attribute__((always_inline)) 335 | void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) 336 | { 337 | if (SS_GPIO == GPIO_TYPE(pin)) { 338 | WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), 339 | ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); 340 | } else { 341 | MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); 342 | } 343 | } 344 | 345 | static inline __attribute__((always_inline)) 346 | void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) 347 | { 348 | if (SS_GPIO == GPIO_TYPE(pin)) { 349 | WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); 350 | } else { 351 | MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); 352 | } 353 | } 354 | 355 | static inline __attribute__((always_inline)) 356 | void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) 357 | { 358 | if (SS_GPIO == GPIO_TYPE(pin)) { 359 | WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); 360 | } else { 361 | MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); 362 | } 363 | } 364 | 365 | #define DIRECT_READ(base, pin) directRead(base, pin) 366 | #define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) 367 | #define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) 368 | #define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) 369 | #define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) 370 | 371 | #elif defined(__riscv) 372 | 373 | /* 374 | * Tested on highfive1 375 | * 376 | * Stable results are achieved operating in the 377 | * two high speed modes of the highfive1. It 378 | * seems to be less reliable in slow mode. 379 | */ 380 | #define PIN_TO_BASEREG(pin) (0) 381 | #define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) 382 | #define IO_REG_TYPE uint32_t 383 | #define IO_REG_BASE_ATTR 384 | #define IO_REG_MASK_ATTR 385 | 386 | static inline __attribute__((always_inline)) 387 | IO_REG_TYPE directRead(IO_REG_TYPE mask) 388 | { 389 | return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0; 390 | } 391 | 392 | static inline __attribute__((always_inline)) 393 | void directModeInput(IO_REG_TYPE mask) 394 | { 395 | GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; 396 | GPIO_REG(GPIO_IOF_EN) &= ~mask; 397 | 398 | GPIO_REG(GPIO_INPUT_EN) |= mask; 399 | GPIO_REG(GPIO_OUTPUT_EN) &= ~mask; 400 | } 401 | 402 | static inline __attribute__((always_inline)) 403 | void directModeOutput(IO_REG_TYPE mask) 404 | { 405 | GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; 406 | GPIO_REG(GPIO_IOF_EN) &= ~mask; 407 | 408 | GPIO_REG(GPIO_INPUT_EN) &= ~mask; 409 | GPIO_REG(GPIO_OUTPUT_EN) |= mask; 410 | } 411 | 412 | static inline __attribute__((always_inline)) 413 | void directWriteLow(IO_REG_TYPE mask) 414 | { 415 | GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask; 416 | } 417 | 418 | static inline __attribute__((always_inline)) 419 | void directWriteHigh(IO_REG_TYPE mask) 420 | { 421 | GPIO_REG(GPIO_OUTPUT_VAL) |= mask; 422 | } 423 | 424 | #define DIRECT_READ(base, mask) directRead(mask) 425 | #define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) 426 | #define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) 427 | #define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) 428 | #define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) 429 | 430 | #elif defined(ARDUINO_ARCH_MBED_RP2040)|| defined(ARDUINO_ARCH_RP2040) 431 | #define delayMicroseconds(time) busy_wait_us(time) 432 | #define PIN_TO_BASEREG(pin) (0) 433 | #define PIN_TO_BITMASK(pin) (pin) 434 | #define IO_REG_TYPE unsigned int 435 | #define IO_REG_BASE_ATTR 436 | #define IO_REG_MASK_ATTR 437 | #define DIRECT_READ(base, pin) digitalRead(pin) 438 | #define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) 439 | #define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) 440 | #define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) 441 | #define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) 442 | #warning "OneWire. RP2040 in Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite." 443 | 444 | #else 445 | #define PIN_TO_BASEREG(pin) (0) 446 | #define PIN_TO_BITMASK(pin) (pin) 447 | #define IO_REG_TYPE unsigned int 448 | #define IO_REG_BASE_ATTR 449 | #define IO_REG_MASK_ATTR 450 | #define DIRECT_READ(base, pin) digitalRead(pin) 451 | #define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) 452 | #define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) 453 | #define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) 454 | #define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) 455 | #warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." 456 | 457 | #endif 458 | 459 | #endif 460 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /include/agon_audio.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Agon Video BIOS - Audio class 3 | // Author: Dean Belfield 4 | // Contributors: Steve Sims (enhancements for more sophisticated audio support) 5 | // Created: 05/09/2022 6 | // Last Updated: 04/08/2023 7 | // 8 | // Modinfo: 9 | 10 | #ifndef AGON_AUDIO_H 11 | #define AGON_AUDIO_H 12 | 13 | #include "audio_channel.h" 14 | 15 | extern std::unordered_map> audioChannels; 16 | 17 | void audioDriver(void * parameters); 18 | void initAudioChannel(uint8_t channel); 19 | void audioTaskAbortDelay(uint8_t channel); 20 | void audioTaskKill(uint8_t channel); 21 | void setSampleRate(uint16_t sampleRate); 22 | void initAudio(); 23 | bool channelEnabled(uint8_t channel); 24 | uint8_t playNote(uint8_t channel, uint8_t volume, uint16_t frequency, uint16_t duration); 25 | uint8_t getChannelStatus(uint8_t channel); 26 | uint8_t setVolume(uint8_t channel, uint8_t volume); 27 | uint8_t setFrequency(uint8_t channel, uint16_t frequency); 28 | uint8_t setWaveform(uint8_t channel, int8_t waveformType, uint16_t sampleId); 29 | uint8_t seekTo(uint8_t channel, uint32_t position); 30 | uint8_t setDuration(uint8_t channel, uint16_t duration); 31 | uint8_t setSampleRate(uint8_t channel, uint16_t sampleRate); 32 | uint8_t enableChannel(uint8_t channel); 33 | uint8_t disableChannel(uint8_t channel); 34 | uint8_t clearSample(uint16_t sampleId); 35 | void resetSamples(); 36 | 37 | #endif // AGON_AUDIO_H 38 | -------------------------------------------------------------------------------- /include/audio_channel.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_CHANNEL_H 2 | #define AUDIO_CHANNEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "types.h" 10 | #include "envelopes/types.h" 11 | 12 | extern fabgl::SoundGenerator *soundGenerator; // audio handling sub-system 13 | extern void audioTaskAbortDelay(uint8_t channel); 14 | 15 | #define AUDIO_CHANNELS 6 // Default number of audio channels 16 | #define AUDIO_DEFAULT_SAMPLE_RATE 16384 // Default sample rate 17 | #define MAX_AUDIO_CHANNELS 32 // Maximum number of audio channels 18 | #define PLAY_SOUND_PRIORITY 3 // Sound driver task priority with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest 19 | 20 | // Audio command definitions 21 | // 22 | #define AUDIO_CMD_PLAY 0 // Play a sound 23 | #define AUDIO_CMD_STATUS 1 // Get the status of a channel 24 | #define AUDIO_CMD_VOLUME 2 // Set the volume of a channel 25 | #define AUDIO_CMD_FREQUENCY 3 // Set the frequency of a channel 26 | #define AUDIO_CMD_WAVEFORM 4 // Set the waveform type for a channel 27 | #define AUDIO_CMD_SAMPLE 5 // Sample management 28 | #define AUDIO_CMD_ENV_VOLUME 6 // Define/set a volume envelope 29 | #define AUDIO_CMD_ENV_FREQUENCY 7 // Define/set a frequency envelope 30 | #define AUDIO_CMD_ENABLE 8 // Enables a channel 31 | #define AUDIO_CMD_DISABLE 9 // Disables (destroys) a channel 32 | #define AUDIO_CMD_RESET 10 // Reset audio channel 33 | #define AUDIO_CMD_SEEK 11 // Seek to a position in a sample 34 | #define AUDIO_CMD_DURATION 12 // Set the duration of a channel 35 | #define AUDIO_CMD_SAMPLERATE 13 // Set the samplerate for channel or underlying audio system 36 | #define AUDIO_CMD_SET_PARAM 14 // Set a waveform parameter 37 | 38 | #define AUDIO_SAMPLE_LOAD 0 // Send a sample to the VDP 39 | #define AUDIO_SAMPLE_CLEAR 1 // Clear/delete a sample 40 | #define AUDIO_SAMPLE_FROM_BUFFER 2 // Load a sample from a buffer 41 | #define AUDIO_SAMPLE_SET_FREQUENCY 3 // Set the base frequency of a sample 42 | #define AUDIO_SAMPLE_BUFFER_SET_FREQUENCY 4 // Set the base frequency of a sample (using buffer ID) 43 | #define AUDIO_SAMPLE_SET_REPEAT_START 5 // Set the repeat start point of a sample 44 | #define AUDIO_SAMPLE_BUFFER_SET_REPEAT_START 6 // Set the repeat start point of a sample (using buffer ID) 45 | #define AUDIO_SAMPLE_SET_REPEAT_LENGTH 7 // Set the repeat length of a sample 46 | #define AUDIO_SAMPLE_BUFFER_SET_REPEAT_LENGTH 8 // Set the repeat length of a sample (using buffer ID) 47 | #define AUDIO_SAMPLE_DEBUG_INFO 0x10 // Get debug info about a sample 48 | 49 | #define AUDIO_DEFAULT_FREQUENCY 523 // Default sample frequency (C5, or C above middle C) 50 | 51 | #define AUDIO_FORMAT_8BIT_SIGNED 0 // 8-bit signed sample 52 | #define AUDIO_FORMAT_8BIT_UNSIGNED 1 // 8-bit unsigned sample 53 | #define AUDIO_FORMAT_DATA_MASK 7 // data bit mask for format 54 | #define AUDIO_FORMAT_WITH_RATE 8 // OR this with the format to indicate a sample rate follows 55 | #define AUDIO_FORMAT_TUNEABLE 16 // OR this with the format to indicate sample can be tuned (frequency adjustable) 56 | 57 | #define AUDIO_ENVELOPE_NONE 0 // No envelope 58 | #define AUDIO_ENVELOPE_ADSR 1 // Simple ADSR volume envelope 59 | #define AUDIO_ENVELOPE_MULTIPHASE_ADSR 2 // Multi-phase ADSR envelope 60 | 61 | #define AUDIO_FREQUENCY_ENVELOPE_STEPPED 1 // Stepped frequency envelope 62 | 63 | #define AUDIO_FREQUENCY_REPEATS 0x01 // Repeat/loop the frequency envelope 64 | #define AUDIO_FREQUENCY_CUMULATIVE 0x02 // Reset frequency envelope when looping 65 | #define AUDIO_FREQUENCY_RESTRICT 0x04 // Restrict frequency envelope to the range 0-65535 66 | 67 | #define AUDIO_PARAM_DUTY_CYCLE 0 // Square wave duty cycle 68 | #define AUDIO_PARAM_VOLUME 2 // Volume 69 | #define AUDIO_PARAM_FREQUENCY 3 // Frequency 70 | #define AUDIO_PARAM_16BIT 0x80 // 16-bit value 71 | #define AUDIO_PARAM_MASK 0x0F // Parameter mask 72 | 73 | #define AUDIO_STATUS_ACTIVE 0x01 // Has an active waveform 74 | #define AUDIO_STATUS_PLAYING 0x02 // Playing a note (not in release phase) 75 | #define AUDIO_STATUS_INDEFINITE 0x04 // Indefinite duration sound playing 76 | #define AUDIO_STATUS_HAS_VOLUME_ENVELOPE 0x08 // Channel has a volume envelope set 77 | #define AUDIO_STATUS_HAS_FREQUENCY_ENVELOPE 0x10 // Channel has a frequency envelope set 78 | 79 | #define BUFFERED_SAMPLE_BASEID 0xFB00 // Base ID for buffered samples 80 | 81 | #define AUDIO_WAVE_DEFAULT 0 // Default waveform (Square wave) 82 | #define AUDIO_WAVE_SQUARE 0 // Square wave 83 | #define AUDIO_WAVE_TRIANGLE 1 // Triangle wave 84 | #define AUDIO_WAVE_SAWTOOTH 2 // Sawtooth wave 85 | #define AUDIO_WAVE_SINE 3 // Sine wave 86 | #define AUDIO_WAVE_NOISE 4 // Noise (simple, no frequency support) 87 | #define AUDIO_WAVE_VICNOISE 5 // VIC-style noise (supports frequency) 88 | #define AUDIO_WAVE_SAMPLE 8 // Sample playback, explicit buffer ID sent in following 2 bytes 89 | #define AUDIO_WAVE_AY_3_8910_NOISE 6 90 | // negative values for waveforms indicate a sample number 91 | 92 | enum AudioState : uint8_t { // Audio channel state 93 | Idle = 0, // currently idle/silent 94 | Pending, // note will be played next loop call 95 | Playing, // playing (passive) 96 | PlayLoop, // active playing loop (used when an envelope is active) 97 | Release, // in "release" phase 98 | Abort // aborting a note 99 | }; 100 | 101 | // The audio channel class 102 | // 103 | class AudioChannel { 104 | public: 105 | AudioChannel(uint8_t channel); 106 | ~AudioChannel(); 107 | uint8_t playNote(uint8_t volume, uint16_t frequency, int32_t duration); 108 | uint8_t getStatus(); 109 | uint8_t setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId = 0); 110 | uint8_t setVolume(uint8_t volume); 111 | uint8_t setFrequency(uint16_t frequency); 112 | uint8_t setDuration(int32_t duration); 113 | uint8_t setVolumeEnvelope(std::unique_ptr envelope); 114 | uint8_t setFrequencyEnvelope(std::unique_ptr envelope); 115 | uint8_t setSampleRate(uint16_t sampleRate); 116 | uint8_t setDutyCycle(uint8_t dutyCycle); 117 | uint8_t setParameter(uint8_t parameter, uint16_t value); 118 | WaveformGenerator * getWaveform() { return this->_waveform.get(); } 119 | void attachSoundGenerator(); 120 | void detachSoundGenerator(); 121 | uint8_t seekTo(uint32_t position); 122 | void loop(); 123 | uint8_t channel() { return _channel; } 124 | private: 125 | std::shared_ptr getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef); 126 | void waitForAbort(); 127 | uint8_t getVolume(uint32_t elapsed); 128 | uint16_t getFrequency(uint32_t elapsed); 129 | bool isReleasing(uint32_t elapsed); 130 | bool isFinished(uint32_t elapsed); 131 | uint8_t _channel; 132 | uint8_t _volume; 133 | uint16_t _frequency; 134 | int32_t _duration; 135 | uint32_t _startTime; 136 | uint8_t _waveformType; 137 | std::atomic _state; 138 | std::shared_ptr _waveform = nullptr; 139 | std::unique_ptr _volumeEnvelope; 140 | std::unique_ptr _frequencyEnvelope; 141 | }; 142 | 143 | #endif // AUDIO_CHANNEL_H 144 | -------------------------------------------------------------------------------- /include/audio_sample.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_SAMPLE_H 2 | #define AUDIO_SAMPLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "types.h" 9 | #include "audio_channel.h" 10 | #include "buffer_stream.h" 11 | 12 | struct AudioSample { 13 | AudioSample(std::vector> streams, uint8_t format, uint32_t sampleRate = AUDIO_DEFAULT_SAMPLE_RATE, uint16_t frequency = 0) : 14 | blocks(streams), format(format), sampleRate(sampleRate), baseFrequency(frequency) {} 15 | ~AudioSample(); 16 | 17 | int8_t getSample(uint32_t & index, uint32_t & blockIndex); 18 | void seekTo(uint32_t position, uint32_t & index, uint32_t & blockIndex, int32_t & repeatCount); 19 | uint32_t getSize(); 20 | 21 | std::vector> blocks; 22 | uint8_t format; // Format of the sample data 23 | uint32_t sampleRate; // Sample rate of the sample 24 | uint16_t baseFrequency = 0; // Base frequency of the sample 25 | int32_t repeatStart = 0; // Start offset for repeat, in samples 26 | int32_t repeatLength = -1; // Length of the repeat section in samples, -1 means to end of sample 27 | // std::unordered_map> channels; // Channels playing this sample 28 | }; 29 | 30 | #endif // AUDIO_SAMPLE_H -------------------------------------------------------------------------------- /include/ay_3_8910.h: -------------------------------------------------------------------------------- 1 | #ifndef __AY_3_8910_H_ 2 | #define __AY_3_8910_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define MASTER_FREQUENCY 1789772 8 | #define MASTER_FREQUENCY_DIV (MASTER_FREQUENCY/16) 9 | #define FABGL_MAX_AMPLITUDE 127 10 | #define FABGL_AMPLITUDE_MULTIPLIER (128/16) 11 | 12 | class AY_3_8910_Volume 13 | { 14 | private: 15 | uint8_t volumeTab [16]; 16 | public: 17 | 18 | AY_3_8910_Volume () 19 | { 20 | // initialize volumeTab 21 | // 22 | // Calculate the volume->voltage conversion table. The AY-3-8910 has 16 levels, 23 | // in a logarithmic scale (3dB per step). YM2149 has 32 levels, the 16 extra 24 | // levels are only used for envelope volumes 25 | // 1/sqrt(sqrt(2)) ~= 1/(1.5dB) 26 | // initialize volumeTab 27 | // 28 | // Calculate the volume->voltage conversion table. The AY-3-8910 has 16 levels, 29 | // in a logarithmic scale (3dB per step). YM2149 has 32 levels, the 16 extra 30 | // levels are only used for envelope volumes 31 | // 1/sqrt(sqrt(2)) ~= 1/(1.5dB) 32 | for (int i=0;i<16;i++) 33 | volumeTab[i] = 127.0 / pow(sqrt(2.0),15.0-i); 34 | } 35 | uint8_t getAgonVolume (uint8_t ay_3_8190_volume) 36 | { 37 | return volumeTab[ay_3_8190_volume]; 38 | } 39 | }; 40 | 41 | class AY_3_8910 42 | { 43 | public: 44 | AY_3_8910 (); 45 | 46 | void init (); 47 | void write (uint8_t port, uint8_t value); 48 | uint8_t read (uint8_t port); 49 | 50 | private: 51 | uint8_t register_select; 52 | uint16_t toneA; 53 | uint16_t toneB; 54 | uint16_t toneC; 55 | uint8_t noise; 56 | uint8_t mixer; 57 | uint16_t env_period; 58 | uint8_t env_shape; 59 | uint8_t amplA; 60 | uint8_t amplB; 61 | uint8_t amplC; 62 | uint8_t gpio; 63 | uint8_t gpio_control; 64 | 65 | void updateSound (uint8_t channel, uint8_t mixer, uint8_t amp, uint32_t tone_freq, uint32_t noise_freq); 66 | void updateChannel (uint8_t channel, uint32_t tone_freq,bool volume_envelope,uint8_t volume); 67 | }; 68 | 69 | #endif // __AY_3_8910_H_ -------------------------------------------------------------------------------- /include/ay_3_8910_noise.h: -------------------------------------------------------------------------------- 1 | #ifndef __AY_3_8910_H 2 | #define __AY_3_8910_H 3 | 4 | #include "fabgl.h" 5 | 6 | class AY_3_8910_NoiseGenerator : public fabgl::WaveformGenerator { 7 | public: 8 | AY_3_8910_NoiseGenerator(); 9 | 10 | void setFrequency(int value); 11 | uint16_t frequency(); 12 | 13 | int getSample(); 14 | 15 | private: 16 | uint16_t m_frequency; 17 | uint32_t m_noise; 18 | uint16_t m_counter; 19 | int m_prevsample; 20 | }; 21 | 22 | #endif // __AY_3_8910_H -------------------------------------------------------------------------------- /include/buffer_stream.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_STREAM_H 2 | #define BUFFER_STREAM_H 3 | 4 | #include 5 | #include 6 | 7 | #include "types.h" 8 | 9 | class BufferStream : public Stream { 10 | public: 11 | BufferStream(uint32_t bufferLength); 12 | int available(); 13 | int read(); 14 | int peek(); 15 | size_t write(uint8_t b); 16 | virtual bool isWritable() { 17 | return false; 18 | } 19 | 20 | void rewind() { 21 | seekTo(0); 22 | } 23 | 24 | void seekTo(uint32_t position) { 25 | bufferPosition = position; 26 | } 27 | 28 | inline uint8_t * getBuffer() { 29 | return buffer.get(); 30 | } 31 | inline uint32_t size() { 32 | return bufferLength; 33 | } 34 | bool writeBuffer(uint8_t * data, uint32_t length, uint32_t offset); 35 | void writeBufferByte(uint8_t data, uint32_t offset); 36 | bool incrementBufferByte(uint32_t offset, int8_t by); 37 | protected: 38 | std::unique_ptr buffer; 39 | uint32_t bufferLength; 40 | uint32_t bufferPosition; 41 | }; 42 | 43 | class WritableBufferStream : public BufferStream { 44 | public: 45 | WritableBufferStream(uint32_t bufferLength) : BufferStream(bufferLength), bufferWritePosition(0) {}; 46 | size_t write(uint8_t b); 47 | bool isWritable() override { 48 | return true; 49 | }; 50 | 51 | void rewindWrite() { 52 | bufferWritePosition = 0; 53 | } 54 | 55 | private: 56 | uint32_t bufferWritePosition; 57 | 58 | }; 59 | 60 | 61 | #endif // BUFFER_STREAM_H 62 | -------------------------------------------------------------------------------- /include/enhanced_samples_generator.h: -------------------------------------------------------------------------------- 1 | #ifndef ENHANCED_SAMPLES_GENERATOR_H 2 | #define ENHANCED_SAMPLES_GENERATOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "audio_sample.h" 10 | #include "types.h" 11 | 12 | // Enhanced samples generator 13 | // 14 | class EnhancedSamplesGenerator : public WaveformGenerator { 15 | public: 16 | EnhancedSamplesGenerator(std::shared_ptr sample); 17 | 18 | void setFrequency(int value); 19 | void setSampleRate(int value); 20 | int getSample(); 21 | 22 | int getDuration(uint16_t frequency); 23 | 24 | void seekTo(uint32_t position); 25 | private: 26 | std::shared_ptr _sample; 27 | 28 | uint32_t index; // Current index inside the current sample block 29 | uint32_t blockIndex; // Current index into the sample data blocks 30 | int32_t repeatCount; // Sample count when repeating 31 | // TODO consider whether repeatStart and repeatLength may need to be here 32 | // which would allow for per-channel repeat settings 33 | 34 | int frequency; 35 | int previousSample; 36 | int currentSample; 37 | double samplesPerGet; 38 | double fractionalSampleOffset; 39 | 40 | double calculateSamplerate(uint16_t frequency); 41 | int8_t getNextSample(); 42 | }; 43 | 44 | 45 | #endif // ENHANCED_SAMPLES_GENERATOR_H 46 | -------------------------------------------------------------------------------- /include/envelopes/adsr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Audio ADSR Volume Envelope support 3 | // Author: Steve Sims 4 | // Created: 06/08/2023 5 | // Last Updated: 14/01/2024 6 | 7 | #ifndef ENVELOPE_ADSR_H 8 | #define ENVELOPE_ADSR_H 9 | 10 | #include "./types.h" 11 | 12 | class ADSRVolumeEnvelope : public VolumeEnvelope { 13 | public: 14 | ADSRVolumeEnvelope(uint16_t attack, uint16_t decay, uint8_t sustain, uint16_t release); 15 | uint8_t getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration); 16 | bool isReleasing(uint32_t elapsed, int32_t duration); 17 | bool isFinished(uint32_t elapsed, int32_t duration); 18 | uint32_t getRelease() { 19 | return this->_release; 20 | } 21 | private: 22 | uint16_t _attack; 23 | uint16_t _decay; 24 | uint8_t _sustain; 25 | uint16_t _release; 26 | }; 27 | 28 | 29 | #endif // ENVELOPE_ADSR_H 30 | -------------------------------------------------------------------------------- /include/envelopes/ay_3_8910_envelope.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: AY_3_8910_ENVELOPE_H Envelope support 3 | // Author: S0urceror 4 | // Created: 06/08/2023 5 | // Last Updated: 14/01/2024 6 | 7 | #ifndef ENVELOPE_AY_3_8910_ENVELOPE_H 8 | #define ENVELOPE_AY_3_8910_ENVELOPE_H 9 | 10 | #include "./types.h" 11 | 12 | class AY_3_8910_VolumeEnvelope : public VolumeEnvelope { 13 | public: 14 | AY_3_8910_VolumeEnvelope(uint8_t envelope,uint16_t period); 15 | uint8_t getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration); 16 | bool isReleasing(uint32_t elapsed, int32_t duration); 17 | bool isFinished(uint32_t elapsed, int32_t duration); 18 | uint32_t getRelease() { 19 | return 0; 20 | } 21 | private: 22 | uint32_t _period; // in msecs 23 | uint8_t _hold_volume; 24 | 25 | bool _continue; 26 | bool _attack; 27 | bool _alternate; 28 | bool _hold; 29 | }; 30 | 31 | 32 | #endif // ENVELOPE_AY_3_8910_ENVELOPE_H 33 | -------------------------------------------------------------------------------- /include/envelopes/frequency.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Audio Frequency Envelope support 3 | // Author: Steve Sims 4 | // Created: 13/08/2023 5 | // Last Updated: 14/01/2024 6 | 7 | #ifndef ENVELOPE_FREQUENCY_H 8 | #define ENVELOPE_FREQUENCY_H 9 | 10 | #include 11 | #include 12 | 13 | #include "./types.h" 14 | 15 | struct FrequencyStepPhase { 16 | int16_t adjustment; // change of frequency per step 17 | uint16_t number; // number of steps 18 | }; 19 | 20 | class SteppedFrequencyEnvelope : public FrequencyEnvelope { 21 | public: 22 | SteppedFrequencyEnvelope(std::shared_ptr> phases, uint16_t stepLength, bool repeats, bool cumulative, bool restrict); 23 | uint16_t getFrequency(uint16_t baseFrequency, uint32_t elapsed, int32_t duration); 24 | bool isFinished(uint32_t elapsed, int32_t duration); 25 | private: 26 | std::shared_ptr> _phases; 27 | uint16_t _stepLength; 28 | uint32_t _totalSteps; 29 | uint32_t _totalAdjustment; 30 | uint32_t _totalLength; 31 | bool _repeats; 32 | bool _cumulative; 33 | bool _restrict; 34 | }; 35 | 36 | #endif // ENVELOPE_FREQUENCY_H 37 | -------------------------------------------------------------------------------- /include/envelopes/multiphase_adsr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Multi-phase ADSR Envelope support 3 | // Author: Steve Sims 4 | // Created: 14/01/2024 5 | // Last Updated: 14/01/2024 6 | 7 | #ifndef ENVELOPE_MULTIPHASE_ADSR_H 8 | #define ENVELOPE_MULTIPHASE_ADSR_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "./types.h" 15 | 16 | struct VolumeSubPhase { 17 | uint8_t level; // relative volume level for sub-phase 18 | uint16_t duration; // number of steps 19 | }; 20 | 21 | class MultiphaseADSREnvelope : public VolumeEnvelope { 22 | public: 23 | MultiphaseADSREnvelope(std::shared_ptr> attack, std::shared_ptr> sustain, std::shared_ptr> release); 24 | uint8_t getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration); 25 | bool isReleasing(uint32_t elapsed, int32_t duration); 26 | bool isFinished(uint32_t elapsed, int32_t duration); 27 | uint32_t getRelease() { 28 | return _releaseDuration; 29 | }; 30 | private: 31 | uint8_t getTargetVolume(uint8_t baseVolume, uint8_t level); 32 | std::shared_ptr> _attack; 33 | std::shared_ptr> _sustain; 34 | std::shared_ptr> _release; 35 | uint32_t _attackDuration; 36 | uint32_t _sustainDuration; 37 | uint32_t _releaseDuration; 38 | uint8_t _sustainSubphases; 39 | uint8_t _attackLevel; // final levels 40 | uint8_t _sustainLevel; 41 | uint8_t _releaseLevel; 42 | bool _sustainLoops; 43 | }; 44 | 45 | 46 | #endif // ENVELOPE_MULTIPHASE_ADSR_H 47 | -------------------------------------------------------------------------------- /include/envelopes/types.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Audio Envelope support 3 | // Author: Steve Sims 4 | // Created: 14/01/2024 5 | // Last Updated: 14/01/2024 6 | 7 | #ifndef ENVELOPE_TYPES_H 8 | #define ENVELOPE_TYPES_H 9 | 10 | #include 11 | #include "Arduino.h" 12 | 13 | class VolumeEnvelope { 14 | public: 15 | virtual uint8_t getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration) = 0; 16 | virtual bool isReleasing(uint32_t elapsed, int32_t duration) = 0; 17 | virtual bool isFinished(uint32_t elapsed, int32_t duration) = 0; 18 | virtual uint32_t getRelease() = 0; 19 | }; 20 | 21 | class FrequencyEnvelope { 22 | public: 23 | virtual uint16_t getFrequency(uint16_t baseFrequency, uint32_t elapsed, int32_t duration) = 0; 24 | virtual bool isFinished(uint32_t elapsed, int32_t duration) = 0; 25 | }; 26 | 27 | #endif // ENVELOPE_TYPES_H 28 | -------------------------------------------------------------------------------- /include/eos.h: -------------------------------------------------------------------------------- 1 | #ifndef __EOS_H_ 2 | #define __EOS_H_ 3 | 4 | #define ESC 0x1b 5 | #define CTRL_W 0x17 // 23 decimal, MOS escape code, ElectronOS 8 bits ASCII value 6 | #define CTRL_Z 0x1a 7 | 8 | bool eos_wakeup (); 9 | 10 | #endif // __EOS_H_ -------------------------------------------------------------------------------- /include/globals.h: -------------------------------------------------------------------------------- 1 | #ifndef __GLOBALS_H_ 2 | #define __GLOBALS_H_ 3 | 4 | #define HAL_major 0 5 | #define HAL_minor 9 6 | #define HAL_revision 0 7 | 8 | void set_display_direct (); 9 | void set_display_normal (bool force=false); 10 | extern fabgl::VGABaseController* display; 11 | extern TaskHandle_t mainTaskHandle; 12 | 13 | #endif // __GLOBALS_H_ -------------------------------------------------------------------------------- /include/hal.h: -------------------------------------------------------------------------------- 1 | #ifndef __HAL_H_ 2 | #define __HAL_H_ 3 | 4 | #include "Arduino.h" 5 | #include "HardwareSerial.h" 6 | #include "fabgl.h" 7 | 8 | #define ez80_serial Serial2 9 | 10 | #define UART_BR 1152000 11 | #define UART_NA -1 12 | #define UART_TX 2 13 | #define UART_RX 34 14 | #define UART_RTS 13 // The ESP32 RTS pin 15 | #define UART_CTS 14 // The ESP32 CTS pin 16 | #define UART_RX_THRESH 64 // Point at which RTS is toggled 17 | 18 | #define GPIO_ITRP 17 // VSync Interrupt Pin - for reference only 19 | 20 | #define ESC 0x1b 21 | #define CTRL_X 0x18 22 | #define CTRL_Y 0x19 23 | #define CTRL_Z 0x1a 24 | 25 | extern HardwareSerial hal_serial; 26 | 27 | void hal_printf (const char* format, ...); 28 | void hal_hostpc_printf (const char* format, ...); 29 | void hal_terminal_printf (const char* format, ...); 30 | void hal_hostpc_serial_init (); 31 | void hal_set_terminal (fabgl::Terminal*); 32 | char hal_hostpc_serial_read (); 33 | void hal_ez80_serial_init (); 34 | void hal_ez80_serial_half_duplex (); 35 | void hal_ez80_serial_full_duplex (); 36 | 37 | #endif -------------------------------------------------------------------------------- /include/mos.h: -------------------------------------------------------------------------------- 1 | #ifndef __MOS_H_ 2 | #define __MOS_H_ 3 | 4 | #ifdef MOS_COMPATIBILITY 5 | 6 | #include "hal.h" 7 | 8 | const byte PACKET_KEYCODE = 0x01; // Keyboard data 9 | const byte PACKET_SCRCHAR = 0x03; 10 | const byte PACKET_MODE = 0x06; 11 | const byte PACKET_CURSOR = 0x02; 12 | const byte PACKET_GP = 0x00; // general poll data 13 | 14 | // MOS compatibility layer 15 | void mos_init (); 16 | void mos_send_packet(byte code, byte len, byte data[]); 17 | void mos_send_general_poll (); 18 | void mos_send_vdp_mode (); 19 | void mos_send_cursor_pos (); 20 | void mos_col_left (); 21 | void mos_col_right (); 22 | void mos_set_column (uint8_t col); 23 | void mos_send_character (byte ch); 24 | void mos_send_virtual_key (fabgl::VirtualKeyItem item); 25 | void mos_handle_escape_code (); 26 | 27 | #endif 28 | 29 | #endif -------------------------------------------------------------------------------- /include/ppi-8255.h: -------------------------------------------------------------------------------- 1 | #ifndef __PPI_8255_H 2 | #define __PPI_8255_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class PPI8255 9 | { 10 | protected: 11 | uint8_t portA,portB,portC,control; 12 | uint8_t current_row; 13 | 14 | public: 15 | PPI8255 (); 16 | virtual void write (uint8_t port, uint8_t value); 17 | virtual uint8_t read (uint8_t port)=0; 18 | virtual uint8_t record_keypress (uint8_t ascii,uint8_t modifier,uint8_t vk,uint8_t down)=0; 19 | virtual uint8_t get_row_bits (uint8_t row)=0; 20 | virtual uint8_t get_next_row ()=0; 21 | }; 22 | 23 | class SG1000_PPI8255 : public PPI8255 24 | { 25 | private: 26 | uint16_t rows[8]; 27 | public: 28 | SG1000_PPI8255 (); 29 | uint8_t read (uint8_t port); 30 | uint8_t record_keypress (uint8_t ascii,uint8_t modifier,uint8_t vk,uint8_t down); 31 | uint8_t get_row_bits (uint8_t row); 32 | uint8_t get_next_row (); 33 | }; 34 | 35 | typedef struct 36 | { 37 | uint8_t vk; 38 | uint8_t modifier; 39 | } ascii_to_vk_modifier; 40 | 41 | typedef struct 42 | { 43 | uint8_t msx_matrix_row; 44 | uint8_t msx_matrix_mask; 45 | } vk_to_msx_matrix; 46 | 47 | class MSX_PPI8255 : public PPI8255 48 | { 49 | private: 50 | uint8_t rows[11]; 51 | std::vector vk_key_up; 52 | public: 53 | MSX_PPI8255 (); 54 | uint8_t read (uint8_t port); 55 | uint8_t record_keypress (uint8_t ascii,uint8_t modifier,uint8_t vk,uint8_t down); 56 | uint8_t get_row_bits (uint8_t row); 57 | uint8_t get_next_row (); 58 | }; 59 | 60 | #endif // __PPI_8255_H -------------------------------------------------------------------------------- /include/sn76489an.h: -------------------------------------------------------------------------------- 1 | #ifndef __SN76489AN_H_ 2 | #define __SN76489AN_H_ 3 | 4 | #include 5 | 6 | #define SG1000_MASTER_FREQUENCY 3579545 7 | #define SG1000_MASTER_FREQUENCY_DIV (SG1000_MASTER_FREQUENCY/32) 8 | #define FABGL_AMPLITUDE_MULTIPLIER (128/16) 9 | 10 | 11 | class SN76489AN 12 | { 13 | public: 14 | SN76489AN (); 15 | 16 | void init (); 17 | void write (uint8_t value); 18 | 19 | private: 20 | uint8_t register_select; 21 | uint16_t toneA; 22 | uint16_t toneB; 23 | uint16_t toneC; 24 | uint8_t noise; 25 | uint8_t amplA; 26 | uint8_t amplB; 27 | uint8_t amplC; 28 | uint8_t amplNoise; 29 | uint8_t fb; 30 | uint8_t nf; 31 | bool first_byte; 32 | 33 | void updateSound (uint8_t channel, uint8_t amp, uint16_t freq); 34 | }; 35 | 36 | #endif // __SN76489AN_H_ -------------------------------------------------------------------------------- /include/tms9918.h: -------------------------------------------------------------------------------- 1 | #ifndef __TMS9918_H_ 2 | #define __TMS9918_H_ 3 | 4 | #include 5 | 6 | class TMS9918 7 | { 8 | public: 9 | TMS9918 (); 10 | 11 | void write (uint8_t port, uint8_t value); 12 | uint8_t read (uint8_t port); 13 | void draw_screen (uint8_t* dest,int scanline); 14 | void set_display (fabgl::VGADirectController* dsply); 15 | void toggle_enable (); 16 | void cycle_screen2_debug (); 17 | 18 | private: 19 | int screen2_debug_view; 20 | bool display_enabled; 21 | uint8_t registers [8]; 22 | uint8_t memory [16*1024]; 23 | uint8_t data_register; 24 | uint8_t status_register; 25 | bool port99_write; 26 | uint16_t write_address; 27 | uint16_t read_address; 28 | uint8_t mode; 29 | bool mem16kb; 30 | bool screen_enable; 31 | bool interrupt_enable; 32 | bool sprite_size_16; 33 | bool sprite_magnify; 34 | uint16_t nametable; 35 | uint16_t colortable; 36 | uint16_t patterntable; 37 | uint16_t sprite_attribute_table; 38 | uint16_t sprite_pattern_table; 39 | uint8_t textmode_colors; 40 | fabgl::VGADirectController* display; 41 | const uint16_t screen_width=320; 42 | const uint16_t screen_height=240; 43 | const uint16_t vdp_width=256; 44 | const uint16_t vdp_height=192; 45 | const uint8_t screen_border_vert=(screen_height - vdp_height)/2; 46 | const uint8_t screen_border_horz=(screen_width - vdp_width)/2; 47 | uint16_t memory_bytes_written; 48 | uint16_t memory_bytes_read; 49 | uint16_t write_address_start; 50 | uint16_t read_address_start; 51 | uint8_t frame_counter; 52 | 53 | const fabgl::RGB222 colors [16] = { 54 | RGB222(0x00, 0x00, 0x00), //0 - transparent 55 | RGB222(0x00, 0x00, 0x00), //1 - black 56 | RGB222(0x02, 0x03, 0x02), //2 - medium green 57 | RGB222(0x01, 0x03, 0x01), //3 - light green 58 | RGB222(0x00, 0x00, 0x02), //4 - dark blue 59 | RGB222(0x01, 0x01, 0x03), //5 - light blue 60 | RGB222(0x02, 0x00, 0x00), //6 - dark red 61 | RGB222(0x01, 0x02, 0x03), //7 - cyan 62 | RGB222(0x03, 0x01, 0x01), //8 - medium red 63 | RGB222(0x03, 0x02, 0x02), //9 - light red 64 | RGB222(0x02, 0x02, 0x00), //A - dark yellow 65 | RGB222(0x03, 0x03, 0x01), //B - light yellow 66 | RGB222(0x00, 0x02, 0x00), //C - dark green 67 | RGB222(0x02, 0x01, 0x02), //D - magenta 68 | RGB222(0x02, 0x02, 0x02), //E - gray 69 | RGB222(0x03, 0x03, 0x03) //F - white 70 | }; 71 | void init_mode (); 72 | void write_register (uint8_t reg, uint8_t value); 73 | void draw_screen0 (uint8_t* dest,int scanline); 74 | void draw_screen1 (uint8_t* dest,int scanline); 75 | void draw_screen2 (uint8_t* dest,int scanline); 76 | void draw_screen2_patterns (uint8_t* dest,int scanline); 77 | void draw_screen2_colours (uint8_t* dest,int scanline); 78 | void draw_screen2_nametable_indexes (uint8_t* dest,int scanline); 79 | void draw_screen3 (uint8_t* dest,int scanline); 80 | }; 81 | 82 | #endif // __TMS9918_H_ -------------------------------------------------------------------------------- /include/types.h: -------------------------------------------------------------------------------- 1 | //+-------------------------------------------------------------------------- 2 | // 3 | // File: types.h 4 | // 5 | // Original Source info: 6 | // NightDriverStrip - (c) 2023 Plummer's Software LLC. All Rights Reserved. 7 | // 8 | // This file is part of the NightDriver software project. 9 | // 10 | // NightDriver is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // NightDriver is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with Nightdriver. It is normally found in copying.txt 22 | // If not, see . 23 | // 24 | // Description: 25 | // 26 | // Types of a somewhat general use 27 | // 28 | // History: May-23-2023 Rbergen Created 29 | // 30 | //--------------------------------------------------------------------------- 31 | 32 | 33 | #pragma once 34 | 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | // PreferPSRAMAlloc 42 | // 43 | // Will return PSRAM if it's available, regular ram otherwise 44 | 45 | inline void * PreferPSRAMAlloc(size_t s) 46 | { 47 | if (psramInit()) 48 | { 49 | //debug_log("PSRAM Array Request for %u bytes\n", s); 50 | return ps_malloc(s); 51 | } 52 | else 53 | { 54 | return malloc(s); 55 | } 56 | } 57 | 58 | // psram_allocator 59 | // 60 | // A C++ allocator that allocates from PSRAM instead of the regular heap. Initially 61 | // I had just overloaded new for the classes I wanted in PSRAM, but that doesn't work 62 | // with make_shared<> so I had to provide this allocator instead. 63 | // 64 | // When enabled, this puts all of the LEDBuffers in PSRAM. The table that keeps track 65 | // of them is still in base ram. 66 | // 67 | // (Davepl - I opted to make this *prefer* psram but return regular ram otherwise. It 68 | // avoids a lot of ifdef USE_PSRAM in the code. But I've only proved it 69 | // correct, not tried it on a chip without yet. 70 | 71 | template 72 | class psram_allocator 73 | { 74 | public: 75 | typedef size_t size_type; 76 | typedef ptrdiff_t difference_type; 77 | typedef T* pointer; 78 | typedef const T* const_pointer; 79 | typedef T& reference; 80 | typedef const T& const_reference; 81 | typedef T value_type; 82 | 83 | psram_allocator(){} 84 | ~psram_allocator(){} 85 | 86 | template struct rebind { typedef psram_allocator other; }; 87 | template psram_allocator(const psram_allocator&){} 88 | 89 | pointer address(reference x) const {return &x;} 90 | const_pointer address(const_reference x) const {return &x;} 91 | size_type max_size() const throw() {return size_t(-1) / sizeof(value_type);} 92 | 93 | pointer allocate(size_type n, const void * hint = 0) 94 | { 95 | void * pmem = PreferPSRAMAlloc(n*sizeof(T)); 96 | return static_cast(pmem) ; 97 | } 98 | 99 | void deallocate(pointer p, size_type n) 100 | { 101 | free(p); 102 | } 103 | 104 | template< class U, class... Args > 105 | void construct( U* p, Args&&... args ) 106 | { 107 | ::new((void *) p ) U(std::forward(args)...); 108 | } 109 | 110 | void destroy(pointer p) 111 | { 112 | p->~T(); 113 | } 114 | }; 115 | 116 | // Typically we do not need a deleter because the regular one can handle PSRAM deallocations just fine, 117 | // but for completeness, here it is. 118 | 119 | template 120 | struct psram_deleter 121 | { 122 | void operator()(T* ptr) 123 | { 124 | psram_allocator allocator; 125 | allocator.destroy(ptr); 126 | allocator.deallocate(ptr, 1); 127 | } 128 | }; 129 | 130 | // make_unique_psram 131 | // 132 | // Like std::make_unique, but returns PSRAM instead of base RAM. We cheat a little here by not providing 133 | // a deleter, because we know that PSRAM can be freed with the regular free() call and does not require 134 | // special handling. 135 | 136 | template 137 | std::unique_ptr make_unique_psram(Args&&... args) 138 | { 139 | psram_allocator allocator; 140 | T* ptr = allocator.allocate(1); 141 | allocator.construct(ptr, std::forward(args)...); 142 | return std::unique_ptr(ptr); 143 | } 144 | 145 | template 146 | std::unique_ptr make_unique_psram_array(size_t size) 147 | { 148 | psram_allocator allocator; 149 | T* ptr = allocator.allocate(size); 150 | // No need to call construct since arrays don't have constructors 151 | return std::unique_ptr(ptr); 152 | } 153 | 154 | // make_shared_psram 155 | // 156 | // Same as std::make_shared except allocates preferentially from the PSRAM pool 157 | 158 | template 159 | std::shared_ptr make_shared_psram(Args&&... args) 160 | { 161 | psram_allocator allocator; 162 | return std::allocate_shared(allocator, std::forward(args)...); 163 | } 164 | 165 | template 166 | std::shared_ptr make_shared_psram_array(size_t size) 167 | { 168 | psram_allocator allocator; 169 | return std::allocate_shared(allocator, size); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /include/updater.h: -------------------------------------------------------------------------------- 1 | #ifndef __UPDATER_H 2 | #define __UPDATER_H 3 | 4 | #include 5 | 6 | static constexpr uint8_t unlockCode[] = "unlock"; 7 | static constexpr uint8_t unlock_response[] = "unlocked! "; 8 | 9 | class vdu_updater 10 | { 11 | void unlock (); 12 | void receiveFirmware(); 13 | void switchFirmware(); 14 | void test (); 15 | 16 | public: 17 | bool locked; 18 | 19 | vdu_updater(); 20 | void command (uint8_t mode); 21 | uint8_t get_unlock_response (uint8_t index); 22 | }; 23 | 24 | #endif //__UPDATER_H -------------------------------------------------------------------------------- /include/zdi.h: -------------------------------------------------------------------------------- 1 | #ifndef __ZDI_H_ 2 | #define __ZDI_H_ 3 | 4 | #define ZDI_TCK 26 5 | #define ZDI_TDI 27 6 | 7 | #define ZDI_READ 1 8 | #define ZDI_WRITE 0 9 | #define ZDI_CMD_CONTINUE 0 10 | #define ZDI_CMD_DONE 1 11 | 12 | // nr of microseconds to wait for next bit 13 | // documentation says 4Mhz ZCLK speed is possible, so 0.25 usecs 14 | // we stay a bit on the safe side with 10 usecs 15 | #define ZDI_WAIT_MICRO 1 16 | 17 | // ZDI write registers 18 | #define ZDI_ADDR0_L 0x00 19 | #define ZDI_ADDR0_H 0x01 20 | #define ZDI_ADDR0_U 0x02 21 | #define ZDI_ADDR1_L 0x04 22 | #define ZDI_ADDR1_H 0x05 23 | #define ZDI_ADDR1_U 0x06 24 | #define ZDI_ADDR2_L 0x08 25 | #define ZDI_ADDR2_H 0x09 26 | #define ZDI_ADDR2_U 0x0A 27 | #define ZDI_ADDR3_L 0x0c 28 | #define ZDI_ADDR3_H 0x0d 29 | #define ZDI_ADDR3_U 0x0e 30 | #define ZDI_BRK_CTL 0x10 31 | #define ZDI_MASTER_CTL 0x11 32 | #define ZDI_WR_DATA_L 0x13 33 | #define ZDI_WR_DATA_H 0x14 34 | #define ZDI_WR_DATA_U 0x15 35 | #define ZDI_RW_CTL 0x16 36 | #define ZDI_BUS_CTL 0x17 37 | #define ZDI_IS4 0x21 38 | #define ZDI_IS3 0x22 39 | #define ZDI_IS2 0x23 40 | #define ZDI_IS1 0x24 41 | #define ZDI_IS0 0x25 42 | #define ZDI_WR_MEM 0x30 43 | 44 | // ZDI read registers 45 | #define ZDI_ID_L 0x00 46 | #define ZDI_ID_H 0x01 47 | #define ZDI_ID_REV 0x02 48 | #define ZDI_STAT 0x03 49 | #define ZDI_RD_L 0x10 50 | #define ZDI_RD_H 0x11 51 | #define ZDI_RD_U 0x12 52 | #define ZDI_BUS_STAT 0x17 53 | #define ZDI_RD_MEM 0x20 54 | 55 | // EZ80 registers 56 | 57 | /* Programmable Reload Counter/Timers */ 58 | #define TMR0_CTL 0x80 59 | #define TMR0_DR_L 0x81 60 | #define TMR0_RR_L 0x81 61 | #define TMR0_DR_H 0x82 62 | #define TMR0_RR_H 0x82 63 | #define TMR1_CTL 0x83 64 | #define TMR1_DR_L 0x84 65 | #define TMR1_RR_L 0x84 66 | #define TMR1_DR_H 0x85 67 | #define TMR1_RR_H 0x85 68 | #define TMR2_CTL 0x86 69 | #define TMR2_DR_L 0x87 70 | #define TMR2_RR_L 0x87 71 | #define TMR2_DR_H 0x88 72 | #define TMR2_RR_H 0x88 73 | #define TMR3_CTL 0x89 74 | #define TMR3_DR_L 0x8A 75 | #define TMR3_RR_L 0x8A 76 | #define TMR3_DR_H 0x8B 77 | #define TMR3_RR_H 0x8B 78 | #define TMR4_CTL 0x8C 79 | #define TMR4_DR_L 0x8D 80 | #define TMR4_RR_L 0x8D 81 | #define TMR4_DR_H 0x8E 82 | #define TMR4_RR_H 0x8E 83 | #define TMR5_CTL 0x8F 84 | #define TMR5_DR_L 0x90 85 | #define TMR5_RR_L 0x90 86 | #define TMR5_DR_H 0x91 87 | #define TMR5_RR_H 0x91 88 | #define TMR_ISS 0x92 89 | 90 | /* Watch-Dog Timer */ 91 | #define WDT_CTL 0x93 92 | #define WDT_RR 0x94 93 | 94 | /* General-Purpose Input/Output Ports */ 95 | #define PB_DR 0x9A 96 | #define PB_DDR 0x9B 97 | #define PB_ALT1 0x9C 98 | #define PB_ALT2 0x9D 99 | #define PC_DR 0x9E 100 | #define PC_DDR 0x9F 101 | #define PC_ALT1 0xA0 102 | #define PC_ALT2 0xA1 103 | #define PD_DR 0xA2 104 | #define PD_DDR 0xA3 105 | #define PD_ALT1 0xA4 106 | #define PD_ALT2 0xA5 107 | 108 | /* Chip Select/Wait State Generator */ 109 | #define CS0_LBR 0xA8 110 | #define CS0_UBR 0xA9 111 | #define CS0_CTL 0xAA 112 | #define CS1_LBR 0xAB 113 | #define CS1_UBR 0xAC 114 | #define CS1_CTL 0xAD 115 | #define CS2_LBR 0xAE 116 | #define CS2_UBR 0xAF 117 | #define CS2_CTL 0xB0 118 | #define CS3_LBR 0xB1 119 | #define CS3_UBR 0xB2 120 | #define CS3_CTL 0xB3 121 | 122 | /* On-Chip Random Access Memory Control */ 123 | #define RAM_CTL 0xB4 124 | #define RAM_ADDR_U 0xB5 125 | 126 | /* Serial Peripheral Interface */ 127 | #define SPI_BRG_L 0xB8 128 | #define SPI_BRG_H 0xB9 129 | #define SPI_CTL 0xBA 130 | #define SPI_SR 0xBB 131 | #define SPI_TSR 0xBC 132 | #define SPI_RBR 0xBC 133 | 134 | /* Infrared Encoder/Decoder */ 135 | #define IR_CTL 0xBF 136 | 137 | /* Universal Asynchronous Receiver/Transmitter 0 (UART0 */ 138 | #define UART0_RBR 0xC0 139 | #define UART0_THR 0xC0 140 | #define UART0_BRG_L 0xC0 141 | #define UART0_IER 0xC1 142 | #define UART0_BRG_H 0xC1 143 | #define UART0_IIR 0xC2 144 | #define UART0_FCTL 0xC2 145 | #define UART0_LCTL 0xC3 146 | #define UART0_MCTL 0xC4 147 | #define UART0_LSR 0xC5 148 | #define UART0_MSR 0xC6 149 | #define UART0_SPR 0xC7 150 | 151 | /* Inter-Integrated Circuit Bus Control (I2C */ 152 | #define I2C_SAR 0xC8 153 | #define I2C_XSAR 0xC9 154 | #define I2C_DR 0xCA 155 | #define I2C_CTL 0xCB 156 | #define I2C_SR 0xCC 157 | #define I2C_CCR 0xCC 158 | #define I2C_SRR 0xCD 159 | 160 | /* Universal Asynchronous Receiver/Transmitter 1 (UART1 */ 161 | #define UART1_RBR 0xD0 162 | #define UART1_THR 0xD0 163 | #define UART1_BRG_L 0xD0 164 | #define UART1_IER 0xD1 165 | #define UART1_BRG_H 0xD1 166 | #define UART1_IIR 0xD2 167 | #define UART1_FCTL 0xD2 168 | #define UART1_LCTL 0xD3 169 | #define UART1_MCTL 0xD4 170 | #define UART1_LSR 0xD5 171 | #define UART1_MSR 0xD6 172 | #define UART1_SPR 0xD7 173 | 174 | /* Low-Power Control */ 175 | #define CLK_PPD1 0xDB 176 | #define CLK_PPD2 0xDC 177 | 178 | /* Real-Time Clock */ 179 | #define RTC_SEC 0xE0 180 | #define RTC_MIN 0xE1 181 | #define RTC_HRS 0xE2 182 | #define RTC_DOW 0xE3 183 | #define RTC_DOM 0xE4 184 | #define RTC_MON 0xE5 185 | #define RTC_YR 0xE6 186 | #define RTC_CEN 0xE7 187 | #define RTC_ASEC 0xE8 188 | #define RTC_AMIN 0xE9 189 | #define RTC_AHRS 0xEA 190 | #define RTC_ADOW 0xEB 191 | #define RTC_ACTRL 0xEC 192 | #define RTC_CTRL 0xED 193 | 194 | /* Chip Select Bus Mode Control */ 195 | #define CS0_BMC 0xF0 196 | #define CS1_BMC 0xF1 197 | #define CS2_BMC 0xF2 198 | #define CS3_BMC 0xF3 199 | 200 | /* On-Chip Flash Memory Control */ 201 | #define FLASH_KEY 0xF5 202 | #define FLASH_DATA 0xF6 203 | #define FLASH_ADDR_U 0xF7 204 | #define FLASH_CTRL 0xF8 205 | #define FLASH_FDIV 0xF9 206 | #define FLASH_PROT 0xFA 207 | #define FLASH_IRQ 0xFB 208 | #define FLASH_PAGE 0xFC 209 | #define FLASH_ROW 0xFD 210 | #define FLASH_COL 0xFE 211 | #define FLASH_PGCTL 0xFF 212 | 213 | /* Unspecified register definitions, retained for compatibility */ 214 | #define RAM_CTL0 RAM_CTL 215 | 216 | // CPU read/write values 217 | typedef enum 218 | { 219 | REG_AF, 220 | REG_BC, 221 | REG_DE, 222 | REG_HL, 223 | REG_IX, 224 | REG_IY, 225 | REG_SP, 226 | REG_PC, 227 | SET_ADL, 228 | RESET_ADL, 229 | EXX, 230 | MEM 231 | } rw_control_t; 232 | 233 | typedef enum 234 | { 235 | BREAK, 236 | STEP, 237 | RUN, 238 | REGONLY 239 | } debug_state_t; 240 | 241 | // low-level bit stream 242 | void zdi_delay (uint8_t); 243 | void zdi_start (); 244 | void zdi_write_bit (bool bit); 245 | bool zdi_read_bit (); 246 | void zdi_register (byte regnr,bool read); 247 | void zdi_separator (bool done_or_continue); 248 | void zdi_write (byte value); 249 | byte zdi_read (); 250 | 251 | // medium-level register read and writes 252 | byte zdi_read_register (byte regnr); 253 | void zdi_write_register (byte regnr, byte value); 254 | void zdi_read_registers (byte startregnr, byte count, byte* values); 255 | void zdi_write_registers (byte startregnr, byte count, byte* values); 256 | 257 | // high-level debugging, register and memory read functions 258 | bool zdi_mode (); 259 | void zdi_enter (); 260 | void zdi_exit (); 261 | uint8_t zdi_available_break_point(); 262 | 263 | // zdi interface functions 264 | void zdi_debug_status (debug_state_t state); 265 | void zdi_intel_hex (byte* memory,uint32_t start,uint16_t size); 266 | void zdi_process_line (); 267 | void zdi_process_cmd (uint8_t key); 268 | 269 | 270 | #endif -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = Electron 13 | 14 | [env] 15 | platform = espressif32 16 | board = esp32dev 17 | framework = arduino 18 | lib_deps = fdivitto/FabGL@^1.0.9 19 | lib_ldf_mode = deep+ 20 | upload_speed = 500000 21 | monitor_speed = 500000 22 | #monitor_filters = time 23 | upload_port = /dev/tty.usbserial-11120 #/dev/tty.usbserial-211240 24 | monitor_port = /dev/tty.usbserial-11120 #/dev/tty.usbserial-211240 25 | monitor_filters = esp32_exception_decoder 26 | #upload_port = /dev/tty.usbserial-10 27 | #monitor_port = /dev/tty.usbserial-10 28 | 29 | [env:Electron] 30 | build_flags = -D 31 | 32 | [env:ElectronMOS] 33 | build_flags = -D MOS_COMPATIBLE 34 | 35 | [env:ElectronConsole8] 36 | build_flags = -D CONSOLE8 37 | upload_port = /dev/tty.usbserial-02B1CCD1 #/dev/tty.usbserial-21130 ;/dev/tty.usbserial-211240 38 | monitor_port = /dev/tty.usbserial-02B1CCD1 #/dev/tty.usbserial-21130 ;/dev/tty.usbserial-211240 39 | 40 | [env:ElectronFR] 41 | build_flags = -D SHOW_FRAME_RATE -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S0urceror/AgonElectronHAL/0fcc3b6029401ada0a33080173e402a5d1fbfa28/src/.DS_Store -------------------------------------------------------------------------------- /src/audio_channel.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Agon Video BIOS - Audio class 3 | // Author: Dean Belfield 4 | // Contributors: Steve Sims (enhancements for more sophisticated audio support) 5 | // S0urceror (PlatformIO compatibility) 6 | // Created: 05/09/2022 7 | // Last Updated: 08/10/2023 8 | // 9 | // Modinfo: 10 | 11 | #include "audio_channel.h" 12 | #include "audio_sample.h" 13 | #include "enhanced_samples_generator.h" 14 | #include "ay_3_8910_noise.h" 15 | 16 | #include "audio_sample.h" 17 | #include "enhanced_samples_generator.h" 18 | extern std::unordered_map> samples; // Storage for the sample data 19 | 20 | AudioChannel::AudioChannel(uint8_t channel) : _channel(channel), _state(AudioState::Idle), _volume(64), _frequency(750), _duration(-1) { 21 | setWaveform(AUDIO_WAVE_DEFAULT, nullptr); 22 | //debug_log("AudioChannel: init %d\n\r", channel); 23 | //debug_log("free mem: %d\n\r", heap_caps_get_free_size(MALLOC_CAP_8BIT)); 24 | } 25 | 26 | AudioChannel::~AudioChannel() { 27 | //debug_log("AudioChannel: deiniting %d\n\r", channel()); 28 | if (this->_waveform != nullptr) { 29 | this->_waveform->enable(false); 30 | soundGenerator->detach(getWaveform()); 31 | } 32 | //debug_log("AudioChannel: deinit %d\n\r", channel()); 33 | } 34 | 35 | uint8_t AudioChannel::playNote(uint8_t volume, uint16_t frequency, int32_t duration) { 36 | if (!this->_waveform) { 37 | //debug_log("AudioChannel: no waveform on channel %d\n\r", channel()); 38 | return 0; 39 | } 40 | if (this->_waveformType == AUDIO_WAVE_SAMPLE && this->_volume == 0 && this->_state != AudioState::Idle) { 41 | // we're playing a silenced sample, so we're free to play a new note, so abort 42 | this->_state = AudioState::Abort; 43 | audioTaskAbortDelay(this->_channel); 44 | waitForAbort(); 45 | } 46 | switch (this->_state.load()) { 47 | case AudioState::Idle: 48 | case AudioState::Release: 49 | this->_volume = volume; 50 | this->_frequency = frequency; 51 | this->_duration = duration == 65535 ? -1 : duration; 52 | if (this->_duration == 0 && this->_waveformType == AUDIO_WAVE_SAMPLE) { 53 | // zero duration means play whole sample 54 | // NB this can only work out sample duration based on sample provided 55 | // so if sample data is streaming in an explicit length should be used instead 56 | this->_duration = ((EnhancedSamplesGenerator *)getWaveform())->getDuration(frequency); 57 | if (this->_volumeEnvelope) { 58 | // subtract the "release" time from the duration 59 | this->_duration -= this->_volumeEnvelope->getRelease(); 60 | } 61 | if (this->_duration < 0) { 62 | this->_duration = 1; 63 | } 64 | } 65 | this->_state = AudioState::Pending; 66 | //debug_log("AudioChannel: playNote %d,%d,%d,%d\n\r", channel(), volume, frequency, this->_duration); 67 | return 1; 68 | } 69 | return 0; 70 | } 71 | 72 | uint8_t AudioChannel::getStatus() { 73 | uint8_t status = 0; 74 | if (this->_waveform && this->_waveform->enabled()) { 75 | status |= AUDIO_STATUS_ACTIVE; 76 | if (this->_duration == -1) { 77 | status |= AUDIO_STATUS_INDEFINITE; 78 | } 79 | } 80 | switch (this->_state.load()) { 81 | case AudioState::Pending: 82 | case AudioState::Playing: 83 | case AudioState::PlayLoop: 84 | status |= AUDIO_STATUS_PLAYING; 85 | break; 86 | } 87 | if (this->_volumeEnvelope) { 88 | status |= AUDIO_STATUS_HAS_VOLUME_ENVELOPE; 89 | } 90 | if (this->_frequencyEnvelope) { 91 | status |= AUDIO_STATUS_HAS_FREQUENCY_ENVELOPE; 92 | } 93 | 94 | //debug_log("AudioChannel: getStatus %d\n\r", status); 95 | return status; 96 | } 97 | 98 | std::shared_ptr AudioChannel::getSampleWaveform(uint16_t sampleId, std::shared_ptr channelRef) { 99 | if (samples.find(sampleId) != samples.end()) { 100 | auto sample = samples.at(sampleId); 101 | // if (sample->channels.find(_channel) != sample->channels.end()) { 102 | // // this channel is already playing this sample, so do nothing 103 | // //debug_log("AudioChannel: already playing sample %d on channel %d\n\r", sampleId, channel()); 104 | // return nullptr; 105 | // } 106 | 107 | // TODO remove channel tracking?? 108 | // remove this channel from other samples 109 | // for (auto &samplePair : samples) { 110 | // if (samplePair.second) { 111 | // samplePair.second->channels.erase(_channel); 112 | // } 113 | // } 114 | // sample->channels[_channel] = channelRef; 115 | 116 | return std::make_shared(sample); 117 | } 118 | //debug_log("sample %d not found\n\r", sampleId); 119 | return nullptr; 120 | } 121 | 122 | uint8_t AudioChannel::setWaveform(int8_t waveformType, std::shared_ptr channelRef, uint16_t sampleId) { 123 | std::shared_ptr newWaveform = nullptr; 124 | 125 | switch (waveformType) { 126 | case AUDIO_WAVE_SAWTOOTH: 127 | newWaveform = std::make_shared(); 128 | break; 129 | case AUDIO_WAVE_SQUARE: 130 | newWaveform = std::make_shared(); 131 | break; 132 | case AUDIO_WAVE_SINE: 133 | newWaveform = std::make_shared(); 134 | break; 135 | case AUDIO_WAVE_TRIANGLE: 136 | newWaveform = std::make_shared(); 137 | break; 138 | case AUDIO_WAVE_NOISE: 139 | newWaveform = std::make_shared(); 140 | break; 141 | case AUDIO_WAVE_VICNOISE: 142 | newWaveform = std::make_shared(); 143 | break; 144 | case AUDIO_WAVE_AY_3_8910_NOISE: 145 | newWaveform = std::make_shared(); 146 | break; 147 | case AUDIO_WAVE_SAMPLE: 148 | // Buffer-based sample playback 149 | //debug_log("AudioChannel: using sample buffer %d for waveform on channel %d\n\r", sampleId, channel()); 150 | newWaveform = getSampleWaveform(sampleId, channelRef); 151 | break; 152 | default: 153 | // negative values indicate a sample number 154 | if (waveformType < 0) { 155 | // convert our negative sample number to a positive sample number starting at our base buffer ID 156 | int16_t sampleNum = BUFFERED_SAMPLE_BASEID + (-waveformType - 1); 157 | //debug_log("AudioChannel: using sample %d for waveform (%d) on channel %d\n\r", waveformType, sampleNum, channel()); 158 | newWaveform = getSampleWaveform(sampleNum, channelRef); 159 | waveformType = AUDIO_WAVE_SAMPLE; 160 | } else { 161 | //debug_log("AudioChannel: unknown waveform type %d on channel %d\n\r", waveformType, channel()); 162 | } 163 | break; 164 | } 165 | 166 | if (newWaveform != nullptr) { 167 | //debug_log("AudioChannel: setWaveform %d on channel %d\n\r", waveformType, channel()); 168 | if (this->_state != AudioState::Idle) { 169 | //debug_log("AudioChannel: aborting current playback\n\r"); 170 | // some kind of playback is happening, so abort any current task delay to allow playback to end 171 | this->_state = AudioState::Abort; 172 | audioTaskAbortDelay(this->_channel); 173 | waitForAbort(); 174 | } 175 | if (this->_waveform != nullptr) { 176 | //debug_log("AudioChannel: detaching old waveform\n\r"); 177 | detachSoundGenerator(); 178 | } 179 | this->_waveform = newWaveform; 180 | _waveformType = waveformType; 181 | attachSoundGenerator(); 182 | //debug_log("AudioChannel: setWaveform %d done on channel %d\n\r", waveformType, channel()); 183 | return 1; 184 | } 185 | // waveform not changed, so return a failure 186 | return 0; 187 | } 188 | 189 | uint8_t AudioChannel::setVolume(uint8_t volume) { 190 | //debug_log("AudioChannel: setVolume %d on channel %d\n\r", volume, channel()); 191 | if (volume == 255) { 192 | return this->_volume; 193 | } 194 | if (volume > 127) { 195 | volume = 127; 196 | } 197 | 198 | if (this->_waveform) { 199 | waitForAbort(); 200 | switch (this->_state.load()) { 201 | case AudioState::Idle: 202 | if (volume > 0) { 203 | // new note playback 204 | this->_volume = volume; 205 | this->_duration = -1; // indefinite duration 206 | this->_state = AudioState::Pending; 207 | } 208 | break; 209 | case AudioState::PlayLoop: 210 | // we are looping, so an envelope may be active 211 | if (volume == 0 && this->_waveformType != AUDIO_WAVE_SAMPLE) { 212 | // silence whilst looping always stops playback - curtail duration 213 | this->_duration = millis() - this->_startTime; 214 | // if there's a volume envelope, just allow release to happen, otherwise... 215 | if (!this->_volumeEnvelope) { 216 | this->_volume = 0; 217 | } 218 | } else { 219 | // Change base volume level, so next loop iteration will use it 220 | this->_volume = volume; 221 | } 222 | break; 223 | case AudioState::Pending: 224 | case AudioState::Release: 225 | // Set level so next loop will pick up the new volume 226 | this->_volume = volume; 227 | break; 228 | default: 229 | // All other states we'll set volume immediately 230 | this->_volume = volume; 231 | this->_waveform->setVolume(volume); 232 | if (volume == 0 && this->_waveformType != AUDIO_WAVE_SAMPLE) { 233 | // we're going silent, so abort any current playback 234 | this->_state = AudioState::Abort; 235 | audioTaskAbortDelay(this->_channel); 236 | } 237 | break; 238 | } 239 | return this->_volume; 240 | } 241 | return 255; 242 | } 243 | 244 | uint8_t AudioChannel::setFrequency(uint16_t frequency) { 245 | //debug_log("AudioChannel: setFrequency %d on channel %d\n\r", frequency, channel()); 246 | this->_frequency = frequency; 247 | 248 | if (this->_waveform) { 249 | waitForAbort(); 250 | switch (this->_state.load()) { 251 | case AudioState::Pending: 252 | case AudioState::PlayLoop: 253 | case AudioState::Release: 254 | // Do nothing as next loop will pick up the new frequency 255 | break; 256 | default: 257 | this->_waveform->setFrequency(frequency); 258 | } 259 | return 1; 260 | } 261 | return 0; 262 | } 263 | 264 | uint8_t AudioChannel::setDuration(int32_t duration) { 265 | //debug_log("AudioChannel: setDuration %d on channel %d\n\r", duration, channel()); 266 | if (duration == 0xFFFFFF) { 267 | duration = -1; 268 | } 269 | this->_duration = duration; 270 | 271 | if (this->_waveform) { 272 | waitForAbort(); 273 | switch (this->_state.load()) { 274 | case AudioState::Idle: 275 | // kick off a new note playback 276 | this->_state = AudioState::Pending; 277 | break; 278 | case AudioState::Playing: 279 | audioTaskAbortDelay(this->_channel); 280 | break; 281 | default: 282 | // any other state we should be looping so it will just get picked up 283 | break; 284 | } 285 | return 1; 286 | } 287 | return 0; 288 | } 289 | 290 | uint8_t AudioChannel::setVolumeEnvelope(std::unique_ptr envelope) { 291 | if (this->_volumeEnvelope) 292 | { 293 | this->_waveform->enable(false); 294 | //debug_log("AudioChannel: end (released %d)\n\r", channel()); 295 | this->_state = AudioState::Idle; 296 | } 297 | 298 | this->_volumeEnvelope = std::move(envelope); 299 | if (envelope && this->_state == AudioState::Playing) { 300 | // swap to looping 301 | this->_state = AudioState::PlayLoop; 302 | audioTaskAbortDelay(this->_channel); 303 | } 304 | return 1; 305 | } 306 | 307 | uint8_t AudioChannel::setFrequencyEnvelope(std::unique_ptr envelope) { 308 | this->_frequencyEnvelope = std::move(envelope); 309 | if (envelope && this->_state == AudioState::Playing) { 310 | // swap to looping 311 | this->_state = AudioState::PlayLoop; 312 | audioTaskAbortDelay(this->_channel); 313 | } 314 | return 1; 315 | } 316 | 317 | uint8_t AudioChannel::setSampleRate(uint16_t sampleRate) { 318 | if (this->_waveform) { 319 | this->_waveform->setSampleRate(sampleRate); 320 | return 1; 321 | } 322 | return 0; 323 | } 324 | 325 | uint8_t AudioChannel::setDutyCycle(uint8_t dutyCycle) { 326 | if (this->_waveform && this->_waveformType == AUDIO_WAVE_SQUARE) { 327 | ((SquareWaveformGenerator *)getWaveform())->setDutyCycle(dutyCycle); 328 | return 1; 329 | } 330 | return 0; 331 | } 332 | 333 | uint8_t AudioChannel::setParameter(uint8_t parameter, uint16_t value) { 334 | if (this->_waveform) { 335 | bool use16Bit = parameter & AUDIO_PARAM_16BIT; 336 | auto param = parameter & AUDIO_PARAM_MASK; 337 | switch (param) { 338 | case AUDIO_PARAM_DUTY_CYCLE: { 339 | return setDutyCycle(value); 340 | } break; 341 | case AUDIO_PARAM_VOLUME: { 342 | return setVolume(value); 343 | } break; 344 | case AUDIO_PARAM_FREQUENCY: { 345 | if (!use16Bit) { 346 | value = _frequency & 0xFF00 | value & 0x00FF; 347 | } 348 | return setFrequency(value); 349 | } break; 350 | } 351 | } 352 | return 0; 353 | } 354 | 355 | void AudioChannel::attachSoundGenerator() { 356 | if (this->_waveform) { 357 | soundGenerator->attach(getWaveform()); 358 | } 359 | } 360 | 361 | void AudioChannel::detachSoundGenerator() { 362 | if (this->_waveform) { 363 | soundGenerator->detach(getWaveform()); 364 | } 365 | } 366 | 367 | uint8_t AudioChannel::seekTo(uint32_t position) { 368 | if (this->_waveformType == AUDIO_WAVE_SAMPLE) { 369 | ((EnhancedSamplesGenerator *)getWaveform())->seekTo(position); 370 | return 1; 371 | } 372 | return 0; 373 | } 374 | 375 | void AudioChannel::waitForAbort() { 376 | while (this->_state == AudioState::Abort) { 377 | // wait for abort to complete 378 | vTaskDelay(1); 379 | } 380 | } 381 | 382 | uint8_t AudioChannel::getVolume(uint32_t elapsed) { 383 | if (this->_volumeEnvelope) { 384 | return this->_volumeEnvelope->getVolume(this->_volume, elapsed, this->_duration); 385 | } 386 | return this->_volume; 387 | } 388 | 389 | uint16_t AudioChannel::getFrequency(uint32_t elapsed) { 390 | if (this->_frequencyEnvelope) { 391 | return this->_frequencyEnvelope->getFrequency(this->_frequency, elapsed, this->_duration); 392 | } 393 | return this->_frequency; 394 | } 395 | 396 | bool AudioChannel::isReleasing(uint32_t elapsed) { 397 | if (this->_volumeEnvelope) { 398 | return this->_volumeEnvelope->isReleasing(elapsed, this->_duration); 399 | } 400 | if (this->_duration == -1) { 401 | return false; 402 | } 403 | return elapsed >= this->_duration; 404 | } 405 | 406 | bool AudioChannel::isFinished(uint32_t elapsed) { 407 | if (this->_volumeEnvelope) { 408 | return this->_volumeEnvelope->isFinished(elapsed, this->_duration); 409 | } 410 | if (this->_duration == -1) { 411 | return false; 412 | } 413 | return (elapsed >= this->_duration); 414 | } 415 | 416 | void AudioChannel::loop() { 417 | int delay = 0; 418 | 419 | switch (this->_state.load()) { 420 | case AudioState::Pending: 421 | //debug_log("AudioChannel: play %d,%d,%d,%d\n\r", channel(), this->_volume, this->_frequency, this->_duration); 422 | // we have a new note to play 423 | this->_startTime = millis(); 424 | // set our initial volume and frequency 425 | this->_waveform->setVolume(this->getVolume(0)); 426 | this->seekTo(0); 427 | this->_waveform->setFrequency(this->getFrequency(0)); 428 | this->_waveform->enable(true); 429 | // if we have an envelope then we loop, otherwise just delay for duration 430 | if (this->_volumeEnvelope || this->_frequencyEnvelope) { 431 | this->_state = AudioState::PlayLoop; 432 | } else { 433 | this->_state = AudioState::Playing; 434 | // if delay value is negative then this delays for a super long time 435 | delay = this->_duration; 436 | } 437 | break; 438 | 439 | case AudioState::Playing: 440 | if (this->_duration >= 0) { 441 | // simple playback - delay until we have reached our duration 442 | uint32_t elapsed = millis() - this->_startTime; 443 | //debug_log("AudioChannel: %d elapsed %d\n\r", channel(), elapsed); 444 | if (elapsed >= this->_duration) { 445 | this->_waveform->enable(false); 446 | //debug_log("AudioChannel: %d end\n\r", channel()); 447 | this->_state = AudioState::Idle; 448 | } else { 449 | //debug_log("AudioChannel: %d loop (%d)\n\r", channel(), this->_duration - elapsed); 450 | delay = this->_duration - elapsed; 451 | } 452 | } else { 453 | // our duration is indefinite, so delay for a long time 454 | //debug_log("AudioChannel: %d loop (indefinite playback)\n\r", channel()); 455 | delay = -1; 456 | } 457 | break; 458 | 459 | // loop and release states used for envelopes 460 | case AudioState::PlayLoop: { 461 | uint32_t elapsed = millis() - this->_startTime; 462 | if (isReleasing(elapsed)) { 463 | //debug_log("AudioChannel: releasing %d...\n\r", channel()); 464 | this->_state = AudioState::Release; 465 | } 466 | // update volume and frequency as appropriate 467 | if (this->_volumeEnvelope) 468 | this->_waveform->setVolume(this->getVolume(elapsed)); 469 | if (this->_frequencyEnvelope) 470 | this->_waveform->setFrequency(this->getFrequency(elapsed)); 471 | break; 472 | } 473 | 474 | case AudioState::Release: { 475 | uint32_t elapsed = millis() - this->_startTime; 476 | // update volume and frequency as appropriate 477 | if (this->_volumeEnvelope) 478 | this->_waveform->setVolume(this->getVolume(elapsed)); 479 | if (this->_frequencyEnvelope) 480 | this->_waveform->setFrequency(this->getFrequency(elapsed)); 481 | 482 | if (isFinished(elapsed)) { 483 | this->_waveform->enable(false); 484 | //debug_log("AudioChannel: end (released %d)\n\r", channel()); 485 | this->_state = AudioState::Idle; 486 | } 487 | break; 488 | } 489 | 490 | case AudioState::Abort: 491 | this->_waveform->enable(false); 492 | //debug_log("AudioChannel: abort %d\n\r", channel()); 493 | this->_state = AudioState::Idle; 494 | break; 495 | } 496 | 497 | /* 498 | * in USERSPACE, xTaskAbortDelay is not implemented, so audioTaskAbortDelay 499 | * won't work, and waitForAbort() will freeze vdu stream processor for the 500 | * whole delay duration. So don't delay here, allowing 'abort' state to be 501 | * transitioned rapidly (at some small CPU cost). 502 | if (delay > 0) { 503 | // -TM- 504 | vTaskDelay(pdMS_TO_TICKS(delay)); 505 | }*/ 506 | } 507 | -------------------------------------------------------------------------------- /src/audio_driver.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Agon Video BIOS - Audio class 3 | // Author: Dean Belfield 4 | // Contributors: Steve Sims (enhancements for more sophisticated audio support) 5 | // S0urceror (PlatformIO compatibility) 6 | // Created: 05/09/2022 7 | // Last Updated: 08/10/2023 8 | // 9 | // Modinfo: 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "audio_channel.h" 18 | #include "audio_sample.h" 19 | #include "types.h" 20 | 21 | // audio channels and their associated tasks 22 | std::unordered_map> audioChannels; 23 | std::vector> audioHandlers; 24 | 25 | std::unordered_map> samples; // Storage for the sample data 26 | 27 | fabgl::SoundGenerator *soundGenerator; // audio handling sub-system 28 | std::mutex soundGeneratorMutex; 29 | 30 | // Audio channel driver task 31 | // 32 | void audioDriver(void * parameters) { 33 | uint8_t channelNum = *(uint8_t *)parameters; 34 | auto channel = make_shared_psram(channelNum); 35 | 36 | audioChannels[channelNum] = channel; 37 | while (true) { 38 | channel->loop(); 39 | vTaskDelay(1); 40 | } 41 | } 42 | 43 | void initAudioChannel(uint8_t channel) { 44 | xTaskCreatePinnedToCore(audioDriver, "audioDriver", 45 | 4096, // This stack size can be checked & adjusted by reading the Stack Highwater 46 | &channel, // Parameters 47 | PLAY_SOUND_PRIORITY, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. 48 | &audioHandlers[channel], // Task handle 49 | ARDUINO_RUNNING_CORE 50 | ); 51 | } 52 | 53 | void audioTaskAbortDelay(uint8_t channel) { 54 | if (audioHandlers[channel]) { 55 | xTaskAbortDelay(audioHandlers[channel]); 56 | } 57 | } 58 | 59 | void audioTaskKill(uint8_t channel) { 60 | if (audioHandlers[channel]) { 61 | vTaskDelete(audioHandlers[channel]); 62 | audioChannels[channel]->detachSoundGenerator(); 63 | audioHandlers[channel] = nullptr; 64 | audioChannels.erase(channel); 65 | //debug_log("audioTaskKill: channel %d killed\n\r", channel); 66 | } else { 67 | //debug_log("audioTaskKill: channel %d not found\n\r", channel); 68 | } 69 | } 70 | 71 | // Change the sample rate 72 | // 73 | void setSampleRate(uint16_t sampleRate) { 74 | // make a new sound generator and re-attach all our active channels 75 | if (sampleRate == 65535) { 76 | sampleRate = AUDIO_DEFAULT_SAMPLE_RATE; 77 | } 78 | auto lock = std::unique_lock(soundGeneratorMutex); 79 | // detatch the old sound generator 80 | if (soundGenerator) { 81 | soundGenerator->play(false); 82 | for (const auto &channelPair : audioChannels) { 83 | if (!channelPair.second) { 84 | //debug_log("duff channel pair for channel %d, skipping\n\r", channelPair.first); 85 | audioChannels.erase(channelPair.first); 86 | continue; 87 | } 88 | channelPair.second->detachSoundGenerator(); 89 | } 90 | delete soundGenerator; 91 | } 92 | // delete the old sound generator 93 | soundGenerator = new fabgl::SoundGenerator(sampleRate); 94 | for (const auto &channelPair : audioChannels) { 95 | channelPair.second->attachSoundGenerator(); 96 | } 97 | soundGenerator->play(true); 98 | } 99 | 100 | // Initialise the sound driver 101 | // 102 | void initAudio() { 103 | // make new sound generator 104 | setSampleRate(AUDIO_DEFAULT_SAMPLE_RATE); 105 | audioHandlers.reserve(MAX_AUDIO_CHANNELS); 106 | //debug_log("initAudio: we have reserved %d channels\n\r", audioHandlers.capacity()); 107 | for (uint8_t i = 0; i < AUDIO_CHANNELS; i++) { 108 | initAudioChannel(i); 109 | } 110 | } 111 | 112 | // Channel enabled? 113 | // 114 | bool channelEnabled(uint8_t channel) { 115 | return channel < MAX_AUDIO_CHANNELS && audioChannels.find(channel) != audioChannels.end(); 116 | } 117 | 118 | // Play a note 119 | // 120 | uint8_t playNote(uint8_t channel, uint8_t volume, uint16_t frequency, uint16_t duration) { 121 | if (channelEnabled(channel)) { 122 | return audioChannels[channel]->playNote(volume, frequency, duration); 123 | } 124 | return 1; 125 | } 126 | 127 | // Get channel status 128 | // 129 | uint8_t getChannelStatus(uint8_t channel) { 130 | if (channelEnabled(channel)) { 131 | return audioChannels[channel]->getStatus(); 132 | } 133 | return -1; 134 | } 135 | 136 | // Set channel volume 137 | // 138 | uint8_t setVolume(uint8_t channel, uint8_t volume) { 139 | if (channel == 255) { 140 | if (volume == 255) { 141 | return soundGenerator->volume(); 142 | } 143 | soundGenerator->setVolume(volume < 128 ? volume : 127); 144 | return soundGenerator->volume(); 145 | } else if (channelEnabled(channel)) { 146 | return audioChannels[channel]->setVolume(volume); 147 | } 148 | return 255; 149 | } 150 | 151 | // Set channel frequency 152 | // 153 | uint8_t setFrequency(uint8_t channel, uint16_t frequency) { 154 | if (channelEnabled(channel)) { 155 | return audioChannels[channel]->setFrequency(frequency); 156 | } 157 | return 0; 158 | } 159 | 160 | // Set channel waveform 161 | // 162 | uint8_t setWaveform(uint8_t channel, int8_t waveformType, uint16_t sampleId) { 163 | if (channelEnabled(channel)) { 164 | auto channelRef = audioChannels[channel]; 165 | return channelRef->setWaveform(waveformType, channelRef, sampleId); 166 | } 167 | return 0; 168 | } 169 | 170 | // Seek to a position on a channel 171 | // 172 | uint8_t seekTo(uint8_t channel, uint32_t position) { 173 | if (channelEnabled(channel)) { 174 | return audioChannels[channel]->seekTo(position); 175 | } 176 | return 0; 177 | } 178 | 179 | // Set channel duration 180 | // 181 | uint8_t setDuration(uint8_t channel, uint16_t duration) { 182 | if (channelEnabled(channel)) { 183 | return audioChannels[channel]->setDuration(duration); 184 | } 185 | return 0; 186 | } 187 | 188 | // Set channel sample rate 189 | // 190 | uint8_t setSampleRate(uint8_t channel, uint16_t sampleRate) { 191 | if (channel == 255) { 192 | // set underlying sample rate 193 | setSampleRate(sampleRate); 194 | return 0; 195 | } 196 | if (channelEnabled(channel)) { 197 | return audioChannels[channel]->setSampleRate(sampleRate); 198 | } 199 | return 0; 200 | } 201 | 202 | // Enable a channel 203 | // 204 | uint8_t enableChannel(uint8_t channel) { 205 | if (channelEnabled(channel)) { 206 | // channel already enabled 207 | return 1; 208 | } 209 | if (channel >= 0 && channel < MAX_AUDIO_CHANNELS && audioChannels[channel] == nullptr) { 210 | // channel not enabled, so enable it 211 | initAudioChannel(channel); 212 | return 1; 213 | } 214 | return 0; 215 | } 216 | 217 | // Disable a channel 218 | // 219 | uint8_t disableChannel(uint8_t channel) { 220 | if (channelEnabled(channel)) { 221 | audioTaskKill(channel); 222 | return 1; 223 | } 224 | return 0; 225 | } 226 | 227 | // Clear a sample 228 | // 229 | uint8_t clearSample(uint16_t sampleId) { 230 | //debug_log("clearSample: sample %d\n\r", sampleId); 231 | if (samples.find(sampleId) == samples.end()) { 232 | //debug_log("clearSample: sample %d not found\n\r", sampleId); 233 | return 1; 234 | } 235 | samples[sampleId] = nullptr; 236 | //debug_log("reset sample\n\r"); 237 | return 0; 238 | } 239 | 240 | // Reset samples 241 | // 242 | void resetSamples() { 243 | //debug_log("resetSamples\n\r"); 244 | samples.clear(); 245 | } 246 | 247 | 248 | -------------------------------------------------------------------------------- /src/audio_sample.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Agon Video BIOS - Audio class 3 | // Author: Dean Belfield 4 | // Contributors: Steve Sims (enhancements for more sophisticated audio support) 5 | // S0urceror (PlatformIO compatibility) 6 | // Created: 05/09/2022 7 | // Last Updated: 08/10/2023 8 | // 9 | // Modinfo: 10 | 11 | #include "audio_sample.h" 12 | 13 | AudioSample::~AudioSample() { 14 | // iterate over channels 15 | // for (auto &channelPair : this->channels) { 16 | // auto channel = channelPair.second.lock(); 17 | // if (channel) { 18 | // // Remove sample from channel 19 | // debug_log("AudioSample: removing sample from channel %d\n\r", channel->channel()); 20 | // // TODO change so only removes if channel is definitely set to this sample 21 | // channel->setWaveform(AUDIO_WAVE_DEFAULT, nullptr); 22 | // } 23 | // } 24 | } 25 | 26 | int8_t AudioSample::getSample(uint32_t & index, uint32_t & blockIndex) { 27 | // get the next sample 28 | if (blockIndex >= blocks.size()) { 29 | // we've reached the end of the sample, and haven't looped, so return 0 (silence) 30 | return 0; 31 | } 32 | 33 | auto block = blocks[blockIndex]; 34 | int8_t sample = block->getBuffer()[index++]; 35 | 36 | if (index >= block->size()) { 37 | // block reached end, move to next block 38 | index = 0; 39 | blockIndex++; 40 | } 41 | 42 | if (format == AUDIO_FORMAT_8BIT_UNSIGNED) { 43 | sample = sample - 128; 44 | } 45 | 46 | return sample; 47 | } 48 | 49 | void AudioSample::seekTo(uint32_t position, uint32_t & index, uint32_t & blockIndex, int32_t & repeatCount) { 50 | // NB repeatCount calculation here can result in zero, or a negative number, 51 | // or a number that's beyond the end of the sample, which is fine 52 | // it just means that the sample will never loop 53 | if (repeatLength < 0) { 54 | // repeat to end of sample 55 | repeatCount = getSize() - position; 56 | } else if (repeatLength > 0) { 57 | auto repeatEnd = repeatStart + repeatLength; 58 | repeatCount = repeatEnd - position; 59 | } else { 60 | repeatCount = 0; 61 | } 62 | 63 | blockIndex = 0; 64 | index = position; 65 | while (blockIndex < blocks.size() && index >= blocks[blockIndex]->size()) { 66 | index -= blocks[blockIndex]->size(); 67 | blockIndex++; 68 | } 69 | } 70 | 71 | uint32_t AudioSample::getSize() { 72 | uint32_t samples = 0; 73 | for (auto block : blocks) { 74 | samples += block->size(); 75 | } 76 | return samples; 77 | } 78 | -------------------------------------------------------------------------------- /src/ay_3_8910.cpp: -------------------------------------------------------------------------------- 1 | #include "ay_3_8910.h" 2 | #include "fabgl.h" 3 | #include "hal.h" 4 | #include "agon_audio.h" 5 | #include "audio_channel.h" 6 | #include "envelopes/ay_3_8910_envelope.h" 7 | 8 | #define PLAY_SOUND_PRIORITY 3 9 | 10 | AY_3_8910_Volume ay_3_8910_volume; 11 | 12 | AY_3_8910::AY_3_8910 () 13 | { 14 | register_select=0; 15 | toneA=0; 16 | toneB=0; 17 | toneC=0; 18 | noise=0; 19 | mixer=0; 20 | env_period=0; 21 | env_shape=0; 22 | amplA=0; 23 | amplB=0; 24 | amplC=0; 25 | } 26 | 27 | void AY_3_8910::init () 28 | { 29 | // tone 30 | setWaveform (0,AUDIO_WAVE_SQUARE,0); 31 | setWaveform (1,AUDIO_WAVE_SQUARE,0); 32 | setWaveform (2,AUDIO_WAVE_SQUARE,0); 33 | // noise 34 | setWaveform (3,AUDIO_WAVE_AY_3_8910_NOISE,0); 35 | setWaveform (4,AUDIO_WAVE_AY_3_8910_NOISE,0); 36 | setWaveform (5,AUDIO_WAVE_AY_3_8910_NOISE,0); 37 | } 38 | 39 | void AY_3_8910::updateChannel (uint8_t channel, uint32_t tone_freq,bool volume_envelope,uint8_t volume) 40 | { 41 | // on 42 | setFrequency (channel,MASTER_FREQUENCY_DIV / tone_freq); 43 | if (!volume_envelope) 44 | { 45 | // normal volume control, switch off envelopes, if any 46 | if (channelEnabled(channel)) 47 | { 48 | if (audioChannels[channel]->getStatus() & AUDIO_STATUS_HAS_VOLUME_ENVELOPE) 49 | audioChannels[channel]->setVolumeEnvelope (nullptr); 50 | } 51 | setVolume (channel,ay_3_8910_volume.getAgonVolume(volume)); 52 | } 53 | else 54 | { 55 | // volume envelope 56 | std::unique_ptr envelope = make_unique_psram(env_shape,env_period); 57 | if (channelEnabled(channel)) 58 | { 59 | audioChannels[channel]->setVolumeEnvelope (std::move (envelope)); 60 | // base volume 61 | setVolume (channel,ay_3_8910_volume.getAgonVolume(15)); 62 | } 63 | } 64 | } 65 | 66 | void AY_3_8910::updateSound (uint8_t channel, uint8_t mixer, uint8_t amp, uint32_t tone_freq, uint32_t noise_freq) 67 | { 68 | bool tone = (mixer & (0b00000001< 0) 74 | { 75 | updateChannel (channel,tone_freq,volume_envelope,volume); 76 | } 77 | else 78 | { 79 | // off 80 | if (!volume_envelope) 81 | setVolume (channel,0); 82 | if (audioChannels[channel]->getStatus() & AUDIO_STATUS_HAS_VOLUME_ENVELOPE) 83 | audioChannels[channel]->setVolumeEnvelope (nullptr); 84 | } 85 | // noise channel + 3 86 | if (noise && noise_freq > 0) 87 | { 88 | updateChannel (channel+3,noise_freq,volume_envelope,volume); 89 | } 90 | else 91 | { 92 | // off 93 | if (!volume_envelope) 94 | setVolume (channel+3,0); 95 | if (audioChannels[channel+3]->getStatus() & AUDIO_STATUS_HAS_VOLUME_ENVELOPE) 96 | audioChannels[channel+3]->setVolumeEnvelope (nullptr); 97 | } 98 | } 99 | void AY_3_8910::write (uint8_t port, uint8_t value) 100 | { 101 | bool updateA=false,updateB=false,updateC = false; 102 | uint8_t mixer_diff; 103 | 104 | if (port==0xa0) 105 | register_select = value; 106 | if (port==0xa1) 107 | { 108 | switch (register_select) 109 | { 110 | // Tone A - fine 111 | case 0x00: 112 | toneA = toneA & 0b111100000000; 113 | toneA = toneA + value; 114 | updateA = true; 115 | break; 116 | // Tone A - coarse 117 | case 0x01: 118 | toneA = toneA & 0b000011111111; 119 | toneA = toneA + (value << 8); 120 | updateA = true; 121 | break; 122 | // Tone B - fine 123 | case 0x02: 124 | toneB = toneB & 0b111100000000; 125 | toneB = toneB + value; 126 | updateB = true; 127 | break; 128 | // Tone B - coarse 129 | case 0x03: 130 | toneB = toneB & 0b000011111111; 131 | toneB = toneB + (value << 8); 132 | updateB = true; 133 | break; 134 | // Tone C - fine 135 | case 0x04: 136 | toneC = toneC & 0b111100000000; 137 | toneC = toneC + value; 138 | updateC = true; 139 | break; 140 | // Tone C - coarse 141 | case 0x05: 142 | toneC = toneC & 0b000011111111; 143 | toneC = toneC + (value << 8); 144 | updateC = true; 145 | break; 146 | // Noise generator 147 | case 0x06: 148 | noise = value & 0b00011111; 149 | // noise frequency change applies to all channels 150 | updateA = true; 151 | updateB = true; 152 | updateC = true; 153 | break; 154 | // Voice, I/O port control 155 | case 0x07: 156 | mixer_diff = mixer ^ value; 157 | mixer = value; 158 | if (mixer_diff&0b00001001!=0) 159 | updateA = true; 160 | if (mixer_diff&0b00010010!=0) 161 | updateB = true; 162 | if (mixer_diff&0b00100100!=0) 163 | updateC = true; 164 | break; 165 | // Amplitude, volume control - A 166 | case 0x08: 167 | amplA = value & 0b00011111; 168 | updateA = true; 169 | break; 170 | // Amplitude, volume control - B 171 | case 0x09: 172 | amplB = value & 0b00011111; 173 | updateB = true; 174 | break; 175 | // Amplitude, volume control - C 176 | case 0x0a: 177 | amplC = value & 0b00011111; 178 | updateC = true; 179 | break; 180 | // Envelope period - fine 181 | case 0x0b: 182 | env_period = env_period & 0b1111111100000000; 183 | env_period = env_period + value; 184 | break; 185 | // Envelope period - coarse 186 | case 0x0c: 187 | env_period = env_period & 0b0000000011111111; 188 | env_period = env_period + (value << 8); 189 | break; 190 | // Envelope shape 191 | case 0x0d: 192 | env_shape = value & 0b00001111; 193 | if (amplA&0b10000) 194 | updateA = true; 195 | if (amplB&0b10000) 196 | updateC = true; 197 | if (amplC&0b10000) 198 | updateC = true; 199 | break; 200 | // gpio read/write 201 | case 0x0e: 202 | gpio = value; 203 | break; 204 | // gpio control 205 | case 0x0f: 206 | gpio_control = value; 207 | break; 208 | default: 209 | break; 210 | } 211 | } 212 | 213 | // update sound 214 | if (updateA) 215 | { 216 | updateSound (0,mixer,amplA,toneA,noise); 217 | updateA = false; 218 | } 219 | if (updateB) 220 | { 221 | updateSound (1,mixer,amplB,toneB,noise); 222 | updateB = false; 223 | } 224 | if (updateC) 225 | { 226 | updateSound (2,mixer,amplC,toneC,noise); 227 | updateC = false; 228 | } 229 | } 230 | 231 | uint8_t AY_3_8910::read (uint8_t port) 232 | { 233 | uint8_t val=0; 234 | 235 | if (port==0xa2) 236 | { 237 | switch (register_select) 238 | { 239 | // gpio read/write 240 | case 0x0e: 241 | if (gpio_control & 0b01000000) 242 | // joystick portB 243 | val = 0x3f; // no things pressed on the joystick (yet), all signals 1-High 244 | else 245 | // joystick portA 246 | val = 0x3f; // no things pressed on the joystick (yet), all signals 1-High 247 | break; 248 | // gpio control 249 | case 0x0f: 250 | val = gpio_control; 251 | break; 252 | } 253 | } 254 | 255 | return val; 256 | } -------------------------------------------------------------------------------- /src/ay_3_8910_noise.cpp: -------------------------------------------------------------------------------- 1 | #include "ay_3_8910_noise.h" 2 | 3 | AY_3_8910_NoiseGenerator::AY_3_8910_NoiseGenerator() 4 | : m_frequency (0) , 5 | m_noise(0xFAB7), 6 | m_counter (0) 7 | { 8 | 9 | } 10 | 11 | void AY_3_8910_NoiseGenerator::setFrequency(int value) 12 | { 13 | m_frequency = value; 14 | } 15 | uint16_t AY_3_8910_NoiseGenerator::frequency() 16 | { 17 | return m_frequency; 18 | } 19 | 20 | // The Random Number Generator of the 8910 is a 17-bit shift register. 21 | // The input to the shift register is bit0 XOR bit3 (bit0 is the 22 | // output). Verified on real AY8910 and YM2149 chips. 23 | // 24 | // Fibonacci configuration: 25 | // random ^= ((random & 1) ^ ((random >> 3) & 1)) << 17; 26 | // random >>= 1; 27 | // Galois configuration: 28 | // if (random & 1) random ^= 0x24000; 29 | // random >>= 1; 30 | // or alternatively: 31 | int AY_3_8910_NoiseGenerator::getSample() 32 | { 33 | int sample; 34 | 35 | if (duration() == 0) { 36 | return 0; 37 | } 38 | 39 | if (m_counter>0) 40 | { 41 | m_counter--; 42 | sample = m_prevsample; 43 | } 44 | else 45 | { 46 | m_counter = sampleRate()/m_frequency; 47 | 48 | // noise generator based on Galois LFSR 49 | // m_noise = (m_noise >> 1) ^ (-(m_noise & 1) & 0xB400u); 50 | m_noise = (m_noise >> 1) ^ ((m_noise & 1) << 13) ^ ((m_noise & 1) << 16); 51 | sample = m_noise&1 ? 127 : -128; 52 | m_prevsample = sample; 53 | } 54 | 55 | // process volume 56 | sample = sample * volume() / 127; 57 | 58 | decDuration(); 59 | 60 | return sample; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/buffer_stream.cpp: -------------------------------------------------------------------------------- 1 | #include "buffer_stream.h" 2 | 3 | BufferStream::BufferStream(uint32_t bufferLength) : bufferLength(bufferLength), bufferPosition(0) { 4 | buffer = make_unique_psram_array(bufferLength); 5 | } 6 | 7 | int BufferStream::available() { 8 | return bufferLength - bufferPosition; 9 | } 10 | 11 | int BufferStream::read() { 12 | if (bufferPosition < bufferLength) { 13 | return buffer[bufferPosition++]; 14 | } 15 | return -1; 16 | } 17 | 18 | int BufferStream::peek() { 19 | if (bufferPosition < bufferLength) { 20 | return buffer[bufferPosition]; 21 | } 22 | return -1; 23 | } 24 | 25 | size_t BufferStream::write(uint8_t b) { 26 | // write is not supported 27 | return 0; 28 | } 29 | 30 | bool BufferStream::writeBuffer(uint8_t * data, uint32_t length, uint32_t offset = 0) { 31 | // TODO consider return type - we could support writing to buffer limit, 32 | // and returning how many bytes were written 33 | if (length + offset <= bufferLength) { 34 | memcpy(buffer.get() + offset, data, length); 35 | return true; 36 | } else { 37 | //debug_log("BufferStream::writeBuffer: buffer overflow\n\r"); 38 | return false; 39 | } 40 | } 41 | 42 | void BufferStream::writeBufferByte(uint8_t data, uint32_t offset = 0) { 43 | if (offset < bufferLength) { 44 | buffer[offset] = data; 45 | } 46 | } 47 | 48 | // incrementBufferByte 49 | // accepts an offset and a value to increment by 50 | // returns true if value overflowed 51 | bool BufferStream::incrementBufferByte(uint32_t offset = 0, int8_t by = 1) { 52 | if (offset < bufferLength) { 53 | auto oldValue = buffer[offset]; 54 | buffer[offset] += by; 55 | 56 | // check for overflow 57 | if (by > 0) { 58 | return buffer[offset] < oldValue; 59 | } else { 60 | return buffer[offset] > oldValue; 61 | } 62 | } 63 | return false; 64 | } 65 | 66 | 67 | size_t WritableBufferStream::write(uint8_t b) { 68 | if (bufferWritePosition < bufferLength) { 69 | buffer[bufferWritePosition++] = b; 70 | return 1; 71 | } 72 | //debug_log("WritableBufferStream::write: buffer overflow\n\r"); 73 | return 0; 74 | } -------------------------------------------------------------------------------- /src/enhanced_samples_generator.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Agon Video BIOS - Audio class 3 | // Author: Dean Belfield 4 | // Contributors: Steve Sims (enhancements for more sophisticated audio support) 5 | // S0urceror (PlatformIO compatibility) 6 | // Created: 05/09/2022 7 | // Last Updated: 08/10/2023 8 | // 9 | // Modinfo: 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "enhanced_samples_generator.h" 17 | #include "audio_sample.h" 18 | 19 | EnhancedSamplesGenerator::EnhancedSamplesGenerator(std::shared_ptr sample) 20 | : _sample(sample), repeatCount(0), index(0), blockIndex(0), frequency(0), previousSample(0), currentSample(0), samplesPerGet(1.0), fractionalSampleOffset(0.0) 21 | {} 22 | 23 | void EnhancedSamplesGenerator::setFrequency(int value) { 24 | frequency = value; 25 | samplesPerGet = calculateSamplerate(value); 26 | } 27 | 28 | void EnhancedSamplesGenerator::setSampleRate(int value) { 29 | WaveformGenerator::setSampleRate(value); 30 | samplesPerGet = calculateSamplerate(frequency); 31 | } 32 | 33 | int EnhancedSamplesGenerator::getSample() { 34 | if (duration() == 0) { 35 | return 0; 36 | } 37 | 38 | // if we've moved far enough along, read the next sample 39 | while (fractionalSampleOffset >= 1.0) { 40 | previousSample = currentSample; 41 | currentSample = getNextSample(); 42 | fractionalSampleOffset = fractionalSampleOffset - 1.0; 43 | } 44 | 45 | // Interpolate between the samples to reduce aliasing 46 | int sample = currentSample * fractionalSampleOffset + previousSample * (1.0-fractionalSampleOffset); 47 | 48 | fractionalSampleOffset = fractionalSampleOffset + samplesPerGet; 49 | 50 | // process volume 51 | sample = sample * volume() / 127; 52 | 53 | decDuration(); 54 | 55 | return sample; 56 | } 57 | 58 | int EnhancedSamplesGenerator::getDuration(uint16_t frequency) { 59 | // TODO this will produce an incorrect duration if the sample rate for the channel has been 60 | // adjusted to differ from the underlying audio system sample rate 61 | // At this point it's not clear how to resolve this, so we'll assume it hasn't been adjusted 62 | return !_sample ? 0 : (_sample->getSize() * 1000 / sampleRate()) / calculateSamplerate(frequency); 63 | } 64 | 65 | void EnhancedSamplesGenerator::seekTo(uint32_t position) { 66 | _sample->seekTo(position, index, blockIndex, repeatCount); 67 | 68 | // prepare our fractional sample data for playback 69 | fractionalSampleOffset = 0.0; 70 | previousSample = _sample->getSample(index, blockIndex); 71 | currentSample = _sample->getSample(index, blockIndex); 72 | } 73 | 74 | double EnhancedSamplesGenerator::calculateSamplerate(uint16_t frequency) { 75 | auto baseFrequency = _sample->baseFrequency; 76 | auto frequencyAdjust = baseFrequency > 0 ? (double)frequency / (double)baseFrequency : 1.0; 77 | return frequencyAdjust * ((double)_sample->sampleRate / (double)(sampleRate())); 78 | } 79 | 80 | int8_t EnhancedSamplesGenerator::getNextSample() { 81 | auto sample = _sample->getSample(index, blockIndex); 82 | 83 | // looping magic 84 | repeatCount--; 85 | if (repeatCount == 0) { 86 | // we've reached the end of the repeat section, so loop back 87 | seekTo(_sample->repeatStart); 88 | } 89 | 90 | return sample; 91 | } -------------------------------------------------------------------------------- /src/envelopes/adsr.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "envelopes/adsr.h" 3 | 4 | ADSRVolumeEnvelope::ADSRVolumeEnvelope(uint16_t attack, uint16_t decay, uint8_t sustain, uint16_t release) 5 | : _attack(attack), _decay(decay), _sustain(sustain), _release(release) 6 | { 7 | // attack, decay, release are time values in milliseconds 8 | // sustain is 0-255, centered on 127, and is the relative sustain level 9 | //debug_log("audioDriver: ADSRVolumeEnvelope: attack=%d, decay=%d, sustain=%d, release=%d\n\r", this->_attack, this->_decay, this->_sustain, this->_release); 10 | } 11 | 12 | uint8_t ADSRVolumeEnvelope::getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration) { 13 | // returns volume for the given elapsed time 14 | // baseVolume is the level the attack phase should reach 15 | // sustain volume level is calculated relative to baseVolume 16 | // volume for fab-gl is 0-127 but accepts higher values, so we're not clamping 17 | // a duration of -1 means we're playing forever 18 | auto phaseTime = elapsed; 19 | if (phaseTime < this->_attack) { 20 | return (phaseTime * baseVolume) / this->_attack; 21 | } 22 | phaseTime -= this->_attack; 23 | uint8_t sustainVolume = baseVolume * this->_sustain / 127; 24 | if (phaseTime < this->_decay) { 25 | return map(phaseTime, 0, this->_decay, baseVolume, sustainVolume); 26 | } 27 | phaseTime -= this->_decay; 28 | int32_t sustainDuration = duration < 0 ? elapsed : duration - (this->_attack + this->_decay); 29 | if (sustainDuration < 0) sustainDuration = 0; 30 | if (phaseTime < sustainDuration) { 31 | return sustainVolume; 32 | } 33 | phaseTime -= sustainDuration; 34 | if (phaseTime < this->_release) { 35 | return map(phaseTime, 0, this->_release, sustainVolume, 0); 36 | } 37 | return 0; 38 | } 39 | 40 | bool ADSRVolumeEnvelope::isReleasing(uint32_t elapsed, int32_t duration) { 41 | if (duration < 0) return false; 42 | auto minDuration = this->_attack + this->_decay; 43 | if (duration < minDuration) duration = minDuration; 44 | 45 | return (elapsed >= duration); 46 | } 47 | 48 | bool ADSRVolumeEnvelope::isFinished(uint32_t elapsed, int32_t duration) { 49 | if (duration < 0) return false; 50 | auto minDuration = this->_attack + this->_decay; 51 | if (duration < minDuration) duration = minDuration; 52 | 53 | return (elapsed >= duration + this->_release); 54 | } -------------------------------------------------------------------------------- /src/envelopes/ay_3_8910_envelope.cpp: -------------------------------------------------------------------------------- 1 | #include "ay_3_8910.h" 2 | #include "envelopes/ay_3_8910_envelope.h" 3 | 4 | extern AY_3_8910_Volume ay_3_8910_volume; 5 | // switch (env_shape) 6 | // { 7 | // case 0b0000: 8 | // case 0b0001: 9 | // case 0b0010: 10 | // case 0b0011: 11 | // case 0b1001: 12 | // attack = 0; 13 | // decay = (MASTER_FREQUENCY / (16*env_period)) * 1000; 14 | // sustain = 0; 15 | // release = 0; 16 | // // waveform: \_______ 17 | // break; 18 | // case 4: 19 | // case 5: 20 | // case 6: 21 | // case 7: 22 | // case 15: 23 | // // waveform: /_______ 24 | // attack = (MASTER_FREQUENCY / (16*env_period)) * 1000; 25 | // decay = 0; 26 | // sustain = 0; 27 | // release = 0; 28 | // break; 29 | // case 8: 30 | // // waveform: \\\\\\\\ 31 | // attack = 0; 32 | // decay = (MASTER_FREQUENCY / (16*env_period)) * 1000; 33 | // sustain = 0; 34 | // release = 0; 35 | // break; 36 | // case 10: 37 | // // waveform: \/\/\/\/ 38 | // break; 39 | // case 11: 40 | // // waveform: \ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 41 | // attack = 0; 42 | // decay = (MASTER_FREQUENCY / (16*env_period)) * 1000; 43 | // sustain = 0; 44 | // release = 0; 45 | // break; 46 | // case 12: 47 | // // waveform: //////// 48 | // attack = (MASTER_FREQUENCY / (16*env_period)) * 1000; 49 | // decay = 0; 50 | // sustain = 0; 51 | // release = 0; 52 | // break; 53 | // case 13: 54 | // // waveform: / ̄ ̄ ̄ ̄ ̄ ̄ ̄ 55 | // attack = (MASTER_FREQUENCY / (16*env_period)) * 1000; 56 | // decay = 0; 57 | // sustain = 255; 58 | // release = 0; 59 | // end_after_period = false; 60 | // break; 61 | // case 14: 62 | // // waveform: /\/\/\/\ 63 | // break; 64 | // } 65 | 66 | AY_3_8910_VolumeEnvelope::AY_3_8910_VolumeEnvelope(uint8_t envelope,uint16_t period) 67 | { 68 | _continue = envelope & 0b1000; 69 | _attack = envelope & 0b0100; 70 | _alternate = envelope & 0b0010; 71 | _hold = envelope & 0b0001; 72 | _period = 256.0 * float(period) / float (MASTER_FREQUENCY) * 1000.0; 73 | //debug_log("audioDriver: AY_3_8910_VolumeEnvelope: continue=%d, attack=%d, alternate=%d, hold=%d, period=%d\n\r", _continue,_attack,_alternate,_hold,_period); 74 | } 75 | 76 | uint8_t AY_3_8910_VolumeEnvelope::getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration) { 77 | uint8_t volume=0; 78 | float factor; 79 | uint8_t period_cnt=0; 80 | // returns volume for the given elapsed time 81 | // baseVolume is the level the attack phase should reach 82 | // volume for fab-gl is 0-127 but accepts higher values, so we're not clamping 83 | // a duration of -1 means we're playing forever 84 | if (elapsed<_period) 85 | { 86 | factor = ((float) elapsed)/((float) _period); 87 | if (_attack) 88 | volume = 15*factor; // waveform: / 89 | else 90 | volume = 15-15*factor; // waveform: \ 91 | 92 | _hold_volume = volume; 93 | } 94 | else 95 | { 96 | // signal releasing 97 | if (_continue) 98 | { 99 | // continue emitting sound, never finished 100 | if (_hold) 101 | { 102 | volume = _hold_volume; // waveform: _______ or  ̄ ̄ ̄ ̄ ̄ ̄ ̄ 103 | if (_alternate) 104 | volume = 15-volume; // flip 105 | } 106 | else 107 | { 108 | // calculations 109 | period_cnt = elapsed / _period; 110 | elapsed = elapsed % _period; 111 | factor = ((float) elapsed)/((float) _period); 112 | 113 | // normal waveform 114 | if (_attack) 115 | volume = 15*factor; // waveform: / 116 | else 117 | volume = 15-15*factor; // waveform: \ 118 | 119 | // alternate 120 | if (period_cnt%2 == 1 && _alternate) 121 | volume = 15-volume; 122 | } 123 | } 124 | else 125 | // stop emitting sound, signal finished 126 | volume = 0; 127 | } 128 | return ay_3_8910_volume.getAgonVolume(volume); 129 | } 130 | 131 | bool AY_3_8910_VolumeEnvelope::isReleasing(uint32_t elapsed, int32_t duration) { 132 | if (elapsed<_period) 133 | return false; 134 | else 135 | return true; 136 | } 137 | 138 | bool AY_3_8910_VolumeEnvelope::isFinished(uint32_t elapsed, int32_t duration) { 139 | if (elapsed<_period || _continue) 140 | return false; 141 | else 142 | return true; 143 | } -------------------------------------------------------------------------------- /src/envelopes/frequency.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Agon Video BIOS - Audio class 3 | // Author: Dean Belfield 4 | // Contributors: Steve Sims (enhancements for more sophisticated audio support) 5 | // S0urceror (PlatformIO compatibility) 6 | // Created: 05/09/2022 7 | // Last Updated: 08/10/2023 8 | // 9 | // Modinfo: 10 | 11 | #include "envelopes/frequency.h" 12 | 13 | SteppedFrequencyEnvelope::SteppedFrequencyEnvelope(std::shared_ptr> phases, uint16_t stepLength, bool repeats, bool cumulative, bool restrict) 14 | : _phases(phases), _stepLength(stepLength), _repeats(repeats), _cumulative(cumulative), _restrict(restrict) 15 | { 16 | _totalSteps = 0; 17 | _totalLength = 0; 18 | _totalAdjustment = 0; 19 | 20 | for (auto phase : *this->_phases) { 21 | _totalSteps += phase.number; 22 | _totalLength += phase.number * _stepLength; 23 | _totalAdjustment += (phase.number * phase.adjustment); 24 | } 25 | 26 | //debug_log("audioDriver: SteppedFrequencyEnvelope: totalSteps=%d, totalAdjustment=%d\n\r", this->_totalSteps, this->_totalAdjustment); 27 | //debug_log("audioDriver: SteppedFrequencyEnvelope: stepLength=%d, repeats=%d, restricts=%d, totalLength=%d\n\r", this->_stepLength, this->_repeats, this->_restrict, _totalLength); 28 | } 29 | 30 | uint16_t SteppedFrequencyEnvelope::getFrequency(uint16_t baseFrequency, uint32_t elapsed, int32_t duration) { 31 | // returns frequency for the given elapsed time 32 | // a duration of -1 means we're playing forever 33 | auto currentStep = (elapsed / this->_stepLength) % this->_totalSteps; 34 | auto loopCount = elapsed / this->_totalLength; 35 | 36 | if (!_repeats && loopCount > 0) { 37 | // we're not repeating and we've finished the envelope 38 | return baseFrequency + this->_totalAdjustment; 39 | } 40 | 41 | // otherwise we need to calculate the frequency 42 | int32_t frequency = baseFrequency; 43 | 44 | if (_cumulative) { 45 | frequency += (loopCount * _totalAdjustment); 46 | } 47 | 48 | for (auto phase : *this->_phases) { 49 | if (currentStep < phase.number) { 50 | frequency += (currentStep * phase.adjustment); 51 | break; 52 | } else { 53 | frequency += (phase.number * phase.adjustment); 54 | } 55 | 56 | currentStep -= phase.number; 57 | } 58 | 59 | if (_restrict) { 60 | if (frequency < 0) { 61 | return 0; 62 | } else if (frequency > 65535) { 63 | return 0; 64 | } 65 | } 66 | 67 | return frequency; 68 | } 69 | 70 | bool SteppedFrequencyEnvelope::isFinished(uint32_t elapsed, int32_t duration) { 71 | if (_repeats) { 72 | // a repeating frequency envelope never finishes 73 | return false; 74 | } 75 | 76 | return elapsed >= _totalLength; 77 | } 78 | -------------------------------------------------------------------------------- /src/envelopes/multiphase_adsr.cpp: -------------------------------------------------------------------------------- 1 | #include "envelopes/multiphase_adsr.h" 2 | 3 | MultiphaseADSREnvelope::MultiphaseADSREnvelope(std::shared_ptr> attack, std::shared_ptr> sustain, std::shared_ptr> release) 4 | : _attack(attack), _sustain(sustain), _release(release) 5 | { 6 | _attackDuration = 0; 7 | // add durations from attack subphases to attackDuration 8 | for (const auto& subPhase : *_attack) { 9 | _attackDuration += subPhase.duration; 10 | } 11 | _sustainSubphases = _sustain->size(); 12 | _sustainDuration = 0; 13 | for (const auto& subPhase : *_sustain) { 14 | _sustainDuration += subPhase.duration; 15 | } 16 | _releaseDuration = 0; 17 | for (const auto& subPhase : *_release) { 18 | _releaseDuration += subPhase.duration; 19 | } 20 | _attackLevel = 127; 21 | // set attackLevel to last level from attack vector, if there are values 22 | if (!_attack->empty()) { 23 | _attackLevel = _attack->back().level; 24 | } 25 | _sustainLevel = 127; 26 | if (!_sustain->empty()) { 27 | _sustainLevel = _sustain->back().level; 28 | } 29 | _releaseLevel = 0; 30 | if (!_release->empty()) { 31 | _releaseLevel = _release->back().level; 32 | } 33 | _sustainLoops = false; 34 | // loop over our sustain entries, and if we have any non-zero duration values set sustainLoops to true 35 | for (const auto& subPhase : *_sustain) { 36 | if (subPhase.duration > 0) { 37 | _sustainLoops = true; 38 | break; 39 | } 40 | } 41 | //debug_log("MultiphaseADSREnvelope created with %d attack, %d sustain, %d release phases\n\r", _attack->size(), _sustain->size(), _release->size()); 42 | //debug_log(" attackDuration %d, sustainDuration %d, releaseDuration %d\n\r", _attackDuration, _sustainDuration, _releaseDuration); 43 | //debug_log(" attackLevel %d, sustainLevel %d, releaseLevel %d\n\r", _attackLevel, _sustainLevel, _releaseLevel); 44 | // for (auto subPhase : *this->_attack) { 45 | // debug_log(" level %d, duration %d\n\r", subPhase.level, subPhase.duration); 46 | // } 47 | } 48 | 49 | uint8_t MultiphaseADSREnvelope::getVolume(uint8_t baseVolume, uint32_t elapsed, int32_t duration) { 50 | auto subPhasePos = elapsed; 51 | uint32_t pos = 0; 52 | uint8_t startVolume = 0; 53 | // work out what sub-phase we're on, based on our elapsed time 54 | if (subPhasePos < _attackDuration) { 55 | // we're in an attack sub-phase 56 | for (const auto& subPhase : *this->_attack) { 57 | if (subPhasePos < subPhase.duration) { 58 | // we're inside this sub-phase 59 | return map(subPhasePos, 0, subPhase.duration, startVolume, getTargetVolume(baseVolume, subPhase.level)); 60 | } 61 | subPhasePos -= subPhase.duration; 62 | startVolume = getTargetVolume(baseVolume, subPhase.level); 63 | } 64 | } else { 65 | startVolume = getTargetVolume(baseVolume, _attackLevel); 66 | } 67 | subPhasePos -= _attackDuration; 68 | pos += _attackDuration; 69 | 70 | auto sustainVolume = getTargetVolume(baseVolume, _sustainLevel); 71 | if (_sustainLoops) { 72 | // if we have sustain data, and it's not just zero duration, then loop around it 73 | while (pos < duration) { 74 | // short-cut looping sub-phases if we can for complete loops 75 | if (subPhasePos > _sustainDuration) { 76 | subPhasePos -= _sustainDuration; 77 | pos += _sustainDuration; 78 | startVolume = sustainVolume; 79 | } else { 80 | for (auto subPhase : *this->_sustain) { 81 | if (subPhasePos < subPhase.duration) { 82 | // we're in this sub-phase 83 | return map(subPhasePos, 0, subPhase.duration, startVolume, getTargetVolume(baseVolume, subPhase.level)); 84 | } 85 | subPhasePos -= subPhase.duration; 86 | pos += subPhase.duration; 87 | startVolume = getTargetVolume(baseVolume, subPhase.level); 88 | } 89 | } 90 | } 91 | } else if (elapsed < duration) { 92 | // non-looping sustain - so we're spreading time between the phases, if there are any 93 | if (_sustainSubphases <= 1) { 94 | return map(subPhasePos, 0, duration - _attackDuration, startVolume, sustainVolume); 95 | } 96 | int phaseDuration = (duration - _attackDuration) / _sustainSubphases; 97 | for (auto subPhase : *this->_sustain) { 98 | if (subPhasePos < phaseDuration) { 99 | // we're in this subphase 100 | return map(subPhasePos, 0, phaseDuration, startVolume, getTargetVolume(baseVolume, subPhase.level)); 101 | } 102 | subPhasePos -= phaseDuration; 103 | startVolume = getTargetVolume(baseVolume, subPhase.level); 104 | } 105 | return startVolume; 106 | } else { 107 | // end of sustain reached for non-looping sustain, so adjust our subPhasePos to our time within release 108 | subPhasePos = elapsed - duration; 109 | startVolume = sustainVolume; 110 | } 111 | 112 | // work out our release phase volume 113 | for (auto subPhase : *this->_release) { 114 | if (subPhasePos < subPhase.duration) { 115 | return map(subPhasePos, 0, subPhase.duration, startVolume, getTargetVolume(baseVolume, subPhase.level)); 116 | } 117 | subPhasePos -= subPhase.duration; 118 | startVolume = getTargetVolume(baseVolume, subPhase.level); 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | bool MultiphaseADSREnvelope::isReleasing(uint32_t elapsed, int32_t duration) { 125 | if (duration < 0) return false; 126 | auto minDuration = this->_attackDuration; 127 | if (duration < minDuration) duration = minDuration; 128 | 129 | // NB this is an approximation. we may not actually be using "release" phase of envelope 130 | // but we'll consider ourselves to be in the "release" if the following check is true 131 | // this is good enough for the channel state machine, as the isFinished check is correct 132 | return (elapsed >= duration); 133 | } 134 | 135 | bool MultiphaseADSREnvelope::isFinished(uint32_t elapsed, int32_t duration) { 136 | if (duration < 0) return false; 137 | 138 | // we're finished if we have reached the end of sustain and then end of release 139 | auto minDuration = _attackDuration + _sustainDuration; 140 | if (_sustainDuration != 0) { 141 | while ((minDuration + _sustainDuration) <= duration) { 142 | minDuration += _sustainDuration; 143 | } 144 | } 145 | 146 | if (duration < minDuration) duration = minDuration; 147 | 148 | return (elapsed >= duration + this->_releaseDuration); 149 | } 150 | 151 | uint8_t MultiphaseADSREnvelope::getTargetVolume(uint8_t baseVolume, uint8_t level) { 152 | return baseVolume * level / 127; 153 | } -------------------------------------------------------------------------------- /src/eos.cpp: -------------------------------------------------------------------------------- 1 | #include "hal.h" 2 | #include "eos.h" 3 | 4 | bool eos_running = false; 5 | 6 | bool eos_wakeup () 7 | { 8 | // for 5 seconds 9 | // send ESC until we get CTRL-Z back 10 | for (int i=0;i<50;i++) 11 | { 12 | ez80_serial.write (ESC); 13 | if (ez80_serial.available() > 0) 14 | { 15 | // read character 16 | byte c = ez80_serial.read(); 17 | if (c==CTRL_Z) 18 | { 19 | eos_running = true; 20 | return true; 21 | } 22 | } 23 | delay (100); 24 | } 25 | return false; 26 | } -------------------------------------------------------------------------------- /src/hal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: Electron - HAL 3 | * a playful alternative to Quark 4 | * quarks and electrons combined are matter. 5 | * Author: Mario Smit (S0urceror) 6 | */ 7 | 8 | // Hardware Abstraction Layer 9 | 10 | #include "hal.h" 11 | 12 | HardwareSerial host_serial(0); 13 | fabgl::Terminal* fabgl_terminal=nullptr; 14 | 15 | void hal_ez80_serial_init () 16 | { 17 | ez80_serial.end(); 18 | ez80_serial.begin(UART_BR, SERIAL_8N1, UART_RX, UART_TX); 19 | ez80_serial.setPins(UART_RX, UART_TX, UART_CTS, UART_RTS); // Must be called after begin 20 | hal_ez80_serial_half_duplex(); 21 | } 22 | void hal_ez80_serial_half_duplex () 23 | { 24 | ez80_serial.setHwFlowCtrlMode(HW_FLOWCTRL_RTS); // default to half-duplex 25 | } 26 | void hal_ez80_serial_full_duplex () 27 | { 28 | ez80_serial.setHwFlowCtrlMode(HW_FLOWCTRL_CTS_RTS); // set to full-duplex when EZ80 OS indicates this 29 | } 30 | void hal_hostpc_serial_init () 31 | { 32 | host_serial.begin(500000, SERIAL_8N1, 3, 1); 33 | } 34 | void hal_set_terminal (fabgl::Terminal* term) 35 | { 36 | fabgl_terminal = term; 37 | } 38 | char hal_hostpc_serial_read () 39 | { 40 | if (host_serial.available()) { 41 | return host_serial.read(); 42 | } 43 | return 0; 44 | } 45 | 46 | void _hal_printf (uint8_t dest, const char* format, va_list ap) { 47 | char buf[255]; 48 | vsnprintf(buf, sizeof(buf), format, ap); 49 | // print to host PC and/or fabgl terminal 50 | if (dest & 0b00000010) 51 | #ifdef USERSPACE 52 | debug_log (buf); 53 | #else 54 | host_serial.print(buf); 55 | #endif 56 | 57 | if (fabgl_terminal!=nullptr && (dest & 0b00000001)) 58 | fabgl_terminal->write (buf); 59 | } 60 | 61 | // print both on the Terminal and the connecting Host via serial (if any) 62 | void hal_printf (const char* format, ...) { 63 | va_list args; 64 | va_start(args,format); 65 | _hal_printf (0b11,format,args); 66 | va_end (args); 67 | } 68 | 69 | void hal_hostpc_printf (const char* format, ...) { 70 | va_list args; 71 | va_start(args,format); 72 | _hal_printf (0b10,format,args); 73 | va_end (args); 74 | } 75 | 76 | void hal_terminal_printf (const char* format, ...) { 77 | va_list args; 78 | va_start(args,format); 79 | _hal_printf (0b01,format,args); 80 | va_end (args); 81 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: Electron - HAL 3 | * a playful alternative to Quark 4 | * quarks and electrons combined are matter. 5 | * Author: Mario Smit (S0urceror) 6 | * Credits: Dean Belfield 7 | * Koenraad Van Nieuwenhove (Cocoacrumbs) 8 | * Created: 01/03/2023 9 | */ 10 | 11 | #include "fabgl.h" 12 | #include "globals.h" 13 | #include "hal.h" 14 | #include "zdi.h" 15 | #pragma message ("Building a Electron OS compatible version of Electron HAL") 16 | #include "eos.h" 17 | #include "tms9918.h" 18 | #include "ay_3_8910.h" 19 | #include "sn76489an.h" 20 | #include "ppi-8255.h" 21 | #include "agon_audio.h" 22 | #include "updater.h" 23 | 24 | fabgl::PS2Controller ps2; 25 | fabgl::VGABaseController* display = nullptr; 26 | fabgl::Terminal* terminal; 27 | TMS9918 vdp; 28 | AY_3_8910 psg; 29 | SN76489AN psg2; 30 | PPI8255* ppi=NULL; 31 | bool ignore_escape = false; 32 | bool display_mode_direct=false; 33 | TaskHandle_t mainTaskHandle; 34 | vdu_updater updater; 35 | 36 | #define VDU_SYSTEM 0 37 | #define VDU_GP 0x80 38 | #define VDU_CURSOR 0x82 39 | #define VDU_SCRCHAR 0x83 40 | #define VDU_MODE 0x86 41 | #define VDU_UPDATE 0xA1 42 | #define OS_UNKNOWN 0 43 | #define OS_MOS 1 44 | #define OS_MOS_FULLDUPLEX 2 45 | #define OS_ELECTRON 128 46 | #define OS_ELECTRON_SG1000 129 47 | #define OS_ELECTRON_MSX 130 48 | 49 | uint8_t os_identifier = OS_UNKNOWN; 50 | 51 | #define PACKET_GP 0x00 // General poll data 52 | #define PACKET_KEYCODE 0x01 53 | #define PACKET_CURSOR 0x02 54 | #define PACKET_SCRCHAR 0x03 55 | #define PACKET_MODE 0x06 56 | 57 | void mos_send_packet(uint8_t code, uint16_t len, uint8_t data[]) { 58 | ez80_serial.write(code + 0x80); 59 | ez80_serial.write(len); 60 | for (int i = 0; i < len; i++) { 61 | ez80_serial.write(data[i]); 62 | } 63 | } 64 | 65 | void boot_screen() 66 | { 67 | // initialize terminal 68 | terminal->write("\e[44;37m"); // background: blue, foreground: white 69 | terminal->write("\e[2J"); // clear screen 70 | terminal->write("\e[1;1H"); // move cursor to 1,1 71 | 72 | hal_printf("\r\nElectron - HAL - version %d.%d.%d\r\n",HAL_major,HAL_minor,HAL_revision); 73 | } 74 | 75 | void set_display_direct () 76 | { 77 | if (display_mode_direct) 78 | return; 79 | display_mode_direct = true; 80 | // destroy old display instance 81 | if (display) 82 | { 83 | terminal->deactivate(); 84 | // destroy old display 85 | display->end(); 86 | delete display; 87 | display = nullptr; 88 | // destroy old terminal 89 | //terminal->end(); 90 | // remove terminal from HAL 91 | hal_set_terminal (nullptr); 92 | // TODO: commented out below because deleting terminal turns off SoundGenerator 93 | //delete terminal; 94 | } 95 | // create new display instance 96 | display = new fabgl::VGADirectController (); 97 | 98 | // tell vdp to use the new display 99 | vdp.set_display((fabgl::VGADirectController*) display); 100 | 101 | // setup new display 102 | display->begin(); 103 | display->setResolution(QVGA_320x240_60Hz); 104 | 105 | //hal_hostpc_printf ("display set to DIRECT\r\n"); 106 | } 107 | 108 | void set_display_normal (bool force) 109 | { 110 | if (!force && !display_mode_direct) 111 | return; 112 | 113 | display_mode_direct = false; 114 | 115 | if (display) 116 | { 117 | display->end(); 118 | delete display; 119 | display = nullptr; 120 | // tell vdp to not use the display 121 | vdp.set_display((fabgl::VGADirectController*) nullptr); 122 | } 123 | // create new display instance 124 | display = new fabgl::VGA16Controller (); 125 | display->begin(); 126 | display->setResolution(VGA_640x480_60Hz); 127 | 128 | // setup terminal 129 | terminal = new fabgl::Terminal (); 130 | terminal->begin(display); 131 | terminal->enableCursor(true); 132 | 133 | // let hal know what our terminal is 134 | hal_set_terminal (terminal); 135 | 136 | // hello world 137 | boot_screen (); 138 | } 139 | 140 | void do_serial_hostpc () 141 | { 142 | byte ch; 143 | while (true) 144 | { 145 | // characters in the buffer? 146 | if((ch=hal_hostpc_serial_read())>0) 147 | { 148 | if (!zdi_mode() && ch==CTRL_Z) 149 | { 150 | // CTRL-Z? 151 | zdi_enter(); 152 | } 153 | else if (ch==CTRL_Y) 154 | { 155 | // CTRL-Y 156 | vdp.toggle_enable (); 157 | } 158 | else if (ch==CTRL_X) 159 | { 160 | // CTRL-X 161 | //vdp.cycle_screen2_debug (); 162 | if (display_mode_direct) 163 | set_display_normal (); 164 | else 165 | set_display_direct (); 166 | } 167 | else 168 | { 169 | if (zdi_mode()) 170 | // handle keys on the ESP32 171 | zdi_process_cmd (ch); 172 | else 173 | { 174 | // MOS wants a whole packet for 1 character 175 | // allows to detect special key combo's 176 | if (os_identifier==OS_MOS || os_identifier==OS_MOS_FULLDUPLEX) 177 | { 178 | uint8_t packet[] = { 179 | ch, 180 | 0, 181 | 0, 182 | 1, 183 | }; 184 | mos_send_packet(PACKET_KEYCODE, sizeof packet, packet); 185 | } 186 | if (os_identifier==OS_ELECTRON) 187 | { 188 | // ElectronOS is simpler, just send the character 189 | ez80_serial.write(ch); 190 | } 191 | if (os_identifier==OS_ELECTRON_MSX) 192 | { 193 | if (ppi) 194 | ppi->record_keypress (ch,0,0,true); 195 | } 196 | } 197 | } 198 | } 199 | else 200 | break; 201 | } 202 | } 203 | 204 | void do_keys_ps2 () 205 | { 206 | fabgl::Keyboard *kb = ps2.keyboard(); 207 | fabgl::VirtualKeyItem item; 208 | 209 | if(kb->getNextVirtualKey(&item, 0)) 210 | { 211 | uint8_t modifier = item.CTRL << 0 | 212 | item.SHIFT << 1 | 213 | item.LALT << 2 | 214 | item.RALT << 3 | 215 | item.CAPSLOCK << 4 | 216 | item.NUMLOCK << 5 | 217 | item.SCROLLLOCK << 6 | 218 | item.GUI << 7; 219 | 220 | // MOS wants a whole packet 221 | // allows to detect special key combo's 222 | switch (os_identifier) 223 | { 224 | case OS_MOS: 225 | case OS_MOS_FULLDUPLEX: 226 | { 227 | uint8_t packet[] = { 228 | item.ASCII, 229 | modifier, 230 | item.vk, 231 | item.down, 232 | }; 233 | mos_send_packet(PACKET_KEYCODE, sizeof packet, packet); 234 | break; 235 | } 236 | case OS_ELECTRON_SG1000: 237 | { 238 | if (ppi) 239 | { 240 | // record keypress, EZ80 will fetch this later in a separate PPI IN/OUT operation 241 | ppi->record_keypress (item.ASCII,modifier,item.vk,item.down); 242 | } 243 | break; 244 | } 245 | case OS_ELECTRON_MSX: 246 | { 247 | if (ppi) 248 | { 249 | // record keypress 250 | uint8_t row = ppi->record_keypress (item.ASCII,modifier,item.vk,item.down); 251 | // uint8_t bits = ppi->get_row_bits (row); 252 | // immediately send it over to ElectronOS 253 | // ez80_serial.write(0b11000000); // signals command mode 254 | // ez80_serial.write(row); // keyboard matrix row 255 | // ez80_serial.write(bits); // new keyboard matrix bits 256 | //hal_terminal_printf ("row:%d bits:%02x\r\n",row,bits); 257 | } 258 | break; 259 | } 260 | case OS_ELECTRON: 261 | { 262 | // ElectronOS gets just the ASCII code 263 | if (item.ASCII!=0) 264 | { 265 | if (item.down) 266 | ez80_serial.write(item.ASCII); 267 | } 268 | break; 269 | } 270 | 271 | } 272 | } 273 | } 274 | 275 | void setup() 276 | { 277 | mainTaskHandle = xTaskGetCurrentTaskHandle(); 278 | 279 | // Disable the watchdog timers 280 | disableCore0WDT(); delay(200); 281 | disableCore1WDT(); delay(200); 282 | 283 | // setup connection from ESP to EZ80 284 | hal_ez80_serial_init(); 285 | 286 | // setup keyboard/PS2 287 | ps2.begin(PS2Preset::KeyboardPort0, KbdMode::CreateVirtualKeysQueue); 288 | 289 | // setup VGA display 290 | set_display_normal (true); 291 | 292 | // setup audio driver 293 | initAudio (); 294 | 295 | // setup serial to hostpc 296 | hal_hostpc_serial_init (); 297 | 298 | // boot screen and wait for ElectronOS on EZ80 299 | boot_screen(); 300 | // if (!eos_wakeup ()) 301 | // { 302 | // hal_printf ("Electron - OS - not running\r\n"); 303 | // while (1) 304 | // do_serial_hostpc(); // wait for ZDI mode hotkey to reprogram ElectronOS 305 | // } 306 | } 307 | 308 | // Minimal implementation just to make ElectronHAL somewhat compatible with Quark MOS 309 | // ElectronOS has implemented PACKET_GP to make it operable with Quark VDP. 310 | // ElectronOS sends a 2 while MOS sends a 1 in this phase. We use this to distinguish both 311 | void process_vdu () 312 | { 313 | uint8_t mode,cmd,new_os_identifier; 314 | // read VDU mode ID 315 | ez80_serial.readBytes(&mode,1); 316 | switch (mode) 317 | { 318 | case VDU_SYSTEM: 319 | ez80_serial.readBytes(&cmd,1); 320 | switch (cmd) 321 | { 322 | case VDU_GP: 323 | // read EZ80 OS identifier 324 | // MOS always sends a 1 325 | // ElectronOS sends a 2, and mimics this behaviour to be somewhat compatible with MOS/VDP 326 | ez80_serial.readBytes(&new_os_identifier,1); 327 | if (new_os_identifier!=os_identifier) 328 | { 329 | os_identifier = new_os_identifier; 330 | if (os_identifier==OS_MOS) 331 | { 332 | hal_ez80_serial_half_duplex (); 333 | hal_printf ("MOS personality activated [HD]\r\n"); 334 | } 335 | if (os_identifier==OS_MOS_FULLDUPLEX) 336 | { 337 | hal_ez80_serial_full_duplex (); 338 | hal_printf ("MOS personality activated [FD]\r\n"); 339 | } 340 | if (os_identifier==OS_ELECTRON) 341 | { 342 | hal_ez80_serial_full_duplex (); 343 | // default, not needed to show this 344 | //hal_printf ("ElectronOS personality activated\r\n"); 345 | } 346 | if (os_identifier==OS_ELECTRON_MSX) 347 | { 348 | hal_ez80_serial_full_duplex (); 349 | hal_printf ("MSX personality activated\r\n"); 350 | psg.init (); 351 | // instantiate correct PPI 352 | if (ppi) 353 | delete ppi; 354 | ppi = new MSX_PPI8255 (); 355 | } 356 | if (os_identifier==OS_ELECTRON_SG1000) 357 | { 358 | hal_ez80_serial_full_duplex (); 359 | hal_printf ("SG1000 personality activated\r\n"); 360 | psg2.init (); 361 | // instantiate correct PPI 362 | if (ppi) 363 | delete ppi; 364 | ppi = new SG1000_PPI8255 (); 365 | } 366 | } 367 | mos_send_packet(PACKET_GP, sizeof(new_os_identifier), &new_os_identifier); // only MOS packet that ElectronOS understands 368 | break; 369 | case VDU_CURSOR: 370 | { 371 | uint8_t cursor_packet[] = { 1, 1 }; 372 | mos_send_packet(PACKET_CURSOR, sizeof(cursor_packet), cursor_packet); 373 | break; 374 | } 375 | case VDU_SCRCHAR: 376 | { 377 | // VDU 23, 0, &83, x; y; 378 | uint16_t x,y; 379 | uint8_t scrchar=0; 380 | ez80_serial.readBytes((uint8_t*) &x,2); 381 | ez80_serial.readBytes((uint8_t*) &y,2); 382 | // if this is requested after unlocking the updater 383 | // then it's verify the 'unlocked!' response 384 | if (!updater.locked) 385 | scrchar = updater.get_unlock_response(x-8); 386 | uint8_t packet[] = { 387 | scrchar, 388 | }; 389 | mos_send_packet(PACKET_SCRCHAR, sizeof packet, packet); 390 | break; 391 | } 392 | case VDU_MODE: 393 | { 394 | int canvasW = 640; 395 | int canvasH = 480; 396 | int fontW = 8,fontH = 8; 397 | uint8_t mode_packet[] = { 398 | (uint8_t) (canvasW & 0xFF), // Width in pixels (L) 399 | (uint8_t) ((canvasW >> 8) & 0xFF), // Width in pixels (H) 400 | (uint8_t) (canvasH & 0xFF), // Height in pixels (L) 401 | (uint8_t) ((canvasH >> 8) & 0xFF), // Height in pixels (H) 402 | (uint8_t) (canvasW / fontW), // Width in characters (byte) 403 | (uint8_t) (canvasH / fontH), // Height in characters (byte) 404 | 64, // Colour depth 405 | 0, // The video mode number 406 | }; 407 | mos_send_packet(PACKET_MODE, sizeof mode_packet, mode_packet); 408 | break; 409 | } 410 | case VDU_UPDATE: 411 | { 412 | uint8_t update_mode; 413 | ez80_serial.readBytes(&update_mode,1); 414 | updater.command(update_mode); 415 | break; 416 | } 417 | default: 418 | hal_printf ("Unknown VDU system command:%d",cmd); 419 | break; 420 | } 421 | break; 422 | default: 423 | hal_printf ("Unknown VDU mode:%d",mode); 424 | break; 425 | } 426 | } 427 | 428 | void process_character (byte c) 429 | { 430 | //hal_hostpc_printf ("%02X",c); 431 | 432 | if (ignore_escape) 433 | { 434 | // absorb 2 byte and 3 byte (0x78,0x79) escape codes 435 | if (!(c==0x78 || c==0x79)) 436 | ignore_escape=false; 437 | return; 438 | } 439 | if (c>=0x20 && c<=0x7f) 440 | { 441 | // normal ASCII? 442 | hal_printf ("%c",c); 443 | } 444 | else 445 | { 446 | // special character or ElectronOS escape code 447 | switch (c) 448 | { 449 | case '\r': 450 | case '\n': 451 | case 0x07: // BELL 452 | hal_printf ("%c",c); 453 | break; 454 | case 0x08: // backspace 455 | hal_printf ("\b \b"); 456 | break; 457 | case 0x09: // TAB 458 | hal_printf ("%c",c); 459 | break; 460 | case 0x0c: // formfeed 461 | terminal->clear (); 462 | break; 463 | case CTRL_W: 464 | break; 465 | case CTRL_Z: 466 | break; 467 | case ESC: 468 | ignore_escape=true; 469 | break; 470 | default: 471 | hal_printf ("ERR:0x%02X;",c); 472 | break; 473 | } 474 | } 475 | } 476 | 477 | void process_virtual_io_cmd (uint8_t cmd) 478 | { 479 | // strip high bit 480 | uint8_t subcmd = (cmd & 0b01100000) >> 5; 481 | uint8_t port,value; 482 | uint8_t normal_out_req[2],repeatable_io_req[3],fillable_io_req[4]; 483 | int repeat_length,i; 484 | 485 | switch (subcmd) 486 | { 487 | case 0b00: // IO requests take 3 bytes 488 | switch (cmd&0b00000111) 489 | { 490 | case 0b000: 491 | // OUTput, no repeat, no fill 492 | // single OUTput 493 | if (ez80_serial.readBytes (normal_out_req,2)==2) 494 | { 495 | port = normal_out_req[0]; 496 | value = normal_out_req[1]; 497 | 498 | if (os_identifier==OS_ELECTRON_MSX) 499 | { 500 | if (port==0x98 || port==0x99) // MSX TMS9918 501 | vdp.write (port-0x98,value); 502 | if (port==0xa0 || port==0xa1) // MSX AY-3-8910 503 | psg.write (port,value); 504 | if (port==0xa8 || port==0xa9 || port==0xaa|| port==0xab) 505 | ppi->write ( port-0xa8,value); 506 | if (port==0x90 || port==0x91 || port==0x92|| port==0x93) 507 | hal_printf ("printer port not yet supported"); 508 | } 509 | if (os_identifier==OS_ELECTRON_SG1000) 510 | { 511 | if (port==0xBE || port==0xBF) // SG-1000 TMS9918 512 | vdp.write (port-0xbe,value); 513 | if (port==0x7e || port==0x7f) // SG-1000 SN76489AN 514 | psg2.write (value); 515 | if (port==0xdc || port==0xdd || port==0xde|| port==0xdf) 516 | ppi->write (port-0xdc,value); 517 | } 518 | //hal_printf ("OUT (%02X), %02X\r\n",port,value); 519 | } 520 | break; 521 | case 0b010: 522 | if (os_identifier!=OS_ELECTRON_MSX) // not supported 523 | break; 524 | // OUTput, LDIRVM, multiple times, different values 525 | if (ez80_serial.readBytes (repeatable_io_req,3)==3) 526 | { 527 | port = repeatable_io_req[0]; 528 | repeat_length = (repeatable_io_req[1]<<8) + repeatable_io_req[2]; 529 | //hal_printf ("%02X,%02X,%02X - OUT repeat %d times\r\n",repeatable_io_req[0],repeatable_io_req[1],repeatable_io_req[2],repeat_length); 530 | for (i=0;iread (port-0xa8); 575 | if (port==0x90 || port==0x91 || port==0x92|| port==0x93) 576 | hal_printf ("printer port not yet supported"); 577 | } 578 | if (os_identifier==OS_ELECTRON_SG1000) 579 | { 580 | if (port==0xbe || port==0xbf) 581 | value = vdp.read (port-0xbe); 582 | if (port==0xdc || port==0xdd || port==0xde|| port==0xdf) 583 | value = ppi->read (port-0xdc); 584 | } 585 | ez80_serial.write(value); 586 | //hal_printf ("IN A,(%02X) => %02X\r\n",port,value); 587 | } 588 | break; 589 | case 0b011: 590 | if (os_identifier!=OS_ELECTRON_MSX) // not supported 591 | break; 592 | // INput, LDIRMV, multiple times, multiple value 593 | if (ez80_serial.readBytes (repeatable_io_req,3)==3) 594 | { 595 | port = repeatable_io_req[0]; 596 | repeat_length = (repeatable_io_req[1]<<8) + repeatable_io_req[2]; 597 | //hal_printf ("%02Xh,%02Xh,%02Xh - IN repeat %d times\r\n",repeatable_io_req[0],repeatable_io_req[1],repeatable_io_req[2],repeat_length); 598 | for (i=0;i %02Xh\r\n",port,value); 601 | if (port==0x98 || port==0x99) 602 | value = vdp.read (port-0x98); 603 | // if (port==0xa2) 604 | // value = psg.read (port); 605 | if (port==0xa9) 606 | { 607 | ppi->write (0xaa-0xa8,i); 608 | value = ppi->read (port-0xa8); 609 | } 610 | ez80_serial.write(value); 611 | } 612 | //hal_printf ("IN (%02Xh), xx - %d repeated\r\n",port,i); 613 | } 614 | break; 615 | } 616 | break; 617 | default: 618 | hal_printf ("Unsupported in/out to: 0x%02X\r\n",cmd); 619 | break; 620 | } 621 | } 622 | 623 | void do_serial_ez80 () 624 | { 625 | int read_character; 626 | while (true) 627 | { 628 | // check if ez80 has send us something 629 | read_character = ez80_serial.read(); 630 | if (read_character!=-1) 631 | { 632 | // read character 633 | uint8_t ch = (uint8_t) read_character; 634 | if (ch & 0x80 /*0b10000000*/) 635 | process_virtual_io_cmd (ch); 636 | else if (ch==CTRL_W) 637 | process_vdu (); 638 | else 639 | process_character (ch); 640 | } 641 | else 642 | break; 643 | } 644 | } 645 | 646 | void loop() 647 | { 648 | // first check if EZ80 has send something and handle this completely 649 | do_serial_ez80(); 650 | // then check keyboard and serial interface 651 | do_keys_ps2(); 652 | do_serial_hostpc(); 653 | } 654 | -------------------------------------------------------------------------------- /src/mos.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Title: Electron - HAL 4 | * a playful alternative to Quark 5 | * quarks and electrons combined are matter. 6 | * Author: Mario Smit (S0urceror) 7 | */ 8 | 9 | // MOS interoperability layer (not complete) 10 | 11 | #include "mos.h" 12 | 13 | #ifdef MOS_COMPATIBILITY 14 | 15 | uint8_t col,row; 16 | 17 | void mos_init () 18 | { 19 | col=1; 20 | row=1; 21 | } 22 | 23 | // Send a packet of data to the MOS 24 | // 25 | void mos_send_packet(byte code, byte len, byte data[]) { 26 | ez80_serial.write(code + 0x80); 27 | ez80_serial.write(len); 28 | for(int i = 0; i < len; i++) { 29 | ez80_serial.write(data[i]); 30 | } 31 | } 32 | 33 | void mos_send_general_poll () 34 | { 35 | byte uv = ez80_serial.read(); 36 | byte packet[] = { 37 | uv 38 | }; 39 | mos_send_packet(PACKET_GP, sizeof packet, packet); 40 | } 41 | 42 | void mos_send_vdp_mode () 43 | { 44 | byte packet[] = { 45 | 0x80, // Width in pixels (L) 46 | 0x02, // Width in pixels (H) 47 | 0xe0, // Height in pixels (L) 48 | 0x01, // Height in pixels (H) 49 | 80 , // Width in characters (byte) 50 | 60 , // Height in characters (byte) 51 | 64, // Colour depth 52 | }; 53 | mos_send_packet(PACKET_MODE, sizeof packet, packet); 54 | } 55 | void mos_send_cursor_pos () 56 | { 57 | byte packet[] = { 58 | (byte) (col), 59 | (byte) (row), 60 | }; 61 | //hal_printf ("x:%d,y:%d",x,y); 62 | mos_send_packet(PACKET_CURSOR, sizeof packet, packet); 63 | } 64 | void mos_send_character (byte ch) 65 | { 66 | byte packet[] = { 67 | ch, 68 | 0, 69 | }; 70 | // send to MOS on the EZ80 71 | mos_send_packet(PACKET_KEYCODE, sizeof packet, packet); 72 | } 73 | void mos_send_virtual_key (fabgl::VirtualKeyItem item) 74 | { 75 | byte keycode = 0; // Last pressed key code 76 | byte modifiers = 0; // Last pressed key modifiers 77 | // send to MOS on the EZ80 78 | modifiers = 79 | item.CTRL << 0 | 80 | item.SHIFT << 1 | 81 | item.LALT << 2 | 82 | item.RALT << 3 | 83 | item.CAPSLOCK << 4 | 84 | item.NUMLOCK << 5 | 85 | item.SCROLLLOCK << 6 | 86 | item.GUI << 7 87 | ; 88 | byte packet[] = { 89 | item.ASCII, 90 | modifiers, 91 | item.vk, 92 | item.down, 93 | }; 94 | mos_send_packet(PACKET_KEYCODE, sizeof packet, packet); 95 | } 96 | void mos_col_left () 97 | { 98 | if (col>1) 99 | col--; 100 | } 101 | void mos_col_right () 102 | { 103 | col++; 104 | } 105 | void mos_set_column (uint8_t c) 106 | { 107 | col = c; 108 | } 109 | void mos_handle_escape_code () 110 | { 111 | byte c; 112 | if ((c=ez80_serial.read())==0) 113 | { 114 | c=((byte) ez80_serial.read()); 115 | switch (c) 116 | { 117 | case 0x80: // general poll 118 | mos_send_general_poll (); 119 | break; 120 | case 0x86: // VDP_MODE 121 | mos_send_vdp_mode (); 122 | break; 123 | case 0x82: // VDP_CURSOR 124 | mos_send_cursor_pos (); 125 | break; 126 | default: 127 | hal_printf ("VDP:0x%02X;",c); 128 | break; 129 | } 130 | } 131 | else 132 | hal_printf ("MOS:0x%02X;",c); 133 | 134 | 135 | } 136 | 137 | #endif -------------------------------------------------------------------------------- /src/ppi-8255.cpp: -------------------------------------------------------------------------------- 1 | #include "ppi-8255.h" 2 | #include "hal.h" 3 | #include 4 | 5 | ascii_to_vk_modifier ascii_to_vk[] = { 6 | {0,0}, // 0 7 | {0,0}, // 1 8 | {0,0}, // 2 9 | {0,0}, // 3 10 | {0,0}, // 4 11 | {0,0}, // 5 12 | {0,0}, // 6 13 | {0,0}, // 7 14 | {fabgl::VirtualKey::VK_BACKSPACE,0}, // 8 15 | {fabgl::VirtualKey::VK_TAB,0}, // 9 16 | {fabgl::VirtualKey::VK_RETURN,0}, // 10 - LF 17 | {0,0}, // 11 18 | {0,0}, // 12 19 | {0,0}, // 13 - CR 20 | {0,0}, // 14 21 | {0,0}, // 15 22 | {0,0}, // 16 23 | {0,0}, // 17 24 | {0,0}, // 18 25 | {0,0}, // 19 26 | {0,0}, // 20 27 | {0,0}, // 21 28 | {0,0}, // 22 29 | {0,0}, // 23 30 | {0,0}, // 24 31 | {0,0}, // 25 32 | {0,0}, // 26 33 | {0,0}, // 27 34 | {0,0}, // 28 35 | {0,0}, // 29 36 | {0,0}, // 30 37 | {0,0}, // 31 38 | {fabgl::VirtualKey::VK_SPACE,0}, // 32 39 | {fabgl::VirtualKey::VK_EXCLAIM,1}, // 33 40 | {fabgl::VirtualKey::VK_QUOTEDBL,1}, // 34 41 | {fabgl::VirtualKey::VK_HASH,1}, // 35 42 | {fabgl::VirtualKey::VK_DOLLAR,1}, // 36 43 | {fabgl::VirtualKey::VK_PERCENT,1}, // 37 44 | {fabgl::VirtualKey::VK_AMPERSAND,1}, // 38 45 | {fabgl::VirtualKey::VK_QUOTE,0}, // 39 46 | {fabgl::VirtualKey::VK_LEFTPAREN,1}, // 40 47 | {fabgl::VirtualKey::VK_RIGHTPAREN,1}, // 41 48 | {fabgl::VirtualKey::VK_ASTERISK,1}, // 42 49 | {fabgl::VirtualKey::VK_PLUS,1}, // 43 50 | {fabgl::VirtualKey::VK_COMMA,0}, // 44 51 | {fabgl::VirtualKey::VK_MINUS,0}, // 45 52 | {fabgl::VirtualKey::VK_PERIOD,0}, // 46 53 | {fabgl::VirtualKey::VK_SLASH,0}, // 47 54 | {fabgl::VirtualKey::VK_0,0}, // 48 55 | {fabgl::VirtualKey::VK_1,0}, // 49 56 | {fabgl::VirtualKey::VK_2,0}, // 50 57 | {fabgl::VirtualKey::VK_3,0}, // 51 58 | {fabgl::VirtualKey::VK_4,0}, // 52 59 | {fabgl::VirtualKey::VK_5,0}, // 53 60 | {fabgl::VirtualKey::VK_6,0}, // 54 61 | {fabgl::VirtualKey::VK_7,0}, // 55 62 | {fabgl::VirtualKey::VK_8,0}, // 56 63 | {fabgl::VirtualKey::VK_9,0}, // 57 64 | {fabgl::VirtualKey::VK_COLON,1}, 65 | {fabgl::VirtualKey::VK_SEMICOLON,0}, 66 | {fabgl::VirtualKey::VK_LESS,1}, 67 | {fabgl::VirtualKey::VK_EQUALS,0}, 68 | {fabgl::VirtualKey::VK_GREATER,1}, 69 | {fabgl::VirtualKey::VK_QUESTION,1}, 70 | {fabgl::VirtualKey::VK_AT,1}, 71 | {fabgl::VirtualKey::VK_A,1}, 72 | {fabgl::VirtualKey::VK_B,1}, 73 | {fabgl::VirtualKey::VK_C,1}, 74 | {fabgl::VirtualKey::VK_D,1}, 75 | {fabgl::VirtualKey::VK_E,1}, 76 | {fabgl::VirtualKey::VK_F,1}, 77 | {fabgl::VirtualKey::VK_G,1}, 78 | {fabgl::VirtualKey::VK_H,1}, 79 | {fabgl::VirtualKey::VK_I,1}, 80 | {fabgl::VirtualKey::VK_J,1}, 81 | {fabgl::VirtualKey::VK_K,1}, 82 | {fabgl::VirtualKey::VK_L,1}, 83 | {fabgl::VirtualKey::VK_M,1}, 84 | {fabgl::VirtualKey::VK_N,1}, 85 | {fabgl::VirtualKey::VK_O,1}, 86 | {fabgl::VirtualKey::VK_P,1}, 87 | {fabgl::VirtualKey::VK_Q,1}, 88 | {fabgl::VirtualKey::VK_R,1}, 89 | {fabgl::VirtualKey::VK_S,1}, 90 | {fabgl::VirtualKey::VK_T,1}, 91 | {fabgl::VirtualKey::VK_U,1}, 92 | {fabgl::VirtualKey::VK_V,1}, 93 | {fabgl::VirtualKey::VK_W,1}, 94 | {fabgl::VirtualKey::VK_X,1}, 95 | {fabgl::VirtualKey::VK_Y,1}, 96 | {fabgl::VirtualKey::VK_Z,1}, 97 | {fabgl::VirtualKey::VK_LEFTBRACKET,0}, 98 | {fabgl::VirtualKey::VK_BACKSLASH,0}, 99 | {fabgl::VirtualKey::VK_RIGHTBRACKET,0}, 100 | {fabgl::VirtualKey::VK_CARET,1}, 101 | {fabgl::VirtualKey::VK_UNDERSCORE,1}, 102 | {fabgl::VirtualKey::VK_GRAVEACCENT,0}, 103 | {fabgl::VirtualKey::VK_a,0}, 104 | {fabgl::VirtualKey::VK_b,0}, 105 | {fabgl::VirtualKey::VK_c,0}, 106 | {fabgl::VirtualKey::VK_d,0}, 107 | {fabgl::VirtualKey::VK_e,0}, 108 | {fabgl::VirtualKey::VK_f,0}, 109 | {fabgl::VirtualKey::VK_g,0}, 110 | {fabgl::VirtualKey::VK_h,0}, 111 | {fabgl::VirtualKey::VK_i,0}, 112 | {fabgl::VirtualKey::VK_j,0}, 113 | {fabgl::VirtualKey::VK_k,0}, 114 | {fabgl::VirtualKey::VK_l,0}, 115 | {fabgl::VirtualKey::VK_m,0}, 116 | {fabgl::VirtualKey::VK_n,0}, 117 | {fabgl::VirtualKey::VK_o,0}, 118 | {fabgl::VirtualKey::VK_p,0}, 119 | {fabgl::VirtualKey::VK_q,0}, 120 | {fabgl::VirtualKey::VK_r,0}, 121 | {fabgl::VirtualKey::VK_s,0}, 122 | {fabgl::VirtualKey::VK_t,0}, 123 | {fabgl::VirtualKey::VK_u,0}, 124 | {fabgl::VirtualKey::VK_v,0}, 125 | {fabgl::VirtualKey::VK_w,0}, 126 | {fabgl::VirtualKey::VK_x,0}, 127 | {fabgl::VirtualKey::VK_y,0}, 128 | {fabgl::VirtualKey::VK_z,0}, 129 | {fabgl::VirtualKey::VK_LEFTBRACE,1}, 130 | {fabgl::VirtualKey::VK_VERTICALBAR,1}, 131 | {fabgl::VirtualKey::VK_RIGHTBRACE,1}, 132 | {fabgl::VirtualKey::VK_TILDE,1} 133 | }; 134 | 135 | vk_to_msx_matrix msx_keys_to_matrix[] = { 136 | { 00, 0b00000000 }, // VK_NONE, /**< No character (marks the first virtual key) */ 137 | { 8, 0b00000001 }, // VK_SPACE, /**< Space */ 138 | 139 | { 0, 0b00000001 }, // VK_0, /**< Number 0 */ 140 | { 0, 0b00000010 }, // VK_1, /**< Number 1 */ 141 | { 0, 0b00000100 }, // VK_2, /**< Number 2 */ 142 | { 0, 0b00001000 }, // VK_3, /**< Number 3 */ 143 | { 0, 0b00010000 }, // VK_4, /**< Number 4 */ 144 | { 0, 0b00100000 }, // VK_5, /**< Number 5 */ 145 | { 0, 0b01000000 }, // VK_6, /**< Number 6 */ 146 | { 0, 0b10000000 }, // VK_7, /**< Number 7 */ 147 | { 1, 0b00000001 }, // VK_8, /**< Number 8 */ 148 | { 1, 0b00000010 }, // VK_9, /**< Number 9 */ 149 | { 9, 0b00001000 }, // VK_KP_0, /**< Keypad number 0 */ 150 | { 9, 0b00010000 }, // VK_KP_1, /**< Keypad number 1 */ 151 | { 9, 0b00100000 }, // VK_KP_2, /**< Keypad number 2 */ 152 | { 9, 0b01000000 }, // VK_KP_3, /**< Keypad number 3 */ 153 | { 9, 0b10000000 }, // VK_KP_4, /**< Keypad number 4 */ 154 | { 10, 0b00000001 }, // VK_KP_5, /**< Keypad number 5 */ 155 | { 10, 0b00000010 }, // VK_KP_6, /**< Keypad number 6 */ 156 | { 10, 0b00000100 }, // VK_KP_7, /**< Keypad number 7 */ 157 | { 10, 0b00001000 }, // VK_KP_8, /**< Keypad number 8 */ 158 | { 10, 0b00010000 }, // VK_KP_9, /**< Keypad number 9 */ 159 | 160 | { 2, 0b01000000 }, // VK_a, /**< Lower case letter 'a' */ 161 | { 2, 0b10000000 }, // VK_b, /**< Lower case letter 'b' */ 162 | { 3, 0b00000001 }, // VK_c, /**< Lower case letter 'c' */ 163 | { 3, 0b00000010 }, // VK_d, /**< Lower case letter 'd' */ 164 | { 3, 0b00000100 }, // VK_e, /**< Lower case letter 'e' */ 165 | { 3, 0b00001000 }, // VK_f, /**< Lower case letter 'f' */ 166 | { 3, 0b00010000 }, // VK_g, /**< Lower case letter 'g' */ 167 | { 3, 0b00100000 }, // VK_h, /**< Lower case letter 'h' */ 168 | { 3, 0b01000000 }, // VK_i, /**< Lower case letter 'i' */ 169 | { 3, 0b10000000 }, // VK_j, /**< Lower case letter 'j' */ 170 | { 4, 0b00000001 }, // VK_k, /**< Lower case letter 'k' */ 171 | { 4, 0b00000010 }, // VK_l, /**< Lower case letter 'l' */ 172 | { 4, 0b00000100 }, // VK_m, /**< Lower case letter 'm' */ 173 | { 4, 0b00001000 }, // VK_n, /**< Lower case letter 'n' */ 174 | { 4, 0b00010000 }, // VK_o, /**< Lower case letter 'o' */ 175 | { 4, 0b00100000 }, // VK_p, /**< Lower case letter 'p' */ 176 | { 4, 0b01000000 }, // VK_q, /**< Lower case letter 'q' */ 177 | { 4, 0b10000000 }, // VK_r, /**< Lower case letter 'r' */ 178 | { 5, 0b00000001 }, // VK_s, /**< Lower case letter 's' */ 179 | { 5, 0b00000010 }, // VK_t, /**< Lower case letter 't' */ 180 | { 5, 0b00000100 }, // VK_u, /**< Lower case letter 'u' */ 181 | { 5, 0b00001000 }, // VK_v, /**< Lower case letter 'v' */ 182 | { 5, 0b00010000 }, // VK_w, /**< Lower case letter 'w' */ 183 | { 5, 0b00100000 }, // VK_x, /**< Lower case letter 'x' */ 184 | { 5, 0b01000000 }, // VK_y, /**< Lower case letter 'y' */ 185 | { 5, 0b10000000 }, // VK_z, /**< Lower case letter 'z' */ 186 | { 2, 0b01000000 }, // VK_A, /**< Upper case letter 'A' */ 187 | { 2, 0b10000000 }, // VK_B, /**< Upper case letter 'B' */ 188 | { 3, 0b00000001 }, // VK_C, /**< Upper case letter 'C' */ 189 | { 3, 0b00000010 }, // VK_D, /**< Upper case letter 'D' */ 190 | { 3, 0b00000100 }, // VK_E, /**< Upper case letter 'E' */ 191 | { 3, 0b00001000 }, // VK_F, /**< Upper case letter 'F' */ 192 | { 3, 0b00010000 }, // VK_G, /**< Upper case letter 'G' */ 193 | { 3, 0b00100000 }, // VK_H, /**< Upper case letter 'H' */ 194 | { 3, 0b01000000 }, // VK_I, /**< Upper case letter 'I' */ 195 | { 3, 0b10000000 }, // VK_J, /**< Upper case letter 'J' */ 196 | { 4, 0b00000001 }, // VK_K, /**< Upper case letter 'K' */ 197 | { 4, 0b00000010 }, // VK_L, /**< Upper case letter 'L' */ 198 | { 4, 0b00000100 }, // VK_M, /**< Upper case letter 'M' */ 199 | { 4, 0b00001000 }, // VK_N, /**< Upper case letter 'N' */ 200 | { 4, 0b00010000 }, // VK_O, /**< Upper case letter 'O' */ 201 | { 4, 0b00100000 }, // VK_P, /**< Upper case letter 'P' */ 202 | { 4, 0b01000000 }, // VK_Q, /**< Upper case letter 'Q' */ 203 | { 4, 0b10000000 }, // VK_R, /**< Upper case letter 'R' */ 204 | { 5, 0b00000001 }, // VK_S, /**< Upper case letter 'S' */ 205 | { 5, 0b00000010 }, // VK_T, /**< Upper case letter 'T' */ 206 | { 5, 0b00000100 }, // VK_U, /**< Upper case letter 'U' */ 207 | { 5, 0b00001000 }, // VK_V, /**< Upper case letter 'V' */ 208 | { 5, 0b00010000 }, // VK_W, /**< Upper case letter 'W' */ 209 | { 5, 0b00100000 }, // VK_X, /**< Upper case letter 'X' */ 210 | { 5, 0b01000000 }, // VK_Y, /**< Upper case letter 'Y' */ 211 | { 5, 0b10000000 }, // VK_Z, /**< Upper case letter 'Z' */ 212 | 213 | { 2, 0b00000010 }, // VK_GRAVEACCENT, /**< Grave accent: ` */ 214 | { 0, 0b00000000 }, // VK_ACUTEACCENT, /**< Acute accent: ´ */ 215 | { 2, 0b00000001 }, // VK_QUOTE, /**< Quote: ' */ 216 | { 2, 0b00000001 }, // VK_QUOTEDBL, /**< Double quote: " */ 217 | { 1, 0b00001000 }, // VK_EQUALS, /**< Equals: = */ 218 | { 1, 0b00000100 }, // VK_MINUS, /**< Minus: - */ 219 | { 10, 0b00100000 }, // VK_KP_MINUS, /**< Keypad minus: - */ 220 | { 1, 0b00001000 }, // VK_PLUS, /**< Plus: + */ 221 | { 9, 0b00000010 }, // VK_KP_PLUS, /**< Keypad plus: + */ 222 | { 9, 0b00000001 }, // VK_KP_MULTIPLY, /**< Keypad multiply: * */ 223 | { 1, 0b00000001 }, // VK_ASTERISK, /**< Asterisk: * */ 224 | { 1, 0b00010000 }, // VK_BACKSLASH, /**< Backslash: \ */ 225 | { 9, 0b00000100 }, // VK_KP_DIVIDE, /**< Keypad divide: / */ 226 | { 2, 0b00010000 }, // VK_SLASH, /**< Slash: / */ 227 | { 10, 0b10000000 }, // VK_KP_PERIOD, /**< Keypad period: . */ 228 | { 2, 0b00001000 }, // VK_PERIOD, /**< Period: . */ 229 | { 1, 0b10000000 }, // VK_COLON, /**< Colon: : */ 230 | { 2, 0b00000100 }, // VK_COMMA, /**< Comma: , */ 231 | { 1, 0b10000000 }, // VK_SEMICOLON, /**< Semicolon: ; */ 232 | { 0, 0b10000000 }, // VK_AMPERSAND, /**< Ampersand: & */ 233 | { 1, 0b00010000 }, // VK_VERTICALBAR, /**< Vertical bar: | */ 234 | { 0, 0b00001000 }, // VK_HASH, /**< Hash: # */ 235 | { 0, 0b00000100 }, // VK_AT, /**< At: @ */ 236 | { 0, 0b01000000 }, // VK_CARET, /**< Caret: ^ */ 237 | { 0, 0b00010000 }, // VK_DOLLAR, /**< Dollar: $ */ 238 | { 0, 0b00010000 }, // VK_POUND, /**< Pound: £ */ 239 | { 0, 0b00010000 }, // VK_EURO, /**< Euro: € */ 240 | { 0, 0b00100000 }, // VK_PERCENT, /**< Percent: % */ 241 | { 0, 0b00000010 }, // VK_EXCLAIM, /**< Exclamation mark: ! */ 242 | { 2, 0b00010000 }, // VK_QUESTION, /**< Question mark: ? */ 243 | { 1, 0b00100000 }, // VK_LEFTBRACE, /**< Left brace: { */ 244 | { 1, 0b01000000 }, // VK_RIGHTBRACE, /**< Right brace: } */ 245 | { 1, 0b00100000 }, // VK_LEFTBRACKET, /**< Left bracket: [ */ 246 | { 1, 0b01000000 }, // VK_RIGHTBRACKET, /**< Right bracket: ] */ 247 | { 1, 0b00000010 }, // VK_LEFTPAREN, /**< Left parenthesis: ( */ 248 | { 0, 0b00000001 }, // VK_RIGHTPAREN, /**< Right parenthesis: ) */ 249 | { 2, 0b00000100 }, // VK_LESS, /**< Less: < */ 250 | { 2, 0b00001000 }, // VK_GREATER, /**< Greater: > */ 251 | { 1, 0b00000100 }, // VK_UNDERSCORE, /**< Underscore: _ */ 252 | { 2, 0b00010000 }, // VK_DEGREE, /**< Degree: ° */ 253 | { 0, 0b00001000 }, // VK_SECTION, /**< Section: § */ 254 | { 2, 0b00000010 }, // VK_TILDE, /**< Tilde: ~ */ 255 | { 0, 0b00000000 }, // VK_NEGATION, /**< Negation: ¬ */ 256 | { 6, 0b00000001 }, // VK_LSHIFT, /**< Left SHIFT */ 257 | { 6, 0b00000001 }, // VK_RSHIFT, /**< Right SHIFT */ 258 | { 6, 0b00000100 }, // VK_LALT, /**< Left ALT */ 259 | { 6, 0b00000100 }, // VK_RALT, /**< Right ALT */ 260 | { 6, 0b00000010 }, // VK_LCTRL, /**< Left CTRL */ 261 | { 6, 0b00000010 }, // VK_RCTRL, /**< Right CTRL */ 262 | { 6, 0b00010000 }, // VK_LGUI, /**< Left GUI */ 263 | { 6, 0b00010000 }, // VK_RGUI, /**< Right GUI */ 264 | 265 | { 7, 0b00000100 }, // VK_ESCAPE, /**< ESC */ 266 | 267 | { 0, 0b00000000 }, // VK_PRINTSCREEN, /**< PRINTSCREEN */ 268 | { 0, 0b00000000 }, // VK_SYSREQ, /**< SYSREQ */ 269 | 270 | { 8, 0b00000100 }, // VK_INSERT, /**< INS */ 271 | { 8, 0b00000100 }, // VK_KP_INSERT, /**< Keypad INS */ 272 | { 8, 0b00001000 }, // VK_DELETE, /**< DEL */ 273 | { 8, 0b00001000 }, // VK_KP_DELETE, /**< Keypad DEL */ 274 | { 7, 0b00100000 }, // VK_BACKSPACE, /**< Backspace */ 275 | { 8, 0b00000010 }, // VK_HOME, /**< HOME */ 276 | { 8, 0b00000010 }, // VK_KP_HOME, /**< Keypad HOME */ 277 | { 0, 0b00000000 }, // VK_END, /**< END */ 278 | { 0, 0b00000000 }, // VK_KP_END, /**< Keypad END */ 279 | { 7, 0b00010000 }, // VK_PAUSE, /**< PAUSE */ 280 | { 7, 0b00010000 }, // VK_BREAK, /**< CTRL + PAUSE */ 281 | { 0, 0b00000000 }, // VK_SCROLLLOCK, /**< SCROLLLOCK */ 282 | { 0, 0b00000000 }, // VK_NUMLOCK, /**< NUMLOCK */ 283 | { 6, 0b00001000 }, // VK_CAPSLOCK, /**< CAPSLOCK */ 284 | { 7, 0b00001000 }, // VK_TAB, /**< TAB */ 285 | { 7, 0b10000000 }, // VK_RETURN, /**< RETURN */ 286 | { 7, 0b10000000 }, // VK_KP_ENTER, /**< Keypad ENTER */ 287 | { 6, 0b00000100 }, // VK_APPLICATION, /**< APPLICATION / MENU key */ 288 | { 0, 0b00000000 }, // VK_PAGEUP, /**< PAGEUP */ 289 | { 0, 0b00000000 }, // VK_KP_PAGEUP, /**< Keypad PAGEUP */ 290 | { 0, 0b00000000 }, // VK_PAGEDOWN, /**< PAGEDOWN */ 291 | { 0, 0b00000000 }, // VK_KP_PAGEDOWN, /**< Keypad PAGEDOWN */ 292 | { 8, 0b00100000 }, // VK_UP, /**< Cursor UP */ 293 | { 8, 0b00100000 }, // VK_KP_UP, /**< Keypad cursor UP */ 294 | { 8, 0b01000000 }, // VK_DOWN, /**< Cursor DOWN */ 295 | { 8, 0b01000000 }, // VK_KP_DOWN, /**< Keypad cursor DOWN */ 296 | { 8, 0b00010000 }, // VK_LEFT, /**< Cursor LEFT */ 297 | { 8, 0b00010000 }, // VK_KP_LEFT, /**< Keypad cursor LEFT */ 298 | { 8, 0b10000000 }, // VK_RIGHT, /**< Cursor RIGHT */ 299 | { 8, 0b10000000 }, // VK_KP_RIGHT, /**< Keypad cursor RIGHT */ 300 | { 0, 0b00000000 }, // VK_KP_CENTER, /**< Keypad CENTER key */ 301 | 302 | { 6, 0b00100000 }, // VK_F1, /**< F1 function key */ 303 | { 6, 0b01000000 }, // VK_F2, /**< F2 function key */ 304 | { 6, 0b10000000 }, // VK_F3, /**< F3 function key */ 305 | { 7, 0b00000001 }, // VK_F4, /**< F4 function key */ 306 | { 7, 0b00000010 }, // VK_F5, /**< F5 function key */ 307 | { 0, 0b00000000 }, // VK_F6, /**< F6 function key */ 308 | { 0, 0b00000000 }, // VK_F7, /**< F7 function key */ 309 | { 0, 0b00000000 }, // VK_F8, /**< F8 function key */ 310 | { 0, 0b00000000 }, // VK_F9, /**< F9 function key */ 311 | { 0, 0b00000000 }, // VK_F10, /**< F10 function key */ 312 | { 0, 0b00000000 }, // VK_F11, /**< F11 function key */ 313 | { 0, 0b00000000 }, // VK_F12, /**< F12 function key */ 314 | 315 | { 0, 0b00000000 }, // VK_GRAVE_a, /**< Grave a: à */ 316 | { 0, 0b00000000 }, // VK_GRAVE_e, /**< Grave e: è */ 317 | { 0, 0b00000000 }, // VK_GRAVE_i, /**< Grave i: ì */ 318 | { 0, 0b00000000 }, // VK_GRAVE_o, /**< Grave o: ò */ 319 | { 0, 0b00000000 }, // VK_GRAVE_u, /**< Grave u: ù */ 320 | { 0, 0b00000000 }, // VK_GRAVE_y, /**< Grave y: ỳ */ 321 | 322 | { 0, 0b00000000 }, // VK_ACUTE_a, /**< Acute a: á */ 323 | { 0, 0b00000000 }, // VK_ACUTE_e, /**< Acute e: é */ 324 | { 0, 0b00000000 }, // VK_ACUTE_i, /**< Acute i: í */ 325 | { 0, 0b00000000 }, // VK_ACUTE_o, /**< Acute o: ó */ 326 | { 0, 0b00000000 }, // VK_ACUTE_u, /**< Acute u: ú */ 327 | { 0, 0b00000000 }, // VK_ACUTE_y, /**< Acute y: ý */ 328 | 329 | { 0, 0b00000000 }, // VK_GRAVE_A, /**< Grave A: À */ 330 | { 0, 0b00000000 }, // VK_GRAVE_E, /**< Grave E: È */ 331 | { 0, 0b00000000 }, // VK_GRAVE_I, /**< Grave I: Ì */ 332 | { 0, 0b00000000 }, // VK_GRAVE_O, /**< Grave O: Ò */ 333 | { 0, 0b00000000 }, // VK_GRAVE_U, /**< Grave U: Ù */ 334 | { 0, 0b00000000 }, // VK_GRAVE_Y, /**< Grave Y: Ỳ */ 335 | 336 | { 0, 0b00000000 }, // VK_ACUTE_A, /**< Acute A: Á */ 337 | { 0, 0b00000000 }, // VK_ACUTE_E, /**< Acute E: É */ 338 | { 0, 0b00000000 }, // VK_ACUTE_I, /**< Acute I: Í */ 339 | { 0, 0b00000000 }, // VK_ACUTE_O, /**< Acute O: Ó */ 340 | { 0, 0b00000000 }, // VK_ACUTE_U, /**< Acute U: Ú */ 341 | { 0, 0b00000000 }, // VK_ACUTE_Y, /**< Acute Y: Ý */ 342 | 343 | { 0, 0b00000000 }, // VK_UMLAUT_a, /**< Diaeresis a: ä */ 344 | { 0, 0b00000000 }, // VK_UMLAUT_e, /**< Diaeresis e: ë */ 345 | { 0, 0b00000000 }, // VK_UMLAUT_i, /**< Diaeresis i: ï */ 346 | { 0, 0b00000000 }, // VK_UMLAUT_o, /**< Diaeresis o: ö */ 347 | { 0, 0b00000000 }, // VK_UMLAUT_u, /**< Diaeresis u: ü */ 348 | { 0, 0b00000000 }, // VK_UMLAUT_y, /**< Diaeresis y: ÿ */ 349 | 350 | { 0, 0b00000000 }, // VK_UMLAUT_A, /**< Diaeresis A: Ä */ 351 | { 0, 0b00000000 }, // VK_UMLAUT_E, /**< Diaeresis E: Ë */ 352 | { 0, 0b00000000 }, // VK_UMLAUT_I, /**< Diaeresis I: Ï */ 353 | { 0, 0b00000000 }, // VK_UMLAUT_O, /**< Diaeresis O: Ö */ 354 | { 0, 0b00000000 }, // VK_UMLAUT_U, /**< Diaeresis U: Ü */ 355 | { 0, 0b00000000 }, // VK_UMLAUT_Y, /**< Diaeresis Y: Ÿ */ 356 | 357 | { 0, 0b00000000 }, // VK_CARET_a, /**< Caret a: â */ 358 | { 0, 0b00000000 }, // VK_CARET_e, /**< Caret e: ê */ 359 | { 0, 0b00000000 }, // VK_CARET_i, /**< Caret i: î */ 360 | { 0, 0b00000000 }, // VK_CARET_o, /**< Caret o: ô */ 361 | { 0, 0b00000000 }, // VK_CARET_u, /**< Caret u: û */ 362 | { 0, 0b00000000 }, // VK_CARET_y, /**< Caret y: ŷ */ 363 | 364 | { 0, 0b00000000 }, // VK_CARET_A, /**< Caret A: Â */ 365 | { 0, 0b00000000 }, // VK_CARET_E, /**< Caret E: Ê */ 366 | { 0, 0b00000000 }, // VK_CARET_I, /**< Caret I: Î */ 367 | { 0, 0b00000000 }, // VK_CARET_O, /**< Caret O: Ô */ 368 | { 0, 0b00000000 }, // VK_CARET_U, /**< Caret U: Û */ 369 | { 0, 0b00000000 }, // VK_CARET_Y, /**< Caret Y: Ŷ */ 370 | 371 | { 0, 0b00000000 }, // VK_CEDILLA_c, /**< Cedilla c: ç */ 372 | { 0, 0b00000000 }, // VK_CEDILLA_C, /**< Cedilla C: Ç */ 373 | 374 | { 0, 0b00000000 }, // VK_TILDE_a, /**< Lower case tilde a: ã */ 375 | { 0, 0b00000000 }, // VK_TILDE_o, /**< Lower case tilde o: õ */ 376 | { 0, 0b00000000 }, // VK_TILDE_n, /**< Lower case tilde n: ñ */ 377 | 378 | { 0, 0b00000000 }, // VK_TILDE_A, /**< Upper case tilde A: Ã */ 379 | { 0, 0b00000000 }, // VK_TILDE_O, /**< Upper case tilde O: Õ */ 380 | { 0, 0b00000000 }, // VK_TILDE_N, /**< Upper case tilde N: Ñ */ 381 | 382 | { 0, 0b00000000 }, // VK_UPPER_a, /**< primera: a */ 383 | { 0, 0b00000000 }, // VK_ESZETT, /**< Eszett: ß */ 384 | { 0, 0b00000000 }, // VK_EXCLAIM_INV, /**< Inverted exclamation mark: ! */ 385 | { 0, 0b00000000 }, // VK_QUESTION_INV, /**< Inverted question mark : ? */ 386 | { 0, 0b00000000 }, // VK_INTERPUNCT, /**< Interpunct : · */ 387 | { 0, 0b00000000 }, // VK_DIAERESIS, /**< Diaeresis : ¨ */ 388 | { 0, 0b00000000 }, // VK_SQUARE, /**< Square : ² */ 389 | { 0, 0b00000000 }, // VK_CURRENCY, /**< Currency : ¤ */ 390 | { 0, 0b00000000 }, // VK_MU, /**< Mu : µ */ 391 | { 0, 0b00000000 }, // VK_HALF, /**< 1/2 fraction : 1/2 */ 392 | { 0, 0b00000000 }, // VK_MASCULIN_ORD, /**< Masculin ordinal superscript o */ 393 | { 0, 0b00000000 }, // VK_FEMININ_ORD, /**< Feminin ordinal superscript a */ 394 | { 0, 0b00000000 }, // VK_LEFTGUILLEMET, /**< Left guillemet: « */ 395 | { 0, 0b00000000 }, // VK_RIGHTGUILLEMET, /**< Right guillemet: » */ 396 | { 0, 0b00000000 }, // VK_aelig, /** Lower case aelig : æ */ 397 | { 0, 0b00000000 }, // VK_oslash, /** Lower case oslash : ø */ 398 | { 0, 0b00000000 }, // VK_aring, /** Lower case aring : å */ 399 | 400 | { 0, 0b00000000 }, // VK_AELIG, /** Upper case aelig : Æ */ 401 | { 0, 0b00000000 }, // VK_OSLASH, /** Upper case oslash : Ø */ 402 | { 0, 0b00000000 }, // VK_ARING, /** Upper case aring : Å */ 403 | 404 | { 0, 0b00000000 }, // VK_DEAD_GRAVEACCENT,/** Grave accent when we need seperate code for dead key*/ 405 | { 0, 0b00000000 }, // VK_DEAD_CARET, /** Caret when we need seperate code for dead key*/ 406 | { 0, 0b00000000 }, // VK_DEAD_TILDE, /** Tilde when we need seperate code for dead key*/ 407 | 408 | // // Missing code page 1252 virtual keys 409 | { 0, 0b00000000 }, // VK_UPPER_1, /** Superscript 1 : ¹ */ 410 | { 0, 0b00000000 }, // VK_CUBE, /** Superscript 3 : ³ */ 411 | { 0, 0b00000000 }, // VK_CENT, /** Cent (currency) : ¢ */ 412 | 413 | // // Japanese layout support 414 | { 0, 0b00000000 }, // VK_YEN, 415 | { 0, 0b00000000 }, // VK_MUHENKAN, 416 | { 0, 0b00000000 }, // VK_HENKAN, 417 | { 0, 0b00000000 }, // VK_KATAKANA_HIRAGANA_ROMAJI, 418 | { 0, 0b00000000 }, // VK_HANKAKU_ZENKAKU_KANJI, 419 | { 0, 0b00000000 }, // VK_SHIFT_0, 420 | 421 | { 0, 0b00000000 }, // VK_ASCII, /**< Specifies an ASCII code - used when virtual key is embedded in VirtualKeyItem structure and VirtualKeyItem.ASCII is valid */ 422 | { 0, 0b00000000 }, // VK_LAST, // marks the last virtual key 423 | }; 424 | 425 | MSX_PPI8255::MSX_PPI8255 () : PPI8255() 426 | { 427 | for (int i=0;i<(sizeof (rows)/sizeof (uint8_t));i++) 428 | rows [i]=0xff; 429 | } 430 | uint8_t MSX_PPI8255::read (uint8_t port) 431 | { 432 | uint8_t row; 433 | uint8_t return_value; 434 | uint8_t vk; 435 | 436 | switch (port) 437 | { 438 | case 0: 439 | // slot selection register, returns 0 for page 0 to 3 440 | return 0x00; 441 | case 1: 442 | // return bits belonging to row indicated by portC 443 | row = portC & 0x0f; 444 | return_value = rows[row]; 445 | if (!vk_key_up.empty()) 446 | { 447 | vk = vk_key_up.back(); 448 | if (msx_keys_to_matrix[vk].msx_matrix_row==row) 449 | { 450 | record_keypress (0,0,vk,false); 451 | vk_key_up.pop_back(); 452 | } 453 | } 454 | return return_value; 455 | case 2: 456 | return portC; 457 | case 3: 458 | return control; 459 | } 460 | return 0xff; 461 | } 462 | 463 | uint8_t MSX_PPI8255::record_keypress (uint8_t ascii,uint8_t modifier,uint8_t vk,uint8_t down) 464 | { 465 | uint8_t mask; 466 | 467 | if (ascii>0 && vk==0 && ascii=sizeof(rows)) 514 | current_row = 0; 515 | return row; 516 | } 517 | SG1000_PPI8255::SG1000_PPI8255 () : PPI8255() 518 | { 519 | for (int i=0;i<(sizeof (rows)/sizeof (uint16_t));i++) 520 | rows [i]=0xffff; 521 | } 522 | 523 | uint8_t SG1000_PPI8255::read (uint8_t port) 524 | { 525 | // hal_printf ("PPI read port %c\r\n",port+'A'); 526 | switch (port) 527 | { 528 | case 0: 529 | // return low 8 bits belonging to row indicated by portC 530 | return uint8_t (rows[portC] & 0x00ff); 531 | case 1: 532 | // return high 8 bits belonging to row indicated by portC 533 | return uint8_t ((rows[portC] & 0xff00) >> 8); 534 | case 2: 535 | return 0xff; // return portC if you want to emulate keyboard scanning, return 0xff if you just want keypads 536 | case 3: 537 | return 0xff; 538 | } 539 | return 0xff; 540 | } 541 | 542 | // SG-1000 543 | // PPI Port A Port B 544 | // Rows D0 D1 D2 D3 D4 D5 D6 D7 D0 D1 D2 D3 545 | // 0 '1' 'Q' 'A' 'Z' ED ',' 'K' 'I' '8' — — — 546 | // 1 '2' 'W' 'S' 'X' SPC '.' 'L' 'O' '9' — — — 547 | // 2 '3' 'E' 'D' 'C' HC '/' ';' 'P' '0' — — — 548 | // 3 '4' 'R' 'F' 'V' ID PI ':' '@' '-' — — — 549 | // 4 '5' 'T' 'G' 'B' — DA ']' '[' '^' — — — 550 | // 5 '6' 'Y' 'H' 'N' — LA CR — YEN — — FNC 551 | // 6 '7' 'U' 'J' 'M' — RA UA — BRK GRP CTL SHF 552 | // 7 1U 1D 1L 1R 1TL 1TR 2U 2D 2L 2R 2TL 2TR 553 | uint8_t SG1000_PPI8255::record_keypress (uint8_t ascii,uint8_t modifier,uint8_t vk,uint8_t down) 554 | { 555 | current_row = 7; 556 | // only supporting row 7 557 | if (down!=1) 558 | { 559 | // up, make bits 1 560 | switch (vk) 561 | { 562 | case fabgl::VK_UP: 563 | rows[7]=rows[7]|0b0000000000000001; 564 | break; 565 | case fabgl::VK_DOWN: 566 | rows[7]=rows[7]|0b0000000000000010; 567 | break; 568 | case fabgl::VK_LEFT: 569 | rows[7]=rows[7]|0b0000000000000100; 570 | break; 571 | case fabgl::VK_RIGHT: 572 | rows[7]=rows[7]|0b0000000000001000; 573 | break; 574 | case fabgl::VK_SPACE: 575 | rows[7]=rows[7]|0b0000000000010000; 576 | break; 577 | case fabgl::VK_LSHIFT: 578 | case fabgl::VK_RSHIFT: 579 | rows[7]=rows[7]|0b0000000000100000; 580 | break; 581 | } 582 | } 583 | else 584 | { 585 | // down, make bits 0 586 | switch (vk) 587 | { 588 | case fabgl::VK_UP: 589 | rows[7]=rows[7]&0b1111111111111110; 590 | break; 591 | case fabgl::VK_DOWN: 592 | rows[7]=rows[7]&0b1111111111111101; 593 | break; 594 | case fabgl::VK_LEFT: 595 | rows[7]=rows[7]&0b1111111111111011; 596 | break; 597 | case fabgl::VK_RIGHT: 598 | rows[7]=rows[7]&0b1111111111110111; 599 | break; 600 | case fabgl::VK_SPACE: 601 | rows[7]=rows[7]&0b1111111111101111; 602 | break; 603 | case fabgl::VK_LSHIFT: 604 | case fabgl::VK_RSHIFT: 605 | rows[7]=rows[7]&0b1111111111011111; 606 | break; 607 | } 608 | } 609 | return 7; 610 | } 611 | 612 | uint8_t SG1000_PPI8255::get_row_bits (uint8_t row) 613 | { 614 | if (row=sizeof(rows)) 625 | current_row = 0; 626 | return row; 627 | } 628 | 629 | PPI8255::PPI8255 () 630 | { 631 | portA = portB = portC = control = 0; 632 | current_row = 0; 633 | } 634 | void PPI8255::write (uint8_t port, uint8_t value) 635 | { 636 | switch (port) 637 | { 638 | case 0: 639 | portA=value; 640 | // hal_printf ("PPI write port A: %02x\r\n",value); 641 | break; 642 | case 1: 643 | portB=value; 644 | // hal_printf ("PPI write port B: %02x\r\n",value); 645 | break; 646 | case 2: 647 | portC=value; 648 | // hal_printf ("PPI write port C: %02x\r\n",value); 649 | break; 650 | case 3: 651 | control=value; 652 | // hal_printf ("PPI write control port: %02x\r\n",value); 653 | break; 654 | } 655 | } 656 | -------------------------------------------------------------------------------- /src/sn76489an.cpp: -------------------------------------------------------------------------------- 1 | #include "sn76489an.h" 2 | #include "fabgl.h" 3 | #include "hal.h" 4 | #include "agon_audio.h" 5 | #include "audio_channel.h" 6 | 7 | #define PLAY_SOUND_PRIORITY 3 8 | 9 | SN76489AN::SN76489AN () 10 | { 11 | register_select=-1; 12 | toneA=1; 13 | toneB=1; 14 | toneC=1; 15 | noise=0; 16 | amplA=0; 17 | amplB=0; 18 | amplC=0; 19 | amplNoise=0; 20 | first_byte = false; 21 | } 22 | 23 | void SN76489AN::init () 24 | { 25 | // tone 26 | setWaveform (0,AUDIO_WAVE_SQUARE,0); 27 | setWaveform (1,AUDIO_WAVE_SQUARE,0); 28 | setWaveform (2,AUDIO_WAVE_SQUARE,0); 29 | // noise 30 | setWaveform (3,AUDIO_WAVE_AY_3_8910_NOISE,0); 31 | } 32 | 33 | void SN76489AN::updateSound (uint8_t channel, uint8_t amp, uint16_t freq) 34 | { 35 | // tone channel 36 | if (channel < 3) 37 | { 38 | // on 39 | if (freq>0) 40 | { 41 | // hal_printf ("%c tone freq: %d, vol: %d\r\n",channel+'A',SG1000_MASTER_FREQUENCY_DIV / freq,(15-amp) * FABGL_AMPLITUDE_MULTIPLIER); 42 | setFrequency (channel,SG1000_MASTER_FREQUENCY_DIV / freq); 43 | } 44 | // hal_printf ("%c frequency:%d Hz, %d attenuation\r\n",channel+'A',freq,amp); 45 | setVolume (channel,(15-amp) * FABGL_AMPLITUDE_MULTIPLIER); 46 | } 47 | // noise channel 48 | else 49 | { 50 | if (freq&0b00000100) 51 | freq = FABGL_SOUNDGEN_DEFAULT_SAMPLE_RATE; 52 | else 53 | { 54 | switch (freq&0b00000011) 55 | { 56 | case 0b00: // N/512 57 | freq = SG1000_MASTER_FREQUENCY/512; 58 | break; 59 | case 0b01: // N/1024 60 | freq = SG1000_MASTER_FREQUENCY/1024; 61 | break; 62 | case 0b10: // N/2048 63 | freq = SG1000_MASTER_FREQUENCY/2048; 64 | break; 65 | case 0b11: 66 | freq = toneC; 67 | break; 68 | } 69 | 70 | } 71 | // on 72 | // hal_printf ("%c frequency:%d Hz, %d attenuation\r\n",channel+'A',freq,amp); 73 | setVolume (channel,(15-amp) * FABGL_AMPLITUDE_MULTIPLIER); 74 | if (freq>0) 75 | { 76 | // hal_printf ("%c tone freq: %d, vol: %d\r\n",channel+'A',SG1000_MASTER_FREQUENCY_DIV / freq,(15-amp) * FABGL_AMPLITUDE_MULTIPLIER); 77 | setFrequency (channel,SG1000_MASTER_FREQUENCY_DIV / freq); 78 | } 79 | } 80 | } 81 | void SN76489AN::write (uint8_t value) 82 | { 83 | bool updateA=false,updateB=false,updateC=false,updateNoise = false; 84 | 85 | if (value & 0b10000000) 86 | { 87 | register_select = value & 0b01110000; 88 | register_select = register_select >> 4; 89 | first_byte = true; 90 | } 91 | else 92 | first_byte = false; 93 | 94 | switch (register_select) 95 | { 96 | // Tone A 97 | case 0x00: 98 | if (first_byte) 99 | { 100 | toneA = toneA & 0b1111111111110000; 101 | toneA = toneA + (value & 0b00001111); 102 | } 103 | else 104 | { 105 | toneA = toneA & 0b1111110000001111; 106 | toneA = toneA + ((value & 0b00111111)<<4); 107 | updateA = true; 108 | } 109 | break; 110 | // Tone B - fine 111 | case 0x02: 112 | if (first_byte) 113 | { 114 | toneB = toneB & 0b1111111111110000; 115 | toneB = toneB + (value & 0b00001111); 116 | } 117 | else 118 | { 119 | toneB = toneB & 0b1111110000001111; 120 | toneB = toneB + ((value & 0b00111111)<<4); 121 | updateB = true; 122 | } 123 | break; 124 | // Tone C - fine 125 | case 0x04: 126 | if (first_byte) 127 | { 128 | toneC = toneC & 0b1111111111110000; 129 | toneC = toneC + (value & 0b00001111); 130 | } 131 | else 132 | { 133 | toneC = toneC & 0b1111110000001111; 134 | toneC = toneC + ((value & 0b00111111)<<4); 135 | updateC = true; 136 | } 137 | break; 138 | // Noise control 139 | case 0x06: 140 | if (first_byte) 141 | { 142 | fb = (value & 0b000000100)>>2; 143 | nf = value & 0b000000011; 144 | noise = value & 0b00000111; 145 | } 146 | break; 147 | // Amplitude, volume control - A 148 | case 0x01: 149 | if (first_byte) 150 | { 151 | amplA = value & 0b00001111; 152 | updateA = true; 153 | } 154 | break; 155 | // Amplitude, volume control - B 156 | case 0x03: 157 | if (first_byte) 158 | { 159 | amplB = value & 0b00001111; 160 | updateB = true; 161 | } 162 | break; 163 | // Amplitude, volume control - C 164 | case 0x05: 165 | if (first_byte) 166 | { 167 | amplC = value & 0b00001111; 168 | updateC = true; 169 | } 170 | break; 171 | // Amplitude, volume control - noise 172 | case 0x07: 173 | if (first_byte) 174 | { 175 | amplNoise = value & 0b00001111; 176 | updateNoise = true; 177 | } 178 | break; 179 | default: 180 | break; 181 | } 182 | 183 | // update sound 184 | if (updateA) 185 | { 186 | updateSound (0,amplA,toneA); 187 | updateA = false; 188 | } 189 | if (updateB) 190 | { 191 | updateSound (1,amplB,toneB); 192 | updateB = false; 193 | } 194 | if (updateC) 195 | { 196 | updateSound (2,amplC,toneC); 197 | updateC = false; 198 | } 199 | if (updateNoise) 200 | { 201 | updateSound (3,amplNoise,noise); 202 | updateC = false; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/updater.cpp: -------------------------------------------------------------------------------- 1 | #include "updater.h" 2 | #include "hal.h" 3 | #include "esp_ota_ops.h" 4 | 5 | vdu_updater::vdu_updater () : 6 | locked (true) 7 | { 8 | } 9 | void vdu_updater::unlock () 10 | { 11 | uint8_t buffer[sizeof(unlockCode)] = {0}; 12 | size_t bytes_read = ez80_serial.readBytes (buffer, sizeof(buffer)-1); 13 | if(bytes_read!=(sizeof(buffer)-1)) { 14 | hal_printf("Read unlock code failed!\n\r"); 15 | } 16 | else if(memcmp(buffer, unlockCode, sizeof(unlockCode)-1) == 0) 17 | { 18 | hal_printf("Updater %s\n\r",unlock_response); 19 | locked = false; 20 | } 21 | } 22 | uint8_t vdu_updater::get_unlock_response (uint8_t index) 23 | { 24 | return unlock_response [index]; 25 | } 26 | void vdu_updater::receiveFirmware() 27 | { 28 | hal_printf("Initialize VDP firmware update\r\n"); 29 | uint32_t update_size = 0; 30 | size_t bytes_read = ez80_serial.readBytes((uint8_t*)&update_size, sizeof(update_size) - 1); // -1 because its 24bits 31 | if(bytes_read!=sizeof(update_size) - 1) 32 | { 33 | hal_printf("Read size failed!\n\r"); 34 | return; 35 | } 36 | 37 | if(locked) { 38 | hal_printf("Updater is locked, bytes will be discarded!\n\r"); 39 | return; 40 | } 41 | 42 | hal_printf("Received update size: %u bytes\n\r", update_size); 43 | hal_printf("Receiving VDP firmware update"); 44 | 45 | uint32_t start = millis(); 46 | 47 | esp_err_t err; 48 | esp_ota_handle_t update_handle = 0 ; 49 | const esp_partition_t *update_partition = NULL; 50 | 51 | update_partition = esp_ota_get_next_update_partition(NULL); 52 | err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); 53 | if (err != ESP_OK) 54 | { 55 | hal_printf("esp_ota_begin failed, error=%d\n\r", err); 56 | return; 57 | } 58 | 59 | uint32_t remaining_bytes = update_size; 60 | uint8_t code = 0; 61 | const size_t buffer_size = 1024; 62 | while(remaining_bytes > 0) 63 | { 64 | size_t bytes_to_read = buffer_size; 65 | 66 | if(remaining_bytes < buffer_size) 67 | bytes_to_read = remaining_bytes; 68 | 69 | uint8_t buffer[buffer_size]; 70 | bytes_read = ez80_serial.readBytes(buffer, bytes_to_read); 71 | if(bytes_read!=bytes_to_read) { 72 | hal_printf("\n\rRead buffer failed at byte %u!\n\r", remaining_bytes); 73 | return; 74 | } 75 | 76 | for(int i = 0; i < bytes_to_read; i++) { 77 | code += ((uint8_t*)buffer)[i]; 78 | } 79 | 80 | hal_printf("."); 81 | remaining_bytes -= bytes_to_read; 82 | 83 | err = esp_ota_write( update_handle, (const void *)buffer, bytes_to_read); 84 | if (err != ESP_OK) 85 | { 86 | hal_printf("\n\resp_ota_write failed, error=%d\n\r", err); 87 | return; 88 | } 89 | } 90 | hal_printf("\n\r"); 91 | 92 | uint32_t end = millis(); 93 | hal_printf("Upload done in %u ms\n\r", end - start); 94 | hal_printf("Bandwidth: %u kbit/s\n\r", update_size / (end - start) * 8); 95 | 96 | // checksum check 97 | auto checksum_complement = ez80_serial.read(); 98 | if (checksum_complement == -1) { 99 | hal_printf("Checksum not received!\n\r"); 100 | return; 101 | } 102 | hal_printf("checksum_complement: 0x%x\n\r", checksum_complement); 103 | if(uint8_t(code + (uint8_t)checksum_complement)) 104 | { 105 | hal_printf("checksum error!\n\r"); 106 | return; 107 | } 108 | hal_printf("checksum ok!\n\r"); 109 | 110 | err = esp_ota_set_boot_partition(update_partition); 111 | if (err != ESP_OK) 112 | { 113 | hal_printf("esp_ota_set_boot_partition failed! err=0x%x\n\r", err); 114 | return; 115 | } 116 | 117 | hal_printf("Rebooting in "); 118 | for(int i = 3; i > 0; i--) 119 | { 120 | hal_printf("%d...", i); 121 | delay(1000); 122 | } 123 | hal_printf("0!\n\r"); 124 | 125 | esp_restart(); 126 | } 127 | void vdu_updater::switchFirmware() 128 | { 129 | if (locked) { 130 | hal_printf("Updater is locked!\n\r"); 131 | return; 132 | } 133 | 134 | esp_err_t err; 135 | const esp_partition_t *running = esp_ota_get_running_partition(); 136 | const esp_partition_t *update_partition = esp_ota_get_next_update_partition(running); 137 | 138 | // TODO: check if update_partition is valid 139 | err = esp_ota_set_boot_partition(update_partition); 140 | if (err != ESP_OK) 141 | hal_printf("esp_ota_set_boot_partition failed! err=0x%x\n\r", err); 142 | hal_printf("restart!\n\r"); 143 | esp_restart(); 144 | } 145 | void vdu_updater::test () 146 | { 147 | uint8_t cnt = esp_ota_get_app_partition_count(); 148 | hal_printf ("we have %d partitions\r\n",cnt); 149 | hal_printf ("boot partition\r\n"); 150 | const esp_partition_t* boot = esp_ota_get_boot_partition(); 151 | hal_printf (" type: %d\r\n",boot->type); 152 | hal_printf (" subtype: %d\r\n",boot->subtype); 153 | hal_printf (" size: %d\r\n",boot->size); 154 | hal_printf (" address: %d\r\n",boot->address); 155 | hal_printf (" label: %s\r\n",boot->label); 156 | hal_printf ("running partition\r\n"); 157 | const esp_partition_t* running = esp_ota_get_running_partition(); 158 | hal_printf (" type: %d\r\n",running->type); 159 | hal_printf (" subtype: %d\r\n",running->subtype); 160 | hal_printf (" size: %d\r\n",running->size); 161 | hal_printf (" address: %d\r\n",running->address); 162 | hal_printf (" label: %s\r\n",running->label); 163 | hal_printf ("next partition\r\n"); 164 | const esp_partition_t *update_partition = esp_ota_get_next_update_partition(running); 165 | hal_printf (" type: %d\r\n",update_partition->type); 166 | hal_printf (" subtype: %d\r\n",update_partition->subtype); 167 | hal_printf (" size: %d\r\n",update_partition->size); 168 | hal_printf (" address: %d\r\n",update_partition->address); 169 | hal_printf (" label: %s\r\n",update_partition->label); 170 | hal_printf ("next next partition\r\n"); 171 | const esp_partition_t *update_partition2 = esp_ota_get_next_update_partition(update_partition); 172 | hal_printf (" type: %d\r\n",update_partition2->type); 173 | hal_printf (" subtype: %d\r\n",update_partition2->subtype); 174 | hal_printf (" size: %d\r\n",update_partition2->size); 175 | hal_printf (" address: %d\r\n",update_partition2->address); 176 | hal_printf (" label: %s\r\n",update_partition2->label); 177 | } 178 | 179 | void vdu_updater::command (uint8_t mode) 180 | { 181 | switch(mode) 182 | { 183 | case 0: 184 | unlock(); 185 | break; 186 | case 1: 187 | receiveFirmware(); 188 | break; 189 | case 2: 190 | switchFirmware(); 191 | break; 192 | case 3: 193 | test(); 194 | break; 195 | } 196 | } -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /vdu_updater_cmds.txt: -------------------------------------------------------------------------------- 1 | Unlock: 2 | VDU 23 0 161 0 117 110 108 111 99 107 3 | 4 | Fake upload: 5 | VDU 23 0 161 1 1 0 0 1 1 6 | 7 | Switch: 8 | VDU 23 0 161 2 9 | 10 | Test: 11 | VDU 23 0 161 3 12 | 13 | flash vdp firmware/quark-vdp-1.04.bin --------------------------------------------------------------------------------