├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── bbcbasic │ ├── COPYING │ ├── MANUAL.md │ ├── README.md │ ├── TODO.md │ ├── bbcbasic.txt │ ├── build.z80 │ ├── cerberus_cursor.z80 │ ├── cerberus_debug.z80 │ ├── cerberus_dos.z80 │ ├── cerberus_graphics.z80 │ ├── cerberus_init.z80 │ ├── cerberus_io.z80 │ ├── cerberus_sound.z80 │ ├── editor.z80 │ ├── eval.z80 │ ├── examples │ ├── ANIMAL.DAT │ ├── animal.bbc │ ├── f-index.bbc │ ├── f-rand0.bbc │ ├── f-rand1.bbc │ ├── f-rand2.bbc │ ├── f-rser1.bbc │ ├── f-rser2.bbc │ ├── f-rstd.bbc │ ├── f-weser1.bbc │ ├── f-weser2.bbc │ ├── f-wser1.bbc │ ├── f-wser2.bbc │ ├── f-wstd.bbc │ ├── merge.bbc │ ├── sort.bbc │ └── sortreal.bbc │ ├── exec.z80 │ ├── fpp.z80 │ ├── macros.z80 │ ├── main.z80 │ ├── misc.z80 │ ├── patch.z80 │ ├── ram.z80 │ ├── sorry.z80 │ └── tests │ ├── BENCHMARK.md │ ├── README.md │ ├── benchm1.bbc │ ├── benchm2.bbc │ ├── benchm3.bbc │ ├── benchm4.bbc │ ├── benchm5.bbc │ ├── benchm6.bbc │ ├── benchm7.bbc │ ├── benchm8.bbc │ ├── cube.bbc │ ├── sound.bbc │ └── udg.bbc ├── cat ├── README.md ├── cat.ino ├── config.h ├── fileio.h ├── hardware.h └── src │ └── PS2Keyboard │ ├── PS2Keyboard.cpp │ ├── PS2Keyboard.h │ ├── README.md │ ├── docs │ └── issue_template.md │ ├── examples │ ├── International │ │ └── International.pde │ ├── Simple_Test │ │ └── Simple_Test.pde │ └── TypeToDisplay │ │ └── TypeToDisplay.ino │ ├── keywords.txt │ ├── library.json │ ├── library.properties │ └── utility │ └── int_pins.h ├── powershell └── send_file.ps1 ├── python └── send_file.py └── sd ├── basicZ80.bin ├── chardefs.bin ├── help.txt └── icon2080.img /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: breakintoprogram 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sld -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cerberus 2 | 3 | Cerberus 2080 and 2100 firmware 4 | 5 | ### What is the Cerberus 6 | 7 | The Cerberus is an innovative 8-bit multi-processor microcomputer board designed by Bernardo Kastrup aka The Byte Attic. It can run either Z80 or 6502 code off native on-board CPUs, and the board BIOS runs off an ATmega328p. The board is open-source, and you can find more details about it here. 8 | 9 | https://www.thebyteattic.com/p/cerberus-2080.html for the original Cerberus 2080 10 | https://www.thebyteattic.com/p/cerberus-2100.html for the revised Cerberus 2100 11 | 12 | ### What is in this repo 13 | 14 | - A revised version of the BIOS code (CAT) that runs on the ATmega328p 15 | - A version of BBC BASIC for Z80 that is customised to run on the Cerberus 16 | - Files to copy to the SD card (in folder 'sd') 17 | - Python utlities (in folder 'python') 18 | - Powershell utilities (in folder 'powershell) 19 | 20 | ### Licenses 21 | 22 | This code is released under an MIT license, with the exception of BBC BASIC for Z80 which is distributable under the terms of a zlib license. Read the file [COPYING](bin/bbcbasic/COPYING) for more information. 23 | 24 | Dean Belfield 25 | 26 | Twitter: [@breakintoprogram](https://twitter.com/BreakIntoProg) 27 | Blog: http://www.breakintoprogram.co.uk 28 | -------------------------------------------------------------------------------- /bin/bbcbasic/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 1984-2000 R.T. Russell 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /bin/bbcbasic/MANUAL.md: -------------------------------------------------------------------------------- 1 | # manual 2 | 3 | In addition to the core BBC Basic for Z80 core language (details of which [can be found here](bbcbasic.txt)), BBC Basic for Cerberus 2080 adds the following functionality: 4 | 5 | ## Editor 6 | 7 | A line editor is provided, that allows the user to enter a line of code up to 255 characters long. A cursor can be moved around this line, and text can be deleted or inserted at the current character position. 8 | 9 | Pressing INS on a PS/2 keyboard will change the editor mode to COPY. In this mode, the cursor can be moved around the screen using the keyboard cursor keys. Pressing TAB will copy the character under the cursor to the last position in the line editor before COPY mode was invoked. Pressing INS or Enter will exit COPY mode. 10 | 11 | ## BASIC 12 | 13 | The following statements differ from the BBC Basic standard: 14 | 15 | ### GET(x,y) 16 | 17 | Read a character code from the screen position X, Y. If the character cannot be read, return -1 18 | 19 | Example: 20 | 21 | - `A = GET(0,0)` Read character code from screen position(0,0) 22 | 23 | ### GET$(x,y) 24 | 25 | As GET(x,y), but return a string rather than a character code 26 | 27 | Example: 28 | 29 | - `A$ = GET$(0,0)` Read character code from screen position(0,0) 30 | 31 | ### PLOT type,x,y 32 | 33 | The only plot modes supported currently are: 34 | 35 | - `PLOT 0, X, Y` Draw a line from last plot position to X, Y 36 | - `PLOT 64, X, Y` Plot a point 37 | 38 | ### GCOL mode, colour 39 | 40 | Sets the graphic colour, as per COLOUR 41 | 42 | Mode values are: 43 | - 0 and 1 set the pixel 44 | - 2 and 6 clear the pixel 45 | - 3 and 4 invert the pixel 46 | 47 | This is an attempt to stick to the BBC BASIC standard with 1-bit graphics 48 | 49 | The colour part is currently ignored, but will at some point be: 50 | 51 | - 0: Black 52 | - 1: White 53 | 54 | ### VDU 55 | 56 | The VDU command is a work-in-progress with a handful of mappings implemented: 57 | 58 | - `VDU 8` Backspace 59 | - `VDU 9` Advance one character 60 | - `VDU 10` Line feed 61 | - `VDU 11` Move cursor up one line 62 | - `VDU 12` CLS 63 | - `VDU 13` Carriage return 64 | - `VDU 16` CLG 65 | - `VDU 17,col` COLOUR col 66 | - `VDU 18,mode,col` GCOL mode,col 67 | - `VDU 19,l,r,g,b` COLOUR l,r,g,b 68 | - `VDU 22,n` Mode n 69 | - `VDU 23,c,b1,b2,b3,b4,b5,b6,b7,b8` Define UDG c 70 | - `VDU 25,mode,x;y;` PLOT mode,x,y 71 | - `VDU 29,x;y;` Set graphics origin to x,y 72 | - `VDU 30` Home cursor 73 | - `VDU 31,x,y` TAB(x,y) 74 | 75 | Note that some of the commands are not supported - see the bottom of this page for more details 76 | 77 | Examples: 78 | 79 | `VDU 25,64,128;88;` Plot point in middle of screen 80 | 81 | `VDU 22,1` Change to Mode 1 82 | 83 | ### SOUND 84 | 85 | The same format as the BBC Model B sound command, though is not asynchronous. Volume and channel parameters are ignored. 86 | 87 | Example: 88 | 89 | `SOUND 0,0,100,50` 90 | 91 | ## STAR commands 92 | 93 | The star commands are all prefixed with an asterisk. These commands do not accept variables or expressions as parameters. Parameters are separated by spaces. Numeric parameters can be specified in hexadecimal by prefixing with an '&' character. Paths are unquoted. 94 | 95 | If you need to pass a parameter to a star command, call it using the OSCLI command, for example: 96 | 97 | - `LET T% = 3: OSCLI("TURBO " + STR$(T%))` 98 | 99 | ### BYE 100 | 101 | Exits BBC Basic by doing a soft reset (does not work on emulators) 102 | 103 | ### CAT (*.) 104 | 105 | Directory listing of the root directory of the SD card 106 | 107 | ### ERASE 108 | 109 | Erase a file 110 | 111 | - `*ERASE test.bbc` Erase the file test.bbc 112 | 113 | Aliases: DELETE 114 | 115 | ### MEMDUMP start len 116 | 117 | List contents of memory, hexdump and ASCII. 118 | 119 | - `*MEMDUMP 0 200` Dump the first 200 bytes of ROM 120 | 121 | ### FX n 122 | 123 | Implemented as OSBYTE 124 | 125 | - `*FX 19`: Wait for an NMI interrupt 126 | 127 | ### LOAD filename address 128 | 129 | Load a file into memory 130 | 131 | ### SAVE filename address length 132 | 133 | Save a block of memory as a file 134 | 135 | ## Other considerations 136 | 137 | ### Graphics 138 | 139 | A psuedo-graphics system has been implemented using the Cerberus 2x2 block-graphics characters, in a similar fashion to the ZX81. This provides an 80x60 graphics resolution. 140 | 141 | ### Unsupported Commands 142 | 143 | The following commands are not supported: 144 | 145 | - `MODE`: Unlikely to be implemented as there is only one mode 146 | - `CLG`: There is no graphics bitmap 147 | - `COLOUR`: Well, duh! 148 | - `ENVELOPE`: There is only a beeper, so not much scope for this 149 | - `SOUND`: I may implement this at some point 150 | - `ADVAL`: There are no peripherals yet 151 | - `TIME$`: And no real-time clock 152 | 153 | -------------------------------------------------------------------------------- /bin/bbcbasic/README.md: -------------------------------------------------------------------------------- 1 | # cerberus-bbc-basic 2 | 3 | A port of BBC Basic for Z80 to the Cerberus 2080 4 | 5 | ### What is the Cerberus 2080 6 | 7 | The Cerberus 2080 is an innovative 8-bit multi-processor microcomputer board designed by Bernardo Kastrup aka The Byte Attic. It can run either Z80 or 6502 code off native on-board CPUs, and the board BIOS runs off an ATmega328p. The board is open-source, and you can find more details about it here. 8 | 9 | https://www.thebyteattic.com/p/cerberus-2080.html 10 | 11 | ### What is BBC Basic for Z80? 12 | 13 | The original version of BBC Basic was written by Sophie Wilson at Acorn in 1981 for the BBC Micro range of computers, and was designed to support the UK Computer Literacy Project. R.T.Russell was involved in the specification of BBC Basic, and wrote his own Z80 version that was subsequently ported to a number of Z80 based machines. [I highly recommend reading his account of this on his website for more details](http://www.bbcbasic.co.uk/bbcbasic/history.html). 14 | 15 | As an aside, R.T.Russell still supports BBC Basic, and has ported it for a number of modern platforms, including Android, Windows, and SDL, which are [available from his website here](https://www.bbcbasic.co.uk/index.html). 16 | 17 | ### Why am I doing this? 18 | 19 | Bernardo was originally looking for someone to port GW BASIC to the Cerberus, and my name was suggested, given my previous experience porting BBC BASIC to the BSX and the Spectrum Next. I initially declined, until someone suggested to Bernardo that it may be a better idea to port BBC Basic for Z80 to the Cerberus. He agreed, I accepted, and the rest as they say is history. 20 | 21 | ### The Challenges 22 | 23 | The foundation of this port is R.T.Russell's original BBC Basic for Z80 code (for CP/M), with the CP/M specific code stripped out. This foundation code is just pure BASIC interpreter with no graphics, sound or file I/O support. 24 | 25 | The main challenge will be extending the BIOS of the Cerberus to support BASIC language features such as sound and file I/O. 26 | 27 | ### Assembling 28 | 29 | The code is written to be assembled by the SJASMPLUS assembler. Details of the toolchain I use [can be found here on my website](http://www.breakintoprogram.co.uk/computers/zx-spectrum-next/assembly-language/z80-development-toolchain). 30 | 31 | Every endeavour will be made to ensure the code is stable and will assemble, yet as this is a work-in-progress done in my spare time there will be instances where this is not the case. 32 | 33 | > Please avoid raising pull requests: The code will be in a state of flux for at least the next couple of months. I'm not quite geared up for collaboration just yet! 34 | 35 | > Raise an issue if you notice anything amiss; I will endeavour to get back to you. Bear with me if it takes a while to get back to you. 36 | 37 | ### Running 38 | 39 | This code currently requires a slightly customised BIOS. This can be found in the [cat](cat) folder, along with instructions on building and installing it. 40 | 41 | ### Releases 42 | 43 | I'll dump significant updates as executable bin files in the [releases](releases) folder 44 | 45 | ### License 46 | 47 | This code is distributable under the terms of a zlib license. Read the file [COPYING](COPYING) for more information. 48 | 49 | The code, as originally written by R.T. Russell and [downloaded from David Given's GitHub page](https://github.com/davidgiven/cpmish/tree/master/third_party/bbcbasic), has been modified slightly, either for compatibility reasons when assembling using sjasmplus, or for development reasons for this release: 50 | 51 | The original files are: [eval.z80](eval.z80), [exec.z80](exec.z80), [fpp.z80](fpp.z80), [patch.z80](patch.z80), [ram.z80](ram.z80) and [sorry.z80](sorry.z80). 52 | 53 | - General changes: 54 | - The top-of-file comments have been tweaked to match my style 55 | - GLOBAL and EXPORT directives have been removed and any global labels prefixed with @ 56 | - Source in z80 now enclosed in MODULES to prevent label clash 57 | - A handful of '"' values have been converted to 34, and commented with ASCII '"' 58 | - A [build.z80](build.z80) file has been added; this includes all other files and is the file to build 59 | - All CPMish code has been removed as this version is not going to run on CP/M 60 | - File [patch.z80](patch.z80) 61 | - The function GET has been moved into it from [eval.z80](eval.z80) 62 | 63 | Other than that, the source code is equivalent to the code originally authored by R.T.Russell, downloaded on David Given's website: 64 | 65 | http://cowlark.com/2019-06-14-bbcbasic-opensource/index.html 66 | 67 | The bulk of the Cerberus specific code I've written can be found in the z80 files prefixed with "cerberus_". I've clearly commented any changes made to R.T.Russell's original source files in the source code. In addition to the above, I've added the following files: [editor.z80](editor.z80) and [misc.z80](misc.z80) 68 | 69 | Any additions or modifications I've made to port this to the Cerberus 2080 have been released under the same licensing terms as the original code, along with any tools, examples or utilities contained within this project. Code that has been copied or inspired by other sources is clearly marked, with the appropriate accreditations. 70 | 71 | Dean Belfield 72 | 73 | Twitter: [@breakintoprogram](https://twitter.com/BreakIntoProg) 74 | Blog: http://www.breakintoprogram.co.uk 75 | -------------------------------------------------------------------------------- /bin/bbcbasic/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ### Known Bugs / Missing Features 4 | 5 | - No attempt to check whether a loaded file will fit in memory 6 | - No circle or triangle draw 7 | - GCOL colour mode not supported yet 8 | - Editor does not support copy mode 9 | - *LOAD and *SAVE filenames are not \0 terminated 10 | 11 | ### Fixed bugs 12 | 13 | - 2021-08-21: SOUND does not block 14 | - 2021-08-13: Error messages from file IO not reliable 15 | - 2021-11-24: Added CAT command (*.) 16 | -------------------------------------------------------------------------------- /bin/bbcbasic/bbcbasic.txt: -------------------------------------------------------------------------------- 1 | BBC BASIC (Z80) 2 | 3 | Generic CP/M Version 3.00 4 | 5 | (C) Copyright R.T.Russell 1982-1999 6 | 7 | 1. INTRODUCTION 8 | 9 | BBC BASIC (Z80) has been designed to be as compatible as possible with 10 | Version 4 of the 6502 BBC BASIC resident in the BBC Micro Master series. 11 | The language syntax is not always identical to that of the 6502 version, 12 | but in most cases the Z80 version is more tolerant. 13 | 14 | BBC BASIC (Z80) is as machine independent as possible and, as supplied, 15 | it will run on any CP/M 2.2 (or later) system using a Z80 processor 16 | (checks are carried out to ensure that the processor is a Z80 and that 17 | the version of CP/M is at least 2.2). It is minimally configured for an 18 | ADM3a-compatible VDU. 19 | 20 | Few CP/M systems offer colour graphics of the quality provided as 21 | standard on the BBC Microcomputer, and no software can provide colour 22 | high-resolution graphics from a monochrome character-orientated computer. 23 | However, many CP/M system users are interested in the advanced program 24 | structures available from BBC BASIC and, within the limitations of the 25 | host computer, BBC BASIC (Z80) provides the programming structures and 26 | the non-graphic commands and functions specified for BBC BASIC. 27 | 28 | In order to make full use of the facilities available in BBC BASIC (Z80) 29 | it is necessary to install a small patch to adapt it to the capabilities 30 | of the host computer. The source code of the patch present in the 31 | distribution version is supplied as BBCDIST.MAC. 32 | 33 | This documentation should be read in conjunction with a standard BBC 34 | BASIC manual. Only those features which differ from the standard Acorn 35 | versions are documented here. 36 | 37 | 38 | 2. MEMORY UTILISATION 39 | 40 | BBC BASIC (Z80) requires about 16 Kbytes of code space, resulting in a 41 | value of PAGE of about &3E00. The remainder of the user memory is 42 | available for BASIC programs, variables (heap) and stack. Depending on 43 | the system configuration, HIMEM can have a value up to &FE00. 44 | 45 | 46 | 3. COMMANDS, STATEMENTS AND FUNCTIONS 47 | 48 | The syntax of BASIC commands, statements and functions is in most cases 49 | identical to that of the BBC Micro version (BASIC 4). The few 50 | differences are documented here: 51 | 52 | ADVAL 53 | This function is not implemented. 54 | 55 | CALL 56 | CALL sets up a table in RAM containing details of the parameters; the 57 | processor's IX register is set to the address of this parameter table. 58 | The other processor registers are initialised as follows: 59 | 60 | A is initialised to the least significant byte of A% 61 | B is initialised to the least significant byte of B% 62 | C is initialised to the least significant byte of C% 63 | D is initialised to the least significant byte of D% 64 | E is initialised to the least significant byte of E% 65 | F is initialised to the least significant byte of F% 66 | H is initialised to the least significant byte of H% 67 | L is initialised to the least significant byte of L% 68 | 69 | The parameter types are: 70 | 71 | Code No. Parameter Type Example 72 | 0 Byte (8 bits) ?A% 73 | 4 Word (32 bits) !A% or A% 74 | 5 Real (40 bits) A 75 | 128 Fixed string $A% 76 | 129 Movable string A$ 77 | 78 | On entry to the subroutine the parameter table contains the following 79 | values: 80 | 81 | Number of parameters 1 byte (at IX) 82 | 83 | Parameter type 1 byte (at IX+1) 84 | Parameter address 2 bytes (at IX+2, IX+3, LSB first) 85 | 86 | Parameter type ) repeated as often as necessary 87 | Parameter address ) 88 | 89 | Except in the case of a movable string (normal string variable), the 90 | parameter address given is the absolute address at which the item is 91 | stored. In the case of movable strings (type 129) it is the address of a 92 | 4-byte parameter block containing the current length, the maximum length 93 | and the start address of the string (LSB first) in that order. 94 | 95 | Integer variables are stored in twos complement form with their least 96 | significant byte first. 97 | 98 | Fixed strings are stored as the characters of the string followed by a 99 | carriage return (&0D). 100 | 101 | Floating point variables are stored in binary floating point format with 102 | their least significant byte first; the fifth byte is the exponent. The 103 | mantissa is stored as a binary fraction in sign and magnitude format. 104 | Bit 7 of the most significant byte is the sign bit and, for the purposes 105 | of calculating the magnitude of the number, this bit is assumed to be set 106 | to one. The exponent is stored as an integer in excess 127 format (to 107 | find the exponent subtract 127 from the value in the fifth byte). 108 | 109 | If the exponent byte of a floating point number is zero, the number is an 110 | integer stored in integer format in the mantissa bytes. Thus an integer 111 | can be represented in two different ways in a real variable. For example 112 | the value +5 can be stored as: 113 | 114 | 05 00 00 00 00 Integer 5 115 | 00 00 00 20 82 (0.5 + 0.125) * 2^3 116 | 117 | COLOUR (COLOR) 118 | This statement is not implemented. 119 | 120 | DRAW 121 | This statement is not implemented. 122 | 123 | EDIT 124 | A command to edit or concatenate and edit the specified program line(s). 125 | The specified lines (including their line numbers) are listed as a single 126 | line. By changing only the line number you can use EDIT to duplicate a 127 | line. 128 | 129 | EDIT 230 130 | EDIT 200,230 131 | 132 | The following control functions are active both in the EDIT mode and in 133 | the immediate entry mode (i.e. at the BASIC prompt): 134 | 135 | Move the cursor one character position to the left 136 | Move the cursor one character position to the right 137 | Move the cursor to the start of the line 138 | Move the cursor to the end of the line 139 | Insert a space at the current cursor position 140 | Delete the character at the current cursor position 141 | Backspace and delete the character to the left of the cursor 142 | Delete all characters to the left of the cursor 143 | Delete all characters from the cursor to the end of the line 144 | 145 | The choice of which keys activate these functions is made when BBC BASIC 146 | is configured for a particular system. The distribution version uses ^H, 147 | ^I, ^K, ^J, ^A, ^E, DEL (&7F), ^L and ^X. 148 | 149 | To exit EDIT mode and replace the edited line, type RETURN (ENTER). 150 | 151 | To abort the edit and leave the line unchanged, type ESCape. 152 | 153 | ENVELOPE 154 | This statement is not implemented. 155 | 156 | GET 157 | This function waits for a character to be typed at the keyboard, and 158 | returns the ASCII code. 159 | 160 | GET can also be used to read data from a processor I/O port; full 16-bit 161 | port addressing is available: 162 | 163 | N% = GET(X%) : REM input from port X% 164 | 165 | INKEY 166 | This function waits for a specified maximum number of centiseconds for a 167 | character to be typed at the keyboard. If no character is typed in that 168 | time, the value -1 is returned. In the distribution version the delay is 169 | determined by a simple software timing loop, and may be very inaccurate. 170 | The customisation patch allows this to be adjusted to suit the system in 171 | use. 172 | 173 | INPUT# 174 | The format of data files is different from that used by the BBC Micro, in 175 | part to improve compatibility with standard CP/M files. Numeric values 176 | are stored as five bytes in the format documented under CALL; if the 177 | fifth byte is zero the value is an integer. Strings are stored as the 178 | characters of the string (in the correct order!) followed by a carriage 179 | return (&0D). 180 | 181 | MODE 182 | This statement is not implemented. 183 | 184 | MOVE 185 | This statement is not implemented. 186 | 187 | PLOT 188 | This statement is not implemented. 189 | 190 | POINT 191 | This function is not implemented. 192 | 193 | PRINT# 194 | The format of data files is different from that used by the BBC Micro, in 195 | part to improve compatibility with standard CP/M files. Numeric values 196 | are stored as five bytes in the format documented under CALL; if the 197 | fifth byte is zero the value is an integer. Strings are stored as the 198 | characters of the string (in the correct order!) followed by a carriage 199 | return (&0D). 200 | 201 | PUT 202 | A statement to output data to a processor port. Full 16-bit addressing 203 | is available. 204 | 205 | PUT A%,N% : REM Output N% to port A% 206 | 207 | SOUND 208 | This statement is not implemented. 209 | 210 | TIME 211 | This pseudo-variable is not implemented in the distribution version, but 212 | can be supported by means of the customisation patch. See BBCDIST.MAC. 213 | 214 | USR 215 | As with CALL, the processor's registers are initialised as follows: 216 | 217 | A is initialised to the least significant byte of A% 218 | B is initialised to the least significant byte of B% 219 | C is initialised to the least significant byte of C% 220 | D is initialised to the least significant byte of D% 221 | E is initialised to the least significant byte of E% 222 | F is initialised to the least significant byte of F% 223 | H is initialised to the least significant byte of H% 224 | L is initialised to the least significant byte of L% 225 | 226 | USR returns a 32-bit integer result composed of the processor's H, L, H' 227 | and L' registers, with H being the most significant. 228 | 229 | 230 | 4. RESIDENT Z80 ASSEMBLER 231 | 232 | The in-line assembler is accessed in exactly the same way as the 6502 233 | assembler in the BBC Micro version of BBC BASIC. That is, '[' enters 234 | assembler mode and ']' exits assembler mode. 235 | 236 | All standard Zilog mnemonics are accepted: ADD, ADC and SBC must be 237 | followed by A or HL. For example, ADD A,C is accepted but ADD C is not. 238 | However, the brackets around the port number in IN and OUT are optional. 239 | Thus both OUT (5),A and OUT 5,A are accepted. The instruction IN F,(C) 240 | is not accepted, but the equivalent code is produced from IN (HL),C 241 | 242 | The pseudo-ops DEFB, DEFW and DEFM are included. DEFM works like EQUS in 243 | the 6502 version. 244 | 245 | 246 | 5. OPERATING SYSTEM INTERFACE 247 | 248 | The following resident Operating System ("star") commands are 249 | implemented. They may be accessed directly (e.g. *BYE) or via the OSCLI 250 | statement (OSCLI "BYE"). 251 | 252 | Control characters, lower-case characters, DEL and quotation marks may be 253 | incorporated in filenames by using the 'escape' character '|'. However, 254 | there is no equivalent to the BBC Microcomputer's '|!' to set bit 7. 255 | 256 | *BYE 257 | Returns control to the operating system (CP/M). 258 | 259 | *CPM 260 | Same as *BYE. 261 | 262 | *. [filespec] 263 | *DIR [filespec] 264 | List the files which match the (optional) ambiguous filespec. If the 265 | filespec is omitted, all .BBC files are listed: 266 | *DIR List all .BBC files on the disk 267 | *DIR B:*.* List all files on disk B: 268 | *.*.* List all files on the current disk 269 | 270 | *DRIVE d: 271 | Select drive d as the default drive for subsequent disk operations. 272 | 273 | *ERA filespec 274 | Erase (delete) the specified disk file or files. The extension defaults 275 | to .BBC if omitted. 276 | 277 | *ESC [ON|OFF] 278 | *ESC OFF disables the abort action of the ESCape key; after *ESC OFF the 279 | ESCape key simply returns the ASCII code ESC (27). *ESC ON, or *ESC, 280 | restores the normal action of the ESCape key. 281 | 282 | *EXEC filespec 283 | Accept console input from the specified file instead of from the 284 | keyboard. If the extension is omitted, .BBC is assumed. 285 | 286 | *LOAD filespec aaaa 287 | Loads the specified file into memory at address aaaa. The load address 288 | must be specified. If the extension is omitted, .BBC is assumed. 289 | 290 | *OPT [n] 291 | Select the destination for console output characters. The value n is in 292 | the range 0 to 2, as follows: 293 | 294 | 0 Send characters to the console output 295 | 1 Send characters to the auxiliary output 296 | 2 Send characters to the printer (list) output 297 | 298 | *REN newfile=oldfile 299 | *RENAME newfile=oldfile 300 | Renames 'oldfile' as 'newfile'. If the extension is omitted, .BBC is 301 | assumed. 302 | 303 | *RESET 304 | Rest the disk system (CP/M function 13). This command does not close any 305 | files nor does it perform any other housekeeping function. You should 306 | use *RESET after you have changed a disk. 307 | 308 | *SAVE filespec aaaa bbbb 309 | *SAVE filespec aaaa +llll 310 | This command saves a specified range of memory to disk. The address range 311 | is specified either as start (aaaa) and end+1 (bbbb) or as start (aaaa) 312 | and length (llll). If the extension is omitted, .BBC is assumed. 313 | 314 | *SPOOL [filespec] 315 | Copy all subsequent console output to the specified file. If the filename 316 | is omitted, any current spool file is closed and spooling is terminated. 317 | If the extension is omitted, .BBC is assumed. 318 | 319 | *TYPE filespec 320 | Type the specified file to the screen. If the extension is omitted, .BBC 321 | is assumed. 322 | 323 | *| comment 324 | This is a comment line. Anything following the | is ignored. 325 | 326 | 327 | 6. ERROR MESSAGES AND CODES 328 | 329 | Untrappable: 330 | 331 | No room RENUMBER space 332 | Silly LINE space 333 | Sorry Bad program 334 | 335 | Trappable - BASIC: 336 | 337 | 1 Out of range 24 Exp range 338 | 2 25 339 | 3 26 No such variable 340 | 4 Mistake 27 Missing ) 341 | 5 Missing , 28 Bad HEX 342 | 6 Type mismatch 29 No such FN/PROC 343 | 7 No FN 30 Bad call 344 | 8 31 Arguments 345 | 9 Missing " 32 No FOR 346 | 10 Bad DIM 33 Can't match FOR 347 | 11 DIM space 34 FOR variable 348 | 12 Not LOCAL 35 349 | 13 No PROC 36 No TO 350 | 14 Array 37 351 | 15 Subscript 38 No GOSUB 352 | 16 Syntax error 39 ON syntax 353 | 17 Escape 40 ON range 354 | 18 Division by zero 41 No such line 355 | 19 String too long 42 Out of DATA 356 | 20 Too big 43 No REPEAT 357 | 21 -ve root 44 358 | 22 Log range 45 Missing # 359 | 23 Accuracy lost 360 | 361 | Trappable - OS: 362 | 363 | 190 Directory full 214 File not found 364 | 192 Too many open files 222 Channel 365 | 196 File exists 253 Bad string 366 | 198 Disk full 254 Bad command 367 | 200 Close error 255 CP/M error 368 | 204 Bad name 369 |  370 | -------------------------------------------------------------------------------- /bin/bbcbasic/build.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic for Cerberus 2080 - Main build file 3 | ; Author: Dean Belfield 4 | ; Created: 31/07/2021 5 | ; Last Updated: 11/10/2023 6 | ; 7 | ; Modinfo: 8 | ; 10/08/2021: Added macros.z80, changed mailbox labels 9 | ; 12/08/2021: Added cerberus_debug.z80 and cerberus_dos.z80 10 | ; 13/08/2021: Added cerberus_sound.z80 11 | ; 23/11/2021: Increased BUILD_VERSION to 0.04 12 | ; 11/10/2023: Updated to run on Cerberus 2100 13 | 14 | DEFINE BUILD_VERSION "0.05" ; Build version 15 | ; 16 | ; Memory map 17 | ; 18 | Inbox_Flag: EQU 0x0200 ; Data received from the ATMega328p 19 | Inbox_Data: EQU 0x0201 20 | Outbox_Flag: EQU 0x0202 ; Data sent to the ATMega328p 21 | Outbox_Data: EQU 0x0203 22 | Code_Start: EQU 0x0205 ; Code start 23 | 24 | RAM_Top: EQU 0xEF00 ; Top of usable RAM; must be on a page boundary 25 | Stack_Top: EQU 0xEFFE ; Stack at top 26 | Character_RAM: EQU 0xF000 27 | Video_RAM: EQU 0xF800 28 | 29 | CHAR_COLS: EQU 40 ; Screen width in chars 30 | CHAR_ROWS: EQU 30 ; Screen height in chars 31 | 32 | ORG Code_Start 33 | 34 | include "macros.z80" ; Useful macros 35 | ; 36 | ; Cerberus 2080 specific code 37 | ; 38 | include "cerberus_init.z80" ; Cerberus 2080 initialisation 39 | include "cerberus_io.z80" ; I/O 40 | include "cerberus_graphics.z80" ; Graphics 41 | include "cerberus_cursor.z80" ; Cursor 42 | include "cerberus_debug.z80" ; Debug 43 | include "cerberus_dos.z80" ; File I/O routines 44 | include "cerberus_sound.z80" ; Sound routines 45 | ; 46 | ; Other generic Z80N helper functions 47 | ; 48 | include "misc.z80" 49 | ; 50 | ; BBC BASIC for Z80 core code 51 | ; 52 | include "main.z80" 53 | include "exec.z80" 54 | include "eval.z80" 55 | include "fpp.z80" 56 | include "sorry.z80" 57 | include "patch.z80" 58 | include "editor.z80" 59 | include "ram.z80" 60 | 61 | ALIGN 256 ; USER needs to be alignd to a page boundary 62 | 63 | @USER:; incbin "tests/cube.bbc" ; BASIC program to load into memory 64 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_cursor.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Cerberus 2080 Cursor Routines 4 | ; Author: Dean Belfield 5 | ; Created: 31/07/2021 6 | ; Last Updated: 10/08/2021 7 | ; 8 | ; Modinfo: 9 | ; 10/08/2021: Added Show, Move and Hide methods 10 | 11 | MODULE CERBERUS_CURSOR 12 | 13 | ; Initialise the cursor 14 | ; 15 | Initialise: LD A, 32 ; Space 16 | ; 17 | Set_Cursor: PUSH BC 18 | PUSH DE 19 | PUSH HL 20 | CALL CERBERUS_GRAPHICS.Get_CRAM_Address 21 | LD HL, Character_RAM 22 | LD B, 8 23 | 1: LD A, (DE) 24 | CPL 25 | LD (HL), A 26 | INC E 27 | INC L 28 | DJNZ 1B 29 | POP HL 30 | POP DE 31 | POP BC 32 | RET 33 | 34 | ; Move the cursor 35 | ; DE: New position 36 | ; 37 | Move: LD (CURSOR_X), DE ; Falls through to Show 38 | RET 39 | 40 | ; Flash the cursor on interrupt 41 | ; 42 | Flash: LD A, (CURSOR_C) ; Check whether the cursor is visible 43 | OR A 44 | RET Z 45 | LD C, A 46 | LD HL, (CURSOR_X) 47 | CALL CERBERUS_GRAPHICS.Get_VRAM_Address 48 | LD A, (TIME) 49 | AND %00100000 50 | JR Z, 1F 51 | LD (HL), C 52 | RET 53 | 1: LD (HL), A 54 | RET 55 | 56 | 57 | ; Display the cursor at current position 58 | ; 59 | Show: LD A, (CURSOR_C) ; If the cursor character is not 0, then already visible 60 | OR A 61 | RET NZ 62 | PUSH HL 63 | LD HL, (CURSOR_X) 64 | CALL CERBERUS_GRAPHICS.Get_VRAM_Address 65 | LD A, (HL) 66 | LD (CURSOR_C), A 67 | CALL Set_Cursor 68 | LD (HL), 0 69 | POP HL 70 | RET 71 | 72 | ; Hide the cursor 73 | ; 74 | Hide: LD A, (CURSOR_C) ; If the cursor character is 0, then already hidden 75 | OR A 76 | RET Z 77 | PUSH HL 78 | LD HL, (CURSOR_X) 79 | CALL CERBERUS_GRAPHICS.Get_VRAM_Address 80 | LD (HL), A 81 | XOR A 82 | LD (CURSOR_C), A 83 | POP HL 84 | RET 85 | 86 | ENDMODULE 87 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_debug.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Cerberus 2080 debug routines 4 | ; Author: Dean Belfield 5 | ; Created: 12/08/2021 6 | ; Last Updated: 12/08/2021 7 | ; 8 | ; Modinfo: 9 | 10 | MODULE CERBERUS_DEBUG 11 | 12 | ; Dump memory 13 | ; HL: Start address 14 | ; DE: Length 15 | ; 16 | Memory_Dump: LD A, H 17 | CALL Print_Hex8 18 | LD A, L 19 | CALL Print_Hex8 20 | LD A, ':' : CALL CERBERUS_IO.Print_Char 21 | PUSH HL 22 | PUSH DE 23 | CALL Get_Columns 24 | 1: LD A, ' ' : CALL CERBERUS_IO.Print_Char 25 | LD A, (HL) 26 | CALL Print_Hex8 27 | INC HL 28 | DEC DE 29 | DEC B 30 | JR Z, 3F 31 | LD A, D 32 | OR E 33 | JR NZ, 1B 34 | 35 | 2: LD A, ' ' : CALL CERBERUS_IO.Print_Char 36 | LD A, ' ' : CALL CERBERUS_IO.Print_Char 37 | LD A, ' ' : CALL CERBERUS_IO.Print_Char 38 | DJNZ 2B 39 | 40 | 3: POP DE 41 | POP HL 42 | LD A, ' ' : CALL CERBERUS_IO.Print_Char 43 | 44 | CALL Get_Columns 45 | 4: LD A, (HL) 46 | CALL Print_ASCII 47 | INC HL 48 | DEC DE 49 | LD A, D 50 | OR E 51 | JR Z, 5F 52 | DJNZ 4B 53 | CALL 5F ; Print CR/LF 54 | CALL LTRAP ; Check for ESC 55 | JR Memory_Dump 56 | 5: LD A, 0x0D 57 | CALL CERBERUS_IO.Print_Char 58 | LD A, 0x0A 59 | JP CERBERUS_IO.Print_Char 60 | 61 | ; Get # columns to dump 62 | ; 63 | Get_Columns: LD B, 8 64 | RET 65 | 66 | ; A: Ascii char to print 67 | ; 68 | Print_ASCII: CP 127 ; If > 127 69 | JR NC, 2F ; Skip to print '.' 70 | CP 32 ; If >= 32 71 | JR NC, 1F ; Skip to print character 72 | 2: LD A, '.' 73 | 1: JP CERBERUS_IO.Print_Char 74 | 75 | ; A: Hex digit to print 76 | ; 77 | Print_Hex8: PUSH AF ; Store the value 78 | RLCA ; Move to bottom nibble 79 | RLCA 80 | RLCA 81 | RLCA 82 | CALL 1F ; Print the first nibble 83 | POP AF 84 | 1: AND 0x0F ; Get the bottom nibble 85 | ADD A,0x90 ; Convert to HEX 86 | DAA 87 | ADC A,0x40 88 | DAA 89 | JP CERBERUS_IO.Print_Char ; Print 90 | 91 | ENDMODULE 92 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_dos.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Cerberus 2080 File IO routines 4 | ; Author: Dean Belfield 5 | ; Created: 12/08/2021 6 | ; Last Updated: 15/10/2023 7 | ; 8 | ; Modinfo: 9 | ; 13/08/2021: Tweaked error codes 10 | ; 06/10/2021: Added DIR command 11 | ; 15/10/2023: Fixed bug in CAT_FILECMD 12 | 13 | MODULE CERBERUS_DOS 14 | 15 | ; Load an area of memory from a file 16 | ; HL: addresses filename (CR terminated) 17 | ; DE: address at which to load 18 | ; BC: maximum allowed size (bytes) 19 | ; Returns: 20 | ; F: Carry reset indicates no room for file 21 | ; Destroys: A,B,C,D,E,H,L,F 22 | ; 23 | Load: LD A, 2 24 | CALL CAT_FILECMD_3P 25 | SCF 26 | RET 27 | 28 | ; Save an area of memory to a file 29 | ; HL: addresses filename (term CR) 30 | ; DE: start address of data to save 31 | ; BC = length of data to save (bytes) 32 | ; Destroys: A,B,C,D,E,H,L,F 33 | ; 34 | Save: LD A, 3 35 | JR CAT_FILECMD_3P 36 | 37 | ; Delete a file 38 | ; HL: addresses filename (term CR) 39 | ; 40 | Delete: LD A, 4 41 | JP CAT_FILECMD_1P 42 | 43 | ; Directory list: Open 44 | ; 45 | Dir: LD C, 5 46 | JP CAT_TX 47 | 48 | ; Directory List: Get next entry 49 | ; 50 | Dir_Entry: LD HL, DOS_BUFFER ; Address to store the directory entry in 51 | LD (Outbox_Data), HL 52 | LD C, 6 53 | JP CAT_TX 54 | 55 | ; Pass a file command to the CAT 56 | ; HL: Address of the filename 57 | ; A: Command # 58 | ; 59 | CAT_FILECMD_1P: PUSH IX 60 | LD IX, DOS_BUFFER 61 | LD (Outbox_Data), IX 62 | JR CAT_FILECMD 63 | 64 | ; Pass a file command to the CAT 65 | ; HL: Address of the filename 66 | ; DE: Start address of data 67 | ; BC: Length / maximum allowed size 68 | ; A: Command # 69 | ; 70 | CAT_FILECMD_3P: PUSH IX 71 | LD IX, DOS_BUFFER 72 | LD (Outbox_Data), IX 73 | LD (IX + 0), E 74 | LD (IX + 1), D 75 | LD (IX + 2), C 76 | LD (IX + 3), B 77 | LD BC, 4 78 | ADD IX, BC 79 | ; 80 | CAT_FILECMD: LD B, 60 81 | LD C, A ; Store the command # 82 | 1: LD A, (HL) 83 | CP ' ' ; Filenames can be terminated by space if followed by a parameter 84 | JR Z, 2F 85 | CP CR ; Or CR (LOAD and SAVE) 86 | JR Z, 2F 87 | LD (IX + 0), A 88 | INC IX 89 | INC L 90 | DJNZ 1B 91 | POP IX 92 | LD A, 127 ; Filename too large 93 | JR Error 94 | ; 95 | 2: LD (IX + 0), 0 ; Terminate the filename with \0 96 | POP IX 97 | CALL CAT_TX ; Send the command 98 | JR NZ, Error 99 | RET 100 | 101 | ; Send command to CAT 102 | ; C: Command # 103 | ; Returns: 104 | ; F: Z if Outbox_Flag is 0, otherwise NZ 105 | ; 106 | CAT_TX: LD A, C 107 | LD (Outbox_Flag), A 108 | 1: HALT 109 | LD A, (Outbox_Flag) 110 | CP C 111 | JR Z, 1B 112 | OR A ; Check for error code 113 | RET Z ; Just return if no error 114 | PUSH AF 115 | XOR A ; Clear the outbox flag to ack error 116 | LD (Outbox_Flag), A 117 | POP AF 118 | RET 119 | 120 | ; Lookup error codes 121 | ; A: Error code from CAT 122 | ; 123 | Error: AND 0x7F ; Clear the top bit 124 | LD E, A ; Store the error 125 | LD HL, Error_Codes ; Find the error 126 | 0: LD A, E 127 | 1: CP (HL) 128 | JR Z, 3F ; Found it! 129 | BIT 7, (HL) ; Is it the last entry? 130 | JR NZ, 3F 131 | 2: INC HL 132 | LD A, (HL) 133 | OR A 134 | JR NZ, 2B 135 | INC HL 136 | JR 0B 137 | 3: INC HL 138 | PUSH HL 139 | XOR A 140 | JP EXTERR ; Let BASIC handle the error 141 | ; Errors 142 | ; 143 | Error_Codes: DEFB 4, "No such file or directory", 0 144 | DEFB 5, "Unable to open file", 0 145 | DEFB 6, "Missing filename", 0 146 | DEFB 8, "Already exists", 0 147 | DEFB 127, "Filename too long", 0 148 | DEFB 255, "Error", 0 149 | 150 | ENDMODULE 151 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_graphics.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Cerberus 2080 Graphics Routines 4 | ; Author: Dean Belfield 5 | ; Created: 31/07/2021 6 | ; Last Updated: 10/08/2021 7 | ; 8 | ; Modinfo: 9 | ; 10/08/2021: Tidied up the Plot routines, added POINT, support for GCOL, renamed some functions to remove ambiguity 10 | 11 | MODULE CERBERUS_GRAPHICS 12 | 13 | ; Pixel table 14 | ; 15 | PIXEL_TABLE: DB 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 16 | DB 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0xA0 17 | 18 | ; Clear the screen 19 | ; 20 | CLG: 21 | CLS: LD HL, Video_RAM 22 | LD DE, Video_RAM + 1 23 | LD BC, (CHAR_COLS * CHAR_ROWS) - 1 24 | LD (HL), 32 25 | LDIR 26 | XOR A 27 | LD (CHARPOS_X), A 28 | LD (CHARPOS_Y), A 29 | RET 30 | 31 | ; Vertical scroll routines 32 | ; 33 | Scroll_Down: RET 34 | ; 35 | Scroll_Up: LD HL, Video_RAM + CHAR_COLS 36 | LD DE, Video_RAM 37 | LD BC, CHAR_COLS * (CHAR_ROWS - 1) 38 | LDIR 39 | LD H, D 40 | LD L, E 41 | INC DE 42 | LD BC, CHAR_COLS - 1 43 | LD (HL), 32 44 | LDIR 45 | RET 46 | 47 | ; Get a character off screen 48 | ; L: X coordinate 49 | ; H: Y coordinate 50 | ; Returns: 51 | ; A: ASCII character, or 0xFF if no match 52 | ; F: C if character match, otherwise NC 53 | ; 54 | Get_Char: PUSH HL 55 | CALL Get_VRAM_Address 56 | LD A, (HL) 57 | POP HL 58 | SCF 59 | RET 60 | 61 | ; Print a single character out to an X/Y position 62 | ; A: Character to print 63 | ; L: X Coordinate 64 | ; H: Y Coordinate 65 | ; 66 | Print_Char: PUSH HL 67 | CALL Get_VRAM_Address 68 | LD (HL), A 69 | POP HL 70 | RET 71 | 72 | ; Get address of character in video RAM 73 | ; L: X Coordinate 74 | ; H: Y Coordinate 75 | ; Returns 76 | ; HL: Address of character 77 | ; 78 | Get_VRAM_Address: PUSH AF 79 | PUSH DE 80 | LD E, L ; DE: X coordinate, screen address 81 | LD D, high Video_RAM 82 | LD A, H ; First do H * 40; we can do up to x 5 in 8 bits 83 | ADD A, A ; x 2 84 | ADD A, A ; x 4 85 | ADD A, H ; x 5 86 | LD L, A ; And do the rest in 16 bits 87 | LD H, 0 88 | ADD HL, HL ; x 10 89 | ADD HL, HL ; x 20 90 | ADD HL, HL ; x 40 91 | ADD HL, DE 92 | POP DE 93 | POP AF 94 | RET 95 | 96 | ; Get address of character in character RAM 97 | ; A: Character (0-255) 98 | ; Returns 99 | ; DE: Address of character data in character RAM 100 | ; 101 | Get_CRAM_Address: PUSH HL 102 | LD DE, Character_RAM ; Get character location in character RAM 103 | LD H, 0 104 | LD L, A 105 | ADD HL, HL ; Multiply character code by 8 106 | ADD HL, HL 107 | ADD HL, HL 108 | ADD HL, DE ; Add to base address in character RAM 109 | EX DE, HL ; DE: Destination address in character RAM 110 | POP HL 111 | RET 112 | 113 | ; Get pixel address data 114 | ; L: X 115 | ; H: Y 116 | ; Returns 117 | ; HL: Address of character data in VRAM 118 | ; A: Pixel mask data of pixels on-screen 119 | ; E: Pixel mask data of this pixel 120 | ; 121 | Get_Pixel_Data: LD A, 1 122 | SRL L 123 | JR NC, 1F 124 | RLCA 125 | 1: SRL H 126 | JR NC, 2F 127 | RLCA 128 | RLCA 129 | 2: LD E, A ; E: The pixel byte data of this pixel 130 | CALL Get_VRAM_Address ; HL: The char address in VRAM 131 | LD A, (HL) 132 | PUSH HL 133 | CALL Char_To_Pixel 134 | POP HL 135 | RET 136 | 137 | ; Boundary check 138 | ; DE: X 139 | ; HL: Y 140 | ; Returns 141 | ; F: Carry set if on screen 142 | ; 143 | Pixel_Bounds_Check: RET 144 | 145 | ; Scale X and Y coordinates 146 | ; DE: X (0 to 1279) -> (0 to 39) 147 | ; HL: Y (0 to 1023) -> (0 to 29) 148 | ; 149 | Transform_Coords: RET 150 | 151 | ; POINT(x,y) 152 | ; Read a point off the screen 153 | ; L: X 154 | ; H: Y 155 | ; Returns: 156 | ; A: Point value (1 = set, 0 = unset) 157 | ; 158 | Point: PUSH BC 159 | PUSH DE 160 | PUSH HL 161 | CALL Get_Pixel_Data 162 | AND E 163 | JR Z, 1F 164 | LD A, 1 165 | 1: POP HL 166 | POP DE 167 | POP BC 168 | RET 169 | 170 | ; PLOT n,x,y 171 | ; Graphics Operations 172 | ; L: X 173 | ; H: Y 174 | ; A: Type 175 | ; 176 | Plot: AND %11111000 ; Ignore bottom 3 bits of type 177 | CP 64 ; If less than 8, it's a line 178 | JP C, Plot_Line 179 | JR Z, Plot_Point ; If it is 8, then plot a point 180 | ; CP 80 181 | ; JR Z, Plot_Triangle 182 | ; CP 144 183 | ; JP Z, Plot_Circle ; Check for circle 184 | RET 185 | 186 | ; Plot_Line 187 | ; L: X 188 | ; H: Y 189 | ; 190 | Plot_Line: LD BC, (PLOTPOS_X) ; BC: (X1, Y1) 191 | EX DE, HL ; DE: (X2, Y2) 192 | JP Draw_Line 193 | 194 | ; ---------------------------------------------------------------------------- 195 | ; Graphics Primitives 196 | ; ---------------------------------------------------------------------------- 197 | 198 | ; PLOT x,y 199 | ; L: X 200 | ; H: Y 201 | ; 202 | Plot_Point: PUSH BC 203 | PUSH DE 204 | PUSH HL 205 | CALL Plot_Point_1 ; Do the plot 206 | CP 16 ; Is it between 0-15? 207 | JR NC, 1F ; No, so skip the next bit 208 | LD DE, PIXEL_TABLE ; Now convert back to a character value 209 | ADD8U DE ; Add A to DE (macro) 210 | LD A, (DE) ; Fetch the new character value 211 | LD (HL), A ; And write to screen 212 | 1: POP HL 213 | POP DE 214 | POP BC 215 | RET 216 | ; 217 | Plot_Point_1: CALL Get_Pixel_Data ; Get the pixel data 218 | LD D, A ; D: Screen data 219 | LD A, (PLOT_MODE) 220 | CALL SWITCH_A 221 | DEFW Plot_OR 222 | DEFW Plot_OR 223 | DEFW Plot_AND 224 | DEFW Plot_XOR 225 | DEFW Plot_XOR 226 | DEFW Plot_NOP 227 | DEFW Plot_AND 228 | ; 229 | Plot_NOP: LD A, 255 ; A duff character, so ignored 230 | RET 231 | ; 232 | Plot_OR: LD A, E ; Pixel data 233 | OR D ; Screen data 234 | RET 235 | ; 236 | Plot_XOR: LD A, E ; Pixel data 237 | XOR D ; Screen data 238 | RET 239 | ; 240 | Plot_AND: LD A, E ; Pixel data 241 | XOR 0x0F ; Invert 242 | AND D 243 | RET 244 | 245 | ; Lookup a pixel value from the pixel table 246 | ; A: Character to lookup 247 | ; Returns: 248 | ; A: Pixel bitmask (0 to 15) 249 | ; F: Carry if pixel found - if not found then will reset character to space (0) 250 | ; 251 | Char_To_Pixel: LD HL, PIXEL_TABLE 252 | LD B, 16 253 | LD C, 0 254 | 1: CP (HL) 255 | JR Z, 2F 256 | INC HL 257 | INC C 258 | DJNZ 1B 259 | XOR A 260 | RET 261 | 2: LD A, C 262 | SCF 263 | RET 264 | 265 | ; Draw Line routine 266 | ; B = Y pixel position 1 267 | ; C = X pixel position 1 268 | ; D = Y pixel position 2 269 | ; E = X pixel position 2 270 | ; 271 | Draw_Line: LD A, D ; Check whether we are going to be drawing up 272 | CP B 273 | JR NC, 1F 274 | 275 | PUSH BC ; If we are, then this neat trick swaps BC and DE 276 | PUSH DE ; using the stack, forcing the line to be always 277 | POP BC ; drawn downwards 278 | POP DE 279 | ; 280 | ; At this point we have 281 | ; BC = Start coordinate (B=Y1, C=X1) 282 | ; DE = End coordinates (D=Y2, E=X2) 283 | ; 284 | 1: LD H, B ; HL: Pixel address 285 | LD L, C 286 | 287 | LD A, D ; Calculate the line height in B (Y2-Y1) 288 | SUB B 289 | LD B, A 290 | 291 | LD A, E ; Calculate the line width in C (X2-X1) 292 | SUB C 293 | JR C, Draw_Line_RL 294 | LD C, A 295 | ; 296 | ; This is for lines drawn left to right 297 | ; 298 | Draw_Line_LR OR B 299 | JP Z, Plot_Point ; If so, just plot a single point 300 | ; 301 | ; At this point 302 | ; HL = Pixel start coordinates for plot 303 | ; DE = End coordinates 304 | ; B = Line height (YL) 305 | ; C = Line width (XL) - this could be negative for lines drawn 306 | ; 307 | LD A, B ; Work out which diagonal we are on 308 | CP C 309 | JR NC, Draw_Line_LR_Q2 310 | ; 311 | ; This bit of code draws the line where B < C (more horizontal than vertical) 312 | ; 313 | Draw_Line_LR_Q1: LD D, C ; D = XL 314 | LD C, B ; C = YL 315 | LD B, D ; B = XL (loop counter) 316 | LD E, D ; E = XL 317 | SRL E ; E = XL / 2 (error) 318 | 1: CALL Plot_Point ; Plot the pixel 319 | LD A, E ; Add the line height to the error (E = E - YL) 320 | SUB C 321 | LD E, A 322 | JR NC, 2F 323 | ADD A, D ; Add the line width to the error (E = E + XL) 324 | LD E, A 325 | INC H ; Move down one pixel 326 | 2: INC L ; Move to adjacent screen address 327 | DJNZ 1B ; Loop until the line is drawn 328 | JP Plot_Point ; Plot the final point 329 | ; 330 | ; This bit draws the line where B>=C (more vertical than horizontal, or diagonal) 331 | ; 332 | Draw_Line_LR_Q2: LD D, B ; D = YL 333 | LD E, B ; E = YL 334 | SRL E ; E = YL / 2 (error) 335 | 1: CALL Plot_Point ; Plot the pixel 336 | LD A, E ; Add the line width to the error 337 | SUB C 338 | LD E, A 339 | JR NC, 2F ; Skip the next bit if we don't get a carry 340 | ADD A, D ; Add the line height to the error (E = E + XL) 341 | LD E, A 342 | INC L ; Move to adjacent screen address; more self modifying code 343 | 2: INC H 344 | DJNZ 1B 345 | JP Plot_Point 346 | 347 | ; 348 | ; This is for lines drawn right to left 349 | ; 350 | Draw_Line_RL NEG ; Make the line width positive 351 | LD C, A 352 | OR B 353 | JP Z, Plot_Point ; If so, just plot a single point 354 | ; 355 | ; At this point 356 | ; HL = Pixel coordinates for plot 357 | ; B = Line height (YL) 358 | ; C = Line width (XL) - this could be negative for lines drawn 359 | ; 360 | LD A, B ; Work out which diagonal we are on 361 | CP C 362 | JR NC, Draw_Line_RL_Q2 363 | ; 364 | ; This bit of code draws the line where B < C (more horizontal than vertical) 365 | ; 366 | Draw_Line_RL_Q1: LD D, C ; D = XL 367 | LD C, B ; C = YL 368 | LD B, D ; B = XL (loop counter) 369 | LD E, D ; E = XL 370 | SRL E ; E = XL / 2 (error) 371 | 1: CALL Plot_Point ; Plot the pixel 372 | LD A, E ; Add the line height to the error (E = E - YL) 373 | SUB C 374 | LD E, A 375 | JR NC, 2F 376 | ADD A, D ; Add the line width to the error (E = E + XL) 377 | LD E, A 378 | INC H ; Move down one pixel 379 | 2: DEC L ; Move to adjacent screen address 380 | DJNZ 1B ; Loop until the line is drawn 381 | JP Plot_Point ; Plot the final point 382 | ; 383 | ; This bit draws the line where B>=C (more vertical than horizontal, or diagonal) 384 | ; 385 | Draw_Line_RL_Q2: LD D, B ; D = YL 386 | LD E, B ; E = YL 387 | SRL E ; E = YL / 2 (error) 388 | 1: CALL Plot_Point ; Plot the pixel 389 | LD A, E ; Add the line width to the error 390 | SUB C 391 | LD E, A 392 | JR NC, 2F ; Skip the next bit if we don't get a carry 393 | ADD A, D ; Add the line height to the error (E = E + XL) 394 | LD E, A 395 | DEC L ; Move to adjacent screen address; more self modifying code 396 | 2: INC H 397 | DJNZ 1B 398 | JP Plot_Point 399 | 400 | ENDMODULE 401 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_init.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Cerberus 2080 Initialisation 4 | ; Author: Dean Belfield 5 | ; Created: 31/07/2021 6 | ; Last Updated: 11/10/2023 7 | ; 8 | ; Modinfo: 9 | ; 10/08/2021: Initialise and flash the cursor 10 | ; 11/10/2023: Removed Cerberus prompt on boot 11 | 12 | MODULE CERBERUS_INIT 13 | 14 | System: DI ; Disable the maskable interrupts 15 | LD SP,Stack_Top ; Stick the stack somewhere safe for now 16 | 17 | CALL Initialise_RAM 18 | CALL CERBERUS_CURSOR.Initialise 19 | 20 | LD A, 0xC3 ; JP opcode for NMI 21 | LD HL, Interrupt ; Interrupt handler 22 | LD (0x0066), A ; Store at NMI vector address 23 | LD (0x0067), HL 24 | 25 | JP MAIN.COLD 26 | ; 27 | ; Initialise the RAM 28 | ; 29 | Initialise_RAM: LD HL, RAM.Start 30 | LD BC, RAM.Length 31 | LD E, 0 32 | 1: LD (HL), E 33 | INC HL 34 | DEC BC 35 | LD A, B 36 | OR C 37 | JR NZ, 1B 38 | RET 39 | ; 40 | ; Interrupt routine 41 | ; 42 | Interrupt: PUSH AF, BC, DE, HL, IX 43 | EXX 44 | EX AF,AF' 45 | PUSH AF, BC, DE, HL, IY 46 | 47 | CALL CERBERUS_IO.Read_Keyboard 48 | LD (KEY_CODE), A 49 | CP 0x1B 50 | CALL Z, PATCH.ESCSET 51 | CALL CERBERUS_CURSOR.Flash 52 | 53 | LD DE, 2 54 | LD HL, (TIME + 0) 55 | ADD HL, DE 56 | LD (TIME + 0), HL 57 | JR NC, 1F 58 | LD HL, (TIME + 2) 59 | INC HL 60 | LD (TIME + 2), HL 61 | 62 | 1: POP IY, HL, DE, BC, AF 63 | EXX 64 | EX AF,AF' 65 | POP IX, HL, DE, BC, AF 66 | RETI 67 | 68 | ENDMODULE 69 | 70 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_io.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Cerberus 2080 I/O Routines 4 | ; Author: Dean Belfield 5 | ; Created: 31/07/2021 6 | ; Last Updated: 23/11/2021 7 | ; 8 | ; Modinfo: 9 | ; 11/08/2021: Changed label of Mailbox to Inbox 10 | ; 23/11/2021: Insert key now toggles COPY mode in FLAGS 11 | 12 | MODULE CERBERUS_IO 13 | 14 | ; Read the keyboard and return an ASCII character code 15 | ; Returns: 16 | ; A: 0x00 (NUL) if no key pressed 17 | ; 18 | Read_Keyboard: LD A, (Inbox_Flag) 19 | OR A 20 | RET Z 21 | XOR A 22 | LD (Inbox_Flag), A 23 | LD A, (Inbox_Data) 24 | CP 2 ; Key code for INSERT 25 | RET NZ 26 | PUSH AF 27 | LD A, (FLAGS) 28 | XOR %00010000 29 | LD (FLAGS), A 30 | POP AF 31 | RET 32 | 33 | ; Get charpos 34 | ; 35 | Get_Charpos LD HL, (CHARPOS_X) 36 | RET 37 | 38 | ; Print a character at the current charpos 39 | ; Destroys nothing 40 | ; 41 | Print_Char: PUSH AF, BC, DE, HL, IX ; Stack all the registers 42 | CALL 1F ; Call the print routine 43 | POP IX, HL, DE, BC, AF 44 | RET 45 | 1: LD C, A ; Temporarily store the character 46 | LD A, (VDU_STATE) ; What's the current VDU state? 47 | OR A ; If not zero... 48 | JR NZ, VDU_READ_BYTES ; Read the data into the VDU buffer and don't output 49 | LD A, C ; Get the character code back 50 | CP 32 ; Is the character a control routine? 51 | JR C, VDU_CTRL_CHAR ; Yes, so just handle that 52 | CP 0x7F ; Is it backspace? 53 | JR Z, VDU_DEL ; Yes, so deal with that 54 | CP 0xA9 ; Bodge for Spectrum (C) symbol 55 | JR NZ, 2F 56 | LD A, 0x7F 57 | 2: CALL Get_Charpos ; Otherwise print it out 58 | CALL CERBERUS_GRAPHICS.Print_Char 59 | JP VDU_HT 60 | 61 | ; &7F DEL - Delete 62 | ; 63 | VDU_DEL: CALL VDU_BS 64 | LD A, " " 65 | CALL Get_Charpos 66 | JP CERBERUS_GRAPHICS.Print_Char 67 | 68 | ; Just buffer the characters in the VDU buffer 69 | ; Until we have read enough in, then execute the relevant code 70 | ; 71 | VDU_READ_BYTES: LD IX, VDU_STATE ; Indexes - 0: STATE, 1: PTR, 2: COUNT 72 | LD H, high VDU_BUFFER ; HL: VDU buffer 73 | LD L, (IX + 1) ; L: Current position in buffer 74 | LD (HL), C ; Store the character 75 | INC (IX + 1) ; Increase the pointer 76 | DEC (IX + 2) ; Decrease the counter 77 | RET NZ ; If not zero, then return 78 | LD A, (IX + 0) ; Get the state 79 | LD (IX + 0), 0 ; Clear it 80 | DEC A ; Index from 1 81 | LD IX, VDU_BUFFER 82 | CALL SWITCH_A 83 | DEFW VDU_EXEC_PLOT 84 | DEFW VDU_EXEC_TAB 85 | DEFW VDU_EXEC_GORIGIN 86 | DEFW VDU_EXEC_UDG 87 | 88 | ; Handle all control characters 89 | ; A: Character code 90 | ; C: Character code (copied in Print_Char) 91 | ; 92 | VDU_CTRL_CHAR: CALL SWITCH_A 93 | DW VDU_NUL ; &00 NUL - Do nothing 94 | DW VDU_NUL ; &01 SOH - Send next character to printer only 95 | DW VDU_NUL ; &02 STX - Start print job 96 | DW VDU_NUL ; &03 ETX - End print job 97 | DW VDU_NUL ; &04 EOT - Write text at text cursor 98 | DW VDU_NUL ; &05 ENQ - Write text at graphics cursor 99 | DW VDU_NUL ; &06 ACK - Enable VDU drivers 100 | DW VDU_NUL ; &07 BEL - Make a short beep 101 | DW VDU_BS ; &08 BS - Backspace 102 | DW VDU_HT ; &09 HT - Advance cursor one character 103 | DW VDU_LF ; &0A LF - Move cursor down one line 104 | DW VDU_VT ; &0B VT - Move cursor up one line 105 | DW VDU_FF ; &0C FF - Clear text area (CLS) 106 | DW VDU_CR ; &0D CR - Move cursor to start of current line 107 | DW VDU_NUL ; &0E SO - Page mode on 108 | DW VDU_NUL ; &0F SI - Page mode off 109 | DW VDU_DLE ; &10 DLE - Clear graphcs area (CLG) 110 | DW VDU_NUL ; &11 DC1 - Define text colour (COLOUR n) 111 | DW VDU_NUL ; &12 DC2 - Define graphics colour (GCOL a, n) 112 | DW VDU_NUL ; &13 DC3 - Define logical colour (COLOUR l, r, g, b) 113 | DW VDU_NUL ; &14 DC4 - Restore logical colours 114 | DW VDU_NUL ; &15 NAK - Disable VDU drivers or delete current line 115 | DW VDU_NUL ; &16 SYN - Select screen mode (MODE n) 116 | DW VDU_ETB ; &17 ETB - Define display character and other commands; used by ON and OFF 117 | DW VDU_NUL ; &18 CAN - Define graphics windows 118 | DW VDU_EM ; &19 EM - PLOT k, x, y (used by MOVE, DRAW, etc) 119 | DW VDU_NUL ; &1A SUB - Restore default windows 120 | DW VDU_NUL ; &1B ESC - Does nothing 121 | DW VDU_NUL ; &1C FS - Define text window 122 | DW VDU_GS ; &1D GS - Define graphics origin (ORIGIN) 123 | DW VDU_RS ; &1E RS - Home 124 | DW VDU_US ; &1F US - Move text cursor to X, Y (PRINT TAB(x,y)); 125 | 126 | ; &00 NUL - Do nothing 127 | ; 128 | VDU_NUL: RET 129 | 130 | ; &08 BS - Backspace 131 | ; 132 | VDU_BS: LD A, (CHARPOS_X) 133 | OR A 134 | JR Z, 1F 135 | DEC A 136 | LD (CHARPOS_X), A 137 | RET 138 | 1: LD A, CHAR_COLS - 1 139 | LD (CHARPOS_X), A 140 | JR VDU_VT 141 | 142 | 143 | ; &09: HT - Advance to next character 144 | ; 145 | VDU_HT: LD A, (CHARPOS_X) 146 | INC A 147 | LD (CHARPOS_X), A 148 | CP CHAR_COLS 149 | RET C 150 | CALL VDU_CR 151 | 152 | ; &0A LF - Linefeed 153 | ; 154 | VDU_LF: LD A, (CHARPOS_Y) 155 | INC A 156 | LD (CHARPOS_Y), A 157 | CP CHAR_ROWS 158 | RET C 159 | 1: LD A, CHAR_ROWS - 1 160 | LD (CHARPOS_Y), A 161 | JP CERBERUS_GRAPHICS.Scroll_Up 162 | 163 | ; &0B VT - Move cursor up one line 164 | ; 165 | VDU_VT: LD A, (CHARPOS_Y) 166 | DEC A 167 | LD (CHARPOS_Y), A 168 | RLCA 169 | RET NC 170 | XOR A 171 | LD (CHARPOS_Y), A 172 | JP CERBERUS_GRAPHICS.Scroll_Down 173 | 174 | ; &0C FF - CLS 175 | ; 176 | VDU_FF: JP CERBERUS_GRAPHICS.CLS 177 | 178 | 179 | ; &0D CR - Carriage Return 180 | ; 181 | VDU_CR: XOR A 182 | LD (CHARPOS_X), A 183 | RET 184 | 185 | ; &10 DLE - CLG 186 | ; 187 | VDU_DLE: JP CERBERUS_GRAPHICS.CLG 188 | 189 | ; &17 ETB - Define display character and other commands; used by ON and OFF 190 | ; 191 | VDU_ETB: LD B, 9 ; 9 bytes (char, b0, b1, ..., b7) 192 | LD A, 4 ; State 4: UDG 193 | JR VDU_SET_STATE 194 | 195 | ; &19 EM - PLOT k, x, y 196 | ; 197 | VDU_EM: LD B, 5 ; 5 bytes (mode, xl, xh, yl, yh) 198 | LD A, 1 ; State 1: PLOT 199 | JR VDU_SET_STATE 200 | 201 | ; &1D GS - Graphics Origin 202 | ; 203 | VDU_GS: LD B, 4 ; 4 bytes (OX, OY) 204 | LD A, 3 ; State 3: GORIGIN 205 | JR VDU_SET_STATE 206 | 207 | ; &1E RS - HOME 208 | ; 209 | VDU_RS: XOR A 210 | LD (CHARPOS_X), A 211 | LD (CHARPOS_Y), A 212 | RET 213 | 214 | ; &1F US - PRINT TAB(x,y); 215 | ; 216 | VDU_US: LD B, 2 ; 2 bytes (x, y) 217 | LD A, 2 ; State 2: TAB 218 | JR VDU_SET_STATE 219 | 220 | ; Set up the VDU engine to redirect characters into VDU_BUFFER 221 | ; A: Code for the state (the VDU character that initialised it, i.e. 25 for PLOT 222 | ; B: Number of bytes to read before executing the state, i.e. 5 for PLOT 223 | ; 224 | VDU_SET_STATE: LD (VDU_STATE), A 225 | LD A, B 226 | LD (VDU_COUNT), A 227 | XOR A 228 | LD (VDU_PTR), A 229 | RET 230 | 231 | ; PLOT: VDU 25,mode,x;y; 232 | ; 233 | VDU_EXEC_PLOT: LD HL, (PLOTPOS_X) ; Store the previous plot points 234 | LD (PLOTPRE_X), HL 235 | LD HL, (PLOTPOS_Y) 236 | LD (PLOTPRE_Y), HL 237 | LD A, (VDU_BUFFER + 0) ; Plot style 238 | LD DE, (VDU_BUFFER + 1) ; X 239 | LD HL, (VDU_BUFFER + 3) ; Y 240 | LD (PLOTPOS_X), DE ; Store new plot points 241 | LD (PLOTPOS_Y), HL 242 | JP CERBERUS_GRAPHICS.Plot 243 | 244 | ; TAB: VDU 31,x,y 245 | ; 246 | VDU_EXEC_TAB: LD A, (VDU_BUFFER + 0) 247 | LD (CHARPOS_X), A 248 | LD A, (VDU_BUFFER + 1) 249 | LD (CHARPOS_Y), A 250 | RET 251 | 252 | ; Set graphics origin 253 | ; 254 | VDU_EXEC_GORIGIN: LD HL, (VDU_BUFFER + 0) 255 | LD (PLOTORG_X), HL 256 | LD HL, (VDU_BUFFER + 2) 257 | LD (PLOTORG_Y), HL 258 | RET 259 | 260 | ; Define a UDG 261 | ; 262 | VDU_EXEC_UDG: LD HL, VDU_BUFFER 263 | LD A, (HL) ; Get character code 264 | CP 32 ; Is it below ASCII 32? 265 | RET C ; Yes, so do nothing 266 | CALL CERBERUS_GRAPHICS.Get_CRAM_Address 267 | INC L ; Copy the 8 bytes of character 268 | LD BC, 8 ; data into character ram 269 | LDIR 270 | RET 271 | 272 | ENDMODULE 273 | -------------------------------------------------------------------------------- /bin/bbcbasic/cerberus_sound.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Spectrum Next Sound Routines 4 | ; Author: Dean Belfield 5 | ; Created: 28/05/2021 6 | ; Last Updated: 06/06/2021 7 | ; 8 | ; Notes: 9 | 10 | ; Split over 5 complete octaves, with 53 being middle C 11 | ; * C4: 262hz 12 | ; + A4: 440hz 13 | ; 14 | ; 2 3 4 5 6 7 8 15 | ; 16 | ; B 1 49 97 145 193 241 17 | ; A# 0 45 93 141 189 237 18 | ; A 41 89+ 137 185 233 19 | ; G# 37 85 133 181 229 20 | ; G 33 81 129 177 225 21 | ; F# 29 77 125 173 221 22 | ; F 25 73 121 169 217 23 | ; E 21 69 117 165 213 24 | ; D# 17 65 113 161 209 25 | ; D 13 61 109 157 205 253 26 | ; C# 9 57 105 153 201 249 27 | ; C 5 53* 101 149 197 245 28 | ; 29 | ; Modinfo: 30 | ; 06/06/2021: Added LUA script to create note table 31 | ; 08/06/2021: Queue_Note now uses LTRAP to test for ESC 32 | 33 | MODULE CERBERUS_SOUND 34 | 35 | Note_Table: LUA ALLPASS 36 | for i = 0, 255 do 37 | f = 440*(2^((i-89)/48)) 38 | sj.add_word(math.floor(f+0.5)) 39 | end 40 | ENDLUA 41 | 42 | Initialise: RET 43 | 44 | ; Play a note 45 | ; A: Pitch 46 | ; BC: Duration 47 | ; 48 | Play_Note: LD DE, Note_Table ; Index into note table 49 | LD H, 0 50 | LD L, A 51 | ADD HL, HL 52 | ADD HL, DE 53 | LD E, (HL) ; DE: Note frequency 54 | INC HL 55 | LD D, (HL) 56 | ; 57 | LD IX, SOUND_BUFFER ; Sound buffer 58 | LD (IX + 0), E ; Frequency 59 | LD (IX + 1), D 60 | LD (IX + 2), C ; Duration 61 | LD (IX + 3), B 62 | ; 63 | LD IX, Outbox_Flag ; Write out command to CAT 64 | LD (IX + 1), low SOUND_BUFFER 65 | LD (IX + 2), high SOUND_BUFFER 66 | LD (IX + 0), 1 67 | ; 68 | ; Wait for the tone to finish 69 | ; 70 | 1: HALT 71 | LD A, (IX + 0) ; Check the outbound flag 72 | CP 1 ; Has the CAT changed it from 1? 73 | JR Z, 1B ; No, so keep on waiting 74 | RET 75 | 76 | ENDMODULE 77 | -------------------------------------------------------------------------------- /bin/bbcbasic/editor.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic for Cerberus 2080 - Editor 3 | ; Author: Dean Belfield 4 | ; Created: 31/07/2021 5 | ; Last Updated: 24/11/2021 6 | ; 7 | ; Modinfo: 8 | ; 10/08/2021: Added cursor support and fixed editor keycodes 9 | ; 24/11/2021: TAB is now COPY key 10 | 11 | MODULE EDITOR 12 | 13 | ; Read/edit a complete line, terminated by CR. 14 | ; HL: Addresses destination buffer. Must be on a page boundary 15 | ; Returns: 16 | ; Buffer filled, terminated by CR. 17 | ; A: 0 18 | ; Destroys: A,BC,DE,HL,IX,F 19 | ; 20 | Edit_Line: LD (HL), CR ; Initialise buffer 21 | LD A, (FLAGS) ; If ESC flag is set, then do nothing 22 | OR A 23 | LD A, 0 24 | RET M 25 | LD DE, (CHARPOS_X) ; Update the cursor position 26 | CALL CERBERUS_CURSOR.Move 27 | 1: LD A, (KEY_CODE) ; Wait until we've let go of ESC 28 | CP 0x1B 29 | JR Z, 1B 30 | ; 31 | Edit_Line_Loop: CALL CERBERUS_CURSOR.Show 32 | CALL OSRDCH ; Wait for character input 33 | LD C, A ; Take a copy of the character 34 | CALL CERBERUS_CURSOR.Hide ; Cursor off 35 | CALL LTRAP ; Check for escape 36 | ; 37 | LD A, (FLAGS) ; Get the flags in B 38 | LD B, A 39 | LD DE, (CURSOR_X) ; Cursor position 40 | ; 41 | LD A, C ; Check keyboard edit commands 42 | CP 0x7F ; DEL Delete 43 | JP Z, Key_DEL 44 | CP 0x08 ; BS Back one character 45 | JR Z, Key_BS 46 | CP 0x15 ; HT Advance one character 47 | JP Z, Key_HT 48 | CP 0x0A ; LF Down one character 49 | JP Z, Key_LF 50 | CP 0x0B ; VT Up one character 51 | JP Z, Key_VT 52 | CP 0x0D ; CR Enter 53 | JR Z, Key_CR 54 | CP 0x09 ; TAB Copy 55 | JP Z, Key_TAB 56 | ; 57 | LD A, C ; Is it a printable character? 58 | CP 32 59 | JR C, Edit_Line_Loop ; No, so skip 60 | ; 61 | LD E, 0 ; Get length of current line 62 | CALL Get_Length 63 | LD A, B 64 | CP 255 65 | JR NC, Edit_Line_Loop ; Skip if line limit (255) exceeded 66 | ; 67 | CALL Insert ; Insert the character into the buffer 68 | LD (HL), C ; Store the character 69 | CALL Update_1 ; Update characters from cursor position 70 | DEC B 71 | CALL NZ, Update_2 72 | INC L ; Move the cursor 73 | LD DE, (CHARPOS_X) ; Update the cursor position 74 | CALL CERBERUS_CURSOR.Move 75 | JR Edit_Line_Loop 76 | 77 | ; Enter pressed 78 | ; 79 | Key_CR: LD A, (HL) ; Move the cursor to the end of the line 80 | CP CR 81 | JR Z, 1F 82 | INC L 83 | LD A, 0x09: CALL OSWRCH 84 | JR Key_CR 85 | 1: LD A, (FLAGS) 86 | AND %11101111 ; Reset the copy bit 87 | LD (FLAGS), A 88 | CALL CRLF ; Display CRLF 89 | XOR A ; Return A = 0 90 | RET 91 | 92 | ; Cursor Left 93 | ; 94 | Key_BS: BIT 4, B ; Are we in COPY mode? 95 | JR Z, 1F 96 | CALL Move_Cursor_Left 97 | CALL CERBERUS_CURSOR.Move 98 | JP Edit_Line_Loop 99 | 1: INC L 100 | DEC L ; Check for cursor at beginning of line 101 | JP Z, Edit_Line_Loop ; If we are, then do nothing 102 | DEC L ; Move the cursor back 103 | LD A, 8 104 | ; 105 | Key_Out: CALL OSWRCH ; Echo character back to terminal 106 | LD DE, (CHARPOS_X) ; Update the cursor position 107 | CALL CERBERUS_CURSOR.Move 108 | JP Edit_Line_Loop ; Loop 109 | 110 | ; Cursor Right 111 | ; 112 | Key_HT: BIT 4, B ; Are we in COPY mode? 113 | JR Z, 2F 114 | Key_HT_1: CALL Move_Cursor_Right 115 | CALL CERBERUS_CURSOR.Move 116 | JP Edit_Line_Loop 117 | 2: LD A, (HL) 118 | CP CR ; Are we at the end of line? (marked with a CR) 119 | JP Z, Edit_Line_Loop ; Yes, so do nothing 120 | INC L ; Advance the cursor 121 | LD A, 9 122 | JR Key_Out ; Echo character back to terminal 123 | 124 | ; Cursor Down 125 | ; 126 | Key_LF: BIT 4, B ; Are we in COPY mode? 127 | JR Z, 1F 128 | CALL Move_Cursor_Down 129 | CALL CERBERUS_CURSOR.Move 130 | JP Edit_Line_Loop 131 | 1: LD E, 0 132 | CALL Get_Length ; Get length of line in B from start of buffer (E=0) 133 | LD A, CHAR_COLS 134 | ADD A, L ; Down one line 135 | CP B ; Check with line length 136 | JR C, 2F 137 | JP NZ, Edit_Line_Loop 138 | 2: LD L, A 139 | LD A, 10 140 | JR Key_Out ; Echo character back to terminal 141 | 142 | ; Cursor Up 143 | ; 144 | Key_VT: BIT 4, B ; Are we in COPY mode? 145 | JR Z, 1F 146 | CALL Move_Cursor_Up ; Yes, so just move the cursor 147 | CALL CERBERUS_CURSOR.Move 148 | JP Edit_Line_Loop 149 | 1: LD A, -CHAR_COLS 150 | ADD A, L ; Up one line 151 | JP NC, Edit_Line_Loop ; If it takes us past the beginning of the line then do nothing 152 | LD L, A ; Store 153 | LD A, 11 154 | JR Key_Out ; Echo character back to terminal 155 | 156 | ; Delete 157 | ; 158 | Key_DEL: INC L ; Check for input ptr at beginning of line 159 | DEC L 160 | JR Z, 1F 161 | CALL Delete 162 | DEC L 163 | LD A, 0x08: CALL OSWRCH 164 | CALL Update_1 165 | LD A, 0x20: CALL OSWRCH 166 | INC B 167 | CALL Update_2 168 | LD DE, (CHARPOS_X) ; Update the cursor position 169 | CALL CERBERUS_CURSOR.Move 170 | JP Edit_Line_Loop 171 | 172 | ; Copy 173 | ; 174 | Key_TAB: BIT 4, B ; Are we in COPY mode? 175 | JR NZ, 2F ; Yes, so do COPY 176 | 1: JP Edit_Line_Loop 177 | ; 178 | 2: PUSH DE 179 | LD E, 0 ; Check whether we can insert 180 | CALL Get_Length ; Get length of current line 181 | POP DE 182 | LD A, B 183 | CP 255 184 | JR NC, 1B ; Skip if line limit (255) exceeded 185 | ; 186 | PUSH DE 187 | PUSH HL 188 | EX DE, HL 189 | CALL CERBERUS_GRAPHICS.Get_Char 190 | LD C, A ; Store character in C 191 | POP HL 192 | POP DE 193 | JR NC, 1B 194 | PUSH DE 195 | CALL Insert ; Insert the character into the buffer 196 | LD (HL), C ; Store the character 197 | CALL Update_1 ; Update characters from cursor position 198 | DEC B 199 | CALL NZ, Update_2 200 | INC L 201 | POP DE 202 | JP Key_HT_1 203 | 204 | ; Get line length 205 | ; E: Start pointer value in buffer 206 | ; Returns 207 | ; B: Number of characters, excluding CR 208 | ; 209 | Get_Length_From_Cursor: LD E, L 210 | Get_Length: LD B, 0 211 | LD D, H 212 | 1: LD A, (DE) 213 | CP CR 214 | RET Z 215 | INC B 216 | INC E 217 | JR 1B 218 | 219 | ; Move cursor 220 | ; DE: Cursor position 221 | ; 222 | Move_Cursor_Left: EQU DEC_E_NZ ; In misc.z80 223 | Move_Cursor_Up: EQU DEC_D_NZ ; In misc.z80 224 | ; 225 | Move_Cursor_Right: LD A, CHAR_COLS - 1 226 | INC E 227 | CP E 228 | RET NC 229 | LD E, 0 230 | ; 231 | Move_Cursor_Down LD A, CHAR_ROWS - 1 232 | INC D 233 | CP D 234 | RET NC 235 | DEC D 236 | RET 237 | 238 | ; Update from cursor position 239 | ; L: Cursor position 240 | ; 241 | Update_1: LD D, H ; DE: Current cursor position 242 | LD E, L 243 | LD B, 0 ; B: Number of characters output 244 | 1: LD A, (DE) ; Read buffer 245 | CP CR ; Skip if CR 246 | RET Z 247 | CALL OSWRCH ; Print the character out 248 | INC E 249 | INC B ; Increment # of chars output 250 | JR 1B ; And loop 251 | 252 | ; Backspace a number of characters 253 | ; B: Character count 254 | ; 255 | Update_2: INC B ; Is B=0 (EOL) 256 | DEC B 257 | RET Z 258 | LD A, 0x08 ; Restore cursor position 259 | 3: CALL OSWRCH 260 | DJNZ 3B 261 | RET 262 | 263 | ; Insert character 264 | ; C: Character to insert 265 | ; L: Cursor position 266 | ; 267 | Insert: CALL Get_Length_From_Cursor 268 | INC B ; Need to loop at least once 269 | 1: LD A, (DE) 270 | INC E 271 | LD (DE), A 272 | DEC E 273 | DEC E 274 | DJNZ 1B 275 | RET 276 | 277 | ; Delete character 278 | ; L: Cursor position 279 | Delete: CALL Get_Length_From_Cursor 280 | INC B 281 | LD E, L 282 | 1: LD A, (DE) 283 | DEC E 284 | LD (DE), A 285 | INC E 286 | INC E 287 | DJNZ 1B 288 | RET 289 | 290 | ENDMODULE 291 | -------------------------------------------------------------------------------- /bin/bbcbasic/examples/ANIMAL.DAT: -------------------------------------------------------------------------------- 1 | 68 \QDoes it fly\N2\Y3\ \QDoes it live in water\N9\Y8\ \QIs it an insect\Y5\N4\ \QIs it a mammal\Y7\N6\ \QDoes it have black stripes\Y37\N36\ \QIs it yellow\Y19\N18\ \Abat \QIs it a mammal\Y15\N14\ \QIs it an insect\Y11\N10\ \QIs it a mammal\N13\Y12\ \Aant \QDoes it have a long trunk\Y21\N20\ \QDoes it have a pouch\Y27\N26\ \QIs it a fish\N17\Y16\ \QIs it very big\N23\Y22\ \Agoldfish \QDoes it have a hard shell\Y29\N28\ \QDoes it have a red breast\Y25\N24\ \Acanary \QDoes it purr\Y33\N32\ \Aelephant \Awhale \QIs it furry\Y45\N44\ \QDoes it eat fish\Y31\N30\ \Arobin \QDoes it have eight legs\Y39\N38\ \Akangaroo \QDoes it have eight tentacles\N41\Y40\ \QIs its shell almost round\Y35\N34\ \QDoes it quack\Y49\N48\ \Aseagull \QDoes it eat ants\Y47\N46\ \Acat \Alobster \Acrab \QDoes it bite\Y67\N66\ \Awasp \QDoes it have a forked tongue\N43\Y42\ \Aspider \Aoctopus \QDoes it have a nasty smell\Y53\N52\ \Asnake \Aworm \Adolphin \QDoes it build dams\N57\Y56\ \QDoes it bark\N51\Y50\ \Aanteater \Asparrow \Aduck \Adog \QDoes it have a long neck\Y55\N54\ \Afrog \Atoad \QDoes it have antlers\Y59\N58\ \Agiraffe \Abeaver \Aotter \QDoes it like cheese\Y61\N60\ \Adeer \QDoes it like mud\Y63\N62\ \QCan it talk\Y65\N64\ \Arabbit \Ahippopotamus \Amouse \Aman \Abee \Agnat -------------------------------------------------------------------------------- /bin/bbcbasic/examples/animal.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/animal.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-index.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-index.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-rand0.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-rand0.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-rand1.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-rand1.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-rand2.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-rand2.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-rser1.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-rser1.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-rser2.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-rser2.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-rstd.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-rstd.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-weser1.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-weser1.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-weser2.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-weser2.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-wser1.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-wser1.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-wser2.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-wser2.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/f-wstd.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/f-wstd.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/merge.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/merge.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/sort.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/sort.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/examples/sortreal.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/examples/sortreal.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/macros.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Useful macros 4 | ; Author: Dean Belfield 5 | ; Created: 10/08/2021 6 | ; Last Updated: 13/08/2021 7 | ; 8 | ; Modinfo: 9 | ; 13/08/2021: Added EXREG 10 | 11 | MACRO EXREG rp1, rp2 12 | PUSH rp1 13 | POP rp2 14 | ENDM 15 | 16 | MACRO ADD8U reg 17 | ADD A, low reg 18 | LD low reg, A 19 | ADC A, high reg 20 | SUB low reg 21 | LD high reg, A 22 | ENDM 23 | -------------------------------------------------------------------------------- /bin/bbcbasic/misc.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic for Cerberus 2080 - Miscellaneous helper functions 3 | ; Author: Dean Belfield 4 | ; Created: 31/07/2021 5 | ; Last Updated: 13/08/2021 6 | ; 7 | ; Modinfo: 8 | ; 10/08/2021: Fixed ASC_TO_NUMBER (removed Z80N instructions) 9 | ; 13/08/2021: Removed NULLTOSP, SPTONULL, NULLTOCR and CRTONULL 10 | 11 | ; Read a number and convert to binary 12 | ; If prefixed with &, will read as hex, otherwise decimal 13 | ; Inputs: HL: Pointer in string buffer 14 | ; Outputs: HL: Updated text pointer 15 | ; DE: Value 16 | ; A: Terminator (spaces skipped) 17 | ; Destroys: A,D,E,H,L,F 18 | ; 19 | ASC_TO_NUMBER: PUSH BC ; Preserve BC 20 | LD DE, 0 ; Initialise DE 21 | CALL SKIPSP ; Skip whitespace 22 | LD A, (HL) ; Read first character 23 | CP "&" ; Is it prefixed with '&' (HEX number)? 24 | JR NZ, 3F ; Jump to decimal parser if not 25 | INC HL ; Otherwise fall through to ASC_TO_HEX 26 | ; 27 | 1: LD A, (HL) ; Fetch the character 28 | CALL UPPRC ; Convert to uppercase 29 | SUB '0' ; Normalise to 0 30 | JR C, 4F ; Return if < ASCII '0' 31 | CP 10 ; Check if >= 10 32 | JR C,2F ; No, so skip next bit 33 | SUB 7 ; Adjust ASCII A-F to nibble 34 | CP 16 ; Check for > F 35 | JR NC, 4F ; Return if out of range 36 | 2: EX DE, HL ; Shift DE left 4 times 37 | ADD HL, HL 38 | ADD HL, HL 39 | ADD HL, HL 40 | ADD HL, HL 41 | EX DE, HL 42 | OR E ; OR the new digit in to the least significant nibble 43 | LD E, A 44 | INC HL ; Onto the next character 45 | JR 1B ; And loop 46 | ; 47 | 3: LD A, (HL) 48 | SUB '0' ; Normalise to 0 49 | JR C, 4F ; Return if < ASCII '0' 50 | CP 10 ; Check if >= 10 51 | JR NC, 4F ; Return if >= 10 52 | EX DE, HL ; Stick DE in HL 53 | LD B, H ; And copy HL into BC 54 | LD C, L 55 | ADD HL, HL ; x 2 56 | ADD HL, HL ; x 4 57 | ADD HL, BC ; x 5 58 | ADD HL, HL ; x 10 59 | EX DE, HL 60 | ADD8U DE ; Add A to DE (macro) 61 | INC HL 62 | JR 3B 63 | 4: POP BC ; Fall through to SKIPSP here 64 | 65 | ; Skip a space 66 | ; HL: Pointer in string buffer 67 | ; 68 | SKIPSP: LD A, (HL) 69 | CP ' ' 70 | RET NZ 71 | INC HL 72 | JR SKIPSP 73 | 74 | ; Skip a string 75 | ; HL: Pointer in string buffer 76 | ; 77 | SKIPNOTSP: LD A, (HL) 78 | CP ' ' 79 | RET Z 80 | INC HL 81 | JR SKIPNOTSP 82 | 83 | ; Convert a character to upper case 84 | ; A: Character to convert 85 | ; 86 | UPPRC: AND 7FH 87 | CP '`' 88 | RET C 89 | AND 5FH ; CONVERT TO UPPER CASE 90 | RET 91 | 92 | ; Convert the buffer to a null terminated string and back 93 | ; 94 | 95 | 96 | ; Convert BCD to ASCII 97 | ; HL: Pointer in string buffer 98 | ; A: BCD number to convert 99 | ; 100 | BCD_TO_ASC: LD C, A ; Store A 101 | RRCA ; Get high nibble 102 | RRCA 103 | RRCA 104 | RRCA 105 | CALL 1F 106 | LD A, C 107 | 1: AND 0x0F 108 | ADD A, '0' 109 | LD (HL), A 110 | INC HL 111 | RET 112 | 113 | ; Print BCD 114 | ; 115 | PRINT_BCD: ADD A, 0 116 | DAA 117 | PRINT_BCD_1: LD C, A 118 | RRCA 119 | RRCA 120 | RRCA 121 | RRCA 122 | CALL 1F 123 | LD A, C 124 | 1: AND 0x0F 125 | ADD A, '0' 126 | JP OSWRCH 127 | 128 | ; Switch on A - lookup table immediately after call 129 | ; A: Index into lookup table 130 | ; 131 | SWITCH_A: EX (SP), HL ; Swap HL with the contents of the top of the stack 132 | ADD A, A ; Multiply A by two 133 | ADD8U HL ; Add to HL (macro) 134 | LD A, (HL) ; follow the call. Fetch an address from the 135 | INC HL ; table. 136 | LD H, (HL) 137 | LD L, A 138 | EX (SP), HL ; Swap this new address back, restores HL 139 | RET ; Return program control to this new address 140 | 141 | ; Decrease if not 0 142 | ; 143 | DEC_D_NZ: INC D 144 | DEC D 145 | RET Z 146 | DEC D 147 | RET 148 | ; 149 | DEC_E_NZ: INC E 150 | DEC E 151 | RET Z 152 | DEC E 153 | RET 154 | 155 | ; Convert LHED to a FPP 156 | ; 157 | HLDE_TO_FPP: PUSH DE 158 | EXX 159 | POP DE 160 | EX DE, HL ; E, D are the least significant bytes 161 | EXX 162 | LD C, 0 ; Exponent 163 | RET 164 | -------------------------------------------------------------------------------- /bin/bbcbasic/patch.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic for Cerberus 2080 3 | ; Author: Dean Belfield 4 | ; Created: 31/07/2021 5 | ; Last Updated: 06/10/2021 6 | ; 7 | ; Modinfo: 8 | ; 12/08/2021: Added POINT, GCOL, LOAD and SAVE and STAR commands BYE, MEMDUMP, FX, ERASE, DELETE 9 | ; 13/08/2021: Added STAR commands LOAD and SAVE 10 | ; 06/10/2021: Added STAR command CAT 11 | 12 | MODULE PATCH 13 | 14 | @OSWRCH: EQU CERBERUS_IO.Print_Char ; Write a character out 15 | @OSLINE EQU EDITOR.Edit_Line ; Line editor 16 | 17 | ; CLRSCN: clears the screen. 18 | ; 19 | @CLRSCN: LD A, 0x0C 20 | JP OSWRCH 21 | 22 | ; PUTIME: set current time to DE:HL, in centiseconds. 23 | ; 24 | @PUTIME: LD (TIME + 2), DE 25 | LD (TIME + 0), HL 26 | RET 27 | 28 | ; GETIME: return current time in DE:HL, in centiseconds. 29 | ; 30 | @GETIME: LD DE, (TIME + 2) 31 | LD HL, (TIME + 0) 32 | RET 33 | 34 | ; PUTCSR: move to cursor to x=DE, y=HL 35 | ; 36 | @PUTCSR: LD D, L ; E: X, D: Y 37 | LD (CHARPOS_X), DE 38 | RET 39 | 40 | ; GETCSR: return cursor position in x=DE, y=HL 41 | ; 42 | @GETCSR: LD DE, (CHARPOS_X) ; E: X, D: Y 43 | LD L, D 44 | LD H, 0 ; HL: Y 45 | LD D, H ; DE: X 46 | RET 47 | 48 | ; Read character from keyboard (blocking) 49 | ; 50 | @OSRDCH: LD A, (KEY_CODE) 51 | OR A 52 | JR Z, OSRDCH 53 | PUSH AF 54 | 1: LD A, (KEY_CODE) 55 | OR A 56 | JR NZ, 1B 57 | POP AF 58 | RET 59 | 60 | ; PROMPT: output the input prompt 61 | ; 62 | @PROMPT: LD A,'>' 63 | JP OSWRCH 64 | 65 | ;OSKEY - Read key with time-limit, test for ESCape. 66 | ;Main function is carried out in user patch. 67 | ; Inputs: HL = time limit (centiseconds) 68 | ; Outputs: Carry reset if time-out 69 | ; If carry set A = character 70 | ; Destroys: A,H,L,F 71 | ; 72 | @OSKEY: LD A, (KEY_CODE) ; Read keyboard 73 | OR A ; If we have a character 74 | JR NZ, 1F ; Then process it 75 | LD A,H ; Check if HL is 0 (this is passed by INKEY() function 76 | OR L 77 | RET Z ; If it is then ret 78 | HALT ; Bit of a bodge so this is timed in ms 79 | DEC HL ; Decrement the counter and 80 | JR @OSKEY ; loop 81 | 1: CP 0x1B ; If we are not pressing ESC, 82 | SCF ; then flag we've got a character 83 | RET NZ 84 | ; 85 | ESCSET: PUSH HL 86 | LD HL,FLAGS 87 | BIT 6,(HL) ; ESC DISABLED? 88 | JR NZ,ESCDIS 89 | SET 7,(HL) ; SET ESCAPE FLAG 90 | ESCDIS: POP HL 91 | RET 92 | ; 93 | ESCTEST: LD A, (KEY_CODE) 94 | CP 0x1B ; ESC 95 | JR Z,ESCSET 96 | RET 97 | ; 98 | @TRAP: CALL ESCTEST 99 | @LTRAP: LD A,(FLAGS) 100 | OR A 101 | RET P 102 | LD HL,FLAGS 103 | RES 7,(HL) 104 | JP ESCAPE 105 | 106 | ;OSINIT - Initialise RAM mapping etc. 107 | ;If BASIC is entered by BBCBASIC FILENAME then file 108 | ;FILENAME.BBC is automatically CHAINed. 109 | ; Outputs: DE = initial value of HIMEM (top of RAM) 110 | ; HL = initial value of PAGE (user program) 111 | ; Z-flag reset indicates AUTO-RUN. 112 | ; Destroys: A,D,E,H,L,F 113 | ; 114 | @OSINIT: LD HL, @USER 115 | LD DE, @RAM_Top 116 | XOR A 117 | LD (@FLAGS), A ; Clear flags and set F = Z 118 | RET 119 | 120 | ; 121 | ;OSCLI - Process an "operating system" command 122 | ; 123 | @OSCLI: CALL SKIPSP 124 | CP CR 125 | RET Z 126 | CP '|' 127 | RET Z 128 | CP '.' 129 | JP Z,STAR_DOT ; *. 130 | EX DE,HL 131 | LD HL,COMDS 132 | OSCLI0: LD A,(DE) 133 | CALL UPPRC 134 | CP (HL) 135 | JR Z,OSCLI2 136 | JR C,HUH 137 | OSCLI1: BIT 7,(HL) 138 | INC HL 139 | JR Z,OSCLI1 140 | INC HL 141 | INC HL 142 | JR OSCLI0 143 | ; 144 | OSCLI2: PUSH DE 145 | OSCLI3: INC DE 146 | INC HL 147 | LD A,(DE) 148 | CALL UPPRC 149 | CP '.' ; ABBREVIATED? 150 | JR Z,OSCLI4 151 | XOR (HL) 152 | JR Z,OSCLI3 153 | CP 80H 154 | JR Z,OSCLI4 155 | POP DE 156 | JR OSCLI1 157 | ; 158 | OSCLI4: POP AF 159 | INC DE 160 | OSCLI5: BIT 7,(HL) 161 | INC HL 162 | JR Z,OSCLI5 163 | LD A,(HL) 164 | INC HL 165 | LD H,(HL) 166 | LD L,A 167 | PUSH HL 168 | EX DE,HL 169 | JP SKIPSP 170 | 171 | HUH: LD A,254 172 | CALL EXTERR 173 | DEFM 'Bad command' 174 | DEFB 0 175 | 176 | SKIPSP: LD A,(HL) 177 | CP ' ' 178 | RET NZ 179 | INC HL 180 | JR SKIPSP 181 | 182 | UPPRC: AND 7FH 183 | CP '`' 184 | RET C 185 | AND 5FH ; CONVERT TO UPPER CASE 186 | RET 187 | 188 | ; Each command has bit 7 of the last character set, and is followed by the address of the handler 189 | ; 190 | COMDS: DC 'BYE': DEFW STAR_BYE ; Exit to BIOS 191 | DC 'CAT': DEFW STAR_CAT ; Catalogue SD Card 192 | DC 'DELETE': DEFW STAR_DELETE 193 | DC 'ERASE': DEFW STAR_DELETE 194 | DC 'FX': DEFW STAR_FX ; FX command 195 | DC 'LOAD': DEFW STAR_LOAD 196 | DC 'MEMDUMP': DEFW STAR_MEMDUMP 197 | DC 'SAVE': DEFW STAR_SAVE 198 | DC 'TEST': DEFW STAR_TEST 199 | DEFB 0FFH 200 | 201 | ; *BYE 202 | ; 203 | STAR_BYE: LD A, 0x7F 204 | LD (Outbox_Flag), A 205 | LD L, 0 206 | JP EVAL.COUNT0 207 | 208 | ; *CAT / *. 209 | ; 210 | STAR_DOT: 211 | STAR_CAT: CALL CERBERUS_DOS.Dir ; Open the "/" directory 212 | 1: CALL CERBERUS_DOS.Dir_Entry ; Fetch the next entry 213 | RET NZ ; If the CAT has returned EOF, then exit 214 | 215 | LD HL, DOS_BUFFER + 4 ; Skip to the filename 216 | CALL MAIN.TEXT ; Output 217 | 218 | LD HL, DOS_BUFFER ; Point to the file size 219 | LD E, (HL): INC L ; Byte 0 of the file length 220 | LD D, (HL): INC L ; Byte 1 221 | LD A, (HL): INC L ; Byte 2 222 | LD H, (HL) ; Byte 3 223 | LD L, A 224 | CALL HLDE_TO_FPP ; Convert to HLH'L' and C 225 | LD DE, ACCS ; Buffer to output the ASCII number to 226 | LD IX, STAVAR ; Used for the @% format variable 227 | CALL FPP.STR ; Output to DE 228 | EX DE, HL 229 | LD (HL), 0 ; Null terminate the string 230 | LD A, 7 ; Right-align it 231 | SUB L 232 | JR C, 2F 233 | ADD A, 11 234 | LD (CHARPOS_X), A 235 | LD L, 0 236 | CALL MAIN.TEXT ; And print it 237 | 238 | 2: CALL CRLF ; With a carriage return 239 | JR 1B ; Loop back to fetch next entry 240 | 241 | ; *DELETE filename 242 | ; 243 | STAR_DELETE: CALL SKIPSP 244 | JP CERBERUS_DOS.Delete 245 | 246 | ; *MEMDUMP addr count 247 | ; 248 | STAR_MEMDUMP CALL ASC_TO_NUMBER ; Get start address 249 | PUSH DE 250 | CALL ASC_TO_NUMBER ; Get length in DE 251 | POP HL ; Get start address in HL 252 | JP CERBERUS_DEBUG.Memory_Dump 253 | 254 | ; *LOAD file addr 255 | ; 256 | STAR_LOAD: CALL SKIPSP ; First parameter is a string 257 | PUSH HL ; Stack the string pointer 258 | CALL SKIPNOTSP ; Skip to the next parameter 259 | CALL ASC_TO_NUMBER ; DE: Address 260 | POP HL ; HL: Pointer to Filename 261 | LD BC, -1 ; Maximum number of bytes 262 | JP CERBERUS_DOS.Load 263 | 264 | ; *SAVE file addr len 265 | ; 266 | STAR_SAVE: CALL SKIPSP ; First parameter is a string 267 | PUSH HL ; Stack the string pointer 268 | CALL SKIPNOTSP ; Skip to the next parameter 269 | CALL ASC_TO_NUMBER ; Read address 270 | PUSH DE 271 | CALL ASC_TO_NUMBER ; Read length 272 | EXREG DE, BC ; BC: Length 273 | POP DE ; DE: Start address 274 | POP HL ; HL: Pointer to Filename 275 | JP CERBERUS_DOS.Save 276 | 277 | ; *TEST 278 | ; 279 | STAR_TEST RET 280 | 281 | ; *FX 282 | ; 283 | STAR_FX: CALL ASC_TO_NUMBER ; C: FX # 284 | LD C, E 285 | CALL ASC_TO_NUMBER ; B: First parameter 286 | LD B, E 287 | CALL ASC_TO_NUMBER ; E: Second parameter 288 | LD L, B ; L: First parameter 289 | LD H, E ; H: Second parameter 290 | LD A, C ; A: FX #, and fall through to OSBYTE 291 | ; 292 | ; OSBYTE 293 | ; A: FX # 294 | ; L: First parameter 295 | ; H: Second parameter 296 | ; 297 | OSBYTE: CP 0x13 298 | JR Z, OSBYTE_13 299 | JP HUH 300 | 301 | ; OSBYTE 0x13 (FX 19): Wait 1/50th of a second 302 | ; 303 | OSBYTE_13: HALT 304 | LD L, 0 305 | JP EVAL.COUNT0 306 | 307 | ; GET(port) - Read Z80/Nextreg port 308 | ; GET(x, y) - Read character from screen position (x, y) 309 | ; Called from GET in eval.z80 310 | ; 311 | @GET_PORT: INC IY ; Skip '(' 312 | CALL EXPRI ; PORT ADDRESS 313 | EXX 314 | PUSH HL 315 | CALL NXT 316 | CP "," 317 | JR NZ, 1F 318 | ; 319 | ; Get second parameter 320 | ; 321 | CALL COMMA 322 | CALL EXPRI 323 | EXX 324 | POP DE ; DE: X coordinate 325 | LD H, L ; H: Y coordinate 326 | LD L, E ; L: X coordinate 327 | CALL CERBERUS_GRAPHICS.Get_Char 328 | LD L, A ; Character code, or 0xFF if no match 329 | JR C, 3F ; We have a character 330 | CALL BRAKET ; Check for second bracket 331 | JP EVAL.TRUE ; Return -1 332 | ; 333 | ; Read port 334 | ; 335 | 1: POP BC ; Port # in BC 336 | IN L, (C) ; Read the value 337 | 3: CALL BRAKET 338 | JP EVAL.COUNT0 339 | 340 | ; GET$(x, y) - Read character from screen position (x, y) 341 | ; 342 | @GET_CHAR: INC IY 343 | CALL EXPRI ; Get X coordinate 344 | EXX 345 | PUSH HL 346 | CALL COMMA 347 | CALL EXPRI ; Get Y coordinate 348 | EXX 349 | POP DE ; DE: X coordinate 350 | LD H, L ; H: Y coordinate 351 | LD L, E ; L: X coordinate 352 | CALL CERBERUS_GRAPHICS.Get_Char 353 | EX AF, AF 354 | CALL BRAKET 355 | EX AF, AF 356 | JP EVAL.INKEY1 357 | 358 | ; GCOL mode,colour 359 | ; 360 | @GCOL: CALL EXPR_P2 ; DE: mode, HL: colour 361 | LD A, E 362 | LD (PLOT_MODE), A 363 | LD A, L 364 | LD (PLOT_COLOUR), A 365 | JP XEQ 366 | 367 | ; MOVE x, y 368 | ; 369 | @MOVE: CALL EXPR_P2 ; DE: X, HL: Y 370 | LD H, L ; H: Y 371 | LD L, E ; L: X 372 | CALL MOVE_1 373 | JP XEQ 374 | ; 375 | @MOVE_1: LD DE, (PLOTPOS_X) ; Store the previous plot points 376 | LD (PLOTPRE_X), DE 377 | LD (PLOTPOS_X), HL ; And store the latest plot point 378 | RET 379 | ; DRAW x, y 380 | ; 381 | @DRAW: CALL EXPR_P2 ; DE: X, HL: Y 382 | LD H, L ; H: Y 383 | LD L, E ; L: X 384 | CALL NXT ; Are there any more paramters? 385 | CP "," 386 | JR NZ, 1F ; No, so just do 'DRAW x, y' 387 | CALL COMMA ; Okay, we're now doing 'DRAW x1,y1,x2,y2 388 | CALL MOVE_1 ; MOVE x1, y1 (E, L) 389 | CALL EXPR_P2 ; DE: X2, HL: Y2 390 | LD H, L 391 | LD L, E 392 | 1: PUSH HL ; Stack X2 393 | CALL CERBERUS_GRAPHICS.Plot_Line 394 | JR PLOT_1 395 | 396 | 397 | ; PLOT mode,x,y 398 | ; 399 | @PLOT: CALL EXPRI ; Get the plot type (line, plot, etc) 400 | EXX 401 | PUSH HL 402 | CALL COMMA 403 | CALL EXPR_P2 ; DE: X, HL: Y 404 | POP BC ; Plot type in C 405 | LD A, C ; Plot type in A 406 | LD H, L ; H: Y 407 | LD L, E ; L: X 408 | PUSH HL 409 | CALL CERBERUS_GRAPHICS.Plot 410 | @PLOT_1: POP HL 411 | CALL MOVE_1 412 | 1: JP XEQ 413 | 414 | ; POINT(x, y) 415 | ; 416 | @POINT: CALL EXPR_P2 ; DE: X, HL: Y 417 | LD H, L ; H: Y 418 | LD L, E ; L: X 419 | CALL CERBERUS_GRAPHICS.Point 420 | LD L, A 421 | 1: CALL BRAKET 422 | JP EVAL.COUNT0 423 | 424 | ;OSLOAD - Load an area of memory from a file. 425 | ; Inputs: HL addresses filename (CR terminated) 426 | ; DE = address at which to load 427 | ; BC = maximum allowed size (bytes) 428 | ; Outputs: Carry reset indicates no room for file. 429 | ; Destroys: A,B,C,D,E,H,L,F 430 | ; 431 | @OSLOAD: JP CERBERUS_DOS.Load 432 | 433 | ;OSSAVE - Save an area of memory to a file. 434 | ; Inputs: HL addresses filename (term CR) 435 | ; DE = start address of data to save 436 | ; BC = length of data to save (bytes) 437 | ; Destroys: A,B,C,D,E,H,L,F 438 | ; 439 | @OSSAVE: JP CERBERUS_DOS.Save 440 | 441 | ; SOUND channel,volume,pitch,duration 442 | ; volume: 0 (off) to -15 (full volume) 443 | ; pitch: 0 - 255 444 | ; duration: -1 to 254 (duration in 20ths of a second, -1 = play forever) 445 | ; 446 | @SOUND: CALL EXPR_P2 ; DE: Channel/Control, HL: Volume - ignored 447 | CALL COMMA 448 | CALL EXPR_P2 ; DE: Pitch, HL: Duration 449 | LD B, H 450 | LD C, L 451 | LD A, E 452 | CALL CERBERUS_SOUND.Play_Note 453 | JP XEQ 454 | 455 | ; Get two values from EXPR in DE, HL 456 | ; IY: Pointer to expression string 457 | ; Returns: 458 | ; DE: P1 459 | ; HL: P2 460 | ; 461 | @EXPR_P2: CALL EXPRI ; Get first parameter 462 | EXX 463 | PUSH HL 464 | CALL COMMA 465 | CALL EXPRI ; Get second parameter 466 | EXX 467 | POP DE 468 | RET 469 | 470 | ; Stuff not implemented yet 471 | ; 472 | @OSBPUT: 473 | @OSBGET: 474 | @OSSTAT: 475 | @OSSHUT: 476 | @OSOPEN: 477 | @OSCALL: 478 | @GETPTR: 479 | @PUTPTR: 480 | @GETEXT: 481 | @RESET: 482 | RET 483 | 484 | ENDMODULE 485 | -------------------------------------------------------------------------------- /bin/bbcbasic/ram.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; RAM Module for BBC Basic Interpreter 4 | ; For use with Version 2.0 of BBC BASIC 5 | ; Standard CP/M Distribution Version 6 | ; Author: (C) Copyright R.T.Russell 31-12-1983 7 | ; Modified By: Dean Belfield 8 | ; Created: 02/05/2021 9 | ; Last Updated: 13/08/2021 10 | ; 11 | ; Modinfo: 12 | ; 02/05/2021: Modified by Dean Belfield to assemble with SJASMPLUS 13 | ; 09/08/2021: Added all variables required by Cerberus version of BBC basic 14 | ; 12/08/2021: Tweaks for file handling 15 | ; 13/08/2021: Tweaks for sound 16 | 17 | MODULE RAM 18 | 19 | ALIGN 256 20 | 21 | Start: EQU $ 22 | ; 23 | ;n.b. ACCS, BUFFER & STAVAR must be on page boundaries. 24 | ; 25 | @ACCS: DEFS 256 ;STRING ACCUMULATOR 26 | @BUFFER: DEFS 256 ;STRING INPUT BUFFER 27 | @STAVAR: DEFS 27*4 ;STATIC VARIABLES 28 | @OC: EQU STAVAR+15*4 ;CODE ORIGIN (O%) 29 | @PC: EQU STAVAR+16*4 ;PROGRAM COUNTER (P%) 30 | @DYNVAR: DEFS 54*2 ;DYN. VARIABLE POINTERS 31 | @FNPTR: DEFS 2 ;DYN. FUNCTION POINTER 32 | @PROPTR: DEFS 2 ;DYN. PROCEDURE POINTER 33 | ; 34 | @PAGE: DEFS 2 ;START OF USER PROGRAM 35 | @TOP: DEFS 2 ;FIRST LOCN AFTER PROG. 36 | @LOMEM: DEFS 2 ;START OF DYN. STORAGE 37 | @FREE: DEFS 2 ;FIRST FREE-SPACE BYTE 38 | @HIMEM: DEFS 2 ;FIRST PROTECTED BYTE 39 | ; 40 | @LINENO: DEFS 2 ;LINE NUMBER 41 | @TRACEN: DEFS 2 ;TRACE FLAG 42 | @AUTONO: DEFS 2 ;AUTO FLAG 43 | @ERRTRP: DEFS 2 ;ERROR TRAP 44 | @ERRTXT: DEFS 2 ;ERROR MESSAGE POINTER 45 | @DATPTR: DEFS 2 ;DATA POINTER 46 | @ERL: DEFS 2 ;ERROR LINE 47 | @ERRLIN: DEFS 2 ;"ON ERROR" LINE 48 | @RANDOM: DEFS 5 ;RANDOM NUMBER 49 | @COUNT: DEFS 1 ;PRINT POSITION 50 | @WIDTH: DEFS 1 ;PRINT WIDTH 51 | @ERR: DEFS 1 ;ERROR NUMBER 52 | @LISTON: DEFS 1 ;LISTO & OPT FLAG 53 | @INCREM: DEFS 1 ;AUTO INCREMENT 54 | ; 55 | ; Extra Next-implementation specific system variables 56 | ; 57 | @VDU_BUFFER: EQU ACCS ; Storage for VDU commands 58 | ; 59 | @DOS_BUFFER: DEFS 64 ; Buffer for DOS commands to CAT 60 | @SOUND_BUFFER: DEFS 4 ; Buffer for SOUND commands to CAT 61 | @FLAGS: DEFS 1 ; Flags: B7=ESC PRESSED, B6=ESC DISABLED 62 | @KEY_SCAN: DEFS 2 ; Results of last keyscan 63 | @KEY_COUNT: DEFS 1 ; Key repeat counter 64 | @KEY_CODE: DEFS 1 ; Keycode updated by keyscan 65 | @KEY_DELAY: DEFS 1 ; Initial key delay 66 | @KEY_REPEAT: DEFS 1 ; Key repeat 67 | @TIME: DEFS 4 68 | @CURSOR_X: DEFS 1 ; Edit cursor position on screen 69 | @CURSOR_Y: DEFS 1 70 | @CURSOR_C: DEFS 1 ; Temporary store for char behind cursor 71 | @CHARPOS_X: DEFS 1 ; Current character position on screen 72 | @CHARPOS_Y: DEFS 1 73 | @PLOTORG_X: DEFS 1 ; Plot origin 74 | @PLOTORG_Y: DEFS 1 75 | @PLOTPRE_X: DEFS 1 ; Previous plot position 76 | @PLOTPRE_Y: DEFS 1 77 | @PLOTPOS_X: DEFS 1 ; Current plot position on screen 78 | @PLOTPOS_Y: DEFS 1 79 | @PLOT_MODE: DEFS 1 ; Plot mode as set by GCOL 80 | @PLOT_COLOUR: DEFS 1 ; Plot colour as set by GCOL 81 | @VDU_STATE: DEFS 1 ; VDU state 82 | @VDU_PTR: DEFS 1 ; Pointer into VDU buffer 83 | @VDU_COUNT: DEFS 1 ; VDU count of characters left to read 84 | @UDG_MASK: DEFS 1 ; Exploded UDG mode mask 85 | 86 | Length: EQU $ - Start 87 | 88 | ENDMODULE 89 | -------------------------------------------------------------------------------- /bin/bbcbasic/sorry.z80: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: BBC Basic Interpreter - Z80 version 3 | ; Catch-all for unimplemented functionality 4 | ; Author: Dean Belfield 5 | ; Created: 02/05/2021 6 | ; Last Updated: 13/08/2021 7 | ; 8 | ; Modinfo: 9 | ; 10/08/2021: Moved POINT and GCOL to patch.z80 10 | ; 13/08/2021: Moved SOUND to patch.z80 11 | 12 | MODULE SORRY 13 | 14 | @CLG: 15 | @COLOUR: 16 | @ENVEL: 17 | @MODE: 18 | @ADVAL: 19 | @GETIMS: 20 | @PUTIMS: 21 | XOR A 22 | CALL EXTERR 23 | DEFM 'Sorry' 24 | DEFB 0 25 | 26 | ENDMODULE 27 | -------------------------------------------------------------------------------- /bin/bbcbasic/tests/BENCHMARK.md: -------------------------------------------------------------------------------- 1 | # benchmarks 2 | 3 | Tested using the [Rugg/Feldman (PCW) benchmarks](https://en.wikipedia.org/wiki/Rugg/Feldman_benchmarks#:~:text=This%20expanded%20set%20became%20known,many%20computer%20magazines%20and%20journals.) 4 | 5 | NB: 6 | 7 | - The BBC Micro CPU is a 6502 running BBC Basic IV 8 | - The Spectrum is a Z80 running Sinclair BASIC 9 | - The Cerberus is a Z80 running BBC Basic for Z80 10 | 11 | | # | Cerberus (8Mhz)| Spectrum (3.5Mhz) | BBC Micro (2Mhz) | 12 | |---|---------------:|------------------:|-----------------:| 13 | | 1 | 0.42s | 4.4s | 0.8s | 14 | | 2 | 1.82s | 8.2s | 3.1s | 15 | | 3 | 5.78s | 20.0s | 8.1s | 16 | | 4 | 6.40s | 19.2s | 8.7s | 17 | | 5 | 6.84s | 23.1s | 9.0s | 18 | | 6 | 9.84s | 53.4s | 13.9s | 19 | | 7 | 14.16s | 77.6s | 21.1s | 20 | | 8 | 16.84s | 239.1s | 49.9s | 21 | -------------------------------------------------------------------------------- /bin/bbcbasic/tests/README.md: -------------------------------------------------------------------------------- 1 | # tests 2 | 3 | A handful of BBC BASIC programs I use for regression and soak testing: 4 | 5 | - `cube.bbc` Tests the graphics and floating point maths 6 | - `sound.bbc` Tests the SOUND command, should play Amazing Grace 7 | - `udg.bbc` Test VDU 23 8 | 9 | And there are also a set of Rugg/Feldman (PCW) benchmark programs; benchm1.bbc to benchm8.bbc 10 | -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm1.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm1.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm2.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm2.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm3.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm3.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm4.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm4.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm5.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm5.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm6.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm6.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm7.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm7.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/benchm8.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/benchm8.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/cube.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/cube.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/sound.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/sound.bbc -------------------------------------------------------------------------------- /bin/bbcbasic/tests/udg.bbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/bin/bbcbasic/tests/udg.bbc -------------------------------------------------------------------------------- /cat/README.md: -------------------------------------------------------------------------------- 1 | 2 | # cat bios 3 | 4 | This version of the Cerberus CAT code has the following features: 5 | 6 | - Compatible with Cerberus 2080 and Cerberus 2100 with a compile-time switch 7 | - A handful of compile-time configurations in config.h 8 | - Serial keyboard input over FTDI 9 | - Data upload over serial 10 | - 50hz NMI interrupt enabled when CPU is running 11 | - The selected CPU and FAST mode are saved in EEPROM 12 | - Two-way comms between CAT and CPUs 13 | - Tweaks to memory map 14 | - 0x0200: Outgoing mailbox flag (to CPU) 15 | - 0x0201: Outgoing mailbox data (to CPU) 16 | - 0x0202: Incoming mailbox flag (from CPU) 17 | - 0x0203: Incoming mailbox data (from CPU - word) 18 | - 0x0205: Code start 19 | - 0xEFFE: Expansion bus data (Cerberus 2100 only) 20 | - 0xEFFF: Expansion bus flag (Cerberus 2100 only) 21 | 22 | ### Data upload over Serial 23 | 24 | There is a sample Powershell script in the [Powershell](../powershell) folder that will upload bin files directly into the Cerberus RAM. 25 | 26 | ### Prerequisites 27 | 28 | This code requires a slightly modified version of Paul Stoffregen's PS2 Keyboard library, which is included in the source for your convenience within the src subfolder. 29 | 30 | ### Compiling 31 | 32 | The BIOS can be built as per the standard Cerberus BIOS. 33 | 34 | To compile for the Cerberus 2080, change the following line in config.h 35 | 36 | ``` 37 | #define config_board 1 // 0: Compile for Cerberus 2080, 1: Compile for Cerberus 2100 38 | ``` 39 | -------------------------------------------------------------------------------- /cat/cat.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Cerberus 2080/2100 CAT BIOS code 3 | // Author: Bernado Kastrup aka The Byte Attic 4 | // Copyright: 2020-2023 5 | // Updated By: Dean Belfield aka Break Into Program 6 | // Contributors: Aleksandr Sharikhin 7 | // Created: 31/07/2021 8 | // Last Updated: 09/10/2023 9 | // 10 | // For the ATmega328p-PU (CAT) to be compiled in the arduino IDE 11 | // All rights reserved, code distributed under license 12 | // 13 | // Provided AS-IS, without guarantees of any kind 14 | // This is NOT a commercial product as has not been exhaustively tested 15 | // 16 | // The directive "F()", which tells the compiler to put strings in code memory 17 | // instead of dynamic memory, is used as often as possible to save dynamic 18 | // memory, the latter being the bottleneck of this code. 19 | // 20 | // How to compile: 21 | // - Use minicore library(https://github.com/MCUdude/MiniCore) 22 | // - Select board ATmega328 23 | // - Clock: External 16MHz 24 | // - BOD: 4.3V 25 | // - Varian: 328PB 26 | 27 | // Modinfo: 28 | // 10/08/2021: All recognised keystrokes sent to mailbox, F12 now returns to BIOS, 50hz NMI starts when code runs 29 | // 11/08/2021: Memory map now defined in configs, moved code start to 0x0205 to accomodate inbox 30 | // 12/08/2021: Refactored load, save and delFile. Now handles incoming messages from Z80/6502 31 | // 21/08/2021: Tweaks for sound command, bug fixes in incoming message handler 32 | // 06/10/2021: Tweaks for cat command 33 | // 23/11/2021: Moved PS2Keyboard library from Arduino library to src subdirectory 34 | // 22/07/2023: Working on Cerberus 2100 (Aleksandr Sharikhin) 35 | // PS2 keyboard resets on start(more keyboards will work). 36 | // Optimizing loading splash screen. 37 | // Removed unused keymaps. 38 | // One command basic loading. Non blocking sound API. 39 | // Updated load command - returning how many bytes was actually read 40 | // 27/08/2023: EEPROM-based persistent settings for mode and CPU speed (Aleksandr Sharikhin) 41 | // 09/10/2023: Merged with Cerberus 2080 code with conditional compilation 42 | // 43 | 44 | // These libraries are built into the arduino IDE 45 | // 46 | #include 47 | #include 48 | #include 49 | 50 | // These need to be installed in the library manager 51 | // 52 | #include // v1.1.1 53 | 54 | // For more information about this PS2Keyboard library: 55 | // http://www.arduino.cc/playground/Main/PS2Keyboard 56 | // http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html 57 | // Note that the Arduino managed library is not the latest 58 | // Install from the GitHub project in order to build 59 | 60 | #include "src/PS2Keyboard/PS2Keyboard.h" 61 | 62 | // Status constants 63 | // 64 | #define STATUS_DEFAULT 0 65 | #define STATUS_BOOT 1 66 | #define STATUS_READY 2 67 | #define STATUS_UNKNOWN_COMMAND 3 68 | #define STATUS_NO_FILE 4 69 | #define STATUS_CANNOT_OPEN 5 70 | #define STATUS_MISSING_OPERAND 6 71 | #define STATUS_SCROLL_PROMPT 7 72 | #define STATUS_FILE_EXISTS 8 73 | #define STATUS_ADDRESS_ERROR 9 74 | #define STATUS_POWER 10 75 | #define STATUS_EOF 11 76 | 77 | // And all the internal includes 78 | // 79 | #include "config.h" 80 | #include "hardware.h" 81 | #include "fileio.h" 82 | 83 | // Now some stuff required by the libraries in use 84 | // 85 | const int chipSelect = CS; 86 | const int DataPin = KDAT; 87 | const int IRQpin = KCLK; 88 | 89 | const char * bannerFilename = "icon2080.img"; 90 | const char * helpFilename = "help.txt"; 91 | 92 | char editLine[38]; // Current edit line buffer 93 | char previousEditLine[38]; // Previous edit line buffer (allows for repeating previous command) 94 | 95 | volatile byte pos = 1; // Position in edit line currently occupied by cursor 96 | 97 | File cd; // Used by BASIC directory commands 98 | FileIO fileIO; // All the FileIO stuff is in here 99 | PS2Keyboard keyboard; 100 | Hardware cerberus(config_board); 101 | 102 | // Interrupt service routine attached to pin XIRQ (Cerberus 2100 only) 103 | // 104 | #if config_board == 1 105 | ISR(PCINT3_vect) { 106 | cerberus.expFlag = true; 107 | } 108 | #endif 109 | 110 | // CPU Interrupt Routine (50hz) 111 | // 112 | void cpuInterrupt(void) { 113 | if(cerberus.cpuRunning) { // Only run this code if cpu is running 114 | digitalWrite(CPUIRQ, HIGH); // Trigger an NMI interrupt 115 | digitalWrite(CPUIRQ, LOW); 116 | } 117 | cerberus.interruptFlag = true; 118 | } 119 | 120 | void setup() { 121 | cerberus.initialise(); // Initialise the board 122 | cerberus.resetCPU(); // Reset the CPUs 123 | clearEditLine(); // Clear the edit line buffers 124 | storePreviousLine(); 125 | Serial.begin(115200); // Initialise the serial library 126 | keyboard.begin(DataPin, IRQpin); // Initialise the keyboard library 127 | keyboard.send(0xFF); // Reset the keyboard 128 | 129 | // Now access uSD card and load character definitions so we can put something on the screen 130 | // 131 | if (!SD.begin(chipSelect)) { 132 | // SD Card has either failed or is not present 133 | // Since the character definitions thus can't be uploaded, accuse error with repeated tone and hang 134 | while(true) { 135 | beep(); 136 | delay(500); 137 | } 138 | } 139 | // Load character defs into memory 140 | // 141 | if(fileIO.loadFile("chardefs.bin", 0xf000) != STATUS_READY) { 142 | beep(); 143 | } 144 | 145 | // Now prepare the screen 146 | // 147 | cerberus.cls(); 148 | cprintFrames(); 149 | cprintBanner(); 150 | 151 | cprintStatus(STATUS_BOOT); 152 | #if config_silent == 0 153 | playJingle(); // Play a little jingle whilst the keyboard finishes initialising 154 | #endif 155 | delay(1000); 156 | cprintStatus(STATUS_DEFAULT); 157 | cprintEditLine(); 158 | } 159 | 160 | char readKey() { 161 | char ascii = 0; 162 | if(keyboard.available()) { 163 | ascii = keyboard.read(); 164 | tone(SOUND, 750, 5); // Clicking sound for auditive feedback to key presses 165 | #if config_dev_mode == 0 166 | if(!cerberus.cpuRunning) { 167 | cprintStatus(STATUS_DEFAULT); // Update status bar 168 | } 169 | #endif 170 | } 171 | else if(Serial.available()) { 172 | ascii = Serial.read(); 173 | } 174 | return ascii; 175 | } 176 | 177 | // The main loop 178 | // 179 | void loop() { 180 | char ascii = readKey(); // Stores ascii value of key pressed 181 | byte i; // Just a counter 182 | 183 | // Wait for a key to be pressed, then take it from there... 184 | // 185 | if(ascii > 0) { 186 | if(cerberus.cpuRunning) { 187 | if (ascii == PS2_F12) { // This happens if F12 has been pressed... and so on... 188 | stopCode(); 189 | } 190 | else { 191 | cerberus.cpuRunning = false; // Just stops interrupts from happening 192 | digitalWrite(CPUGO, LOW); // Pause the CPU and tristate its buses to high-Z 193 | byte mode = cerberus.peek(ptr_outbox_flag); 194 | cerberus.poke(ptr_outbox_data, byte(ascii)); // Put token code of pressed key in the CPU's mailbox, at ptr_outbox_data 195 | cerberus.poke(ptr_outbox_flag, byte(0x01)); // Flag that there is new mail for the CPU waiting at the mailbox 196 | digitalWrite(CPUGO, HIGH); // Let the CPU go 197 | cerberus.cpuRunning = true; 198 | #if config_enable_nmi == 0 199 | digitalWrite(CPUIRQ, HIGH); // Trigger an interrupt 200 | digitalWrite(CPUIRQ, LOW); 201 | #endif 202 | } 203 | } 204 | else { 205 | switch(ascii) { 206 | case PS2_ENTER: 207 | exec(&editLine[1]); // The first character is a ">" 208 | break; 209 | case PS2_UPARROW: 210 | pos = 0; 211 | for (i = 0; i < 38; i++) editLine[i] = previousEditLine[i]; 212 | cprintEditLine(); 213 | break; 214 | case PS2_DOWNARROW: 215 | clearEditLine(); 216 | break; 217 | case PS2_DELETE: 218 | case PS2_LEFTARROW: 219 | editLine[pos] = 32; // Put an empty space in current cursor position 220 | if (pos > 1) pos--; // Update cursor position, unless reached left-most position already 221 | editLine[pos] = 0; // Put cursor on updated position 222 | cprintEditLine(); // Print the updated edit line 223 | break; 224 | default: 225 | editLine[pos] = ascii; // Put new character in current cursor position 226 | if (pos < 37) pos++; // Update cursor position 227 | editLine[pos] = 0; // Place cursor to the right of new character 228 | #if config_dev_mode == 0 229 | cprintEditLine(); // Print the updated edit line 230 | #endif 231 | break; 232 | } 233 | } 234 | } 235 | if(cerberus.interruptFlag) { // If the interrupt flag is set then 236 | cerberus.interruptFlag = false; 237 | messageHandler(); // Run the inbox message handler 238 | } 239 | cerberus.xbusHandler(); 240 | } 241 | 242 | // Inbox message handler 243 | // 244 | void messageHandler(void) { 245 | int flag, status; 246 | byte retVal = 0x00; // Return status; default is OK 247 | unsigned int address; // Pointer for data 248 | 249 | if(cerberus.cpuRunning) { // Only run this code if cpu is running 250 | cerberus.cpuRunning = false; // Just to prevent interrupts from happening 251 | digitalWrite(CPUGO, LOW); // Pause the CPU and tristate its buses to high-Z 252 | flag = cerberus.peek(ptr_inbox_flag); // Fetch the inbox flag 253 | if(flag > 0 && flag < 0x80) { 254 | address = cerberus.peekWord(ptr_inbox_data); 255 | switch(flag) { 256 | case 0x01: 257 | cmdSound(address, true); 258 | break; 259 | case 0x02: 260 | status = cmdLoad(address); 261 | if(status != STATUS_READY) { 262 | retVal = (byte)(status + 0x80); 263 | } 264 | break; 265 | case 0x03: 266 | status = cmdSave(address); 267 | if(status != STATUS_READY) { 268 | retVal = (byte)(status + 0x80); 269 | } 270 | break; 271 | case 0x04: 272 | status = cmdDelFile(address); 273 | if(status != STATUS_READY) { 274 | retVal = (byte)(status + 0x80); 275 | } 276 | break; 277 | case 0x05: 278 | status = cmdCatOpen(address); 279 | if(status != STATUS_READY) { 280 | retVal = (byte)(status + 0x80); 281 | } 282 | break; 283 | case 0x06: 284 | status = cmdCatEntry(address); 285 | if(status != STATUS_READY) { 286 | retVal = (byte)(status + 0x80); 287 | } 288 | break; 289 | case 0x7E: 290 | cmdSound(address, false); 291 | status = STATUS_READY; 292 | break; 293 | case 0x7F: 294 | cerberus.reset(); 295 | break; 296 | } 297 | cerberus.poke(ptr_inbox_flag, retVal); // Flag we're done - values >= 0x80 are error codes 298 | } 299 | digitalWrite(CPUGO, HIGH); // Restart the CPU 300 | cerberus.cpuRunning = true; 301 | } 302 | } 303 | 304 | // Handle SOUND command from BASIC 305 | // 306 | void cmdSound(unsigned int address, bool blocking) { 307 | unsigned int frequency = cerberus.peekWord(address); 308 | unsigned int duration = cerberus.peekWord(address + 2) * 50; 309 | tone(SOUND, frequency, duration); 310 | if(blocking) { 311 | delay(duration); 312 | } 313 | } 314 | 315 | // Handle ERASE command from BASIC 316 | // 317 | int cmdDelFile(unsigned int address) { 318 | cerberus.peekString(address, (byte *)editLine, 38); 319 | return fileIO.deleteFile((char *)editLine); 320 | } 321 | 322 | // Handle LOAD command from BASIC 323 | // 324 | int cmdLoad(unsigned int address) { 325 | unsigned int startAddr = cerberus.peekWord(address); 326 | unsigned int length = cerberus.peekWord(address + 2); 327 | cerberus.peekString(address + 4, (byte *)editLine, 38); 328 | return fileIO.loadFile((char *)editLine, startAddr); 329 | } 330 | 331 | // Handle SAVE command from BASIC 332 | // 333 | int cmdSave(unsigned int address) { 334 | unsigned int startAddr = cerberus.peekWord(address); 335 | unsigned int length = cerberus.peekWord(address + 2); 336 | cerberus.peekString(address + 4, (byte *)editLine, 38); 337 | return fileIO.saveFile((char *)editLine, startAddr, startAddr + length - 1); 338 | } 339 | 340 | // Handle CAT command from BASIC 341 | // 342 | int cmdCatOpen(unsigned int address) { 343 | cd = SD.open("/"); // Start the process first by opening the directory 344 | return STATUS_READY; 345 | } 346 | 347 | int cmdCatEntry(unsigned int address) { // Subsequent calls to this will read the directory entries 348 | File entry; 349 | entry = cd.openNextFile(); // Open the next file 350 | if(!entry) { // If we've read past the last file in the directory 351 | cd.close(); // Then close the directory 352 | return STATUS_EOF; // And return end of file 353 | } 354 | cerberus.poke(address, entry.size()); // First four bytes are the length 355 | cerberus.poke(address + 4, entry.name()); // Followed by the filename, zero terminated 356 | entry.close(); // Close the directory entry 357 | return STATUS_READY; // Return READY 358 | } 359 | 360 | // Generic beep 361 | // 362 | void beep(void) { 363 | tone(SOUND, 50, 150); 364 | } 365 | 366 | // Get the next word from the command line 367 | // 368 | String getNextParam() { 369 | return String(strtok(NULL, " ")); 370 | } 371 | 372 | // Convert a hex string to a number 373 | // 374 | unsigned int fromHex(String s) { 375 | return strtol(s.c_str(), NULL, 16); 376 | } 377 | 378 | // Execute a command 379 | // Parameters: 380 | // - buffer: Pointer to a zero terminated string that contains the command with arguments 381 | // Returns: 382 | // 383 | void exec(char * buffer) { 384 | String param, response; 385 | unsigned int addr; 386 | byte data, chkA, chkB; 387 | 388 | char * ptr = buffer; 389 | 390 | ptr = strtok(buffer, " "); // Fetch the command (first parameter) 391 | if(ptr != NULL) { 392 | param = String(ptr); // Convert to a lower-case string 393 | param.toLowerCase(); 394 | 395 | if(param.startsWith("0x")) { 396 | chkA = 1; 397 | chkB = 0; 398 | param.remove(0, 2); // Strip the leading '0x' from the address 399 | response = param; // Start of the response 400 | addr = fromHex(param); // Convert the address string to an unsigned int 401 | param = getNextParam(); // Get the next byte 402 | while(param != "") { // For as long as the user as entered bytes 403 | if(param.charAt(0) != '#') { // Check for the terminator 404 | data = fromHex(param); // Convert the byte string to a byte 405 | while(cerberus.peek(addr) != data) { // Retry until written; serial comms may cause writes to be missed? 406 | cerberus.poke(addr, data); 407 | } 408 | chkA += data; // Checksums 409 | chkB += chkA; 410 | addr ++; 411 | } 412 | else { 413 | param.remove(0,1); // Check the checksum 414 | addr = fromHex(param); 415 | if( addr != ((chkA << 8) | chkB) ) { 416 | cprintString(28, 26, param); 417 | tone(SOUND, 50, 50); 418 | } 419 | } 420 | param = getNextParam(); // Fetch the next byte and repeat 421 | } 422 | #if config_dev_mode == 0 423 | cprintStatus(STATUS_READY); 424 | cprintString(28, 27, response); 425 | #endif 426 | Serial.print(response); 427 | Serial.print(' '); 428 | Serial.println((unsigned int)((chkA << 8) | chkB), HEX); 429 | } 430 | else if (param == F("cls")) { 431 | cls(); 432 | cprintStatus(STATUS_READY); 433 | } 434 | else if (param == F("6502")) { 435 | cerberus.setMode(0); 436 | cprintStatus(STATUS_READY); 437 | } 438 | else if (param == F("z80")) { 439 | cerberus.setMode(1); 440 | cprintStatus(STATUS_READY); 441 | } 442 | else if (param == F("reset")) { 443 | cerberus.reset(); 444 | } 445 | else if (param == F("fast")) { 446 | cerberus.setFast(1); 447 | cprintStatus(STATUS_READY); 448 | } 449 | else if (param == F("slow")) { 450 | cerberus.setFast(0); 451 | cprintStatus(STATUS_READY); 452 | } 453 | else if (param == F("run")) { 454 | storePreviousLine(); 455 | runCode(); 456 | } 457 | else if (param == F("testmem")) { 458 | cls(); 459 | cerberus.testMemory(); 460 | cprintStatus(STATUS_READY); 461 | } 462 | else if (param == F("list")) { 463 | cls(); 464 | list(getNextParam()); 465 | cprintStatus(STATUS_READY); 466 | } 467 | else if (param == F("move")) { 468 | String startAddress = getNextParam(); 469 | String endAddress = getNextParam(); 470 | String destAddress = getNextParam(); 471 | binMove(startAddress, endAddress, destAddress); 472 | } 473 | else if (param == F("basic6502")) { 474 | cerberus.mode = false; 475 | cerberus.cls(); 476 | digitalWrite(CPUSLC, LOW); 477 | loadFile("basic65.bin","", false); 478 | runCode(); 479 | } 480 | else if (param == F("basicz80")) { 481 | cerberus.mode = true; 482 | cerberus.cls(); 483 | digitalWrite(CPUSLC, HIGH); 484 | loadFile("basicz80.bin","", false); 485 | runCode(); 486 | } 487 | else if (param == F("dir")) { 488 | dir(); 489 | } 490 | else if (param == F("del")) { 491 | cprintStatus(fileIO.deleteFile(getNextParam())); 492 | } 493 | else if (param == F("load")) { 494 | String filename = getNextParam(); 495 | String startAddress = getNextParam(); 496 | loadFile(filename, startAddress, false); 497 | } 498 | else if (param == F("save")) { 499 | String startAddress = getNextParam(); 500 | String endAddress = getNextParam(); 501 | String filename = getNextParam(); 502 | saveFile(filename, startAddress, endAddress); 503 | } 504 | else if(param == F("help") || param == F("?")) { 505 | help(); 506 | } 507 | else { 508 | cprintStatus(STATUS_UNKNOWN_COMMAND); 509 | } 510 | } 511 | if (!cerberus.cpuRunning) { 512 | storePreviousLine(); 513 | clearEditLine(); 514 | } 515 | } 516 | 517 | void help() { 518 | byte y; 519 | 520 | cls(); 521 | cprintString(3, 2, F("Commands:")); 522 | y = cprintFileToScreen(3, 4, helpFilename); 523 | cprintString(3, y++, F("help / ?: Shows this help screen")); 524 | cprintString(3, y, F("F12 key: Quits CPU program")); 525 | } 526 | 527 | void binMove(String startAddr, String endAddr, String destAddr) { 528 | unsigned int start, finish, destination; // Memory addresses 529 | unsigned int i; // Address counter 530 | if (startAddr == "") cprintStatus(STATUS_MISSING_OPERAND); // Missing the filename 531 | else { 532 | start = fromHex(startAddr); // Convert hexadecimal address string to unsigned int 533 | if (endAddr == "") cprintStatus(STATUS_MISSING_OPERAND); // Missing the start 534 | else { 535 | finish = fromHex(endAddr); // Convert hexadecimal address string to unsigned int 536 | if (destAddr == "") cprintStatus(STATUS_MISSING_OPERAND); // Missing the destination 537 | else { 538 | destination = fromHex(destAddr); // Convert hexadecimal address string to unsigned int 539 | if (finish < start) cprintStatus(STATUS_ADDRESS_ERROR); // Invalid address range 540 | else if ((destination <= finish) && (destination >= start)) cprintStatus(STATUS_ADDRESS_ERROR); // Destination cannot be within original range 541 | else { 542 | for (i = start; i <= finish; i++) { 543 | cerberus.poke(destination, cerberus.peek(i)); 544 | destination++; 545 | } 546 | cprintStatus(STATUS_READY); 547 | } 548 | } 549 | } 550 | } 551 | } 552 | 553 | // Lists the contents of memory from the given address, in a compact format 554 | // 555 | void list(String address) { 556 | byte i, j; // Just counters 557 | unsigned int addr; // Memory address 558 | if (address == "") addr = 0; 559 | else addr = fromHex(address); // Convert hexadecimal address string to unsigned int 560 | for (i = 2; i < 25; i++) { 561 | cprintString(3, i, "0x"); 562 | cprintString(5, i, String(addr, HEX)); 563 | for (j = 0; j < 8; j++) { 564 | cprintString(12+(j*3), i, String(cerberus.peek(addr++), HEX)); /** Print bytes in HEX **/ 565 | } 566 | } 567 | } 568 | 569 | void runCode() { 570 | cerberus.cls(); 571 | cerberus.runCode(); 572 | } 573 | 574 | void stopCode() { 575 | cerberus.stopCode(); 576 | fileIO.loadFile("chardefs.bin", 0xf000); // Reset the character definitions in case the CPU changed them 577 | cerberus.cls(); // Clear screen completely 578 | cprintFrames(); // Reprint the wire frame in case the CPU code messed with it 579 | cprintBanner(); 580 | cprintStatus(STATUS_DEFAULT); // Update status bar 581 | clearEditLine(); // Clear and display the edit line 582 | } 583 | 584 | // Lists the files in the root directory of uSD card, if available 585 | // 586 | void dir() { 587 | byte y = 2; // Screen line 588 | byte x = 0; // Screen column 589 | File root; // Root directory of uSD card 590 | File entry; // A file on the uSD card 591 | cls(); 592 | root = SD.open("/"); // Go to the root directory of uSD card 593 | while (true) { 594 | entry = root.openNextFile(); // Open next file 595 | if (!entry) { // No more files on the uSD card 596 | root.close(); // Close root directory 597 | cprintStatus(STATUS_READY); // Announce completion 598 | break; // Get out of this otherwise infinite while() loop 599 | } 600 | cprintString(3, y, entry.name()); 601 | cprintString(20, y, String(entry.size(), DEC)); 602 | entry.close(); // Close file as soon as it is no longer needed 603 | if (y < 24) y++; // Go to the next screen line 604 | else { 605 | cprintStatus(STATUS_SCROLL_PROMPT); // End of screen has been reached, needs to scroll down 606 | for (x = 2; x < 40; x++) cprintChar(x, 29, ' '); // Hide editline while waiting for key press 607 | while (!keyboard.available()); // Wait for a key to be pressed 608 | if (keyboard.read() == PS2_ESC) { // If the user pressed ESC, break and exit 609 | tone(SOUND, 750, 5); // Clicking sound for auditive feedback to key press 610 | root.close(); // Close the directory before exiting 611 | cprintStatus(STATUS_READY); 612 | break; 613 | } else { 614 | tone(SOUND, 750, 5); // Clicking sound for auditive feedback to key press 615 | cls(); // Clear the screen and... 616 | y = 2; // ...go back to the top of the screen 617 | } 618 | } 619 | } 620 | } 621 | 622 | void saveFile(String filename, String startAddress, String endAddress) { 623 | unsigned int startAddr; 624 | unsigned int endAddr; 625 | int status = STATUS_DEFAULT; 626 | if (startAddress == "") { 627 | status = STATUS_MISSING_OPERAND; 628 | } 629 | else { 630 | startAddr = fromHex(startAddress); 631 | if(endAddress == "") { 632 | status = STATUS_MISSING_OPERAND; 633 | } 634 | else { 635 | endAddr = fromHex(endAddress); 636 | status = fileIO.saveFile(filename, startAddr, endAddr); 637 | } 638 | } 639 | cprintStatus(status); 640 | } 641 | 642 | void loadFile(String filename, String startAddress, bool silent) { 643 | unsigned int startAddr; 644 | int status = STATUS_DEFAULT; 645 | 646 | if (startAddress == "") { 647 | startAddr = ptr_code_start; // If not otherwise specified, load file into start of code area 648 | } 649 | else { 650 | startAddr = fromHex(startAddress); // Convert address string to hexadecimal number 651 | } 652 | status = fileIO.loadFile(filename, startAddr); 653 | if(!silent) { 654 | cprintStatus(status); 655 | } 656 | } 657 | 658 | void cprintEditLine () { 659 | byte i; 660 | for (i = 0; i < 38; i++) cprintChar(i + 2, 29, editLine[i]); 661 | } 662 | 663 | // Resets the contents of edit line and reprints it 664 | // 665 | void clearEditLine() { 666 | byte i; 667 | editLine[0] = 62; 668 | editLine[1] = 0; 669 | for (i = 2; i < 38; i++) editLine[i] = 32; 670 | pos = 1; 671 | cprintEditLine(); 672 | } 673 | 674 | // Store edit line just executed 675 | // 676 | void storePreviousLine() { 677 | for (byte i = 0; i < 38; i++) previousEditLine[i] = editLine[i]; 678 | } 679 | 680 | // Print out a status from a code 681 | // REMEMBER: The macro "F()" simply tells the compiler to put the string in code memory, so to save dynamic memory 682 | // 683 | void cprintStatus(byte status) { 684 | switch( status ) { 685 | case STATUS_BOOT: 686 | center(F("Here we go! Hang on...")); 687 | break; 688 | case STATUS_READY: 689 | center(F("Alright, done!")); 690 | break; 691 | case STATUS_UNKNOWN_COMMAND: 692 | center(F("Darn, unrecognized command")); 693 | beep(); 694 | break; 695 | case STATUS_NO_FILE: 696 | center(F("Oops, file doesn't seem to exist")); 697 | beep(); 698 | break; 699 | case STATUS_CANNOT_OPEN: 700 | center(F("Oops, couldn't open the file")); 701 | beep(); 702 | break; 703 | case STATUS_MISSING_OPERAND: 704 | center(F("Oops, missing an operand!!")); 705 | beep(); 706 | break; 707 | case STATUS_SCROLL_PROMPT: 708 | center(F("Press a key to scroll, ESC to stop")); 709 | break; 710 | case STATUS_FILE_EXISTS: 711 | center(F("The file already exists!")); 712 | break; 713 | case STATUS_ADDRESS_ERROR: 714 | center(F("Oops, invalid address range!")); 715 | break; 716 | case STATUS_POWER: 717 | center(F("Feel the power of Dutch design!!")); 718 | break; 719 | default: 720 | #if config_board == 0 721 | cprintString(2, 27, F(" CERBERUS 2080: ")); 722 | #else 723 | cprintString(2, 27, F(" CERBERUS 2100: ")); 724 | #endif 725 | cprintString(23, 27, cerberus.mode ? F(" Z80, ") : F("6502, ")); 726 | cprintString(29, 27, cerberus.fast ? F("8 MHz") : F("4 MHz")); 727 | cprintString(34, 27, F(" ")); 728 | } 729 | } 730 | 731 | void center(String text) { 732 | clearLine(27); 733 | cprintString(2+(38-text.length())/2, 27, text); 734 | } 735 | 736 | void playJingle() { 737 | unsigned int i; 738 | unsigned int f[6] = { 261, 277, 261, 349, 261, 349 }; 739 | unsigned int d[6] = { 50, 50, 50, 500, 50, 900 }; 740 | 741 | delay(350); // Wait for possible preceding keyboard click to end 742 | for(i = 0; i < 6; i++) { 743 | delay(150); 744 | tone(SOUND, f[i], d[i]); 745 | } 746 | } 747 | 748 | // This clears the screen only WITHIN the main frame 749 | // 750 | void cls() { 751 | unsigned int y; 752 | for (y = 2; y <= 25; y++) { 753 | clearLine(y); 754 | } 755 | } 756 | 757 | void clearLine(byte y) { 758 | unsigned int x; 759 | for (x = 2; x <= 39; x++) { 760 | cprintChar(x, y, 32); 761 | } 762 | } 763 | 764 | void cprintFrames() { 765 | unsigned int x; 766 | unsigned int y; 767 | 768 | for (x = 2; x <= 39; x++) { // First print horizontal bars 769 | cprintChar(x, 1, 3); 770 | cprintChar(x, 30, 131); 771 | cprintChar(x, 26, 3); 772 | } 773 | 774 | for (y = 1; y <= 30; y++) { // Now print vertical bars 775 | cprintChar(1, y, 133); 776 | cprintChar(40, y, 5); 777 | } 778 | } 779 | 780 | // Dump a file to screen 781 | // 782 | byte cprintFileToScreen(byte x, byte y, String filename) { 783 | byte px = x; 784 | byte py = y; 785 | byte b; 786 | 787 | if(SD.exists(filename)) { 788 | File f = SD.open(filename); 789 | if (f) { 790 | while(f.available()) { 791 | b = f.read(); 792 | switch(b) { 793 | case 0x0A: 794 | px = x; 795 | py++; 796 | break; 797 | default: 798 | cprintChar(px++, py, b); 799 | break; 800 | } 801 | } 802 | f.close(); 803 | } 804 | } 805 | return py; 806 | } 807 | 808 | // Load the CERBERUS icon image on the screen 809 | // 810 | void cprintBanner() { 811 | cprintFileToScreen(2, 2, bannerFilename); 812 | } 813 | 814 | void cprintString(byte x, byte y, String text) { 815 | unsigned int i; 816 | for (i = 0; i < text.length(); i++) { 817 | if (((x + i) > 1) && ((x + i) < 40)) { 818 | cprintChar(x + i, y, text[i]); 819 | } 820 | } 821 | } 822 | 823 | void cprintChar(byte x, byte y, byte token) { 824 | unsigned int address = 0xF800 + ((y - 1) * 40) + (x - 1); // Video memory addresses start at 0XF800 825 | cerberus.poke(address, token); 826 | } 827 | -------------------------------------------------------------------------------- /cat/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | // Compilation defaults 5 | // 6 | #define config_board 1 // 0: Compile for Cerberus 2080, 1: Compile for Cerberus 2100 7 | #define config_dev_mode 0 // Turn off various BIOS outputs to speed up development, specifically uploading code 8 | #define config_silent 0 // Turn off the startup jingle 9 | #define config_enable_nmi 1 // Turn on the 50hz NMI timer when CPU is running. If set to 0 will only trigger an NMI on keypress 10 | #define config_default_mode 1 // 0: 6502, 1: Z80 11 | #define config_default_fast 1 // 0: 4mhz, 1: 8mhz 12 | 13 | #define ptr_outbox_flag 0x0200 // Outbox flag memory location (byte) 14 | #define ptr_outbox_data 0x0201 // Outbox data memory location (byte) 15 | #define ptr_inbox_flag 0x0202 // Inbox flag memory location (byte) 16 | #define ptr_inbox_data 0x0203 // Inbox data memory location (word) 17 | #define ptr_code_start 0x0205 // Default start location of code 18 | #define ptr_xbus_data 0xEFFE // Expansion bus data memory location (byte) 19 | #define ptr_xbus_flag 0xEFFF // Expansion bus flag memory location (byte) 20 | 21 | #define config_eeprom_address_mode 0 // First EEPROM location 22 | #define config_eeprom_address_fast 1 // Second EEPROM location 23 | 24 | // Arduino ATMega328 Aliases 25 | // 26 | // NB: MISO, MOSI and SCK for SD Card are hardwired in CAT: 27 | // CLK -> pin 19 on CAT 28 | // MISO -> pin 18 on CAT 29 | // MOSI -> pin 17 on CAT 30 | // 31 | #define SI A5 // Serial Input, pin 28 on CAT 32 | #define SO A4 // Serial Output, pin 27 on CAT 33 | #define SC A3 // Shift Clock, pin 26 on CAT 34 | #define AOE A2 // Address Output Enable, pin 25 on CAT 35 | #define RW A1 // Memory Read/!Write, pin 24 on CAT 36 | #define LD A0 // Latch Data, pin 23 on CAT 37 | #define CPUSLC 5 // CPU SeLeCt, pin 11 on CAT 38 | #define CPUIRQ 6 // CPU Interrupt ReQuest, pin 12 on CAT 39 | #define CPUGO 7 // CPU Go/!Halt, pin 13 on CAT 40 | #define CPURST 8 // CPU ReSeT, pin 14 on CAT 41 | #define CPUSPD 9 // CPU SPeeD, pin 15 on CAT 42 | #define KCLK 2 // CLK pin connected to PS/2 keyboard (CAT pin 4) 43 | #define KDAT 3 // DATA pin connected to PS/2 keyboard (CAT pin 5) 44 | #define SOUND 4 // Sound output to buzzer, pin 6 on CAT 45 | #define CS 10 // Chip Select for SD Card, pin 16 on CAT 46 | #if config_board == 1 47 | #define FREE 25 // Bit 2 of CPU CLocK Speed, pin 19 on FAT-CAT 48 | #define XBUSACK 23 // eXpansion BUS ACKnowledgment, pin 3 on FAT-CAT, active LOW 49 | #define XBUSREQ 24 // eXpansion BUS REQuest, pin 6 on FAT-CAT, active LOW 50 | #define XIRQ 26 // eXpansion Interrupt ReQuest, pin 22 on FAT-CAT, active LOW 51 | #endif 52 | 53 | #endif // CONFIG_H -------------------------------------------------------------------------------- /cat/fileio.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEIO_H 2 | #define FILEIO_H 3 | 4 | extern Hardware cerberus; 5 | 6 | class FileIO { 7 | public: 8 | FileIO() {}; 9 | 10 | int loadFile(String filename, unsigned int startAddr); 11 | int saveFile(String filename, unsigned int startAddress, unsigned int endAddress); 12 | int deleteFile(String filename); 13 | }; 14 | 15 | // Loads a binary file from the uSD card into memory 16 | // 17 | int FileIO::loadFile(String filename, unsigned int startAddr) { 18 | File dataFile; // File for reading from on SD Card, if present 19 | unsigned int addr = startAddr; // Address where to load the file into memory 20 | int status = STATUS_DEFAULT; 21 | 22 | if (filename == "") { 23 | status = STATUS_MISSING_OPERAND; 24 | } 25 | else { 26 | if (!SD.exists(filename)) { 27 | status = STATUS_NO_FILE; // The file does not exist, so stop with error 28 | } 29 | else { 30 | dataFile = SD.open(filename); // Open the binary file 31 | if (!dataFile) { 32 | status = STATUS_CANNOT_OPEN; // Cannot open the file 33 | } 34 | else { 35 | while (dataFile.available()) { // While there is data to be read.. 36 | cerberus.poke(addr++, byte(dataFile.read())); // Read data from file and store it in memory 37 | if (addr == 0) { // Break if address wraps around to the start of memory 38 | dataFile.close(); 39 | break; 40 | } 41 | } 42 | dataFile.close(); 43 | status = STATUS_READY; 44 | } 45 | } 46 | } 47 | return status; 48 | } 49 | 50 | // Saves contents of a region of memory to a file on uSD card 51 | // 52 | int FileIO::saveFile(String filename, unsigned int startAddress, unsigned int endAddress) { 53 | int status = STATUS_DEFAULT; 54 | unsigned int i; // Memory address counter 55 | byte data; // Data from memory 56 | File dataFile; // File to be created and written to 57 | if (endAddress < startAddress) { 58 | status = STATUS_ADDRESS_ERROR; // Invalid address range 59 | } 60 | else { 61 | if (filename == "") { 62 | status = STATUS_MISSING_OPERAND; // Missing the file's name 63 | } 64 | else { 65 | if (SD.exists(filename)) { 66 | status = STATUS_FILE_EXISTS; // The file already exists, so stop with error 67 | } 68 | else { 69 | dataFile = SD.open(filename, FILE_WRITE); // Try to create the file 70 | if (!dataFile) { 71 | status = STATUS_CANNOT_OPEN; // Cannot create the file 72 | } 73 | else { // Now we can finally write into the created file 74 | for(i = startAddress; i <= endAddress; i++) { 75 | data = cerberus.peek(i); 76 | dataFile.write(data); 77 | } 78 | dataFile.close(); 79 | status = STATUS_READY; 80 | } 81 | } 82 | } 83 | } 84 | return status; 85 | } 86 | 87 | // Deletes a file from the uSD card 88 | // 89 | int FileIO::deleteFile(String filename) { 90 | int status = STATUS_DEFAULT; 91 | if (!SD.exists(filename)) { 92 | status = STATUS_NO_FILE; // The file doesn't exist, so stop with error 93 | } 94 | else { 95 | SD.remove(filename); // Delete the file 96 | status = STATUS_READY; 97 | } 98 | return status; 99 | } 100 | 101 | #endif // FILEIO_H 102 | -------------------------------------------------------------------------------- /cat/hardware.h: -------------------------------------------------------------------------------- 1 | #ifndef HARDWARE_H 2 | #define HARDWARE_H 3 | 4 | extern void cpuInterrupt(void); 5 | 6 | class Hardware { 7 | public: 8 | Hardware(int boardRevision) : 9 | boardRevision(boardRevision) 10 | { 11 | cpuRunning = false; 12 | interruptFlag = false; 13 | expFlag = false; 14 | }; 15 | 16 | void initialise(); 17 | void resetCPU(); 18 | void stopCode(); 19 | void runCode(); 20 | void xbusHandler(); 21 | void testMemory(); 22 | 23 | void setMode(bool mode); 24 | void setFast(bool fast); 25 | 26 | void cls(); 27 | 28 | void poke(unsigned int address, byte data); 29 | void poke(unsigned int address, unsigned int data); 30 | void poke(unsigned int address, unsigned long data); 31 | boolean poke(unsigned int address, String text); 32 | byte peek(unsigned int address); 33 | unsigned int peekWord(unsigned int address); 34 | boolean peekString(unsigned int address, byte * dest, int max); 35 | 36 | void(* reset)(void) = 0; 37 | 38 | volatile bool mode; 39 | volatile bool fast; 40 | volatile bool cpuRunning; 41 | volatile bool interruptFlag; 42 | volatile bool expFlag; 43 | 44 | private: 45 | void resetCPU(bool cpuslc); 46 | unsigned int addressTranslate (unsigned int virtualAddress); 47 | uint8_t readSetting(int idx, uint8_t bound_low, uint8_t bound_high, uint8_t default_value); 48 | byte readShiftRegister(); 49 | void setShiftRegister(unsigned int address, byte data); 50 | 51 | int boardRevision; 52 | }; 53 | 54 | void Hardware::initialise() { 55 | // Read the default settings from EEPROM 56 | // 57 | mode = this->readSetting(config_eeprom_address_mode, 0, 1, config_default_mode); 58 | fast = this->readSetting(config_eeprom_address_fast, 0, 1, config_default_fast); 59 | 60 | // Declare the pins 61 | // 62 | pinMode(SO, OUTPUT); 63 | pinMode(SI, INPUT); // There will be pull-up and pull-down resistors in circuit 64 | pinMode(SC, OUTPUT); 65 | pinMode(AOE, OUTPUT); 66 | pinMode(LD, OUTPUT); 67 | pinMode(RW, OUTPUT); 68 | pinMode(CPUSPD, OUTPUT); 69 | pinMode(KCLK, INPUT_PULLUP); // But here we need CAT's internal pull-up resistor 70 | pinMode(KDAT, INPUT_PULLUP); // And here too 71 | pinMode(CPUSLC, OUTPUT); 72 | pinMode(CPUIRQ, OUTPUT); 73 | pinMode(CPUGO, OUTPUT); 74 | pinMode(CPURST, OUTPUT); 75 | pinMode(SOUND, OUTPUT); 76 | 77 | // Write default values to some of the output pins 78 | // 79 | digitalWrite(RW, HIGH); 80 | digitalWrite(SO, LOW); 81 | digitalWrite(AOE, LOW); 82 | digitalWrite(LD, LOW); 83 | digitalWrite(SC, LOW); 84 | digitalWrite(CPUSPD, fast); 85 | digitalWrite(CPUSLC, mode); 86 | digitalWrite(CPUIRQ, LOW); 87 | digitalWrite(CPUGO, LOW); 88 | digitalWrite(CPURST, LOW); 89 | 90 | // Attach an interrupt to XIRQ so to react to the expansion card timely (Cerberus 2100 only) 91 | // 92 | if(boardRevision == 1) { 93 | pinMode(XBUSACK, OUTPUT); 94 | digitalWrite(XBUSACK, HIGH); 95 | cli(); 96 | PCICR |= 0b00001000; // Enables Port E Pin Change Interrupts 97 | PCMSK3 |= 0b00001000; // Enable Pin Change Interrupt on XIRQ 98 | sei(); 99 | } 100 | } 101 | 102 | // Reset both CPUs 103 | // 104 | void Hardware::resetCPU() { 105 | digitalWrite(CPURST, LOW); 106 | resetCPU(LOW); // Reset the 6502 107 | resetCPU(HIGH); // Reset the Z80 108 | if(!mode) { // Select the correct chip for the mode 109 | digitalWrite(CPUSLC, LOW); 110 | } 111 | } 112 | 113 | void Hardware::resetCPU(bool cpuslc) { 114 | digitalWrite(CPUSLC, cpuslc); 115 | digitalWrite(CPUGO, HIGH); 116 | delay(50); 117 | digitalWrite(CPURST, HIGH); 118 | digitalWrite(CPUGO, LOW); 119 | delay(50); 120 | digitalWrite(CPURST, LOW); 121 | } 122 | 123 | // The XBUS handler (Cerberus 2100 only) 124 | // 125 | void Hardware::xbusHandler() { 126 | if(boardRevision < 1) { 127 | return; 128 | } 129 | // Now we deal with bus access requests from the expansion card 130 | // 131 | if(digitalRead(XBUSREQ) == LOW) { // The expansion card is requesting bus access... 132 | if(cpuRunning) { // If a CPU is running (the internal buses being therefore not tristated)... 133 | digitalWrite(CPUGO, LOW); // ...first pause the CPU and tristate the buses... 134 | digitalWrite(XBUSACK, LOW); // ...then acknowledge request; buses are now under the control of the expansion card 135 | while (digitalRead(XBUSREQ) == LOW); // Wait until the expansion card is done... 136 | digitalWrite(XBUSACK, HIGH); // ...then let the expansion card know that the buses are no longer available to it 137 | digitalWrite(CPUGO, HIGH); // Finally, let the CPU run again 138 | } else { // If a CPU is NOT running... 139 | digitalWrite(XBUSACK, LOW); // Acknowledge request; buses are now under the control of the expansion card 140 | while (digitalRead(XBUSREQ) == LOW); // Wait until the expansion card is done... 141 | digitalWrite(XBUSACK, HIGH); // ...then let the expansion card know that the buses are no longer available to it 142 | } 143 | } 144 | // And finally, deal with the expansion flag (which will be 'true' if there has been an XIRQ strobe from an expansion card) 145 | // 146 | if(expFlag) { 147 | if(cpuRunning) { 148 | digitalWrite(CPUGO, LOW); // Pause the CPU and tristate its buses **/ 149 | poke(ptr_xbus_flag, byte(0x01)); // Flag that there is data from the expansion card waiting for the CPU in memory 150 | digitalWrite(CPUGO, HIGH); // Let the CPU go 151 | digitalWrite(CPUIRQ, HIGH); // Trigger an interrupt so the CPU knows there's data waiting for it in memory 152 | digitalWrite(CPUIRQ, LOW); 153 | } 154 | expFlag = false; // Reset the flag 155 | } 156 | } 157 | 158 | void Hardware::stopCode() { 159 | cpuRunning = false; // Reset this flag 160 | Timer1.detachInterrupt(); // Stop the interrupt timer 161 | digitalWrite(CPURST, HIGH); // Reset the CPU to bring its output signals back to original states 162 | digitalWrite(CPUGO, LOW); // Tristate its buses to high-Z 163 | delay(50); // Give it some time 164 | digitalWrite(CPURST, LOW); // Finish reset cycle 165 | } 166 | 167 | void Hardware::runCode() { 168 | byte runL = ptr_code_start & 0xFF; 169 | byte runH = ptr_code_start >> 8; 170 | 171 | // NB: 172 | // - Byte at ptr_outbox_flag is the new mail flag 173 | // - Byte at ptr_outbox_data is the mail box 174 | // 175 | poke(ptr_outbox_flag, byte(0x00)); // Reset outbox mail flag 176 | poke(ptr_outbox_data, byte(0x00)); // Reset outbox mail data 177 | poke(ptr_inbox_flag, byte(0x00)); // Reset inbox mail flag 178 | 179 | if(!mode) { // 6502 mode 180 | poke(0xFFFA, byte(0xB0)); 181 | poke(0xFFFB, byte(0xFC)); 182 | poke(0xFCB0, byte(0x40)); // The interrupt service routine simply returns FCB0: RTI (40) 183 | poke(0xFFFC, runL); // Set reset vector to the beginning of the code area 184 | poke(0xFFFD, runH); 185 | } 186 | else { // Z80 mode 187 | poke(0x0066, byte(0xED)); // The NMI service routine of the Z80 simply returns 0066: RETN (ED 45) 188 | poke(0x0067, byte(0x45)); 189 | #if ptr_code_start != 0x0000 190 | poke(0x0000, byte(0xC3)); // The Z80 fetches the first instruction from 0x0000, so put a jump to the code area there (C3 ll hh) 191 | poke(0x0001, runL); 192 | poke(0x0002, runH); 193 | #endif 194 | } 195 | cpuRunning = true; 196 | digitalWrite(CPURST, HIGH); // Reset the CPU 197 | digitalWrite(CPUGO, HIGH); // Enable CPU buses and clock 198 | delay(50); 199 | digitalWrite(CPURST, LOW); // CPU should now initialize and then go to its reset vector 200 | 201 | #if config_enable_nmi == 1 202 | Timer1.initialize(20000); 203 | Timer1.attachInterrupt(cpuInterrupt); // Interrupt every 0.02 seconds - 50Hz 204 | #endif 205 | } 206 | 207 | // Read a setting from the EEPROM 208 | // 209 | uint8_t Hardware::readSetting(int idx, uint8_t bound_low, uint8_t bound_high, uint8_t default_value) { 210 | uint8_t b = EEPROM.read(idx); 211 | if(b < bound_low || b > bound_high) { 212 | b = default_value; 213 | EEPROM.write(idx, b); 214 | } 215 | return b; 216 | } 217 | 218 | unsigned int Hardware::addressTranslate (unsigned int virtualAddress) { 219 | byte numberVirtualRows; 220 | numberVirtualRows = (virtualAddress - 0xF800) / 38; 221 | return((virtualAddress + 43) + (2 * (numberVirtualRows - 1))); 222 | } 223 | 224 | byte Hardware::readShiftRegister() { 225 | return shiftIn(SI, SC, MSBFIRST); 226 | } 227 | 228 | void Hardware::setShiftRegister(unsigned int address, byte data) { 229 | shiftOut(SO, SC, LSBFIRST, address); // First 8 bits of address 230 | shiftOut(SO, SC, LSBFIRST, address >> 8); // Then the remaining 8 bits 231 | shiftOut(SO, SC, LSBFIRST, data); // Finally, a byte of data 232 | } 233 | 234 | void Hardware::poke(unsigned int address, byte data) { 235 | setShiftRegister(address, data); 236 | digitalWrite(AOE, HIGH); // Enable address onto bus 237 | digitalWrite(RW, LOW); // Begin writing 238 | digitalWrite(RW, HIGH); // Finish up 239 | digitalWrite(AOE, LOW); 240 | } 241 | 242 | void Hardware::poke(unsigned int address, unsigned int data) { 243 | poke(address, byte(data & 0xFF)); 244 | poke(address + 1, byte((data >> 8) & 0xFF)); 245 | } 246 | 247 | void Hardware::poke(unsigned int address, unsigned long data) { 248 | poke(address, byte(data & 0xFF)); 249 | poke(address + 1, byte((data >> 8) & 0xFF)); 250 | poke(address + 2, byte((data >> 16) & 0xFF)); 251 | poke(address + 3, byte((data >> 24) & 0xFF)); 252 | } 253 | 254 | boolean Hardware::poke(unsigned int address, String text) { 255 | unsigned int i; 256 | for(i = 0; i < text.length(); i++) { 257 | poke(address + i, byte(text[i])); 258 | } 259 | poke(address + i, byte(0)); 260 | return true; 261 | } 262 | 263 | byte Hardware::peek(unsigned int address) { 264 | byte data = 0; 265 | setShiftRegister(address, data); 266 | digitalWrite(AOE, HIGH); // Enable address onto us 267 | // 268 | // This time we do NOT enable the data outputs of the shift register, as we are reading 269 | // 270 | digitalWrite(LD, HIGH); // Prepare to latch byte from data bus into shift register 271 | digitalWrite(SC, HIGH); // Now the clock tics, so the byte is actually latched 272 | digitalWrite(LD, LOW); 273 | digitalWrite(AOE, LOW); 274 | data = readShiftRegister(); 275 | return data; 276 | } 277 | 278 | unsigned int Hardware::peekWord(unsigned int address) { 279 | return peek(address) + (256 * peek(address+1)); 280 | } 281 | 282 | boolean Hardware::peekString(unsigned int address, byte * dest, int max) { 283 | unsigned int i; 284 | byte c; 285 | for(i = 0; i < max; i++) { 286 | c = peek(address + i); 287 | dest[i] = c; 288 | if(c == 0) return true; 289 | } 290 | return false; 291 | } 292 | 293 | // Tests that all four memories are accessible for reading and writing 294 | // 295 | void Hardware::testMemory() { 296 | unsigned int x; 297 | byte i = 0; 298 | for (x = 0; x < 874; x++) { 299 | poke(x, i); // Write to low memory **/ 300 | poke(0x8000 + x, peek(x)); // Read from low memory and write to high memory 301 | poke(addressTranslate(0xF800 + x), peek(0x8000 + x)); // Read from high mem, write to VMEM, read from character mem 302 | if (i < 255) i++; 303 | else i = 0; 304 | } 305 | } 306 | 307 | void Hardware::setMode(bool value) { 308 | mode = value; 309 | EEPROM.write(config_eeprom_address_mode, mode); 310 | digitalWrite(CPUSLC, mode); 311 | } 312 | 313 | void Hardware::setFast(bool value) { 314 | fast = value; 315 | EEPROM.write(config_eeprom_address_fast, fast); 316 | digitalWrite(CPUSPD, fast); 317 | } 318 | 319 | // This clears the entire screen 320 | // 321 | void Hardware::cls() { 322 | unsigned int x; 323 | for (x = 0; x < 1200; x++) { 324 | poke(0xF800 + x, byte(32)); // Video memory addresses start at 0XF800 325 | } 326 | } 327 | 328 | #endif // HARDWARE_H -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/PS2Keyboard.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | PS2Keyboard.cpp - PS2Keyboard library 3 | Copyright (c) 2007 Free Software Foundation. All right reserved. 4 | Written by Christian Weichel 5 | 6 | ** Mostly rewritten Paul Stoffregen 2010, 2011 7 | ** Modified for use beginning with Arduino 13 by L. Abraham Smith, * 8 | ** Modified for easy interrup pin assignement on method begin(datapin,irq_pin). Cuningan ** 9 | 10 | for more information you can read the original wiki in arduino.cc 11 | at http://www.arduino.cc/playground/Main/PS2Keyboard 12 | or http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html 13 | 14 | Version 2.4 (March 2013) 15 | - Support Teensy 3.0, Arduino Due, Arduino Leonardo & other boards 16 | - French keyboard layout, David Chochoi, tchoyyfr at yahoo dot fr 17 | 18 | Version 2.3 (October 2011) 19 | - Minor bugs fixed 20 | 21 | Version 2.2 (August 2011) 22 | - Support non-US keyboards - thanks to Rainer Bruch for a German keyboard :) 23 | 24 | Version 2.1 (May 2011) 25 | - timeout to recover from misaligned input 26 | - compatibility with Arduino "new-extension" branch 27 | - TODO: send function, proposed by Scott Penrose, scooterda at me dot com 28 | 29 | Version 2.0 (June 2010) 30 | - Buffering added, many scan codes can be captured without data loss 31 | if your sketch is busy doing other work 32 | - Shift keys supported, completely rewritten scan code to ascii 33 | - Slow linear search replaced with fast indexed table lookups 34 | - Support for Teensy, Arduino Mega, and Sanguino added 35 | 36 | This library is free software; you can redistribute it and/or 37 | modify it under the terms of the GNU Lesser General Public 38 | License as published by the Free Software Foundation; either 39 | version 2.1 of the License, or (at your option) any later version. 40 | 41 | This library is distributed in the hope that it will be useful, 42 | but WITHOUT ANY WARRANTY; without even the implied warranty of 43 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 44 | Lesser General Public License for more details. 45 | 46 | You should have received a copy of the GNU Lesser General Public 47 | License along with this library; if not, write to the Free Software 48 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 49 | */ 50 | 51 | #include "PS2Keyboard.h" 52 | 53 | #define BUFFER_SIZE 45 54 | static volatile uint8_t buffer[BUFFER_SIZE]; 55 | static volatile uint8_t head, tail; 56 | static uint8_t DataPin; 57 | static uint8_t IrqPin; 58 | static uint8_t CharBuffer=0; 59 | static uint8_t UTF8next=0; 60 | static const PS2Keymap_t *keymap=NULL; 61 | 62 | static volatile uint8_t Sending=0; 63 | static volatile uint8_t Receiving=0; 64 | static volatile uint8_t Ack=0; 65 | static volatile uint8_t Parity=0; 66 | static volatile uint8_t Outgoing=0; 67 | static volatile bool Caps; 68 | 69 | static inline bool is_scan_code_avail(void) { 70 | return (tail != head); 71 | } 72 | 73 | static inline uint8_t get_scan_code(void) { 74 | uint8_t c, i; 75 | 76 | i = tail; 77 | if (i == head) return 0; 78 | i++; 79 | if (i >= BUFFER_SIZE) i = 0; 80 | c = buffer[i]; 81 | tail = i; 82 | return c; 83 | } 84 | 85 | static inline char toUpper(char c) { 86 | if (c>='a' && c<='z') c &= 0xdf; 87 | 88 | return c; 89 | } 90 | 91 | // http://www.quadibloc.com/comp/scan.htm 92 | // http://www.computer-engineering.org/ps2keyboard/scancodes2.html 93 | 94 | // These arrays provide a simple key map, to turn scan codes into ISO8859 95 | // output. If a non-US keyboard is used, these may need to be modified 96 | // for the desired output. 97 | // 98 | 99 | const PROGMEM PS2Keymap_t PS2Keymap_US = { 100 | // without shift 101 | {0, PS2_F9, 0, PS2_F5, PS2_F3, PS2_F1, PS2_F2, PS2_F12, 102 | 0, PS2_F10, PS2_F8, PS2_F6, PS2_F4, PS2_TAB, '`', 0, 103 | 0, 0 /*Lalt*/, 0 /*Lshift*/, 0, 0 /*Lctrl*/, 'q', '1', 0, 104 | 0, 0, 'z', 's', 'a', 'w', '2', 0, 105 | 0, 'c', 'x', 'd', 'e', '4', '3', 0, 106 | 0, ' ', 'v', 'f', 't', 'r', '5', 0, 107 | 0, 'n', 'b', 'h', 'g', 'y', '6', 0, 108 | 0, 0, 'm', 'j', 'u', '7', '8', 0, 109 | 0, ',', 'k', 'i', 'o', '0', '9', 0, 110 | 0, '.', '/', 'l', ';', 'p', '-', 0, 111 | 0, 0, '\'', 0, '[', '=', 0, 0, 112 | 0 /*CapsLock*/, 0 /*Rshift*/, PS2_ENTER /*Enter*/, ']', 0, '\\', 0, 0, 113 | 0, 0, 0, 0, 0, 0, PS2_BACKSPACE, 0, 114 | 0, '1', 0, '4', '7', 0, 0, 0, 115 | '0', '.', '2', '5', '6', '8', PS2_ESC, 0 /*NumLock*/, 116 | PS2_F11, '+', '3', '-', '*', '9', PS2_SCROLL, 0, 117 | 0, 0, 0, PS2_F7 }, 118 | // with shift 119 | {0, PS2_F9, 0, PS2_F5, PS2_F3, PS2_F1, PS2_F2, PS2_F12, 120 | 0, PS2_F10, PS2_F8, PS2_F6, PS2_F4, PS2_TAB, '~', 0, 121 | 0, 0 /*Lalt*/, 0 /*Lshift*/, 0, 0 /*Lctrl*/, 'Q', '!', 0, 122 | 0, 0, 'Z', 'S', 'A', 'W', '@', 0, 123 | 0, 'C', 'X', 'D', 'E', '$', '#', 0, 124 | 0, ' ', 'V', 'F', 'T', 'R', '%', 0, 125 | 0, 'N', 'B', 'H', 'G', 'Y', '^', 0, 126 | 0, 0, 'M', 'J', 'U', '&', '*', 0, 127 | 0, '<', 'K', 'I', 'O', ')', '(', 0, 128 | 0, '>', '?', 'L', ':', 'P', '_', 0, 129 | 0, 0, '"', 0, '{', '+', 0, 0, 130 | 0 /*CapsLock*/, 0 /*Rshift*/, PS2_ENTER /*Enter*/, '}', 0, '|', 0, 0, 131 | 0, 0, 0, 0, 0, 0, PS2_BACKSPACE, 0, 132 | 0, '1', 0, '4', '7', 0, 0, 0, 133 | '0', '.', '2', '5', '6', '8', PS2_ESC, 0 /*NumLock*/, 134 | PS2_F11, '+', '3', '-', '*', '9', PS2_SCROLL, 0, 135 | 0, 0, 0, PS2_F7 }, 136 | 0 137 | }; 138 | 139 | #define BREAK 0x01 140 | #define MODIFIER 0x02 141 | #define SHIFT_L 0x04 142 | #define SHIFT_R 0x08 143 | #define ALTGR 0x10 144 | 145 | static char get_iso8859_code(void) 146 | { 147 | static uint8_t state=0; 148 | uint8_t s; 149 | char c; 150 | 151 | while (1) { 152 | s = get_scan_code(); 153 | if (!s) return 0; 154 | if (s == 0xF0) { 155 | state |= BREAK; 156 | } else if (s == 0xE0) { 157 | state |= MODIFIER; 158 | } else { 159 | if (state & BREAK) { 160 | if (s == 0x12) { 161 | state &= ~SHIFT_L; 162 | } else if (s == 0x59) { 163 | state &= ~SHIFT_R; 164 | } else if (s == 0x11 && (state & MODIFIER)) { 165 | state &= ~ALTGR; 166 | } 167 | // CTRL, ALT & WIN keys could be added 168 | // but is that really worth the overhead? 169 | state &= ~(BREAK | MODIFIER); 170 | continue; 171 | } 172 | if (s == 0x58) { 173 | Caps=!Caps; 174 | return 0; 175 | } else if (s == 0x12) { 176 | state |= SHIFT_L; 177 | continue; 178 | } else if (s == 0x59) { 179 | state |= SHIFT_R; 180 | continue; 181 | } else if (s == 0x11 && (state & MODIFIER)) { 182 | state |= ALTGR; 183 | } 184 | c = 0; 185 | if (state & MODIFIER) { 186 | switch (s) { 187 | case 0x70: c = PS2_INSERT; break; 188 | case 0x6C: c = PS2_HOME; break; 189 | case 0x7D: c = PS2_PAGEUP; break; 190 | case 0x71: c = PS2_DELETE; break; 191 | case 0x69: c = PS2_END; break; 192 | case 0x7A: c = PS2_PAGEDOWN; break; 193 | case 0x75: c = PS2_UPARROW; break; 194 | case 0x6B: c = PS2_LEFTARROW; break; 195 | case 0x72: c = PS2_DOWNARROW; break; 196 | case 0x74: c = PS2_RIGHTARROW; break; 197 | case 0x4A: c = '/'; break; 198 | case 0x5A: c = PS2_ENTER; break; 199 | default: break; 200 | } 201 | } else if ((state & ALTGR) && keymap->uses_altgr) { 202 | if (s < PS2_KEYMAP_SIZE) 203 | c = pgm_read_byte(keymap->altgr + s); 204 | } else if (state & (SHIFT_L | SHIFT_R)) { 205 | if (s < PS2_KEYMAP_SIZE) 206 | c = pgm_read_byte(keymap->shift + s); 207 | } else { 208 | if (s < PS2_KEYMAP_SIZE) 209 | c = pgm_read_byte(keymap->noshift + s); 210 | } 211 | state &= ~(BREAK | MODIFIER); 212 | if (c) return c; 213 | } 214 | } 215 | } 216 | 217 | bool PS2Keyboard::available() { 218 | if (CharBuffer || UTF8next) return true; 219 | CharBuffer = get_iso8859_code(); 220 | if (CharBuffer) return true; 221 | return false; 222 | } 223 | 224 | uint8_t PS2Keyboard::send(uint8_t byteCmd) { 225 | while (Receiving > 0) { // Wait until idle 226 | delayMicroseconds(100); // 1100 uSec Max byte Rx time 227 | } 228 | pinMode(IrqPin,OUTPUT); 229 | digitalWrite(IrqPin,LOW); 230 | Sending = 1; 231 | Outgoing = byteCmd; 232 | Ack = 1; // Should drop to 0 233 | delayMicroseconds(150); // 100 minimum required 234 | pinMode(DataPin,OUTPUT); 235 | digitalWrite(DataPin,LOW); 236 | clear(); // now that send has control, clear the buffer so response is available 237 | 238 | #ifdef INPUT_PULLUP 239 | pinMode(IrqPin, INPUT_PULLUP); 240 | #else 241 | pinMode(IrqPin,INPUT); 242 | digitalWrite(IrqPin,HIGH); 243 | #endif 244 | 245 | unsigned long startTime = micros(); 246 | 247 | while ((Sending > 0) && (2000 > (micros() - startTime))) { 248 | delayMicroseconds(50); 249 | } 250 | uint8_t result = (Sending > 0) ? SEND_TIMED_OUT : SEND_NOT_ACKED; 251 | 252 | if (!Ack) { // Send was Ack'ed 253 | startTime = micros(); 254 | while ((2000 > (micros() - startTime)) && !is_scan_code_avail()) { 255 | delayMicroseconds(50); 256 | } 257 | if (is_scan_code_avail()) { 258 | result = get_scan_code(); // expect Resend or Okay 259 | } 260 | else { 261 | result = SEND_REPLY_TIMEOUT; 262 | } 263 | } 264 | return(result); 265 | } 266 | 267 | void ps2interrupt(void) { 268 | static uint8_t bitcount=0; 269 | static uint8_t incoming=0; 270 | static uint32_t prev_ms=0; 271 | uint32_t now_ms; 272 | uint8_t n, val; 273 | now_ms = millis(); 274 | if (Sending == 0) { // receiving mode 275 | val = digitalRead(DataPin); 276 | if (now_ms - prev_ms > 250) { 277 | bitcount = 0; 278 | incoming = 0; 279 | Receiving = 1; // receive cycle in progress 280 | } 281 | prev_ms = now_ms; 282 | n = bitcount - 1; 283 | if (n <= 7) { 284 | incoming |= (val << n); 285 | } 286 | bitcount++; 287 | if (bitcount == 11) { 288 | uint8_t i = head + 1; 289 | if (i >= BUFFER_SIZE) i = 0; 290 | if (i != tail) { 291 | buffer[i] = incoming; 292 | head = i; 293 | } 294 | bitcount = 0; 295 | incoming = 0; 296 | Receiving = 0; // receive cycle complete 297 | } 298 | } 299 | else { // sending mode 300 | if (now_ms - prev_ms > 250) { 301 | Sending = 1; // signal "need init" 302 | } 303 | prev_ms = now_ms; 304 | if (Sending == 1) { // Sending mode - initialisation 305 | bitcount = 0; 306 | Parity = 1; 307 | Sending = 2; // Sending mode - body 308 | pinMode(DataPin,OUTPUT); 309 | } 310 | if (bitcount < 8) { 311 | n = bitcount; 312 | val = (Outgoing >> n) & 1; 313 | Parity ^= val; 314 | digitalWrite(DataPin,val); 315 | } 316 | if (bitcount == 8) { 317 | digitalWrite(DataPin,Parity); 318 | } 319 | if (bitcount == 9) { 320 | digitalWrite(DataPin,HIGH); // Stop Bit 321 | #ifdef INPUT_PULLUP 322 | pinMode(DataPin, INPUT_PULLUP); 323 | #else 324 | pinMode(DataPin,INPUT); 325 | #endif 326 | } 327 | if (bitcount == 10) { 328 | Ack = digitalRead(DataPin); 329 | } 330 | bitcount++; 331 | if (bitcount == 11) { 332 | Sending = 0; 333 | bitcount = 0; 334 | //pinMode(DataPin,INPUT); 335 | } 336 | } 337 | } 338 | 339 | void PS2Keyboard::clear() { 340 | CharBuffer = 0; 341 | UTF8next = 0; 342 | } 343 | 344 | uint8_t PS2Keyboard::readScanCode(void) { 345 | return get_scan_code(); 346 | } 347 | 348 | int PS2Keyboard::read() { 349 | uint8_t result; 350 | 351 | result = UTF8next; 352 | if (result) { 353 | UTF8next = 0; 354 | } else { 355 | result = CharBuffer; 356 | if (result) { 357 | CharBuffer = 0; 358 | } else { 359 | result = get_iso8859_code(); 360 | } 361 | if (result >= 128) { 362 | UTF8next = (result & 0x3F) | 0x80; 363 | result = ((result >> 6) & 0x1F) | 0xC0; 364 | } 365 | } 366 | if (!result) return -1; 367 | if (Caps) { 368 | return toUpper(result); 369 | } else { 370 | return result; 371 | } 372 | } 373 | 374 | int PS2Keyboard::readUnicode() { 375 | int result; 376 | 377 | result = CharBuffer; 378 | if (!result) result = get_iso8859_code(); 379 | if (!result) return -1; 380 | UTF8next = 0; 381 | CharBuffer = 0; 382 | return result; 383 | } 384 | 385 | PS2Keyboard::PS2Keyboard() { 386 | // nothing to do here, begin() does it all 387 | } 388 | 389 | void PS2Keyboard::begin(uint8_t data_pin, uint8_t irq_pin, const PS2Keymap_t &map) { 390 | uint8_t irq_num=255; 391 | 392 | DataPin = data_pin; 393 | IrqPin = irq_pin; 394 | keymap = ↦ 395 | 396 | 397 | // initialize the pins 398 | #ifdef INPUT_PULLUP 399 | pinMode(irq_pin, INPUT_PULLUP); 400 | pinMode(data_pin, INPUT_PULLUP); 401 | #else 402 | pinMode(irq_pin, INPUT); 403 | digitalWrite(irq_pin, HIGH); 404 | pinMode(data_pin, INPUT); 405 | digitalWrite(data_pin, HIGH); 406 | #endif 407 | 408 | #ifdef CORE_INT_EVERY_PIN 409 | irq_num = irq_pin; 410 | #else 411 | switch(irq_pin) { 412 | #ifdef CORE_INT0_PIN 413 | case CORE_INT0_PIN: 414 | irq_num = 0; 415 | break; 416 | #endif 417 | #ifdef CORE_INT1_PIN 418 | case CORE_INT1_PIN: 419 | irq_num = 1; 420 | break; 421 | #endif 422 | #ifdef CORE_INT2_PIN 423 | case CORE_INT2_PIN: 424 | irq_num = 2; 425 | break; 426 | #endif 427 | #ifdef CORE_INT3_PIN 428 | case CORE_INT3_PIN: 429 | irq_num = 3; 430 | break; 431 | #endif 432 | #ifdef CORE_INT4_PIN 433 | case CORE_INT4_PIN: 434 | irq_num = 4; 435 | break; 436 | #endif 437 | #ifdef CORE_INT5_PIN 438 | case CORE_INT5_PIN: 439 | irq_num = 5; 440 | break; 441 | #endif 442 | #ifdef CORE_INT6_PIN 443 | case CORE_INT6_PIN: 444 | irq_num = 6; 445 | break; 446 | #endif 447 | #ifdef CORE_INT7_PIN 448 | case CORE_INT7_PIN: 449 | irq_num = 7; 450 | break; 451 | #endif 452 | #ifdef CORE_INT8_PIN 453 | case CORE_INT8_PIN: 454 | irq_num = 8; 455 | break; 456 | #endif 457 | #ifdef CORE_INT9_PIN 458 | case CORE_INT9_PIN: 459 | irq_num = 9; 460 | break; 461 | #endif 462 | #ifdef CORE_INT10_PIN 463 | case CORE_INT10_PIN: 464 | irq_num = 10; 465 | break; 466 | #endif 467 | #ifdef CORE_INT11_PIN 468 | case CORE_INT11_PIN: 469 | irq_num = 11; 470 | break; 471 | #endif 472 | #ifdef CORE_INT12_PIN 473 | case CORE_INT12_PIN: 474 | irq_num = 12; 475 | break; 476 | #endif 477 | #ifdef CORE_INT13_PIN 478 | case CORE_INT13_PIN: 479 | irq_num = 13; 480 | break; 481 | #endif 482 | #ifdef CORE_INT14_PIN 483 | case CORE_INT14_PIN: 484 | irq_num = 14; 485 | break; 486 | #endif 487 | #ifdef CORE_INT15_PIN 488 | case CORE_INT15_PIN: 489 | irq_num = 15; 490 | break; 491 | #endif 492 | #ifdef CORE_INT16_PIN 493 | case CORE_INT16_PIN: 494 | irq_num = 16; 495 | break; 496 | #endif 497 | #ifdef CORE_INT17_PIN 498 | case CORE_INT17_PIN: 499 | irq_num = 17; 500 | break; 501 | #endif 502 | #ifdef CORE_INT18_PIN 503 | case CORE_INT18_PIN: 504 | irq_num = 18; 505 | break; 506 | #endif 507 | #ifdef CORE_INT19_PIN 508 | case CORE_INT19_PIN: 509 | irq_num = 19; 510 | break; 511 | #endif 512 | #ifdef CORE_INT20_PIN 513 | case CORE_INT20_PIN: 514 | irq_num = 20; 515 | break; 516 | #endif 517 | #ifdef CORE_INT21_PIN 518 | case CORE_INT21_PIN: 519 | irq_num = 21; 520 | break; 521 | #endif 522 | #ifdef CORE_INT22_PIN 523 | case CORE_INT22_PIN: 524 | irq_num = 22; 525 | break; 526 | #endif 527 | #ifdef CORE_INT23_PIN 528 | case CORE_INT23_PIN: 529 | irq_num = 23; 530 | break; 531 | #endif 532 | } 533 | #endif 534 | 535 | head = 0; 536 | tail = 0; 537 | Receiving = 0; 538 | Sending = 0; 539 | if (irq_num < 255) { 540 | attachInterrupt(irq_num, ps2interrupt, FALLING); 541 | } 542 | } 543 | 544 | 545 | PS2Keyboard::KeyboardState PS2Keyboard::getState() { 546 | KeyboardState result; 547 | 548 | result.receiving = Receiving; 549 | result.sending = Sending; 550 | result.outgoing = Outgoing; 551 | 552 | return result; 553 | } 554 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/PS2Keyboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | PS2Keyboard.h - PS2Keyboard library 3 | Copyright (c) 2007 Free Software Foundation. All right reserved. 4 | Written by Christian Weichel 5 | 6 | ** Mostly rewritten Paul Stoffregen , June 2010 7 | ** Modified for use with Arduino 13 by L. Abraham Smith, * 8 | ** Modified for easy interrup pin assignement on method begin(datapin,irq_pin). Cuningan ** 9 | 10 | This library is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU Lesser General Public 12 | License as published by the Free Software Foundation; either 13 | version 2.1 of the License, or (at your option) any later version. 14 | 15 | This library 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 GNU 18 | Lesser General Public License for more details. 19 | 20 | You should have received a copy of the GNU Lesser General Public 21 | License along with this library; if not, write to the Free Software 22 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | 25 | 26 | #ifndef PS2Keyboard_h 27 | #define PS2Keyboard_h 28 | 29 | #if defined(ARDUINO) && ARDUINO >= 100 30 | #include "Arduino.h" // for attachInterrupt, FALLING 31 | #else 32 | #include "WProgram.h" 33 | #endif 34 | 35 | #include "utility/int_pins.h" 36 | 37 | // Every call to read() returns a single byte for each 38 | // keystroke. These configure what byte will be returned 39 | // for each "special" key. To ignore a key, use zero. 40 | #define PS2_TAB 9 41 | #define PS2_ENTER 13 42 | #define PS2_BACKSPACE 127 43 | #define PS2_ESC 27 44 | #define PS2_INSERT 2 // Modified for Cerberus BIOS 45 | #define PS_CAPS 3 // Modified for Cerberus BIOS 46 | #define PS2_DELETE 127 47 | #define PS2_HOME 0 48 | #define PS2_END 0 49 | #define PS2_PAGEUP 25 50 | #define PS2_PAGEDOWN 26 51 | #define PS2_UPARROW 11 52 | #define PS2_LEFTARROW 8 53 | #define PS2_DOWNARROW 10 54 | #define PS2_RIGHTARROW 21 55 | #define PS2_F1 0 56 | #define PS2_F2 0 57 | #define PS2_F3 0 58 | #define PS2_F4 0 59 | #define PS2_F5 0 60 | #define PS2_F6 0 61 | #define PS2_F7 0 62 | #define PS2_F8 0 63 | #define PS2_F9 0 64 | #define PS2_F10 0 65 | #define PS2_F11 0 66 | #define PS2_F12 1 // Modified for Cerberus BIOS 67 | #define PS2_SCROLL 0 68 | #define PS2_EURO_SIGN 0 69 | 70 | #define PS2_INVERTED_EXCLAMATION 161 // ¡ 71 | #define PS2_CENT_SIGN 162 // ¢ 72 | #define PS2_POUND_SIGN 163 // £ 73 | #define PS2_CURRENCY_SIGN 164 // ¤ 74 | #define PS2_YEN_SIGN 165 // ¥ 75 | #define PS2_BROKEN_BAR 166 // ¦ 76 | #define PS2_SECTION_SIGN 167 // § 77 | #define PS2_DIAERESIS 168 // ¨ 78 | #define PS2_COPYRIGHT_SIGN 169 // © 79 | #define PS2_FEMININE_ORDINAL 170 // ª 80 | #define PS2_LEFT_DOUBLE_ANGLE_QUOTE 171 // « 81 | #define PS2_NOT_SIGN 172 // ¬ 82 | #define PS2_HYPHEN 173 83 | #define PS2_REGISTERED_SIGN 174 // ® 84 | #define PS2_MACRON 175 // ¯ 85 | #define PS2_DEGREE_SIGN 176 // ° 86 | #define PS2_PLUS_MINUS_SIGN 177 // ± 87 | #define PS2_SUPERSCRIPT_TWO 178 // ² 88 | #define PS2_SUPERSCRIPT_THREE 179 // ³ 89 | #define PS2_ACUTE_ACCENT 180 // ´ 90 | #define PS2_MICRO_SIGN 181 // µ 91 | #define PS2_PILCROW_SIGN 182 // ¶ 92 | #define PS2_MIDDLE_DOT 183 // · 93 | #define PS2_CEDILLA 184 // ¸ 94 | #define PS2_SUPERSCRIPT_ONE 185 // ¹ 95 | #define PS2_MASCULINE_ORDINAL 186 // º 96 | #define PS2_RIGHT_DOUBLE_ANGLE_QUOTE 187 // » 97 | #define PS2_FRACTION_ONE_QUARTER 188 // ¼ 98 | #define PS2_FRACTION_ONE_HALF 189 // ½ 99 | #define PS2_FRACTION_THREE_QUARTERS 190 // ¾ 100 | #define PS2_INVERTED_QUESTION_MARK 191 // ¿ 101 | #define PS2_A_GRAVE 192 // À 102 | #define PS2_A_ACUTE 193 // Á 103 | #define PS2_A_CIRCUMFLEX 194 // Â 104 | #define PS2_A_TILDE 195 // Ã 105 | #define PS2_A_DIAERESIS 196 // Ä 106 | #define PS2_A_RING_ABOVE 197 // Å 107 | #define PS2_AE 198 // Æ 108 | #define PS2_C_CEDILLA 199 // Ç 109 | #define PS2_E_GRAVE 200 // È 110 | #define PS2_E_ACUTE 201 // É 111 | #define PS2_E_CIRCUMFLEX 202 // Ê 112 | #define PS2_E_DIAERESIS 203 // Ë 113 | #define PS2_I_GRAVE 204 // Ì 114 | #define PS2_I_ACUTE 205 // Í 115 | #define PS2_I_CIRCUMFLEX 206 // Î 116 | #define PS2_I_DIAERESIS 207 // Ï 117 | #define PS2_ETH 208 // Ð 118 | #define PS2_N_TILDE 209 // Ñ 119 | #define PS2_O_GRAVE 210 // Ò 120 | #define PS2_O_ACUTE 211 // Ó 121 | #define PS2_O_CIRCUMFLEX 212 // Ô 122 | #define PS2_O_TILDE 213 // Õ 123 | #define PS2_O_DIAERESIS 214 // Ö 124 | #define PS2_MULTIPLICATION 215 // × 125 | #define PS2_O_STROKE 216 // Ø 126 | #define PS2_U_GRAVE 217 // Ù 127 | #define PS2_U_ACUTE 218 // Ú 128 | #define PS2_U_CIRCUMFLEX 219 // Û 129 | #define PS2_U_DIAERESIS 220 // Ü 130 | #define PS2_Y_ACUTE 221 // Ý 131 | #define PS2_THORN 222 // Þ 132 | #define PS2_SHARP_S 223 // ß 133 | #define PS2_a_GRAVE 224 // à 134 | #define PS2_a_ACUTE 225 // á 135 | #define PS2_a_CIRCUMFLEX 226 // â 136 | #define PS2_a_TILDE 227 // ã 137 | #define PS2_a_DIAERESIS 228 // ä 138 | #define PS2_a_RING_ABOVE 229 // å 139 | #define PS2_ae 230 // æ 140 | #define PS2_c_CEDILLA 231 // ç 141 | #define PS2_e_GRAVE 232 // è 142 | #define PS2_e_ACUTE 233 // é 143 | #define PS2_e_CIRCUMFLEX 234 // ê 144 | #define PS2_e_DIAERESIS 235 // ë 145 | #define PS2_i_GRAVE 236 // ì 146 | #define PS2_i_ACUTE 237 // í 147 | #define PS2_i_CIRCUMFLEX 238 // î 148 | #define PS2_i_DIAERESIS 239 // ï 149 | #define PS2_eth 240 // ð 150 | #define PS2_n_TILDE 241 // ñ 151 | #define PS2_o_GRAVE 242 // ò 152 | #define PS2_o_ACUTE 243 // ó 153 | #define PS2_o_CIRCUMFLEX 244 // ô 154 | #define PS2_o_TILDE 245 // õ 155 | #define PS2_o_DIAERESIS 246 // ö 156 | #define PS2_DIVISION 247 // ÷ 157 | #define PS2_o_STROKE 248 // ø 158 | #define PS2_u_GRAVE 249 // ù 159 | #define PS2_u_ACUTE 250 // ú 160 | #define PS2_u_CIRCUMFLEX 251 // û 161 | #define PS2_u_DIAERESIS 252 // ü 162 | #define PS2_y_ACUTE 253 // ý 163 | #define PS2_thorn 254 // þ 164 | #define PS2_y_DIAERESIS 255 // ÿ 165 | 166 | #define PS2_KEYMAP_SIZE 136 167 | 168 | #define SEND_TIMED_OUT 0 169 | #define SEND_NOT_ACKED 1 // KBD failed to set ACK bit correctly. 170 | #define SEND_REPLY_TIMEOUT 2 // Timeout waiting for response code from KBD. 171 | #define SEND_BYTE_REJECTED 0xFE // KBD did not understand the byte value; response is resend request. 172 | #define SEND_BYTE_OKAY 0xFA // KBD accepted byte value as valid. 173 | 174 | typedef struct { 175 | uint8_t noshift[PS2_KEYMAP_SIZE]; 176 | uint8_t shift[PS2_KEYMAP_SIZE]; 177 | unsigned int uses_altgr; 178 | /* 179 | * "uint8_t uses_altgr;" makes the ESP8266 - NodeMCU modules crash. 180 | * So, I replaced it with an int and... It works! 181 | * I think it's because of the 32-bit architecture of the ESP8266 182 | * and the use of the flash memory to store the keymaps. 183 | * Maybe I'm wrong, it remains a hypothesis. 184 | */ 185 | uint8_t altgr[PS2_KEYMAP_SIZE]; 186 | } PS2Keymap_t; 187 | 188 | 189 | extern const PROGMEM PS2Keymap_t PS2Keymap_US; 190 | 191 | 192 | /** 193 | * Purpose: Provides an easy access to PS2 keyboards 194 | * Author: Christian Weichel 195 | */ 196 | class PS2Keyboard { 197 | public: 198 | /** 199 | * This constructor does basically nothing. Please call the begin(int,int) 200 | * method before using any other method of this class. 201 | */ 202 | PS2Keyboard(); 203 | 204 | /** 205 | * Starts the keyboard "service" by registering the external interrupt. 206 | * setting the pin modes correctly and driving those needed to high. 207 | * The propably best place to call this method is in the setup routine. 208 | */ 209 | static void begin(uint8_t dataPin, uint8_t irq_pin, const PS2Keymap_t &map = PS2Keymap_US); 210 | 211 | /** 212 | * Returns true if there is a char to be read, false if not. 213 | */ 214 | bool available(); 215 | 216 | /* Discards any received data, sets available() to false without a call to read() 217 | */ 218 | static void clear(); 219 | 220 | /** 221 | * Retutns ps2 scan code. 222 | */ 223 | static uint8_t readScanCode(void); 224 | 225 | /** 226 | * Returns the char last read from the keyboard. 227 | * If there is no char available, -1 is returned. 228 | */ 229 | static int read(); 230 | static int readUnicode(); 231 | /** 232 | * Sends a byte to the keyboard. 233 | * 234 | * @param byteCmd Byte value for command to send. 235 | * 236 | * @return Unsigned 8-bit "send status" value from those named with 237 | * SEND_ prefix. 238 | */ 239 | static uint8_t send(uint8_t byteCmd); 240 | 241 | /** 242 | * Container for the current state of the keyboard interface. 243 | */ 244 | struct KeyboardState { 245 | uint8_t receiving; // Indicates that a byte is being received from the keyboard. 246 | uint8_t sending; // Indicates that a byte is being sent to the keyboard. 247 | uint8_t outgoing; // Outgoing byte either in progress or last sent. 248 | }; 249 | 250 | /** 251 | * Gets the current state of the keyboard interface logic. 252 | * 253 | * @return Current KeyboardState. 254 | */ 255 | static KeyboardState getState(); 256 | }; 257 | 258 | #endif -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/README.md: -------------------------------------------------------------------------------- 1 | #PS/2 Keyboard Library# 2 | 3 | PS2Keyboard allows you to use a keyboard for user input. 4 | 5 | http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html 6 | 7 | ![](http://www.pjrc.com/teensy/td_libs_PS2Keyboard.jpg) 8 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/docs/issue_template.md: -------------------------------------------------------------------------------- 1 | Please use this form only to report code defects or bugs. 2 | 3 | For any question, even questions directly pertaining to this code, post your question on the forums related to the board you are using. 4 | 5 | Arduino: forum.arduino.cc 6 | Teensy: forum.pjrc.com 7 | ESP8266: www.esp8266.com 8 | ESP32: www.esp32.com 9 | Adafruit Feather/Metro/Trinket: forums.adafruit.com 10 | Particle Photon: community.particle.io 11 | 12 | If you are experiencing trouble but not certain of the cause, or need help using this code, ask on the appropriate forum. This is not the place to ask for support or help, even directly related to this code. Only use this form you are certain you have discovered a defect in this code! 13 | 14 | Please verify the problem occurs when using the very latest version, using the newest version of Arduino and any other related software. 15 | 16 | 17 | ----------------------------- Remove above ----------------------------- 18 | 19 | 20 | 21 | ### Description 22 | 23 | Describe your problem. 24 | 25 | 26 | 27 | ### Steps To Reproduce Problem 28 | 29 | Please give detailed instructions needed for anyone to attempt to reproduce the problem. 30 | 31 | 32 | 33 | ### Hardware & Software 34 | 35 | Board 36 | Shields / modules used 37 | Arduino IDE version 38 | Teensyduino version (if using Teensy) 39 | Version info & package name (from Tools > Boards > Board Manager) 40 | Operating system & version 41 | Any other software or hardware? 42 | 43 | 44 | ### Arduino Sketch 45 | 46 | ```cpp 47 | // Change the code below by your sketch (please try to give the smallest code which demonstrates the problem) 48 | #include 49 | 50 | // libraries: give links/details so anyone can compile your code for the same result 51 | 52 | void setup() { 53 | } 54 | 55 | void loop() { 56 | } 57 | ``` 58 | 59 | 60 | ### Errors or Incorrect Output 61 | 62 | If you see any errors or incorrect output, please show it here. Please use copy & paste to give an exact copy of the message. Details matter, so please show (not merely describe) the actual message or error exactly as it appears. 63 | 64 | 65 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/examples/International/International.pde: -------------------------------------------------------------------------------- 1 | /* PS2Keyboard library, International Keyboard Layout Example 2 | http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html 3 | 4 | keyboard.begin() accepts an optional 3rd parameter to 5 | configure the PS2 keyboard layout. Uncomment the line for 6 | your keyboard. If it doesn't exist, you can create it in 7 | PS2Keyboard.cpp and email paul@pjrc.com to have it included 8 | in future versions of this library. 9 | */ 10 | 11 | #include 12 | 13 | const int DataPin = 8; 14 | const int IRQpin = 5; 15 | 16 | PS2Keyboard keyboard; 17 | 18 | void setup() { 19 | //keyboard.begin(DataPin, IRQpin, PS2Keymap_US); 20 | keyboard.begin(DataPin, IRQpin, PS2Keymap_German); 21 | //keyboard.begin(DataPin, IRQpin, PS2Keymap_French); 22 | //keyboard.begin(DataPin, IRQpin, PS2Keymap_Spanish); 23 | //keyboard.begin(DataPin, IRQpin, PS2Keymap_Italian); 24 | //keyboard.begin(DataPin, IRQpin, PS2Keymap_UK); 25 | Serial.begin(9600); 26 | Serial.println("International Keyboard Test:"); 27 | } 28 | 29 | void loop() { 30 | if (keyboard.available()) { 31 | char c = keyboard.read(); 32 | Serial.print(c); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/examples/Simple_Test/Simple_Test.pde: -------------------------------------------------------------------------------- 1 | /* PS2Keyboard library example 2 | 3 | PS2Keyboard now requries both pins specified for begin() 4 | 5 | keyboard.begin(data_pin, irq_pin); 6 | 7 | Valid irq pins: 8 | Arduino Uno: 2, 3 9 | Arduino Due: All pins, except 13 (LED) 10 | Arduino Mega: 2, 3, 18, 19, 20, 21 11 | Teensy 3.0: All pins, except 13 (LED) 12 | Teensy 2.0: 5, 6, 7, 8 13 | Teensy 1.0: 0, 1, 2, 3, 4, 6, 7, 16 14 | Teensy++ 2.0: 0, 1, 2, 3, 18, 19, 36, 37 15 | Teensy++ 1.0: 0, 1, 2, 3, 18, 19, 36, 37 16 | Sanguino: 2, 10, 11 17 | 18 | for more information you can read the original wiki in arduino.cc 19 | at http://www.arduino.cc/playground/Main/PS2Keyboard 20 | or http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html 21 | 22 | Like the Original library and example this is under LGPL license. 23 | 24 | Modified by Cuninganreset@gmail.com on 2010-03-22 25 | Modified by Paul Stoffregen June 2010 26 | */ 27 | 28 | #include 29 | 30 | const int DataPin = 8; 31 | const int IRQpin = 5; 32 | 33 | PS2Keyboard keyboard; 34 | 35 | void setup() { 36 | delay(1000); 37 | keyboard.begin(DataPin, IRQpin); 38 | Serial.begin(9600); 39 | Serial.println("Keyboard Test:"); 40 | } 41 | 42 | void loop() { 43 | if (keyboard.available()) { 44 | 45 | // read the next key 46 | char c = keyboard.read(); 47 | 48 | // check for some of the special keys 49 | if (c == PS2_ENTER) { 50 | Serial.println(); 51 | } else if (c == PS2_TAB) { 52 | Serial.print("[Tab]"); 53 | } else if (c == PS2_ESC) { 54 | Serial.print("[ESC]"); 55 | } else if (c == PS2_PAGEDOWN) { 56 | Serial.print("[PgDn]"); 57 | } else if (c == PS2_PAGEUP) { 58 | Serial.print("[PgUp]"); 59 | } else if (c == PS2_LEFTARROW) { 60 | Serial.print("[Left]"); 61 | } else if (c == PS2_RIGHTARROW) { 62 | Serial.print("[Right]"); 63 | } else if (c == PS2_UPARROW) { 64 | Serial.print("[Up]"); 65 | } else if (c == PS2_DOWNARROW) { 66 | Serial.print("[Down]"); 67 | } else if (c == PS2_DELETE) { 68 | Serial.print("[Del]"); 69 | } else { 70 | 71 | // otherwise, just print all normal characters 72 | Serial.print(c); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/examples/TypeToDisplay/TypeToDisplay.ino: -------------------------------------------------------------------------------- 1 | /* TypeToDisplay - PS2Keyboard keystrokes show on a standard LCD Display 2 | 3 | Lcd support added 18/2/2018 D.R.Patterson 4 | lcd character set: 5 | http://forum.arduino.cc/index.php?topic=19002.0 6 | 7 | Tested on a mega with a Keypad Shield for Arduino 8 | 9 | PS2Keyboard now requries both pins specified for begin() 10 | keyboard.begin(data_pin, irq_pin); 11 | 12 | Valid irq pins: 13 | Arduino Uno: 2, 3 14 | Arduino Due: All pins, except 13 (LED) 15 | Arduino Mega: 2, 3, 18, 19, 20, 21 16 | Teensy 2.0: All pins, except 13 (LED) 17 | Teensy 2.0: 5, 6, 7, 8 18 | Teensy 1.0: 0, 1, 2, 3, 4, 6, 7, 16 19 | Teensy++ 2.0: 0, 1, 2, 3, 18, 19, 36, 37 20 | Teensy++ 1.0: 0, 1, 2, 3, 18, 19, 36, 37 21 | Sanguino: 2, 10, 11 22 | 23 | for more information you can read the original wiki in arduino.cc 24 | at http://www.arduino.cc/playground/Main/PS2Keyboard 25 | or http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html 26 | 27 | Like the Original library and example this is under LGPL license. 28 | 29 | Modified by Cuninganreset@gmail.com on 2010-03-22 30 | Modified by Paul Stoffregen June 2010 31 | */ 32 | 33 | #include 34 | const int DataPin = 19; 35 | const int IRQpin = 18; 36 | PS2Keyboard keyboard; 37 | 38 | #include 39 | // LCD Keypad Shield for Arduino 40 | // http://www.hobbytronics.co.uk/lcd/lcd-displays-5v/arduino-lcd-keypad-shield 41 | 42 | //set constants for number of rows and columns to match your LCD 43 | const int numRows = 2; 44 | const int numCols = 16; 45 | // initialize the library with the numbers of the interface pins 46 | LiquidCrystal lcd(8, 9, 4, 5, 6, 7); 47 | 48 | byte x, y; // track lcd position 49 | 50 | // custom pound using https://omerk.github.io/lcdchargen/ 51 | byte customPound[8] = { 52 | 0b01100, 53 | 0b10010, 54 | 0b01000, 55 | 0b11100, 56 | 0b01000, 57 | 0b01000, 58 | 0b11110, 59 | 0b00000 60 | }; 61 | const char pound = 1; 62 | 63 | byte customkey[8] = { // ¬ 64 | 0b00000, 65 | 0b00000, 66 | 0b00000, 67 | 0b00000, 68 | 0b11111, 69 | 0b00001, 70 | 0b00001, 71 | 0b00000 72 | }; 73 | const char custom = 2; 74 | 75 | byte customslash[8] = { // backslash 76 | 0b00000, 77 | 0b00000, 78 | 0b10000, 79 | 0b01000, 80 | 0b00100, 81 | 0b00010, 82 | 0b00001, 83 | 0b00000 84 | }; 85 | const char backslash = 3; 86 | 87 | char c; 88 | 89 | // currently un-used: 90 | #define is_printable(c) (!(c&0x80)) // don't print if top bit is set 91 | 92 | void setup() { 93 | delay(1000); 94 | keyboard.begin(DataPin, IRQpin); 95 | Serial.begin(115200); 96 | while (!Serial) yield(); 97 | lcd.begin(numRows, numCols); 98 | delay(250); 99 | lcd.clear(); 100 | // create a new custom character 101 | lcd.createChar(pound, customPound); 102 | lcd.createChar(custom, customkey); 103 | lcd.createChar(backslash, customslash); 104 | lcd.cursor(); // Enable Cursor 105 | //lcd.blink(); // Blinking cursor 106 | lcd.clear(); 107 | 108 | lcd.print(F("Keyboard Test:")); 109 | lcd.setCursor(0, 1); 110 | lcd.print(F("Esc to clear LCD")); 111 | Serial.println(F("Keyboard Test:")); 112 | unsigned long t = millis(); 113 | do { 114 | if (keyboard.available()) { 115 | c = keyboard.read(); 116 | if (c == PS2_ESC) break; 117 | } 118 | } while ((millis() - t) < 8000); 119 | lcd.clear(); 120 | } 121 | 122 | void loop() { 123 | 124 | if (keyboard.available()) { 125 | // read the next key 126 | c = keyboard.read(); 127 | 128 | // check for some of the special keys 129 | if (c == PS2_ENTER) { 130 | Serial.println(); 131 | x = 0; 132 | y += 1; 133 | if (y == numRows) { 134 | y = 0; 135 | lcd.clear(); 136 | } 137 | lcd.setCursor(x, y); 138 | 139 | } else if (c == PS2_TAB) { 140 | lcdprint(F("[Tab]")); 141 | 142 | } else if (c == PS2_ESC) { 143 | Serial.println(F("\n[ESC]\n")); 144 | lcd.clear(); 145 | x = 0; y = 0; 146 | lcd.setCursor(x, y); 147 | 148 | } else if (c == PS2_PAGEDOWN) { 149 | lcdprint(F("[PgDn]")); 150 | 151 | } else if (c == PS2_PAGEUP) { 152 | lcdprint(F("[PgUp]")); 153 | 154 | } else if (c == PS2_LEFTARROW) { 155 | lcdprint(F("[Left]")); 156 | 157 | } else if (c == PS2_RIGHTARROW) { 158 | lcdprint(F("[Right]")); 159 | 160 | } else if (c == PS2_UPARROW) { 161 | lcdprint(F("[Up]")); 162 | 163 | } else if (c == PS2_DOWNARROW) { 164 | lcdprint(F("[Down]")); 165 | 166 | } else if (c == PS2_DELETE) { 167 | lcdprint(F("[Del]")); 168 | 169 | } else if (c == PS2_BACKSPACE) { 170 | lcdprint(F("[Back]")); 171 | 172 | } else { 173 | // otherwise, just print all normal characters 174 | lcdprintChar(c); 175 | } 176 | } 177 | } 178 | 179 | void lcdprintChar(char t) { // display char on lcd and Serial 180 | byte test = byte(t); 181 | if (test == 194) return; // this char appears as an indicator of special keys 182 | 183 | if (x > (numCols - 1) ) { 184 | x = 0; 185 | if (y == (numRows - 1)) { 186 | lcd.clear(); 187 | Serial.println(); 188 | y = 0; 189 | } else y += 1; 190 | Serial.println(); 191 | lcd.setCursor(x, y); 192 | } 193 | 194 | if (test == 126) lcd.write(243); // 126 ~ 195 | else if (test == 163) lcd.write(pound); // 163 £ 196 | else if (test == 172) lcd.write(custom); // 194 172 ¬ 197 | else if (test == 92) lcd.write(backslash); // 92 backslash 198 | else lcd.print(t); 199 | Serial.print(t); 200 | x += 1; 201 | } 202 | 203 | void lcdprint(String t) { // display string on lcd and Serial 204 | byte L = t.length(); 205 | if (x > (numCols - L) ) { 206 | x = 0; 207 | if (y == (numRows - 1)) { 208 | lcd.clear(); 209 | Serial.println(); 210 | y = 0; 211 | } else y += 1; 212 | Serial.println(); 213 | lcd.setCursor(x, y); 214 | } 215 | lcd.print(t); 216 | Serial.print(t); 217 | x += L; 218 | } 219 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For PS2Keyboard 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | PS2Keyboard KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | ####################################### 16 | # Constants (LITERAL1) 17 | ####################################### 18 | 19 | PS2_KC_BREAK LITERAL1 20 | PS2_KC_ENTER LITERAL1 21 | PS2_KC_ESC LITERAL1 22 | PS2_KC_KPLUS LITERAL1 23 | PS2_KC_KMINUS LITERAL1 24 | PS2_KC_KMULTI LITERAL1 25 | PS2_KC_NUM LITERAL1 26 | PS2_KC_BKSP LITERAL1 27 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PS2Keyboard", 3 | "keywords": "keyboard, emulation", 4 | "description": "PS2Keyboard (PS/2) allows you to use a keyboard for user input", 5 | "authors": 6 | [ 7 | { 8 | "name": "Paul Stoffregen", 9 | "email": "paul@pjrc.com", 10 | "url": "http://www.pjrc.com", 11 | "maintainer": true 12 | }, 13 | { 14 | "name": "Christian Weichel", 15 | "email": "info@32leaves.net" 16 | }, 17 | { 18 | "name": "L. Abraham Smith", 19 | "email": "n3bah@microcompdesign.com" 20 | }, 21 | { 22 | "name": "Cuningan", 23 | "email": "cuninganreset@gmail.com" 24 | } 25 | ], 26 | "repository": 27 | { 28 | "type": "git", 29 | "url": "https://github.com/PaulStoffregen/PS2Keyboard.git" 30 | }, 31 | "version": "2.4", 32 | "homepage": "http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html", 33 | "frameworks": "arduino", 34 | "platforms": 35 | [ 36 | "atmelavr", 37 | "teensy" 38 | ], 39 | "examples": [ 40 | "examples/*/*.pde" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/library.properties: -------------------------------------------------------------------------------- 1 | name=PS2Keyboard 2 | version=2.4 3 | author=Christian Weichel, Paul Stoffregen, L. Abraham Smith, Cuningan 4 | maintainer=Paul Stoffregen 5 | sentence=Use a PS/2 Keyboard for input 6 | paragraph= 7 | category=Signal Input/Output 8 | url=https://github.com/PaulStoffregen/PS2Keyboard 9 | architectures=* 10 | 11 | -------------------------------------------------------------------------------- /cat/src/PS2Keyboard/utility/int_pins.h: -------------------------------------------------------------------------------- 1 | // interrupt pins for known boards 2 | 3 | // Teensy and maybe others automatically define this info 4 | #if !defined(CORE_INT0_PIN) && !defined(CORE_INT1_PIN) && !defined(CORE_INT2_PIN)&& !defined(CORE_INT3_PIN) 5 | 6 | // Arduino Mega 7 | #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // Arduino Mega 8 | #define CORE_INT0_PIN 2 9 | #define CORE_INT1_PIN 3 10 | #define CORE_INT2_PIN 21 11 | #define CORE_INT3_PIN 20 12 | #define CORE_INT4_PIN 19 13 | #define CORE_INT5_PIN 18 14 | 15 | // Arduino Due (untested) 16 | #elif defined(__SAM3X8E__) 17 | #define CORE_INT_EVERY_PIN 18 | #ifndef PROGMEM 19 | #define PROGMEM 20 | #endif 21 | #ifndef pgm_read_byte 22 | #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) 23 | #endif 24 | 25 | // Arduino Leonardo (untested) 26 | #elif defined(__AVR_ATmega32U4__) && !defined(CORE_TEENSY) 27 | #define CORE_INT0_PIN 3 28 | #define CORE_INT1_PIN 2 29 | #define CORE_INT2_PIN 0 30 | #define CORE_INT3_PIN 1 31 | 32 | // Sanguino (untested) 33 | #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) // Sanguino 34 | #define CORE_INT0_PIN 10 35 | #define CORE_INT1_PIN 11 36 | #define CORE_INT2_PIN 2 37 | 38 | // Chipkit Uno32 (untested) 39 | #elif defined(__PIC32MX__) && defined(_BOARD_UNO_) 40 | #define CORE_INT0_PIN 38 41 | #define CORE_INT1_PIN 2 42 | #define CORE_INT2_PIN 7 43 | #define CORE_INT3_PIN 8 44 | #define CORE_INT4_PIN 35 45 | 46 | // Chipkit Mega32 (untested) 47 | #elif defined(__PIC32MX__) && defined(_BOARD_MEGA_) 48 | #define CORE_INT0_PIN 3 49 | #define CORE_INT1_PIN 2 50 | #define CORE_INT2_PIN 7 51 | #define CORE_INT3_PIN 21 52 | #define CORE_INT4_PIN 20 53 | 54 | // http://hlt.media.mit.edu/?p=1229 55 | #elif defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 56 | #define CORE_INT0_PIN 2 57 | 58 | #elif defined(__AVR_ATtiny84__) 59 | #define CORE_INT0_PIN 8 60 | 61 | //ESP8266 - NodeMCU 0.9 (ESP-12 Module) / NodeMCU 1.0 (ESP-12E Module) 62 | #elif defined(ESP8266) 63 | #define CORE_INT_EVERY_PIN 64 | 65 | //ESP32 support 66 | #elif defined(ESP32) 67 | #define CORE_INT_EVERY_PIN 68 | 69 | //STM32F1 support 70 | #elif defined(__STM32F1__) 71 | #define CORE_INT_EVERY_PIN 72 | 73 | // Arduino Uno, Duemilanove, LilyPad, Mini, Fio, etc... 74 | #else 75 | #define CORE_INT0_PIN 2 76 | #define CORE_INT1_PIN 3 77 | 78 | #endif 79 | #endif 80 | 81 | -------------------------------------------------------------------------------- /powershell/send_file.ps1: -------------------------------------------------------------------------------- 1 | $use = 1 2 | 3 | switch($use) { 4 | 1 { 5 | $filePath = "H:\My Code\Cerberus\Z80\BBC Basic\build.bin" 6 | $startAddress = 0x0205 7 | } 8 | } 9 | 10 | $comPort = "COM5" 11 | $baud = 9600 12 | $filebuffer = [System.IO.File]::ReadAllBytes($filePath) 13 | $fileSize = $filebuffer.length 14 | $checkError = $false 15 | $blockSize = 10 16 | 17 | $port= new-Object System.IO.Ports.SerialPort $comPort, $baud, None, 8, one 18 | $port.open() 19 | if ($port.IsOpen) { 20 | Write-Host "Writing file to com port $($comPort)" 21 | $blockIndex = 0 22 | $blockBytesRemaining = $fileSize 23 | while($blockBytesRemaining -gt 0 -and !$checkError) { 24 | $blockSize = [math]::Min($blockBytesRemaining, $blockSize) 25 | $header = [string]::Format(“0x{0:X4}”, $startAddress + $blockIndex) 26 | $port.Write($header) 27 | Write-Host $header -NoNewline 28 | $checkA = 1 29 | $checkB = 0 30 | For($i=0; $i -lt $blockSize; $i++) { 31 | [byte]$data = $fileBuffer[$blockIndex++] 32 | $checkA = ($checkA + $data) % 256 33 | $checkB = ($checkB + $checkA) % 256 34 | $block = [string]::Format(" {0:X2}", $data) 35 | $port.Write($block) 36 | $blockBytesRemaining-- 37 | Write-Host $block -NoNewline 38 | } 39 | $checksum = [string]::Format(“{0:X}", ($checkA -shl 8) -bor $checkB) 40 | $port.Write([byte[]](13),0,1) 41 | $response = $port.ReadLine().TrimEnd() 42 | Write-Host " > $($response) : $($checksum)" 43 | if (!$response.endsWith($checkSum)) { 44 | $checkError = $true 45 | } 46 | } 47 | $port.Write([byte[]](13),0,1) 48 | $port.Close() 49 | if ($checkError) { 50 | Write-Host "Upload error - mismatched checksum" 51 | } 52 | } 53 | else { 54 | Write-Host "Unable to open com port $($comPort)" 55 | } -------------------------------------------------------------------------------- /python/send_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Title: Send a file to the Cerberus 4 | # Author: Dean Belfield 5 | # Created: 15/10/2023 6 | # Last Updated: 15/10/2023 7 | # 8 | # Modinfo: 9 | 10 | import sys 11 | import os 12 | import serial 13 | import time 14 | 15 | # Configuration 16 | # 17 | startAddress = 0x0205 18 | blockSize = 10 19 | 20 | # Configure serial port here. Last parameter is timeout to stop reading data, in seconds: 21 | # 22 | s = serial.Serial('/dev/ttyUSB1', 115200, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE) 23 | 24 | # Open the file for reading 25 | # 26 | name = sys.argv[1] # Get the filename 27 | fullPath = os.path.expanduser(name) # Expand the full path to the file and 28 | file = open(fullPath, "rb") # Open it up as a binary file 29 | 30 | time.sleep(7) # Wait for the Cerberus to reset 31 | 32 | count = 0 # Number of bytes written 33 | chksA = 1 # Checksums 34 | chksB = 0 35 | reset = True 36 | 37 | # Iterate through the file 38 | # 39 | while True: 40 | data = file.read(1) # Read 1 byte into the buffer data 41 | if not data: 42 | reset = True 43 | 44 | if reset: 45 | if count > 0: 46 | s.write(bytes([0x0D])) # Enter that line 47 | startAddress += count # Update the start address for the next line 48 | checksum = F"{chksA << 8 | chksB:X}" # Calculate the checksum on this side 49 | response = s.readline().decode().rstrip() # Fetch the response 50 | count = 0 # Reset for next line 51 | print(F" > {response} : {checksum}", end="") 52 | if not response.endswith(checksum): # If the checksums don't match then error 53 | print(" - Error: Mismatched Checksum") 54 | break 55 | elif not data: # If there is no more data then 56 | print(" - End") 57 | break # end 58 | else: 59 | print(" - OK") 60 | chksA = 1 # Reset the checksums 61 | chksB = 0 62 | reset = False 63 | s.write(F"0x{startAddress:04X}".encode()) # Write out the start address 64 | print(F"0x{startAddress:04X}", end="") 65 | 66 | byte = data[0] # Fetch the byte 67 | print(F" {byte:02X}", end="") 68 | chksA = (chksA + byte) % 256 # Calculate the checksums 69 | chksB = (chksB + chksA) % 256 70 | s.write(F" {byte:02x}".encode()) # Write the byte out 71 | count += 1 72 | if(count >= blockSize): 73 | reset = True 74 | 75 | file.close() # We've done so close the files -------------------------------------------------------------------------------- /sd/basicZ80.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/sd/basicZ80.bin -------------------------------------------------------------------------------- /sd/chardefs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/sd/chardefs.bin -------------------------------------------------------------------------------- /sd/help.txt: -------------------------------------------------------------------------------- 1 | basicZ80: shortcut for BASIC Z80 2 | basic6502: shortcut for BASIC 6502 3 | 0xADDR BYTE: Writes BYTE at ADDR 4 | list ADDR: Lists memory from ADDR 5 | cls: Clears the screen 6 | testmem: Reads/writes to memories 7 | 6502: Switches to 6502 CPU mode 8 | z80: Switches to Z80 CPU mode 9 | fast: Switches to 8MHz mode 10 | slow: Switches to 4MHz mode 11 | reset: Resets the system 12 | dir: Lists files on uSD card 13 | del FILE: Deletes FILE 14 | load FILE ADDR: Loads FILE at ADDR 15 | save ADDR1 ADDR2 FILE: Saves memory 16 | from ADDR1 to ADDR2 to FILE 17 | run: Executes code in memory 18 | move ADDR1 ADDR2 ADDR3: Moves bytes 19 | between ADDR1 & ADDR2 to ADDR3 20 | -------------------------------------------------------------------------------- /sd/icon2080.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/cerberus/fe34f7587e43e49838b209596e61c9dc61e3ba0f/sd/icon2080.img --------------------------------------------------------------------------------