├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── MOS.wsp ├── MOS.zdsproj ├── README.md ├── eZ80F92_AGON_Flash.ztgt ├── main.c ├── src ├── clock.c ├── clock.h ├── config.h ├── defines.h ├── equs.inc ├── gpio.asm ├── i2c.c ├── i2c.h ├── i2c.inc ├── interrupts.asm ├── keyboard.asm ├── macros.inc ├── misc.asm ├── mos.c ├── mos.h ├── mos_api.asm ├── mos_api.inc ├── mos_editor.c ├── mos_editor.h ├── rtc.asm ├── sd.asm ├── sd.h ├── sd.inc ├── serial.asm ├── spi.asm ├── spi.h ├── strings.c ├── strings.h ├── timer.c ├── timer.h ├── uart.c ├── uart.h └── vdp_protocol.asm ├── src_fatfs ├── LICENSE ├── diskio.c ├── diskio.h ├── ff.c ├── ff.h ├── ffconf.h ├── ffsystem.c └── ffunicode.c └── src_startup ├── cstartup.asm ├── globals.asm ├── init_params_f92.asm └── vectors16.asm /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: breakintoprogram 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Debug/ 2 | Release/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dean Belfield 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. -------------------------------------------------------------------------------- /MOS.wsp: -------------------------------------------------------------------------------- 1 | [WorkState_v1_2] 2 | ptn_Child1=Frames 3 | 4 | [WorkState_v1_2.Frames] 5 | ptn_Child1=ChildFrames 6 | 7 | [WorkState_v1_2.Frames.ChildFrames] 8 | 9 | -------------------------------------------------------------------------------- /MOS.zdsproj: -------------------------------------------------------------------------------- 1 | 2 | eZ80F92 3 | 4 | 5 | 6 | .\main.c 7 | src_startup\globals.asm 8 | src\mos.c 9 | src\mos_editor.c 10 | src\mos_api.asm 11 | src\misc.asm 12 | src\keyboard.asm 13 | src\vdp_protocol.asm 14 | src\interrupts.asm 15 | src\clock.c 16 | src\rtc.asm 17 | src\timer.c 18 | src\sd.asm 19 | src\spi.asm 20 | src_fatfs\diskio.c 21 | src_fatfs\ff.c 22 | src_fatfs\ffsystem.c 23 | src_fatfs\ffunicode.c 24 | src\uart.c 25 | src\serial.asm 26 | src\gpio.asm 27 | src_startup\cstartup.asm 28 | src_startup\init_params_f92.asm 29 | src_startup\vectors16.asm 30 | src\config.h 31 | src\mos_api.inc 32 | src\defines.h 33 | src\i2c.c 34 | src\strings.c 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agon-mos 2 | 3 | Part of the official Quark firmware for the Agon series of microcomputers 4 | 5 | ### What is the Agon 6 | 7 | Agon is a modern, fully open-source, 8-bit microcomputer and microcontroller in one small, low-cost board. As a computer, it is a standalone device that requires no host PC: it puts out its own video (VGA), audio (2 identical mono channels), accepts a PS/2 keyboard and has its own mass-storage in the form of a µSD card. 8 | 9 | https://www.thebyteattic.com/p/agon.html 10 | 11 | ### What is a MOS 12 | 13 | The MOS is a command line machine operating system, similar to CP/M or DOS, that provides a human interface to the Agon file system. 14 | 15 | It also provides an API for file I/O and other common operations for BBC Basic for Z80 and other third-party applications. 16 | 17 | ### Loadig BBC Basic for Z80 18 | 19 | 1. Download bbcbasic.bin from [agon-bbc-basic releases](https://github.com/breakintoprogram/agon-bbc-basic/releases) 20 | 2. Copy it to the root directory of the Agon SD card 21 | 3. Insert the SD card into the AGON and reset/boot it 22 | 4. Check the file is on the SD card with a `CAT` or `.` command 23 | 5. Type the following commands into MOS: 24 | - `LOAD bbcbasic.bin` 25 | - `RUN` 26 | 6. You should then be greeted with the BBC Basic for Z80 prompt 27 | 28 | ### Etiquette 29 | 30 | Please do not issue pull requests or issues for this project; it is very much a work-in-progress. 31 | I will review this policy once the code is approaching live status and I have time to collaborate more. 32 | 33 | ### Build 34 | 35 | The eZ80 is programmed via the ZDI connector on the left-hand side of the board. This requires a Zilog Smart Cable that can be purchased from online stockists such as Mouser or RS Components. 36 | 37 | There are three compatible cables with the following part numbers: 38 | 39 | - `ZUSBSC00100ZACG`: USB Smart Cable (discontinued) 40 | - `ZUSBASC0200ZACG`: USB Smart Cable (in stock - this requires ZDS II version 5.3.5) 41 | - `ZENETSC0100ZACG`: Ethernet Smart Cable (in stock) 42 | 43 | Important! Make sure you get the exact model of cable; there are variants for the Zilog Encore CPU that have similar part numbers, but are not compatible with the Acclaim! eZ80 CPU. 44 | 45 | You can download the ZDS II tools for free via these links. The software contains an IDE, Debugger, C Compiler and eZ80 Assembler. 46 | 47 | - [Zilog ZDS II Tools version 5.3.4](https://zilog.com/index.php?option=com_zcm&task=view&soft_id=38&Itemid=74) 48 | - [Zilog ZDS II Tools version 5.3.5](https://zilog.com/index.php?option=com_zcm&task=view&soft_id=54&Itemid=74) 49 | 50 | You will also need a [Hex2Bin utility](https://sourceforge.net/projects/hex2bin/) to convert the MOS.hex file to MOS.bin ready for release. 51 | 52 | NB: 53 | 54 | - The tools are only available for Windows PC. 55 | - The tools are compatible with Wine on Linux / OSX, but the USB Smart Cable drivers do not work 56 | - The Ethernet Smart Cable may be compatible with the tools running on Wine, though this combination has not been tested by me 57 | 58 | I currently build the official Quark software on a Windows XP KVM running on Ubuntu 20.04 LTS, using ZDS II Tools version 5.3.4, connecting to the Agon with the discontinued USB smart cable. 59 | 60 | It is recommended that building and testing the MOS is done via the ZDS II tools and a Zilog Smart Cable, otherwise there is an increased risk of bricking the Agon. 61 | 62 | Other options for developing C and eZ80 on the Agon are available. Please check the [Agon Programmers Group on Facebook](https://www.facebook.com/groups/667325088311886) for more information. 63 | 64 | Any custom settings for Agon development is contained within the project files, so no further configuration will need to be done. 65 | 66 | ### Documentation 67 | 68 | The AGON documentation can now be found on the [Agon Light Documentation Wiki](https://github.com/breakintoprogram/agon-docs/wiki) 69 | 70 | ### Licenses 71 | 72 | This code is released under an MIT license, with the following exceptions: 73 | 74 | * FatFS: The license for the [FAT filing system by ChaN](http://elm-chan.org/fsw/ff/00index_e.html) can be found here [src_fatfs/LICENSE](src_fatfs/LICENSE) along with the accompanying code. 75 | 76 | ### Links 77 | 78 | - [Zilog eZ80 User Manual](http://www.zilog.com/docs/um0077.pdf) 79 | - [ZiLOG Developer Studio II User Manual](http://www.zilog.com/docs/devtools/um0144.pdf) 80 | - [FatFS - Generic File System](http://elm-chan.org/fsw/ff/00index_e.html) 81 | - [AVRC Tutorials - Initialising an SD Card](http://www.rjhcoding.com/avrc-sd-interface-1.php) -------------------------------------------------------------------------------- /eZ80F92_AGON_Flash.ztgt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oscillator 5 | 18432000 6 | 7 | 8 | 0 9 | C0000 10 | 0 11 | true 12 | 13 | 14 | 15 | 100000 16 | false 17 | 40000 18 | BFFFF 19 | 20 | 1 21 | false 22 | true 23 | 24 | 25 | 26 | 27 | 1 28 | 8 29 | 4 30 | B 31 | 32 | 33 | 0 34 | 08 35 | c0 36 | c7 37 | 38 | 39 | 0 40 | 08 41 | 80 42 | bf 43 | 44 | 45 | 82 46 | 18 47 | 03 48 | 03 49 | 50 | 51 | 52 | 0 53 | b7 54 | true 55 | true 56 | 57 | 1 58 | 59 | EZ80F92 60 | 1.0.1 61 | 1.00 62 | 63 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS 3 | * Author: Dean Belfield 4 | * Created: 19/06/2022 5 | * Last Updated: 11/11/2023 6 | * 7 | * Modinfo: 8 | * 11/07/2022: Version 0.01: Tweaks for Agon Light, Command Line code added 9 | * 13/07/2022: Version 0.02 10 | * 15/07/2022: Version 0.03: Warm boot support, VBLANK interrupt 11 | * 25/07/2022: Version 0.04; Tweaks to initialisation and interrupts 12 | * 03/08/2022: Version 0.05: Extended MOS for BBC Basic, added config file 13 | * 05/08/2022: Version 0.06: Interim release with hardware flow control enabled 14 | * 10/08/2022: Version 0.07: Bug fixes 15 | * 05/09/2022: Version 0.08: Minor updates to MOS 16 | * 02/10/2022: Version 1.00: Improved error handling for languages, changed bootup title to Quark 17 | * 03/10/2022: Version 1.01: Added SET command, tweaked error handling 18 | * 20/10/2022: + Tweaked error handling 19 | * 13/11/2022: Version 1.02 20 | * 14/03/2023 Version 1.03: SD now uses timer0, does not require interrupt 21 | * + Stubbed command history 22 | * 22/03/2023: + Moved command history to mos_editor.c 23 | * 23/03/2023: RC2 + Increased baud rate to 1152000 24 | * + Improved ESP32->eZ80 boot sync 25 | * 29/03/2023: RC3 + Added UART1 initialisation, tweaked startup sequence timings 26 | * 16/05/2023: Version 1.04: Fixed MASTERCLOCK value in uart.h, added startup beep 27 | * 03/08/2023: RC2 + Enhanced low-level keyboard functionality 28 | * 27/09/2023: + Updated RTC 29 | * 11/11/2023: RC3 + See Github for full list of changes 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "defines.h" 39 | #include "config.h" 40 | #include "uart.h" 41 | #include "spi.h" 42 | #include "timer.h" 43 | #include "ff.h" 44 | #include "clock.h" 45 | #include "mos.h" 46 | #include "i2c.h" 47 | 48 | #define MOS_version 1 49 | #define MOS_revision 4 50 | #define MOS_rc 0 51 | 52 | extern void * set_vector(unsigned int vector, void(*handler)(void)); 53 | 54 | extern void vblank_handler(void); 55 | extern void uart0_handler(void); 56 | extern void i2c_handler(void); 57 | 58 | extern char coldBoot; // 1 = cold boot, 0 = warm boot 59 | extern volatile char keycode; // Keycode 60 | extern volatile char gp; // General poll variable 61 | 62 | extern volatile BYTE history_no; 63 | extern volatile BYTE history_size; 64 | 65 | static char cmd[256]; // Array for the command line handler 66 | 67 | // Wait for the ESP32 to respond with a GP packet to signify it is ready 68 | // Parameters: 69 | // - pUART: Pointer to a UART structure 70 | // - baudRate: Baud rate to initialise UART with 71 | // Returns: 72 | // - 1 if the function succeeded, otherwise 0 73 | // 74 | int wait_ESP32(UART * pUART, UINT24 baudRate) { 75 | int i, t; 76 | 77 | pUART->baudRate = baudRate; // Initialise the UART object 78 | pUART->dataBits = 8; 79 | pUART->stopBits = 1; 80 | pUART->parity = PAR_NOPARITY; 81 | pUART->flowControl = FCTL_HW; 82 | pUART->interrupts = UART_IER_RECEIVEINT; 83 | 84 | open_UART0(pUART); // Open the UART 85 | init_timer0(10, 16, 0x00); // 10ms timer for delay 86 | gp = 0; // Reset the general poll byte 87 | for(t = 0; t < 200; t++) { // A timeout loop (200 x 50ms = 10s) 88 | putch(23); // Send a general poll packet 89 | putch(0); 90 | putch(VDP_gp); 91 | putch(1); 92 | for(i = 0; i < 5; i++) { // Wait 50ms 93 | wait_timer0(); 94 | } 95 | if(gp == 1) break; // If general poll returned, then exit for loop 96 | } 97 | enable_timer0(0); // Disable the timer 98 | return gp; 99 | } 100 | 101 | // Initialise the interrupts 102 | // 103 | void init_interrupts(void) { 104 | set_vector(PORTB1_IVECT, vblank_handler); // 0x32 105 | set_vector(UART0_IVECT, uart0_handler); // 0x18 106 | set_vector(I2C_IVECT, i2c_handler); // 0x1C 107 | } 108 | 109 | // The main loop 110 | // 111 | int main(void) { 112 | UART pUART0; 113 | 114 | DI(); // Ensure interrupts are disabled before we do anything 115 | init_interrupts(); // Initialise the interrupt vectors 116 | init_rtc(); // Initialise the real time clock 117 | init_spi(); // Initialise SPI comms for the SD card interface 118 | init_UART0(); // Initialise UART0 for the ESP32 interface 119 | init_UART1(); // Initialise UART1 120 | EI(); // Enable the interrupts now 121 | 122 | if(!wait_ESP32(&pUART0, 1152000)) { // Try to lock onto the ESP32 at maximum rate 123 | if(!wait_ESP32(&pUART0, 384000)) { // If that fails, then fallback to the lower baud rate 124 | gp = 2; // Flag GP as 2, just in case we need to handle this error later 125 | } 126 | } 127 | if(coldBoot == 0) { // If a warm boot detected then 128 | putch(12); // Clear the screen 129 | } 130 | printf("Agon Quark MOS Version %d.%02d", MOS_version, MOS_revision); 131 | #if MOS_rc > 0 132 | printf(" RC%d", MOS_rc); 133 | #endif 134 | printf("\n\r\n\r"); 135 | #if DEBUG > 0 136 | printf("@Baud Rate: %d\n\r\n\r", pUART0.baudRate); 137 | #endif 138 | 139 | mos_mount(); // Mount the SD card 140 | 141 | putch(7); // Startup beep 142 | history_no = 0; 143 | history_size = 0; 144 | 145 | // Load the autoexec.bat config file 146 | // 147 | #if enable_config == 1 148 | if(coldBoot > 0) { // Check it's a cold boot (after reset, not RST 00h) 149 | mos_BOOT("autoexec.txt", cmd, sizeof cmd); // Then load and run the config file 150 | } 151 | #endif 152 | 153 | // The main loop 154 | // 155 | while(1) { 156 | if(mos_input(&cmd, sizeof(cmd)) == 13) { 157 | int err = mos_exec(&cmd); 158 | if(err > 0) { 159 | mos_error(err); 160 | } 161 | } 162 | else { 163 | printf("%cEscape\n\r", MOS_prompt); 164 | } 165 | } 166 | 167 | return 0; 168 | } 169 | -------------------------------------------------------------------------------- /src/clock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS - Real Time Clock 3 | * Author: Dean Belfield 4 | * Created: 09/03/2023 5 | * Last Updated: 26/09/2023 6 | * 7 | * Modinfo: 8 | * 15/03/2023: Added rtc_getDateString, rtc_update 9 | * 21/03/2023: Uses VDP values from defines.h 10 | * 05/06/2023: Added RTC enable flag 11 | * 26/09/2023: Timestamps now packed into 6 bytes 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "defines.h" 22 | #include "uart.h" 23 | #include "clock.h" 24 | 25 | extern volatile BYTE vpd_protocol_flags; // In globals.asm 26 | extern volatile BYTE rtc_enable; // In globals.asm 27 | 28 | const char * rtc_days[7][2] = { 29 | { "Sun", "Sunday" }, 30 | { "Mon", "Monday" }, 31 | { "Tue", "Tuesday" }, 32 | { "Wed", "Wednesday" }, 33 | { "Thu", "Thursday" }, 34 | { "Fri", "Friday" }, 35 | { "Sat", "Saturday" }, 36 | }; 37 | 38 | const char * rtc_months[12][2] = { 39 | { "Jan", "January" }, 40 | { "Feb", "February" }, 41 | { "Mar", "March" }, 42 | { "Apr", "April" }, 43 | { "May", "May" }, 44 | { "Jun", "June" }, 45 | { "Jul", "July" }, 46 | { "Aug", "August" }, 47 | { "Sep", "September" }, 48 | { "Oct", "October" }, 49 | { "Nov", "November" }, 50 | { "Dec", "December" }, 51 | }; 52 | 53 | // Request an update of the RTC from the ESP32 54 | // 55 | void rtc_update() { 56 | if(!rtc_enable) { 57 | return; 58 | } 59 | vpd_protocol_flags &= 0xDF; // Reset bit 5 60 | 61 | putch(23); // Request the time from the ESP32 62 | putch(0); 63 | putch(VDP_rtc); 64 | putch(0); // 0: Get time 65 | 66 | while((vpd_protocol_flags & 0x20) == 0); 67 | } 68 | 69 | // Unpack a 6-byte RTC packet into time struct 70 | // Parameters: 71 | // - buffer: Pointer to the RTC packet data 72 | // - t: Pointer to the time structure to populate 73 | // 74 | void rtc_unpack(UINT8 * buffer, vdp_time_t * t) { 75 | UINT32 d = *(UINT32 *)buffer; 76 | 77 | t->month = (d & 0x0000000F); // uint32_t month : 4; 00000000 00000000 00000000 0000xxxx : 00 00 00 0F >> 0 78 | t->day = (d & 0x000001F0) >> 4; // uint32_t day : 5; 00000000 00000000 0000000x xxxx0000 : 00 00 01 F0 >> 4 79 | t->dayOfWeek = (d & 0x00000E00) >> 9; // uint32_t dayOfWeek : 3; 00000000 00000000 0000xxx0 00000000 : 00 00 0E 00 >> 9 80 | t->dayOfYear = (d & 0x001FF000) >> 12; // uint32_t dayOfYear : 9; 00000000 000xxxxx xxxx0000 00000000 : 00 1F F0 00 >> 12 81 | t->hour = (d & 0x03E00000) >> 21; // uint32_t hour : 5; 000000xx xxx00000 00000000 00000000 : 03 E0 00 00 >> 21 82 | t->minute = (d & 0xFC000000) >> 26; // uint32_t minute : 6; xxxxxx00 00000000 00000000 00000000 : FC 00 00 00 >> 26 83 | 84 | t->second = buffer[4]; 85 | t->year = (char)buffer[5] + EPOCH_YEAR ; 86 | } 87 | 88 | // Format a date/time string 89 | // 90 | void rtc_formatDateTime(char * buffer, vdp_time_t * t) { 91 | sprintf(buffer, "%s, %02d/%02d/%4d %02d:%02d:%02d", rtc_days[t->dayOfWeek][0], t->day, t->month + 1, t->year, t->hour, t->minute, t->second); 92 | } -------------------------------------------------------------------------------- /src/clock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS - Real Time Clock 3 | * Author: Dean Belfield 4 | * Created: 09/03/2023 5 | * Last Updated: 26/09/2023 6 | * 7 | * Modinfo: 8 | * 15/03/2023: Added rtc_getDateString, rtc_update 9 | * 26/09/2023: Timestamps now packed into 6 bytes 10 | */ 11 | 12 | #ifndef RTC_H 13 | #define RTC_H 14 | 15 | #define EPOCH_YEAR 1980 16 | 17 | // RTC time structure 18 | // 19 | typedef struct { 20 | UINT16 year; 21 | UINT8 month; 22 | UINT8 day; 23 | UINT8 dayOfWeek; 24 | UINT16 dayOfYear; 25 | UINT8 hour; 26 | UINT8 minute; 27 | UINT8 second; 28 | } vdp_time_t; 29 | 30 | void init_rtc(); // In rtc.asm 31 | 32 | void rtc_update(); 33 | void rtc_unpack(UINT8 * buffer, vdp_time_t * t); 34 | void rtc_formatDateTime(char * buffer, vdp_time_t * t); 35 | 36 | #endif RTC_H 37 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS - MOS config 3 | * Author: Dean Belfield 4 | * Created: 19/09/2022 5 | * Last Updated: 13/11/2022 6 | * 7 | * Modinfo: 8 | * 13/11/2022: Added MOS_starLoadAddress 9 | */ 10 | 11 | #ifndef CONFIG_H 12 | #define CONFIG_H 13 | 14 | #define enable_config 1 // 0 = disable boot config loading, 1 = enable 15 | 16 | #define MOS_prompt '*' // MOS prompt character 17 | #define MOS_maxOpenFiles 8 // Maximum number of files that mos_FOPEN can open at the same time 18 | #define MOS_defaultLoadAddress 0x040000 // Default load address for LOAD and RUN commands 19 | #define MOS_starLoadAddress 0xB0000 // Address for loading on-SD star commands 20 | 21 | #endif CONFIG_H -------------------------------------------------------------------------------- /src/defines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS - MOS defines 3 | * Author: Dean Belfield 4 | * Created: 21/03/2023 5 | * Last Updated: 10/11/2023 6 | * 7 | * Modinfo: 8 | * 22/03/2023: The VDP commands are now indexed from 0x80 9 | * 24/03/2023: Added DEBUG 10 | * 10/11/2023: Added VDP_consolemode 11 | */ 12 | 13 | #ifndef MOS_DEFINES_H 14 | #define MOS_DEFINES_H 15 | 16 | #define DEBUG 0 // Set to 0 for production, 1 for extra debug information 17 | 18 | // VDP specific (for VDU 23,0,n commands) 19 | // 20 | #define VDP_gp 0x80 21 | #define VDP_keycode 0x81 22 | #define VDP_cursor 0x82 23 | #define VDP_scrchar 0x83 24 | #define VDP_scrpixel 0x84 25 | #define VDP_audio 0x85 26 | #define VDP_mode 0x86 27 | #define VDP_rtc 0x87 28 | #define VDP_keystate 0x88 29 | #define VDP_logicalcoords 0xC0 30 | #define VDP_consolemode 0xFE 31 | #define VDP_terminalmode 0xFF 32 | 33 | #endif MOS_DEFINES_H -------------------------------------------------------------------------------- /src/equs.inc: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: AGON MOS - Equs 3 | ; Author: Dean Belfield 4 | ; Created: 15/07/2022 5 | ; Last Updated: 08/06/2023 6 | ; 7 | ; Modinfo: 8 | ; 24/07/2022: Added TMR2_CTL 9 | ; 03/08/2022: Added UART0_BUFFERLEN 10 | ; 20/08/2022: Added some VDP protocol flags 11 | ; 18/09/2022: Added VDPP_FLAG_MODE 12 | ; 09/03/2023: Renamed TMR2_CTL to TMR0_CTL 13 | ; 15/03/2023: Added VDPP_FLAG_RTC 14 | ; 19/03/2023: Fixed TMR0_RR_H to point to correct register 15 | ; 08/06/2023: Add MASTERCLOCK to permit clock delay calculations 16 | 17 | ; System clock speed in Hz 18 | MASTERCLOCK: EQU 18432000 19 | 20 | ; MOS specific 21 | ; 22 | VDPP_BUFFERLEN: EQU 16 ; VDP Protocol Buffer Length 23 | 24 | VDPP_FLAG_CURSOR: EQU 00000001b 25 | VDPP_FLAG_SCRCHAR: EQU 00000010b 26 | VDPP_FLAG_POINT: EQU 00000100b 27 | VDPP_FLAG_AUDIO: EQU 00001000b 28 | VDPP_FLAG_MODE: EQU 00010000b 29 | VDPP_FLAG_RTC: EQU 00100000b 30 | VDPP_FLAG_MOUSE: EQU 01000000b 31 | ; VDPP_FLAG_BUFFERED: EQU 10000000b 32 | 33 | ; For GPIO 34 | ; PA not available on eZ80F92 35 | ; 36 | PA_DR: EQU 96h 37 | PA_DDR: EQU 97h 38 | PA_ALT1: EQU 98h 39 | PA_ALT2: EQU 99h 40 | PB_DR: EQU 9Ah 41 | PB_DDR: EQU 9Bh 42 | PB_ALT1: EQU 9Ch 43 | PB_ALT2: EQU 9Dh 44 | PC_DR: EQU 9Eh 45 | PC_DDR: EQU 9Fh 46 | PC_ALT1: EQU A0h 47 | PC_ALT2: EQU A1h 48 | PD_DR: EQU A2h 49 | PD_DDR: EQU A3h 50 | PD_ALT1: EQU A4h 51 | PD_ALT2: EQU A5h 52 | 53 | GPIOMODE_OUT: EQU 0 ; Output 54 | GPIOMODE_IN: EQU 1 ; Input 55 | GPIOMODE_DIO: EQU 2 ; Open Drain IO 56 | GPIOMODE_SIO: EQU 3 ; Open Source IO 57 | GPIOMODE_INTD: EQU 4 ; Interrupt, Dual Edge 58 | GPIOMODE_ALTF: EQU 5; ; Alt Function 59 | GPIOMODE_INTAL: EQU 6 ; Interrupt, Active Low 60 | GPIOMODE_INTAH: EQU 7 ; Interrupt, Active High 61 | GPIOMODE_INTFE: EQU 8 ; Interrupt, Falling Edge 62 | GPIOMODE_INTRE: EQU 9 ; Interrupt, Rising Edge 63 | 64 | ; For interrupts.asm 65 | ; 66 | 67 | ;UARTs 68 | ; 69 | UART0_IVECT EQU 18h 70 | UART1_IVECT EQU 1Ah 71 | 72 | ;Ports 73 | ; 74 | PB0_IVECT EQU 30h ; AGON ITRP Interrupt (Pin 28/IO17 of the ESP32) 75 | PB1_IVECT EQU 32h ; AGON VBLANK Interrupt (Pin 23/IO15 of the ESP32) 76 | PB2_IVECT EQU 34h 77 | PB3_IVECT EQU 36h 78 | PB4_IVECT EQU 38h 79 | PB5_IVECT EQU 3Ah 80 | PB6_IVECT EQU 3Ch 81 | PB7_IVECT EQU 3Eh 82 | 83 | PC0_IVECT EQU 40h 84 | PC1_IVECT EQU 42h 85 | PC2_IVECT EQU 44h 86 | PC3_IVECT EQU 46h 87 | PC4_IVECT EQU 48h 88 | PC5_IVECT EQU 4Ah 89 | PC6_IVECT EQU 4Ch 90 | PC7_IVECT EQU 4Eh 91 | 92 | PD0_IVECT EQU 50h 93 | PD1_IVECT EQU 52h 94 | PD2_IVECT EQU 54h 95 | PD3_IVECT EQU 56h 96 | PD4_IVECT EQU 58h 97 | PD5_IVECT EQU 5Ah 98 | PD6_IVECT EQU 5Ch 99 | PD7_IVECT EQU 5Eh 100 | 101 | ; For vectors16.asm 102 | ; 103 | TMR0_CTL EQU 80h 104 | TMR0_DR_L EQU 81h 105 | TMR0_RR_L EQU 81h 106 | TMR0_DR_H EQU 82h 107 | TMR0_RR_H EQU 82h 108 | -------------------------------------------------------------------------------- /src/gpio.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: AGON MOS - gpio code 3 | ; Author: Dean Belfield 4 | ; Created: 15/07/2022 5 | ; Last Updated: 24/07/2022 6 | ; 7 | ; Modinfo: 8 | ; 24/07/2022: Moved SWITCH_A to misc.asm 9 | 10 | INCLUDE "macros.inc" 11 | INCLUDE "equs.inc" 12 | 13 | .ASSUME ADL = 1 14 | 15 | DEFINE .STARTUP, SPACE = ROM 16 | SEGMENT .STARTUP 17 | 18 | XDEF GPIOB_SETMODE 19 | XREF SWITCH_A 20 | 21 | ; A: Mode 22 | ; B: Pins 23 | ; 24 | GPIOB_SETMODE: CALL SWITCH_A 25 | DW GPIOB_M0 ; Output 26 | DW GPIOB_M1 ; Input 27 | DW GPIOB_M2 ; Open Drain IO 28 | DW GPIOB_M3 ; Open Source IO 29 | DW GPIOB_M4 ; Interrupt, Dual Edge 30 | DW GPIOB_M5 ; Alt Function 31 | DW GPIOB_M6 ; Interrupt, Active Low 32 | DW GPIOB_M7 ; Interrupt, Active High 33 | DW GPIOB_M8 ; Interrupt, Falling Edge 34 | DW GPIOB_M9 ; Interrupt, Rising Edge 35 | 36 | ; Output 37 | ; 38 | GPIOB_M0: RES_GPIO PB_DDR, B 39 | RES_GPIO PB_ALT1, B 40 | RES_GPIO PB_ALT2, B 41 | RET 42 | 43 | ; Input 44 | ; 45 | GPIOB_M1: SET_GPIO PB_DDR, B 46 | RES_GPIO PB_ALT1, B 47 | RES_GPIO PB_ALT2, B 48 | RET 49 | 50 | ; Open Drain IO 51 | ; 52 | GPIOB_M2: RES_GPIO PB_DDR, B 53 | SET_GPIO PB_ALT1, B 54 | RES_GPIO PB_ALT2, B 55 | RET 56 | 57 | ; Open Source IO 58 | ; 59 | GPIOB_M3: SET_GPIO PB_DDR, B 60 | SET_GPIO PB_ALT1, B 61 | RES_GPIO PB_ALT2, B 62 | RET 63 | 64 | ; Interrupt, Dual Edge 65 | ; 66 | GPIOB_M4: SET_GPIO PB_DR, B 67 | RES_GPIO PB_DDR, B 68 | RES_GPIO PB_ALT1, B 69 | RES_GPIO PB_ALT2, B 70 | RET 71 | 72 | ; Alt Function 73 | ; 74 | GPIOB_M5: SET_GPIO PB_DDR, B 75 | RES_GPIO PB_ALT1, B 76 | SET_GPIO PB_ALT2, B 77 | RET 78 | 79 | ; Interrupt, Active Low 80 | ; 81 | GPIOB_M6: RES_GPIO PB_DR, B 82 | RES_GPIO PB_DDR, B 83 | SET_GPIO PB_ALT1, B 84 | SET_GPIO PB_ALT2, B 85 | RET 86 | 87 | 88 | ; Interrupt, Active High 89 | ; 90 | GPIOB_M7: SET_GPIO PB_DR, B 91 | RES_GPIO PB_DDR, B 92 | SET_GPIO PB_ALT1, B 93 | SET_GPIO PB_ALT2, B 94 | RET 95 | 96 | 97 | ; Interrupt, Falling Edge 98 | ; 99 | GPIOB_M8: RES_GPIO PB_DR, B 100 | SET_GPIO PB_DDR, B 101 | SET_GPIO PB_ALT1, B 102 | SET_GPIO PB_ALT2, B 103 | RET 104 | 105 | ; Interrupt, Rising Edge 106 | ; 107 | GPIOB_M9: SET_GPIO PB_DR, B 108 | SET_GPIO PB_DDR, B 109 | SET_GPIO PB_ALT1, B 110 | SET_GPIO PB_ALT2, B 111 | RET -------------------------------------------------------------------------------- /src/i2c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS - I2C code 3 | * Author: Jeroen Venema 4 | * Created: 07/11/2023 5 | * Last Updated: 10/11/2023 6 | * 7 | * Modinfo: 8 | */ 9 | 10 | #include 11 | #include "i2c.h" 12 | #include 13 | #include "timer.h" 14 | 15 | // Set I2C clock and sampling frequency 16 | void I2C_setfrequency(UINT8 id) { 17 | switch(id) { 18 | case(I2C_SPEED_115200): 19 | I2C_CCR = (0x01<<3) | 0x03; // 115.2KHz fast-mode (400KHz max), sampling at 4.6MHz 20 | break; 21 | case(I2C_SPEED_230400): 22 | I2C_CCR = (0x01<<3) | 0x02; // 230.4KHz fast-mode (400KHz max), sampling at 2.3Mhz 23 | break; 24 | case(I2C_SPEED_57600): 25 | default: 26 | I2C_CCR = (0x01<<3) | 0x04; // 57.6KHz default standard-mode (100KHz max), sampling at 1.15Mhz 27 | } 28 | } 29 | 30 | // Initializes the I2C bus 31 | void init_I2C(void) { 32 | i2c_msg_size = 0; 33 | CLK_PPD1 = CLK_PPD_I2C_OFF; // Power Down I2C block before enabling it, avoid locking bug 34 | I2C_CTL = I2C_CTL_ENAB; // Enable I2C block, don't enable interrupts yet 35 | I2C_setfrequency(0); 36 | CLK_PPD1 = 0x0; // Power up I2C block 37 | } 38 | 39 | // Internal function 40 | void I2C_handletimeout(void) { 41 | // reset the interface 42 | I2C_CTL = 0; 43 | init_I2C(); 44 | } 45 | 46 | // Open the I2C bus, register the driver interrupt 47 | // Parameters: None 48 | // Returns: None 49 | void mos_I2C_OPEN(UINT8 frequency) { 50 | init_I2C(); 51 | I2C_setfrequency(frequency); 52 | } 53 | 54 | // Close the I2C bus, deregister the driver interrupt 55 | // Parameters: None 56 | // Returns: None 57 | void mos_I2C_CLOSE(void) { 58 | CLK_PPD1 = CLK_PPD_I2C_OFF; // Power Down I2C block 59 | I2C_CTL &= ~(I2C_CTL_ENAB); // Disable I2C block 60 | } 61 | 62 | // Write a number of bytes to an address on the I2C bus 63 | // Parameters: 64 | // - i2c_address: I2C address of the slave device 65 | // - size: number of bytes to write 66 | // - buffer: pointer to the first byte to write 67 | // Returns: 68 | // - 0 on success, or errorcode 69 | UINT8 mos_I2C_WRITE(UINT8 i2c_address, UINT8 size, char * buffer) { 70 | 71 | // send maximum of 32 bytes in a single I2C transaction 72 | if(size > I2C_MAX_BUFFERLENGTH) size = I2C_MAX_BUFFERLENGTH; 73 | if(i2c_address > 127) return RET_NORESPONSE; 74 | // wait for IDLE status 75 | init_timer0(1, 16, 0x00); // 1ms timer for delay 76 | enable_timer0(1); 77 | while(i2c_role) { 78 | // anything but IDLE (00) 79 | if(get_timer0() > I2C_TIMEOUTMS) { 80 | I2C_handletimeout(); 81 | enable_timer0(0); // Disable timer 82 | return RET_ARB_LOST; 83 | } 84 | } 85 | enable_timer0(0); 86 | 87 | i2c_msg_ptr = buffer; 88 | i2c_msg_size = size; 89 | i2c_role = I2C_MTX; // MTX - Master Transmit Mode 90 | i2c_error = RET_OK; 91 | i2c_slave_rw = i2c_address << 1; // shift one bit left, 0 on bit 0 == write action on I2C 92 | 93 | I2C_CTL = I2C_CTL_IEN | I2C_CTL_ENAB | I2C_CTL_STA; // send start condition 94 | 95 | init_timer0(1, 16, 0x00); // 1ms timer for delay 96 | enable_timer0(1); 97 | while(i2c_role == I2C_MTX) { // while MTX 98 | if(get_timer0() > I2C_TIMEOUTMS) { 99 | I2C_handletimeout(); 100 | enable_timer0(0); // Disable timer 101 | return RET_DATA_NACK; 102 | } 103 | } 104 | 105 | enable_timer0(0); // Disable timer 106 | return i2c_error; 107 | } 108 | 109 | // Read a number of bytes from an i2c_address on the I2C bus 110 | // Parameters: 111 | // - i2c_address: I2C address of the slave device 112 | // - size: number of bytes to read 113 | // - buffer: pointer to the first byte to read 114 | // Returns: 115 | // - 0 on success, or errorcode 116 | UINT8 mos_I2C_READ(UINT8 i2c_address, UINT8 size, char* buffer) 117 | { 118 | if(size == 0) return 0; 119 | if(i2c_address > 127) return RET_NORESPONSE; 120 | // receive maximum of 32 bytes in a single I2C transaction 121 | if(size > I2C_MAX_BUFFERLENGTH) size = I2C_MAX_BUFFERLENGTH; 122 | 123 | // wait for IDLE status 124 | init_timer0(1, 16, 0x00); // 1ms timer for delay 125 | enable_timer0(1); 126 | while(i2c_role) { 127 | // anything but IDLE (00) 128 | if(get_timer0() > I2C_TIMEOUTMS) { 129 | I2C_handletimeout(); 130 | enable_timer0(0); // Disable timer 131 | return RET_ARB_LOST; 132 | } 133 | } 134 | enable_timer0(0); 135 | i2c_msg_ptr = buffer; 136 | i2c_msg_size = size; 137 | i2c_role = I2C_MRX; // MRX mode 138 | i2c_error = RET_OK; 139 | i2c_slave_rw = (1<<0); // receive bit 0 140 | i2c_slave_rw |= i2c_address << 1; // shift 7-bit address one bit left 141 | 142 | I2C_CTL = I2C_CTL_IEN | I2C_CTL_ENAB | I2C_CTL_STA; // send start condition 143 | 144 | init_timer0(1, 16, 0x00); // 1ms timer for delay 145 | enable_timer0(1); 146 | while(i2c_role == I2C_MRX) { 147 | if(get_timer0() > I2C_TIMEOUTMS) { 148 | I2C_handletimeout(); 149 | enable_timer0(0); // Disable timer 150 | return RET_ARB_LOST; 151 | } 152 | } 153 | enable_timer0(0); // Disable timer 154 | return i2c_error; 155 | } 156 | -------------------------------------------------------------------------------- /src/i2c.h: -------------------------------------------------------------------------------- 1 | #ifndef _I2C_H_ 2 | #define _I2C_H_ 3 | 4 | #include 5 | 6 | 7 | extern volatile char i2c_slave_rw; 8 | extern volatile char i2c_error; 9 | extern volatile char i2c_role; 10 | extern volatile UINT8 i2c_msg_size; 11 | extern volatile char * i2c_msg_ptr; 12 | 13 | // I2C_CTL register bits 14 | #define I2C_CTL_IEN (1<<7) 15 | #define I2C_CTL_ENAB (1<<6) 16 | #define I2C_CTL_STA (1<<5) 17 | #define I2C_CTL_STP (1<<4) 18 | #define I2C_CTL_IFLG (1<<3) 19 | #define I2C_CTL_AAK (1<<2) 20 | 21 | // ez80 PPD register bits 22 | #define CLK_PPD_I2C_OFF (1<<2) 23 | 24 | // I2C return codes to caller 25 | #define RET_OK 0x00 26 | #define RET_NORESPONSE 0x01 27 | #define RET_DATA_NACK 0x02 28 | #define RET_ARB_LOST 0x04 29 | #define RET_BUS_ERROR 0x08 30 | 31 | // I2C constants 32 | #define I2C_MAX_BUFFERLENGTH 32 33 | #define I2C_TIMEOUTMS 2000 34 | #define I2C_SPEED_57600 0x01 35 | #define I2C_SPEED_115200 0x02 36 | #define I2C_SPEED_230400 0x03 37 | 38 | // I2C role state 39 | #define I2C_IDLE 0x00 40 | #define I2C_MTX 0x01 41 | #define I2C_MRX 0x02 42 | #define I2C_SRX 0x04 43 | #define I2C_STX 0x08 44 | 45 | void init_I2C(void); 46 | void mos_I2C_OPEN(UINT8 frequency); 47 | void mos_I2C_CLOSE(void); 48 | UINT8 mos_I2C_WRITE(UINT8 i2c_address, UINT8 size, char * buffer); 49 | UINT8 mos_I2C_READ(UINT8 i2c_address, UINT8 size, char * buffer); 50 | 51 | #endif _I2C_H_ -------------------------------------------------------------------------------- /src/i2c.inc: -------------------------------------------------------------------------------- 1 | ;* Return status codes to read/write caller 2 | RET_OK .equ 00h ; ok - caller sets this at entry 3 | RET_NORESPONSE .equ 01h ; address sent, nack received 4 | RET_DATA_NACK .equ 02h ; data sent, nack received 5 | RET_ARB_LOST .equ 04h ; arbitration lost 6 | RET_BUS_ERROR .equ 08h ; Bus error 7 | 8 | ;* I2C ROLE STATUS 9 | I2C_IDLE .equ 00h 10 | I2C_MTX .equ 01h 11 | I2C_MRX .equ 02h 12 | I2C_SRX .equ 04h 13 | I2C_STX .equ 08h 14 | 15 | ;* I2C_CTL bits 16 | I2C_CTL_IEN .equ 10000000b 17 | I2C_CTL_ENAB .equ 01000000b 18 | I2C_CTL_STA .equ 00100000b 19 | I2C_CTL_STP .equ 00010000b 20 | I2C_CTL_IFLG .equ 00001000b 21 | I2C_CTL_AAK .equ 00000100b 22 | -------------------------------------------------------------------------------- /src/interrupts.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: AGON MOS - Interrupt handlers 3 | ; Author: Dean Belfield 4 | ; Created: 03/08/2022 5 | ; Last Updated: 29/03/2023 6 | ; 7 | ; Modinfo: 8 | ; 09/03/2023: No longer uses timer interrupt 0 for SD card timing 9 | ; 29/03/2023: Added support for UART1 10 | ; 10/11/2023: Added support for I2C 11 | 12 | INCLUDE "macros.inc" 13 | INCLUDE "equs.inc" 14 | INCLUDE "ez80f92.inc" 15 | INCLUDE "i2c.inc" 16 | 17 | .ASSUME ADL = 1 18 | 19 | DEFINE .STARTUP, SPACE = ROM 20 | SEGMENT .STARTUP 21 | 22 | XDEF _vblank_handler 23 | XDEF _uart0_handler 24 | XDEF _i2c_handler 25 | 26 | XREF _clock 27 | XREF _vdp_protocol_data 28 | 29 | XREF UART0_serial_RX 30 | XREF UART0_serial_TX 31 | XREF mos_api 32 | XREF vdp_protocol 33 | 34 | XREF _i2c_slave_rw 35 | XREF _i2c_error 36 | XREF _i2c_role 37 | XREF _i2c_msg_ptr 38 | XREF _i2c_msg_size 39 | 40 | ; AGON Vertical Blank Interrupt handler 41 | ; 42 | _vblank_handler: DI 43 | PUSH AF 44 | SET_GPIO PB_DR, 2 ; Need to set this to 2 for the interrupt to work correctly 45 | PUSH BC 46 | PUSH DE 47 | PUSH HL 48 | LD HL, (_clock) ; Increment the 32-bit clock counter 49 | LD BC, 2 ; By 2, effectively timing in centiseconds 50 | ADD HL, BC 51 | LD (_clock), HL 52 | LD A, (_clock + 3) 53 | ADC A, 0 54 | LD (_clock + 3), A 55 | POP HL 56 | POP DE 57 | POP BC 58 | POP AF 59 | EI 60 | RETI.L 61 | 62 | ; AGON UART0 Interrupt Handler 63 | ; 64 | _uart0_handler: DI 65 | PUSH AF 66 | PUSH BC 67 | PUSH DE 68 | PUSH HL 69 | CALL UART0_serial_RX 70 | LD C, A 71 | LD HL, _vdp_protocol_data 72 | CALL vdp_protocol 73 | POP HL 74 | POP DE 75 | POP BC 76 | POP AF 77 | EI 78 | RETI.L 79 | 80 | ; AGON I2C Interrupt handler 81 | ; 82 | _i2c_handler: 83 | DI 84 | PUSH AF 85 | PUSH HL 86 | PUSH DE 87 | IN0 A, (I2C_SR) ; input I2C status register - switch case to this status value 88 | AND 11111000b ; cancel lower 3 bits, so we can use RRA. This will save 1 T-state 89 | RRA 90 | RRA 91 | RRA ; bits [7:3] contain the jumpvector 92 | CALL i2c_handle_sr_vector 93 | ; and switch on the vectors in this table 94 | DW i2c_case_buserror ; 00h 95 | DW i2c_case_master_start ; 08h 96 | DW i2c_case_invalid ; 10h 97 | DW i2c_case_aw_acked ; 18h 98 | DW i2c_case_aw_nacked ; 20h 99 | DW i2c_case_db_acked ; 28h 100 | DW i2c_case_db_nacked ; 30h 101 | DW i2c_case_arblost ; 38h 102 | DW i2c_case_mr_ar_ack ; 40h 103 | DW i2c_case_mr_ar_nack ; 48h 104 | DW i2c_case_mr_dbr_ack ; 50h 105 | DW i2c_case_mr_dbr_nack ; 58h 106 | DW i2c_case_toimplement ; 60h - slave 107 | DW i2c_case_toimplement ; 68h - slave 108 | DW i2c_case_toimplement ; 70h - slave 109 | DW i2c_case_toimplement ; 78h - slave 110 | DW i2c_case_toimplement ; 80h - slave 111 | DW i2c_case_toimplement ; 88h - slave 112 | DW i2c_case_toimplement ; 90h - slave 113 | DW i2c_case_toimplement ; 98h - slave 114 | DW i2c_case_toimplement ; A0h - slave 115 | DW i2c_case_toimplement ; A8h - slave 116 | DW i2c_case_toimplement ; B0h - slave 117 | DW i2c_case_toimplement ; B8h - slave 118 | DW i2c_case_toimplement ; C0h - slave 119 | DW i2c_case_toimplement ; C8h - slave 120 | DW i2c_case_toimplement ; D0h - 10bit I2C address 121 | DW i2c_case_toimplement ; D8h - 10bit I2C address 122 | DW i2c_case_invalid ; E0h 123 | DW i2c_case_invalid ; E8h 124 | DW i2c_case_invalid ; F0h 125 | DW i2c_case_invalid ; F8h - Should never produce an interrupt 126 | 127 | i2c_handle_sr_vector: 128 | EX (SP), HL ; Swap HL with the contents of the top of the stack 129 | ADD A, A ; Multiply A by two 130 | ADD A, L 131 | LD L, A 132 | ADC A, H 133 | SUB L 134 | LD H, A ; Add 8bit A to HL 135 | LD A, (HL) ; Fetch vector adress from the table 136 | INC HL 137 | LD H, (HL) 138 | LD L, A 139 | EX (SP), HL ; Swap this new address back, restores HL 140 | RET ; Return program control to this new address 141 | 142 | i2c_case_buserror: ; 00h 143 | LD A, RET_BUS_ERROR 144 | LD (_i2c_error),A 145 | 146 | ; perform software reset of the bus 147 | XOR A 148 | OUT0 (I2C_SRR),A 149 | LD HL, _i2c_role 150 | LD A, I2C_IDLE ; READY state 151 | LD (HL),A 152 | 153 | POP DE 154 | POP HL 155 | POP AF 156 | EI 157 | RETI.L 158 | 159 | i2c_case_master_start: ; 08h 160 | i2c_case_master_repstart: ; 10h 161 | LD A, (_i2c_slave_rw) ; load slave address and r/w bit 162 | OUT0 (I2C_DR), A ; store to I2C Data Register 163 | LD A, I2C_CTL_IEN | I2C_CTL_ENAB | I2C_CTL_AAK 164 | OUT0 (I2C_CTL),A ; set to Control register 165 | 166 | POP DE 167 | POP HL 168 | POP AF 169 | EI 170 | RETI.L 171 | 172 | i2c_case_aw_acked: ; 18h 173 | i2c_case_db_acked: ; 28h 174 | ; Check size and size-- 175 | LD A, (_i2c_msg_size) 176 | OR A 177 | JR Z, i2c_sendstop 178 | DEC A 179 | LD (_i2c_msg_size), A 180 | 181 | ; load pointer 182 | LD HL, _i2c_msg_ptr 183 | LD DE, HL 184 | LD HL, (HL) 185 | 186 | ; Load indexed byte from buffer 187 | LD A, (HL) 188 | 189 | OUT0 (I2C_DR), A ; store to I2C Data Register 190 | LD A, I2C_CTL_IEN | I2C_CTL_ENAB | I2C_CTL_AAK 191 | OUT0 (I2C_CTL),A ; set to Control register 192 | 193 | INC HL ; pointer++ 194 | EX DE, HL 195 | LD (HL), DE 196 | 197 | POP DE 198 | POP HL 199 | POP AF 200 | EI 201 | RETI.L 202 | 203 | i2c_case_aw_nacked: ; 20h 204 | i2c_case_mr_ar_nack: ; 48h 205 | LD A, RET_NORESPONSE 206 | LD (_i2c_error),A 207 | JP i2c_sendstop 208 | 209 | i2c_case_db_nacked: ; 30h 210 | LD A, RET_DATA_NACK 211 | LD (_i2c_error),A 212 | JP i2c_sendstop 213 | 214 | i2c_case_arblost: ; 38h 215 | LD A, RET_ARB_LOST 216 | LD (_i2c_error),A 217 | JP i2c_sendstop 218 | 219 | i2c_case_mr_dbr_ack: ; 50h 220 | ; calculate offset address into i2c_mbuffer 221 | LD HL, _i2c_msg_ptr 222 | LD DE, HL 223 | LD HL, (HL) 224 | 225 | IN0 A,(I2C_DR) ; load byte from I2C Data Register 226 | LD (HL),A ; store in buffer at calculated index 227 | 228 | INC HL ; pointer++ 229 | EX DE, HL 230 | LD (HL), DE 231 | ; ; intentionally falling through to next case 232 | i2c_case_mr_ar_ack: ; 40h 233 | LD A, (_i2c_msg_size) 234 | CP 1 ; last byte to receive? 235 | JR Z, $F 236 | 237 | LD A, I2C_CTL_IEN | I2C_CTL_ENAB | I2C_CTL_AAK ; reply with ACK 238 | JR $end 239 | $$: 240 | LD A, I2C_CTL_IEN | I2C_CTL_ENAB ; reply without ACK 241 | $end: OUT0 (I2C_CTL),A ; set to Control register 242 | ; size-- 243 | LD A, (_i2c_msg_size) 244 | DEC A 245 | LD (_i2c_msg_size), A 246 | 247 | POP DE 248 | POP HL 249 | POP AF 250 | EI 251 | RETI.L 252 | 253 | i2c_case_mr_dbr_nack: ; 58h 254 | ; load pointer 255 | LD HL, _i2c_msg_ptr 256 | LD HL, (HL) 257 | 258 | IN0 A,(I2C_DR) ; load byte from I2C Data Register 259 | LD (HL),A ; store in buffer at calculated index 260 | 261 | JR i2c_sendstop 262 | 263 | i2c_case_invalid: 264 | i2c_case_toimplement: 265 | i2c_sendstop: 266 | ; send stop first to go to idle state 267 | LD A, I2C_CTL_ENAB | I2C_CTL_STP 268 | OUT0 (I2C_CTL),A ; set to Control register 269 | $$: 270 | IN0 A,(I2C_CTL) 271 | AND I2C_CTL_STP ; STP bit still in place? 272 | JR NZ, $B 273 | 274 | ; Release control of the bus 275 | XOR A ; all bits of I2C_CTL to 0 276 | OUT0 (I2C_CTL),A ; set to Control register I2C_CTL 277 | LD HL, _i2c_role 278 | LD A, I2C_IDLE ; IDLE state 279 | LD (HL),A 280 | 281 | POP DE 282 | POP HL 283 | POP AF 284 | EI 285 | RETI.L 286 | 287 | END 288 | -------------------------------------------------------------------------------- /src/keyboard.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: AGON MOS - Keyboard routines 3 | ; Author: Dean Belfield 4 | ; Created: 13/08/2023 5 | ; Last Updated: 13/08/2023 6 | ; 7 | ; Modinfo: 8 | INCLUDE "macros.inc" 9 | INCLUDE "equs.inc" 10 | 11 | .ASSUME ADL = 1 12 | 13 | DEFINE .STARTUP, SPACE = ROM 14 | SEGMENT .STARTUP 15 | 16 | XDEF keyboard_handler 17 | 18 | XREF _keymods ; In globals.asm 19 | XREF _keyascii 20 | XREF _keycode 21 | XREF _keymap 22 | 23 | ; Agon keyboard routines 24 | ; C: Keydown state (1 = pressed 0 = depressed) 25 | ; B: Virtual keycode 26 | ; 27 | keyboard_handler: LD A, B 28 | OR A 29 | CALL NZ, keyboard_map 30 | JP keyboard_reset 31 | 32 | ; Write key up and down states to the keymap 33 | ; C: Keydown state (1 = pressed 0 = depressed) 34 | ; A: Virtual keycode 35 | ; 36 | keyboard_map: DEC A ; A: Virtual keycode - 1 37 | LD DE, keyboard_lookup ; DE: Address of keyboard lookup table 38 | LD HL, 0 39 | LD L, A 40 | ADD HL, HL 41 | ADD HL, HL ; HL: Virtual keycode x 4 42 | ADD HL, DE ; HL: Address of key data in keyboard lookup 43 | LD A, (HL) ; A: Bit to set 44 | OR A ; Check for empty entry 45 | RET Z ; Yep, nothing to set here, so return 46 | INC HL 47 | LD HL, (HL) ; HL: Address to set into 48 | BIT 0, C ; Is the key down or up? 49 | JR NZ, $F ; It is down so jump to the code that sets a bit 50 | CPL ; It is up so complement the bit 51 | AND (HL) ; Then AND it with the keymap entry 52 | LD (HL), A 53 | RET 54 | ; 55 | $$: OR (HL) ; OR with the keymap entry 56 | LD (HL), A 57 | RET 58 | ; 59 | ; Now check for CTRL+ALT+DEL 60 | ; 61 | keyboard_reset: LD A, B ; Get the virtual key code 62 | CP 130 ; Check for DEL (cursor keys) 63 | JR Z, $F 64 | CP 131 ; Check for DEL (no numlock) 65 | JR Z , $F 66 | CP 88 ; And DEL (numlock) 67 | RET NZ 68 | $$: LD A, (_keymods) ; DEL is pressed so check CTRL + ALT 69 | AND 05h ; Bit 0 and 2 70 | CP 05h 71 | RET NZ ; Exit if not pressed 72 | ; 73 | ; Here we're just waiting for the key to go up 74 | ; 75 | LD A, C ; Get key down 76 | DEC A ; Check for 0 (key up) 77 | JP NZ, 0 ; Yes so reset 78 | LD (_keyascii), A ; Otherwise clear the keycode so no interaction with console 79 | LD (_keycode), A 80 | RET 81 | 82 | ; Lookup table to convert virtual keycodes to physical keys (BBC BASIC specification) 83 | ; Uses the macro KEY to store each key as an index and a bit 84 | ; Four bytes of data per key 85 | ; - 1 byte: The bit data to set into the keymap 86 | ; - 3 bytes: The address to set (precalculated) 87 | ; 88 | KEY: MACRO VKEYCODE 89 | IF VKEYCODE = 0 90 | DL 0 91 | ELSE 92 | DB 1 << ((VKEYCODE - 1) & 00000111b) 93 | DW24 _keymap + (((VKEYCODE - 1) & 11111000b) >> 3) 94 | ENDIF 95 | ENDMACRO 96 | ; 97 | keyboard_lookup: KEY(099) ; VK_SPACE 98 | KEY(040) ; VK_0 99 | KEY(049) ; VK_1 100 | KEY(050) ; VK_2 101 | KEY(018) ; VK_3 102 | KEY(019) ; VK_4 103 | KEY(020) ; VK_5 104 | KEY(053) ; VK_6 105 | KEY(037) ; VK_7 106 | KEY(022) ; VK_8 107 | KEY(039) ; VK_9 108 | KEY(107) ; VK_KP_0 109 | KEY(108) ; VK_KP_1 110 | KEY(125) ; VK_KP_2 111 | KEY(109) ; VK_KP_3 112 | KEY(123) ; VK_KP_4 113 | KEY(124) ; VK_KP_5 114 | KEY(027) ; VK_KP_6 115 | KEY(028) ; VK_KP_7 116 | KEY(043) ; VK_KP_8 117 | KEY(044) ; VK_KP_9 118 | KEY(066) ; VK_a 119 | KEY(101) ; VK_b 120 | KEY(083) ; VK_c 121 | KEY(051) ; VK_d 122 | KEY(035) ; VK_e 123 | KEY(068) ; VK_f 124 | KEY(084) ; VK_g 125 | KEY(085) ; VK_h 126 | KEY(038) ; VK_i 127 | KEY(070) ; VK_j 128 | KEY(071) ; VK_k 129 | KEY(087) ; VK_l 130 | KEY(102) ; VK_m 131 | KEY(086) ; VK_n 132 | KEY(055) ; VK_o 133 | KEY(056) ; VK_p 134 | KEY(017) ; VK_q 135 | KEY(052) ; VK_r 136 | KEY(082) ; VK_s 137 | KEY(036) ; VK_t 138 | KEY(054) ; VK_u 139 | KEY(100) ; VK_v 140 | KEY(034) ; VK_w 141 | KEY(067) ; VK_x 142 | KEY(069) ; VK_y 143 | KEY(098) ; VK_z 144 | KEY(066) ; VK_A 145 | KEY(101) ; VK_B 146 | KEY(083) ; VK_C 147 | KEY(051) ; VK_D 148 | KEY(035) ; VK_E 149 | KEY(068) ; VK_F 150 | KEY(084) ; VK_G 151 | KEY(085) ; VK_H 152 | KEY(038) ; VK_I 153 | KEY(070) ; VK_J 154 | KEY(071) ; VK_K 155 | KEY(087) ; VK_L 156 | KEY(102) ; VK_M 157 | KEY(086) ; VK_N 158 | KEY(055) ; VK_O 159 | KEY(056) ; VK_P 160 | KEY(017) ; VK_Q 161 | KEY(052) ; VK_R 162 | KEY(082) ; VK_S 163 | KEY(036) ; VK_T 164 | KEY(054) ; VK_U 165 | KEY(100) ; VK_V 166 | KEY(034) ; VK_W 167 | KEY(067) ; VK_X 168 | KEY(069) ; VK_Y 169 | KEY(098) ; VK_Z 170 | KEY(046) ; VK_GRAVEACCENT 171 | KEY(000) ; VK_ACUTEACCENT 172 | KEY(000) ; VK_QUOTE 173 | KEY(000) ; VK_QUOTEDBL 174 | KEY(094) ; VK_EQUALS 175 | KEY(024) ; VK_MINUS 176 | KEY(060) ; VK_KP_MINUS 177 | KEY(094) ; VK_PLUS 178 | KEY(059) ; VK_KP_PLUS 179 | KEY(092) ; VK_KP_MULTIPLY 180 | KEY(000) ; VK_ASTERISK 181 | KEY(000) ; VK_BACKSLASH 182 | KEY(075) ; VK_KP_DIVIDE 183 | KEY(105) ; VK_SLASH 184 | KEY(077) ; VK_KP_PERIOD 185 | KEY(104) ; VK_PERIOD 186 | KEY(073) ; VK_COLON 187 | KEY(103) ; VK_COMMA 188 | KEY(088) ; VK_SEMICOLON 189 | KEY(000) ; VK_AMPERSAND 190 | KEY(000) ; VK_VERTICALBAR 191 | KEY(000) ; VK_HASH 192 | KEY(072) ; VK_AT 193 | KEY(025) ; VK_CARET 194 | KEY(000) ; VK_DOLLAR 195 | KEY(000) ; VK_POUND 196 | KEY(000) ; VK_EURO 197 | KEY(000) ; VK_PERCENT 198 | KEY(000) ; VK_EXCLAIM 199 | KEY(000) ; VK_QUESTION 200 | KEY(000) ; VK_LEFTBRACE 201 | KEY(000) ; VK_RIGHTBRACE 202 | KEY(057) ; VK_LEFTBRACKET 203 | KEY(089) ; VK_RIGHTBRACKET 204 | KEY(000) ; VK_LEFTPAREN 205 | KEY(000) ; VK_RIGHTPAREN 206 | KEY(103) ; VK_LESS 207 | KEY(104) ; VK_GREATER 208 | KEY(096) ; VK_UNDERSCORE 209 | KEY(000) ; VK_DEGREE 210 | KEY(000) ; VK_SECTION 211 | KEY(046) ; VK_TILDE 212 | KEY(024) ; VK_NEGATION 213 | KEY(004) ; VK_LSHIFT 214 | KEY(007) ; VK_RSHIFT 215 | KEY(006) ; VK_LALT 216 | KEY(009) ; VK_RALT 217 | KEY(005) ; VK_LCTRL 218 | KEY(008) ; VK_RCTRL 219 | KEY(126) ; VK_LGUI 220 | KEY(127) ; VK_RGUI 221 | KEY(113) ; VK_ESCAPE 222 | KEY(033) ; VK_PRINTSCREEN 223 | KEY(000) ; VK_SYSREQ 224 | KEY(062) ; VK_INSERT 225 | KEY(062) ; VK_KP_INSERT 226 | KEY(090) ; VK_DELETE 227 | KEY(076) ; VK_KP_DELETE 228 | KEY(048) ; VK_BACKSPACE 229 | KEY(063) ; VK_HOME 230 | KEY(063) ; VK_KP_HOME 231 | KEY(106) ; VK_END 232 | KEY(106) ; VK_KP_END 233 | KEY(000) ; VK_PAUSE 234 | KEY(045) ; VK_BREAK 235 | KEY(032) ; VK_SCROLLLOCK 236 | KEY(078) ; VK_NUMLOCK 237 | KEY(065) ; VK_CAPSLOCK 238 | KEY(097) ; VK_TAB 239 | KEY(074) ; VK_RETURN 240 | KEY(061) ; VK_KP_ENTER 241 | KEY(128) ; VK_APPLICATION 242 | KEY(064) ; VK_PAGEUP 243 | KEY(064) ; VK_KP_PAGEUP 244 | KEY(079) ; VK_PAGEDOWN 245 | KEY(079) ; VK_KP_PAGEDOWN 246 | KEY(058) ; VK_UP 247 | KEY(058) ; VK_KP_UP 248 | KEY(042) ; VK_DOWN 249 | KEY(042) ; VK_KP_DOWN 250 | KEY(026) ; VK_LEFT 251 | KEY(026) ; VK_KP_LEFT 252 | KEY(122) ; VK_RIGHT 253 | KEY(122) ; VK_KP_RIGHT 254 | KEY(000) ; VK_KP_CENTER 255 | KEY(114) ; VK_F1 256 | KEY(115) ; VK_F2 257 | KEY(116) ; VK_F3 258 | KEY(021) ; VK_F4 259 | KEY(117) ; VK_F5 260 | KEY(118) ; VK_F6 261 | KEY(023) ; VK_F7 262 | KEY(119) ; VK_F8 263 | KEY(120) ; VK_F9 264 | KEY(031) ; VK_F10 265 | KEY(029) ; VK_F11 266 | KEY(030) ; VK_F12 267 | KEY(000) ; VK_GRAVE_a 268 | KEY(000) ; VK_GRAVE_e 269 | KEY(000) ; VK_GRAVE_i 270 | KEY(000) ; VK_GRAVE_o 271 | KEY(000) ; VK_GRAVE_u 272 | KEY(000) ; VK_GRAVE_y 273 | KEY(000) ; VK_ACUTE_a 274 | KEY(000) ; VK_ACUTE_e 275 | KEY(000) ; VK_ACUTE_i 276 | KEY(000) ; VK_ACUTE_o 277 | KEY(000) ; VK_ACUTE_u 278 | KEY(000) ; VK_ACUTE_y 279 | KEY(000) ; VK_GRAVE_A 280 | KEY(000) ; VK_GRAVE_E 281 | KEY(000) ; VK_GRAVE_I 282 | KEY(000) ; VK_GRAVE_O 283 | KEY(000) ; VK_GRAVE_U 284 | KEY(000) ; VK_GRAVE_Y 285 | KEY(000) ; VK_ACUTE_A 286 | KEY(000) ; VK_ACUTE_E 287 | KEY(000) ; VK_ACUTE_I 288 | KEY(000) ; VK_ACUTE_O 289 | KEY(000) ; VK_ACUTE_U 290 | KEY(000) ; VK_ACUTE_Y 291 | KEY(000) ; VK_UMLAUT_a 292 | KEY(000) ; VK_UMLAUT_e 293 | KEY(000) ; VK_UMLAUT_i 294 | KEY(000) ; VK_UMLAUT_o 295 | KEY(000) ; VK_UMLAUT_u 296 | KEY(000) ; VK_UMLAUT_y 297 | KEY(000) ; VK_UMLAUT_A 298 | KEY(000) ; VK_UMLAUT_E 299 | KEY(000) ; VK_UMLAUT_I 300 | KEY(000) ; VK_UMLAUT_O 301 | KEY(000) ; VK_UMLAUT_U 302 | KEY(000) ; VK_UMLAUT_Y 303 | KEY(000) ; VK_CARET_a 304 | KEY(000) ; VK_CARET_e 305 | KEY(000) ; VK_CARET_i 306 | KEY(000) ; VK_CARET_o 307 | KEY(000) ; VK_CARET_u 308 | KEY(000) ; VK_CARET_y 309 | KEY(000) ; VK_CARET_A 310 | KEY(000) ; VK_CARET_E 311 | KEY(000) ; VK_CARET_I 312 | KEY(000) ; VK_CARET_O 313 | KEY(000) ; VK_CARET_U 314 | KEY(000) ; VK_CARET_Y 315 | KEY(000) ; VK_CEDILLA_c 316 | KEY(000) ; VK_CEDILLA_C 317 | KEY(000) ; VK_TILDE_a 318 | KEY(000) ; VK_TILDE_o 319 | KEY(000) ; VK_TILDE_n 320 | KEY(000) ; VK_TILDE_A 321 | KEY(000) ; VK_TILDE_O 322 | KEY(000) ; VK_TILDE_N 323 | KEY(000) ; VK_UPPER_a 324 | KEY(000) ; VK_ESZETT 325 | KEY(000) ; VK_EXCLAIM_INV 326 | KEY(000) ; VK_QUESTION_INV 327 | KEY(000) ; VK_INTERPUNCT 328 | KEY(000) ; VK_DIAERESIS 329 | KEY(000) ; VK_SQUARE 330 | KEY(000) ; VK_CURRENCY 331 | KEY(000) ; VK_MU 332 | KEY(000) ; VK_aelig 333 | KEY(000) ; VK_oslash 334 | KEY(000) ; VK_aring 335 | KEY(000) ; VK_AELIG 336 | KEY(000) ; VK_OSLASH 337 | KEY(000) ; VK_ARING 338 | KEY(000) ; VK_YEN 339 | KEY(000) ; VK_MUHENKAN 340 | KEY(000) ; VK_HENKAN 341 | KEY(000) ; VK_KATAKANA_HIRAGANA_ROMAJI 342 | KEY(000) ; VK_HANKAKU_ZENKAKU_KANJI 343 | KEY(000) ; VK_SHIFT_0 344 | KEY(000) ; VK_ASCII -------------------------------------------------------------------------------- /src/macros.inc: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: AGON MOS - Useful Macros 3 | ; Author: Dean Belfield 4 | ; Created: 15/07/2022 5 | ; Last Updated: 08/06/2023 6 | ; 7 | ; Modinfo: 8 | ; 08/08/2022: Added GET_GPIO 9 | ; 09/09/2022: Commented 10 | ; 08/06/2023: Add timer-related macros 11 | 12 | ; Used by macros 13 | INCLUDE "equs.inc" 14 | 15 | ; References to functions called by Macros 16 | XREF _timer0_delay 17 | 18 | 19 | ; Add A to HL (unsigned) 20 | ; 21 | ADD8U_HL: MACRO 22 | ADD A, L 23 | LD L, A 24 | ADC A, H 25 | SUB L 26 | LD H, A 27 | ENDMACRO 28 | 29 | ; Set a GPIO register 30 | ; Parameters: 31 | ; - REG: Register to set 32 | ; - VAL: Bit(s) to set (1: set, 0: ignore) 33 | ; 34 | SET_GPIO: MACRO REG, VAL 35 | IN0 A,(REG) 36 | OR VAL 37 | OUT0 (REG),A 38 | ENDMACRO 39 | 40 | ; Reset a GPIO register 41 | ; Parameters: 42 | ; - REG: Register to set 43 | ; - VAL: Bit(s) to reset (1: reset, 0: ignore) 44 | ; 45 | RES_GPIO: MACRO REG, VAL 46 | PUSH BC 47 | LD A, VAL 48 | CPL 49 | LD C, A 50 | IN0 A,(REG) 51 | AND C 52 | OUT0 (REG),A 53 | POP BC 54 | ENDMACRO 55 | 56 | ; Get a GPIO register 57 | ; Parameters: 58 | ; - REG: Register to test 59 | ; - VAL: Bit(s) to test 60 | ; 61 | GET_GPIO: MACRO REG, VAL 62 | IN0 A,(REG) 63 | TST A, VAL 64 | ENDMACRO 65 | 66 | ; Set a TIMER to the value in register BC 67 | ; Parameters: 68 | ; - TIMER: Timer to set 69 | ; 70 | TIMER_SET_BC: MACRO TIMER 71 | IF TIMER >= 0 && TIMER <= 5 72 | LD A,%0C 73 | OUT0 (TMR&TIMER&_CTL),A 74 | OUT0 (TMR&TIMER&_RR_L),C 75 | OUT0 (TMR&TIMER&_RR_H),B 76 | ELSE 77 | ERROR 78 | ENDIF 79 | ENDMACRO 80 | 81 | ; Set a timer to the given *constant* value in milliseconds 82 | ; Parameters: 83 | ; - TIMER: Timer set set 84 | ; - DELAY_MS: Constant delay in milliseconds 85 | ; 86 | TIMER_SET: MACRO TIMER,DELAY_MS 87 | IF DELAY_MS < 1 || DELAY_MS > 910 88 | ERROR 89 | ENDIF 90 | LD BC,MASTERCLOCK / 1000 / 256 * DELAY_MS 91 | TIMER_SET_BC 0 92 | ENDMACRO 93 | 94 | ;Start a timer 95 | ; Parameters: 96 | ; - TIMER: Timer to start 97 | ; 98 | TIMER_START: MACRO TIMER 99 | IF TIMER >= 0 && TIMER <= 5 100 | IN0 A,(TMR&TIMER&_CTL) 101 | OR A,%03 102 | OUT0 (TMR&TIMER&_CTL),A 103 | ENDIF 104 | ENDMACRO 105 | 106 | ; Reset a timer to stop it running 107 | ; Leaves the rest of the timer configuration unchanged so it can be restarted 108 | ; Parameters: 109 | ; - TIMER: Timer to reset 110 | ; 111 | TIMER_RESET: MACRO TIMER 112 | IF TIMER >= 0 && TIMER <= 5 113 | IN0 A,(TMR&TIMER&_CTL) 114 | AND A,11111100b 115 | OUT0 (TMR&TIMER&_CTL),A 116 | ELSE 117 | ERROR 118 | ENDIF 119 | ENDMACRO 120 | 121 | ; Test if the given timer has expired 122 | ; Parameters: 123 | ; - TIMER: Timer to test 124 | ; Output: 125 | ; - AF: Carry Flag set or clear depending on whether timer has expired 126 | ; 127 | TIMER_EXP?: MACRO TIMER 128 | IF TIMER >= 0 && TIMER <= 5 129 | IN0 A,(TMR&TIMER&_CTL) 130 | ELSE 131 | ERROR 132 | ENDIF 133 | RLA 134 | ENDMACRO 135 | 136 | ; Wait for a timer to count down to zero 137 | ; Parameters: 138 | ; - TIMER: Timer to wait on 139 | ; 140 | TIMER_WAIT: MACRO TIMER 141 | $$loop: TIMER_EXP? TIMER 142 | JR NC,$$loop 143 | ENDMACRO 144 | 145 | ; Delay for the given (constant) number of milliseconds using *Timer 0* 146 | ; Parameters: 147 | ; - DELAY_MS: Number of milliseconds to delay 148 | ; 149 | ; NB: Can wait between 1 and 910 milliseconds based on 18.432MHz clock 150 | ; 151 | DELAY_MS: MACRO DELAY_MS 152 | IF DELAY_MS < 1 || DELAY_MS > 910 153 | ERROR 154 | ENDIF 155 | LD BC,MASTERCLOCK / 1000 / 256 * DELAY_MS 156 | PUSH BC 157 | CALL _timer0_delay 158 | POP BC 159 | ENDMACRO 160 | 161 | END 162 | -------------------------------------------------------------------------------- /src/misc.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: AGON MOS - Miscellaneous helper functions 3 | ; Author: Dean Belfield 4 | ; Created: 24/07/2022 5 | ; Last Updated: 15/04/2023 6 | ; 7 | ; Modinfo: 8 | ; 03/08/2022: Added SET_AHL24 and SET_ADE24 9 | ; 10/08/2022: Optimised SET_ADE24 10 | ; 08/11/2022: Fixed return value bug in exec16 11 | ; 19/11/2022: Added exec24 and params for exec16/24 functions 12 | ; 09/03/2023: Added wait_timer0 13 | ; 20/03/2023: Function exec24 now preserves MB 14 | ; 15/04/2023: Added GET_AHL24 15 | 16 | INCLUDE "macros.inc" 17 | INCLUDE "equs.inc" 18 | 19 | .ASSUME ADL = 1 20 | 21 | DEFINE .STARTUP, SPACE = ROM 22 | SEGMENT .STARTUP 23 | 24 | XDEF SWITCH_A 25 | XDEF SET_AHL24 26 | XDEF GET_AHL24 27 | XDEF SET_ADE24 28 | 29 | XDEF __exec16 30 | XDEF __exec24 31 | XDEF __wait_timer0 32 | 33 | XDEF _exec16 34 | XDEF _exec24 35 | XDEF _wait_timer0 36 | XDEF _timer0_delay 37 | 38 | XREF _callSM 39 | 40 | ; Switch on A - lookup table immediately after call 41 | ; A: Index into lookup table 42 | ; 43 | SWITCH_A: EX (SP), HL ; Swap HL with the contents of the top of the stack 44 | ADD A, A ; Multiply A by two 45 | ADD8U_HL ; Add to HL (macro) 46 | LD A, (HL) ; follow the call. Fetch an address from the 47 | INC HL ; table. 48 | LD H, (HL) 49 | LD L, A 50 | EX (SP), HL ; Swap this new address back, restores HL 51 | RET ; Return program control to this new address 52 | 53 | ; Set the MSB of HL (U) to A 54 | ; 55 | SET_AHL24: PUSH HL 56 | LD HL, 2 57 | ADD HL, SP 58 | LD (HL), A 59 | POP HL 60 | RET 61 | 62 | ; Get the MSB of HL (U) in A 63 | ; 64 | GET_AHL24: PUSH HL 65 | LD HL, 2 66 | ADD HL, SP 67 | LD A, (HL) 68 | POP HL 69 | RET 70 | 71 | ; Set the MSB of DE (U) to A 72 | ; 73 | SET_ADE24: EX DE, HL 74 | PUSH HL 75 | LD HL, 2 76 | ADD HL, SP 77 | LD (HL), A 78 | POP HL 79 | EX DE, HL 80 | RET 81 | 82 | ; Execute a program in RAM 83 | ; int * _exec24(UINT24 address, char * params) 84 | ; Params: 85 | ; - address: The 24-bit address to call 86 | ; - params: 24-bit pointer to the params buffer 87 | ; 88 | ; This function will call the 24 bit address and keep the eZ80 in ADL mode 89 | ; The called function must do a RET and take care of preserving registers 90 | ; 91 | __exec24: 92 | _exec24: PUSH IY 93 | LD IY, 0 94 | ADD IY, SP ; Standard prologue 95 | PUSH AF ; Stack any registers being used 96 | PUSH DE 97 | PUSH IX 98 | LD A, MB ; Preserve the MBASE register 99 | PUSH AF 100 | LD DE, (IY+6) ; Get the address 101 | LD A, (IY+8) ; And the high byte for the code segment 102 | LD HL, (IY+9) ; Load HLU with the pointer to the params 103 | ; 104 | ; Write out a short subroutine "JP (DE)" to RAM 105 | ; 106 | LD IX, _callSM ; Storage for the self modified routine 107 | LD (IX + 0), C3h ; JP llhhuu 108 | LD (IX + 1), E 109 | LD (IX + 2), D 110 | LD (IX + 3), A 111 | ; 112 | JR _execSM ; Save some bytes, jump to _exec16 to finish the call 113 | 114 | ; Execute a program in RAM 115 | ; int * _exec16(UINT24 address, char * params) 116 | ; Params: 117 | ; - address: The 24-bit address to call 118 | ; - params: 24-bit pointer to the params buffer 119 | ; 120 | ; This function will call the 24 bit address and switch the eZ80 into Z80 mode (ADL=0) 121 | ; The called function must do a RET.LIS (49h, C9h) and take care of preserving registers 122 | ; 123 | __exec16: 124 | _exec16: PUSH IY 125 | LD IY, 0 126 | ADD IY, SP ; Standard prologue 127 | PUSH AF ; Stack any registers being used 128 | PUSH DE 129 | PUSH IX 130 | LD A, MB ; Preserve the MBASE register 131 | PUSH AF 132 | LD DE, (IY+6) ; Get the address 133 | LD A, (IY+8) ; And the high byte for the code segment 134 | LD MB, A ; Set the MBASE register 135 | LD HL, (IY+9) ; Load HLU with the pointer to the params 136 | ; 137 | ; Write out a short subroutine "CALL.IS (DE): RET" to RAM 138 | ; 139 | 140 | LD IX, _callSM ; Storage for the self modified routine 141 | LD (IX + 0), 49h ; CALL.IS llhh 142 | LD (IX + 1), CDh 143 | LD (IX + 2), E 144 | LD (IX + 3), D 145 | LD (IX + 4), C9h ; RET 146 | ; 147 | _execSM: CALL _callSM ; Call the subroutine 148 | ; 149 | POP AF ; Restore the MBASE register 150 | LD MB, A 151 | POP IX 152 | POP DE 153 | POP AF 154 | LD SP, IY ; Standard epilogue 155 | POP IY 156 | RET 157 | 158 | ; Wait for timer0 to hit 0 159 | ; 160 | __wait_timer0: 161 | _wait_timer0: PUSH AF 162 | PUSH BC 163 | IN0 A, (TMR0_CTL) ; Enable the timer 164 | OR 3 165 | OUT0 (TMR0_CTL), A 166 | $$: IN0 B, (TMR0_DR_L) ; Fetch the counter L 167 | IN0 A, (TMR0_DR_H) ; And the counter H 168 | OR B 169 | JR NZ, $B 170 | POP BC 171 | POP AF 172 | RET 173 | 174 | _timer0_delay: 175 | POP HL 176 | POP BC 177 | PUSH BC 178 | TIMER_SET_BC 0 179 | TIMER_START 0 180 | TIMER_WAIT 0 181 | JP (HL) 182 | 183 | END 184 | -------------------------------------------------------------------------------- /src/mos.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: AGON MOS - MOS code 3 | * Author: Dean Belfield 4 | * Created: 10/07/2022 5 | * Last Updated: 11/11/2023 6 | * 7 | * Modinfo: 8 | * 11/07/2022: Removed mos_cmdBYE, Added mos_cmdLOAD 9 | * 12/07/2022: Added mos_cmdJMP 10 | * 13/07/2022: Added mos_cmdSAVE 11 | * 14/07/2022: Added mos_cmdRUN 12 | * 24/07/2022: Added mos_getkey 13 | * 03/08/2022: Added a handful of MOS API calls 14 | * 05/08/2022: Added mos_FEOF 15 | * 05/09/2022: Added mos_cmdREN, mos_cmdBOOT; moved mos_EDITLINE into mos_editline.c 16 | * 25/09/2022: Added mos_GETERROR, mos_MKDIR 17 | * 13/10/2022: Added mos_OSCLI and supporting code 18 | * 20/10/2022: Tweaked error handling 19 | * 13/11/2022: Added mos_cmp 20 | * 21/11/2022: Added support for passing params to executables & ADL mode 21 | * 14/02/2023: Added mos_cmdVDU 22 | * 20/02/2023: Function mos_getkey now returns a BYTE 23 | * 09/03/2023: Added mos_cmdTIME, mos_cmdCREDITS, mos_DIR now accepts a path 24 | * 14/03/2023: Added mos_cmdCOPY and mos_COPY 25 | * 15/03/2023: Added mos_GETRTC, mos_SETRTC 26 | * 21/03/2023: Added mos_SETINTVECTOR 27 | * 14/04/2023: Added fat_EOF 28 | * 15/04/2023: Added mos_GETFIL, mos_FREAD, mos_FWRITE, mos_FLSEEK 29 | * 30/05/2023: Function mos_FGETC now returns EOF flag 30 | * 08/07/2023 Added mos_trim function 31 | * 11/11/2023: Added mos_cmdHELP, mos_cmdTYPE, mos_cmdCLS, mos_cmdMOUNT 32 | */ 33 | 34 | #ifndef MOS_H 35 | #define MOS_H 36 | 37 | #include "ff.h" 38 | 39 | typedef struct { 40 | char * name; 41 | int (*func)(char * ptr); 42 | char * args; 43 | char * help; 44 | char * aliases; 45 | } t_mosCommand; 46 | 47 | typedef struct { 48 | UINT8 free; 49 | FIL fileObject; 50 | } t_mosFileObject; 51 | 52 | void mos_error(int error); 53 | 54 | BYTE mos_getkey(void); 55 | UINT24 mos_input(char * buffer, int bufferLength); 56 | void * mos_getCommand(char * ptr); 57 | BOOL mos_cmp(char *p1, char *p2); 58 | char * mos_trim(char * s); 59 | char * mos_strtok(char *s1, char * s2); 60 | char * mos_strtok_r(char *s1, const char *s2, char **ptr); 61 | int mos_exec(char * buffer); 62 | UINT8 mos_execMode(UINT8 * ptr); 63 | 64 | int mos_mount(void); 65 | 66 | BOOL mos_parseNumber(char * ptr, UINT24 * p_Value); 67 | BOOL mos_parseString(char * ptr, char ** p_Value); 68 | 69 | int mos_cmdDIR(char * ptr); 70 | int mos_cmdLOAD(char * ptr); 71 | int mos_cmdSAVE(char *ptr); 72 | int mos_cmdDEL(char * ptr); 73 | int mos_cmdJMP(char * ptr); 74 | int mos_cmdRUN(char * ptr); 75 | int mos_cmdCD(char * ptr); 76 | int mos_cmdREN(char *ptr); 77 | int mos_cmdCOPY(char *ptr); 78 | int mos_cmdMKDIR(char *ptr); 79 | int mos_cmdSET(char *ptr); 80 | int mos_cmdVDU(char *ptr); 81 | int mos_cmdTIME(char *ptr); 82 | int mos_cmdCREDITS(char *ptr); 83 | int mos_cmdTYPE(char *ptr); 84 | int mos_cmdCLS(char *ptr); 85 | int mos_cmdMOUNT(char *ptr); 86 | int mos_cmdHELP(char *ptr); 87 | 88 | UINT24 mos_LOAD(char * filename, UINT24 address, UINT24 size); 89 | UINT24 mos_SAVE(char * filename, UINT24 address, UINT24 size); 90 | UINT24 mos_TYPE(char * filename); 91 | UINT24 mos_CD(char * path); 92 | UINT24 mos_DIR(char * path); 93 | UINT24 mos_DEL(char * filename); 94 | UINT24 mos_REN(char * filename1, char * filename2); 95 | UINT24 mos_COPY(char * filename1, char * filename2); 96 | UINT24 mos_MKDIR(char * filename); 97 | UINT24 mos_BOOT(char * filename, char * buffer, UINT24 size); 98 | 99 | UINT24 mos_FOPEN(char * filename, UINT8 mode); 100 | UINT24 mos_FCLOSE(UINT8 fh); 101 | UINT24 mos_FGETC(UINT8 fh); 102 | void mos_FPUTC(UINT8 fh, char c); 103 | UINT24 mos_FREAD(UINT8 fh, UINT24 buffer, UINT24 btr); 104 | UINT24 mos_FWRITE(UINT8 fh, UINT24 buffer, UINT24 btw); 105 | UINT8 mos_FLSEEK(UINT8 fh, UINT32 offset); 106 | UINT8 mos_FEOF(UINT8 fh); 107 | 108 | void mos_GETERROR(UINT8 errno, UINT24 address, UINT24 size); 109 | UINT24 mos_OSCLI(char * cmd); 110 | UINT8 mos_GETRTC(UINT24 address); 111 | void mos_SETRTC(UINT24 address); 112 | UINT24 mos_SETINTVECTOR(UINT8 vector, UINT24 address); 113 | UINT24 mos_GETFIL(UINT8 fh); 114 | 115 | UINT8 fat_EOF(FIL * fp); 116 | 117 | #define HELP_CAT "Directory listing of the current directory\r\n" 118 | #define HELP_CAT_ARGS "" 119 | #define HELP_CAT_ALIASES "DIR and ." 120 | #define HELP_DIR_ALIASES "CAT and ." 121 | #define HELP_DOT_ALIASES "CAT and DIR" 122 | 123 | #define HELP_CD "Change current directory\r\n" 124 | #define HELP_CD_ARGS "" 125 | 126 | #define HELP_COPY "Create a copy of a file\r\n" 127 | #define HELP_COPY_ARGS " " 128 | 129 | #define HELP_CREDITS "Output credits and version numbers for\r\n" \ 130 | "third-party libraries used in the Agon firmware\r\n" 131 | 132 | #define HELP_DELETE "Delete a file or folder (must be empty)\r\n" 133 | #define HELP_DELETE_ARGS "" 134 | #define HELP_DELETE_ALIASES "ERASE" 135 | #define HELP_ERASE_ALIASES "DELETE" 136 | 137 | #define HELP_JMP "Jump to the specified address in memory\r\n" 138 | #define HELP_JMP_ARGS "" 139 | 140 | #define HELP_LOAD "Load a file from the SD card to the specified address.\r\n" \ 141 | "If no `addr` parameter is passed it will" \ 142 | "default to &40000\r\n" 143 | #define HELP_LOAD_ARGS " []" 144 | 145 | #define HELP_MKDIR "Create a new folder on the SD card\r\n" 146 | #define HELP_MKDIR_ARGS "" 147 | 148 | #define HELP_RENAME "Rename a file in the same folder\r\n" 149 | #define HELP_RENAME_ARGS " " 150 | #define HELP_RENAME_ALIASES "MOVE" 151 | #define HELP_MOVE_ALIASES "RENAME" 152 | 153 | #define HELP_RUN "Call an executable binary loaded in memory.\r\n" \ 154 | "If no parameters are passed, then addr will " \ 155 | "default to &40000.\r\n" 156 | #define HELP_RUN_ARGS "[]" 157 | 158 | #define HELP_SAVE "Save a block of memory to the SD card\r\n" 159 | #define HELP_SAVE_ARGS " " 160 | 161 | #define HELP_SET "Set a system option\r\n\r\n" \ 162 | "Keyboard Layout\r\n" \ 163 | "SET KEYBOARD n: Set the keyboard layout\r\n" \ 164 | " 0: UK (default)\r\n" \ 165 | " 1: US\r\n" \ 166 | " 2: German\r\n" \ 167 | " 3: Italian\r\n" \ 168 | " 4: Spanish\r\n" \ 169 | " 5: French\r\n" \ 170 | " 6: Belgian\r\n" \ 171 | " 7: Norwegian\r\n" \ 172 | " 8: Japanese\r\n" \ 173 | " 9: US International\r\n" \ 174 | " 10: US International (alternative)\r\n" \ 175 | " 11: Swiss (German)\r\n" \ 176 | " 12: Swiss (French)\r\n" \ 177 | " 13: Danish\r\n" \ 178 | " 14: Swedish\r\n" \ 179 | " 15: Portuguese\r\n\r\n" \ 180 | "Serial Console\r\n" \ 181 | "SET CONSOLE n: Serial console\r\n" \ 182 | " 0: Console off (default)\r\n" \ 183 | " 1: Console on\r\n" 184 | #define HELP_SET_ARGS "