├── LICENSE ├── Manuals ├── Sinclair Scientific Assembly.pdf ├── Sinclair Scientific Operating.pdf ├── a9.gif └── readme.txt ├── README.md ├── SinclairScientific1 ├── CPU.ino ├── DisAssembler.ino ├── DisplayAndKeys.ino ├── SinclairScientific1.ino ├── UseSoftwareSerial.ino └── libraries │ └── Arduino-GPIO-master.zip ├── SinclairScientific5 ├── CPU.ino ├── Disassembler.ino ├── DisplayAndKeys.ino └── SinclairScientific5.ino ├── SinclairScientificExperiments ├── SinclairEnduranceTimer ├── SinclairScientificExperiments.ino └── SinclairScientificKeyBoardTest.ino └── SinclairScientificExperiments2 ├── CPU.ino ├── DisAssembler.ino ├── DisplayAndKeys.ino └── SinclairScientificExperiments2.ino /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Manuals/Sinclair Scientific Assembly.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduinoenigma/ArduinoNanoSinclairScientificCalculator/f6fb9eae2af7c7aea903687984f963b3e953347f/Manuals/Sinclair Scientific Assembly.pdf -------------------------------------------------------------------------------- /Manuals/Sinclair Scientific Operating.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduinoenigma/ArduinoNanoSinclairScientificCalculator/f6fb9eae2af7c7aea903687984f963b3e953347f/Manuals/Sinclair Scientific Operating.pdf -------------------------------------------------------------------------------- /Manuals/a9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduinoenigma/ArduinoNanoSinclairScientificCalculator/f6fb9eae2af7c7aea903687984f963b3e953347f/Manuals/a9.gif -------------------------------------------------------------------------------- /Manuals/readme.txt: -------------------------------------------------------------------------------- 1 | manuals came from: 2 | http://www.wass.net/manuals/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArduinoNanoSinclairScientificCalculator 2 | An Arduino port of Ken Shirriff Sinclair Scientific Calculator Simulator http://righto.com/sinclair 3 | 4 | latest version at: https://gitlab.com/arduinoenigma/ArduinoNanoSinclairScientificCalculator 5 | 6 | SinclairScientific1, master branch for v1,v2 and v3 boards, updated to latest and greatest, all digits working, timing adjusted to match real calculator 7 | SinclairScientificExperiments, an old snapshot of the SinclairScientific tree with ALU functions implemented.
8 | SinclairScientificExperiments2, a lean and mean and slow, no Serial.print, running version of the calculator, needs more step(), less display()
9 | 10 | SinclairScientific5, master branch for v5 boards, the keyboard and display circuit match the real Sinclair Scientific Calculator. 11 | 12 | This simulator is what powers the following emulator: 13 | https://www.tindie.com/products/ArduinoEnigma/sinclair-scientific-calculator-emulator/ 14 | 15 | Board V5:
16 | https://oshpark.com/projects/OSfM0RLN
17 | Board V2:
18 | https://oshpark.com/projects/zOpXIok8
19 | -------------------------------------------------------------------------------- /SinclairScientific1/CPU.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // BUG: play with .display = 0 to see what ia the absolute minimum that works 12 | // 13 | 14 | boolean opsWithK(byte opcode) 15 | { 16 | return (LISTOPSWITHK & (1UL << opcode)); 17 | } 18 | 19 | unsigned int getInstruction(unsigned int PC) 20 | { 21 | return pgm_read_word_near(objectCode + PC); 22 | } 23 | 24 | byte getMaskNum() 25 | { 26 | return getInstruction(SinclairData.address) & 0x0f; 27 | } 28 | 29 | char *getMask() 30 | { 31 | unsigned int instruction = getInstruction(SinclairData.address); 32 | byte classBits = instruction >> 9; 33 | byte opcode = (instruction >> 4) & 0x1f; 34 | 35 | if (classBits == 3 || (classBits == 2 && opcode > 18 && opcode != 21 && opcode != 22)) 36 | { 37 | byte maskno = getMaskNum(); 38 | 39 | for (byte i = 0; i <= 10; i++) 40 | { 41 | char maskdigit = pgm_read_byte_near(masks[maskno] + i); 42 | 43 | if (maskdigit == ' ') 44 | { 45 | SinclairData.mask[i] = maskdigit; 46 | } 47 | else if (classBits == 3 && opsWithK(opcode)) 48 | { 49 | // Register instruction 50 | SinclairData.mask[i] = maskdigit - '0';; 51 | } 52 | else 53 | { 54 | SinclairData.mask[i] = '*'; 55 | } 56 | } 57 | } 58 | else 59 | { 60 | SinclairData.mask[0] = 0; 61 | } 62 | 63 | return SinclairData.mask; 64 | } 65 | 66 | void add(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 67 | { 68 | byte carry = 0; 69 | getMask(); 70 | for (signed char i = 10; i >= 0; i--) 71 | { 72 | if (SinclairData.mask[i] == ' ') 73 | { 74 | // masked out 75 | // continue; 76 | } 77 | else 78 | { 79 | signed char result = src1[i] + src2[i] + carry; 80 | if (!hex && result >= 10) 81 | { 82 | result -= 10; 83 | carry = 1; 84 | } 85 | else if (hex && result >= 16) 86 | { 87 | result -= 16; 88 | carry = 1; 89 | } 90 | else 91 | { 92 | carry = 0; 93 | } 94 | dst[i] = result; 95 | } 96 | } 97 | if (carry) 98 | { 99 | SinclairData.cc = carry; 100 | //SinclairData.ccMeaning = carry ? 'overflow' : 'no overflow'; 101 | } 102 | } 103 | 104 | void sub(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 105 | { 106 | byte borrow = 0; 107 | getMask(); 108 | for (signed char i = 10; i >= 0; i--) 109 | { 110 | if (SinclairData.mask[i] == ' ') 111 | { 112 | // masked out 113 | // continue; 114 | } 115 | else 116 | { 117 | signed char result = src1[i] - src2[i] - borrow; 118 | if (result < 0) 119 | { 120 | result += hex ? 16 : 10; 121 | borrow = 1; 122 | } 123 | else 124 | { 125 | borrow = 0; 126 | } 127 | dst[i] = result; 128 | } 129 | } 130 | if (borrow) 131 | { 132 | SinclairData.cc = borrow; 133 | //SinclairData.ccMeaning = borrow ? 'borrow' : 'no borrow'; 134 | } 135 | } 136 | 137 | void compare(signed char src1[], signed char src2[]) 138 | { 139 | signed char tmp[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 140 | 141 | sub(src1, src2, tmp); 142 | // Compare sets condition if not borrow 143 | //SinclairData.ccMeaning = SinclairData.cc ? "less than" : "not less than"; 144 | } 145 | 146 | void copy(signed char src[], signed char dst[]) 147 | { 148 | getMask(); 149 | for (signed char i = 10; i >= 0; i--) 150 | { 151 | if (SinclairData.mask[i] == ' ') 152 | { 153 | // masked out 154 | // continue; 155 | } 156 | else 157 | { 158 | dst[i] = src[i]; 159 | } 160 | } 161 | } 162 | 163 | void sll(signed char src[]) 164 | { 165 | getMask(); 166 | signed char digit = 0; 167 | for (signed char i = 10; i >= 0; i--) 168 | { 169 | if (SinclairData.mask[i] == ' ') 170 | { 171 | // masked out 172 | // continue; 173 | } 174 | else 175 | { 176 | signed char newdigit = src[i]; 177 | src[i] = digit; 178 | digit = newdigit; 179 | } 180 | } 181 | } 182 | 183 | void srl(signed char src[]) 184 | { 185 | getMask(); 186 | signed char digit = 0; 187 | for (signed char i = 0; i <= 10; i++) 188 | { 189 | if (SinclairData.mask[i] == ' ') 190 | { 191 | // masked out 192 | // continue; 193 | } 194 | else 195 | { 196 | signed char newdigit = src[i]; 197 | src[i] = digit; 198 | digit = newdigit; 199 | } 200 | } 201 | } 202 | 203 | void writeFlag(signed char dest[], signed char val) 204 | { 205 | getMask(); 206 | for (signed char i = 10; i >= 0; i--) 207 | { 208 | if (SinclairData.mask[i] == ' ') 209 | { 210 | // masked out 211 | // continue; 212 | } 213 | else 214 | { 215 | // Flip dst if val == -1, otherwise set to val 216 | dest[i] = (val < 0) ? (1 - dest[i]) : val; 217 | } 218 | } 219 | } 220 | 221 | void compareFlags(signed char src1[], signed char src2[]) 222 | { 223 | signed char cc = 0; 224 | getMask(); 225 | for (signed char i = 10; i >= 0; i--) 226 | { 227 | if (SinclairData.mask[i] == ' ') 228 | { 229 | // masked out 230 | // continue; 231 | } 232 | else 233 | { 234 | if (src1[i] != src2[i]) 235 | { 236 | cc = 1; 237 | } 238 | } 239 | } 240 | if (cc) 241 | { 242 | SinclairData.cc = 1; 243 | //SinclairData.ccMeaning = 'flags not equal'; 244 | } 245 | } 246 | 247 | void exchange(signed char src1[], signed char src2[]) 248 | { 249 | getMask(); 250 | for (signed char i = 10; i >= 0; i--) 251 | { 252 | if (SinclairData.mask[i] == ' ') 253 | { 254 | // masked out 255 | // continue; 256 | } 257 | else 258 | { 259 | signed char t = src1[i]; 260 | src1[i] = src2[i]; 261 | src2[i] = t; 262 | } 263 | } 264 | } 265 | 266 | void testFlag(signed char src[]) 267 | { 268 | signed char cc = 0; 269 | getMask(); 270 | for (signed char i = 10; i >= 0; i--) 271 | { 272 | if (SinclairData.mask[i] == ' ') 273 | { 274 | // masked out 275 | // continue; 276 | } 277 | else 278 | { 279 | if (src[i]) 280 | { 281 | cc = 1; 282 | } 283 | } 284 | } 285 | /* Only update cc if bit set */ 286 | if (cc) 287 | { 288 | SinclairData.cc = cc; 289 | //SinclairData.ccMeaning = 'flag set'; 290 | } 291 | } 292 | 293 | void updateD() 294 | { 295 | for (signed char i = 10; i >= 0; i--) 296 | { 297 | SinclairData.d[i] = 1; 298 | } 299 | 300 | SinclairData.dActive += 1; 301 | if (SinclairData.dActive > 10) 302 | { 303 | SinclairData.dActive = 1; 304 | } 305 | SinclairData.d[SinclairData.dActive - 1] = 0; 306 | } 307 | 308 | void step() 309 | { 310 | //Serial.print(F("addr:")); 311 | //Serial.print(SinclairData.address); 312 | 313 | unsigned long entrytime = micros(); 314 | 315 | unsigned int instruction = getInstruction(SinclairData.address); 316 | byte classBits = instruction >> 9; 317 | byte opcode = (instruction >> 4) & 0x1f; 318 | unsigned int nextAddress = SinclairData.address + 1; 319 | 320 | //Serial.print(F(" data:")); 321 | //Serial.print(opcode); 322 | //Serial.print(F(" classbits:")); 323 | //Serial.print(classBits); 324 | 325 | if (classBits == 3) 326 | { 327 | // Register instruction 328 | byte maskBits = instruction & 0xf; 329 | switch (opcode) 330 | { 331 | case 0: // AABA: A+B -> A 332 | displayInstruction(1); 333 | add(SinclairData.a, SinclairData.b, SinclairData.a); 334 | break; 335 | case 1: // AAKA: A+K -> A 336 | displayInstruction(2); 337 | add(SinclairData.a, getMask(), SinclairData.a); 338 | break; 339 | case 2: // AAKC: A+K -> C 340 | displayInstruction(3); 341 | add(SinclairData.a, getMask(), SinclairData.c); 342 | break; 343 | case 3: 344 | if (SinclairData.sinclair) 345 | { // ACBB C+B -> B 346 | displayInstruction(4); 347 | add(SinclairData.c, SinclairData.b, SinclairData.b); 348 | } 349 | else 350 | { // ABOA: B -> A 351 | displayInstruction(5); 352 | copy(SinclairData.b, SinclairData.a); 353 | } 354 | break; 355 | case 4: // ABOC: B -> C 356 | displayInstruction(6); 357 | copy(SinclairData.b, SinclairData.c); 358 | break; 359 | case 5: // ACKA: C+K -> A 360 | displayInstruction(7); 361 | add(SinclairData.c, getMask(), SinclairData.a); 362 | break; 363 | case 6: // AKCB: C+K -> B 364 | displayInstruction(8); 365 | add(SinclairData.c, getMask(), SinclairData.b); 366 | break; 367 | case 7: // SABA: A-B -> A 368 | displayInstruction(9); 369 | sub(SinclairData.a, SinclairData.b, SinclairData.a); 370 | break; 371 | case 8: // SABC: A-B -> C 372 | displayInstruction(10); 373 | sub(SinclairData.a, SinclairData.b, SinclairData.c); 374 | break; 375 | case 9: // SAKA: A-K -> A 376 | displayInstruction(11); 377 | sub(SinclairData.a, getMask(), SinclairData.a); 378 | break; 379 | case 10: // SCBC: C-B -> C 380 | displayInstruction(12); 381 | sub(SinclairData.c, SinclairData.b, SinclairData.c); 382 | break; 383 | case 11: // SCKC: C-K -> C 384 | displayInstruction(13); 385 | sub(SinclairData.c, getMask(), SinclairData.c); 386 | break; 387 | case 12: // CAB: compare A-B 388 | displayInstruction(14); 389 | compare(SinclairData.a, SinclairData.b); 390 | break; 391 | case 13: // CAK: compare A-K 392 | displayInstruction(15); 393 | compare(SinclairData.a, getMask()); 394 | break; 395 | case 14: // CCB: compare C-B 396 | displayInstruction(16); 397 | compare(SinclairData.c, SinclairData.b); 398 | break; 399 | case 15: // CCK: compare C-K 400 | displayInstruction(17); 401 | compare(SinclairData.c, getMask()); 402 | break; 403 | case 16: // AKA: K -> A 404 | displayInstruction(18); 405 | copy(getMask(), SinclairData.a); 406 | break; 407 | case 17: // AKB: K -> B 408 | displayInstruction(19); 409 | copy(getMask(), SinclairData.b); 410 | break; 411 | case 18: // AKC: K -> C 412 | displayInstruction(20); 413 | copy(getMask(), SinclairData.c); 414 | break; 415 | case 19: // EXAB: exchange A and B 416 | displayInstruction(21); 417 | exchange(SinclairData.a, SinclairData.b); 418 | break; 419 | case 20: // SLLA: shift A left 420 | displayInstruction(22); 421 | sll(SinclairData.a); 422 | break; 423 | case 21: // SLLB: shift B left 424 | displayInstruction(23); 425 | sll(SinclairData.b); 426 | break; 427 | case 22: // SLLC: shift C left 428 | displayInstruction(24); 429 | sll(SinclairData.c); 430 | break; 431 | case 23: // SRLA: shift A right 432 | displayInstruction(25); 433 | srl(SinclairData.a); 434 | break; 435 | case 24: // SRLB: shift B right 436 | displayInstruction(26); 437 | srl(SinclairData.b); 438 | break; 439 | case 25: // SRLC: shift C right 440 | displayInstruction(66); 441 | srl(SinclairData.c); 442 | break; 443 | case 26: // AKCN: A+K -> A until key down on N or D11 [sic] 444 | // Patent says sets condition if key down, but real behavior 445 | // is to set condition if addition overflows (i.e. no key down) 446 | //SinclairData.display = 0; //comment this line to glitch the display when a number key is pressed (SINCLAIR behavior: actual hardware behavior) 447 | add(SinclairData.a, getMask(), SinclairData.a); 448 | if (SinclairData.keyStrobe == KN) 449 | { 450 | displayInstruction(27); 451 | // Advance to next instruction 452 | } 453 | else if (SinclairData.dActive != 10) 454 | { 455 | displayInstruction(28); 456 | // Hold at current instruction and continue scan 457 | nextAddress = SinclairData.address; 458 | } 459 | else 460 | { 461 | displayInstruction(29); 462 | // For state d10, fall through 463 | } 464 | break; 465 | case 27: 466 | if (SinclairData.sinclair) 467 | { // SCBA C-B -> A 468 | displayInstruction(30); 469 | sub(SinclairData.c, SinclairData.b, SinclairData.a); 470 | } 471 | else 472 | { // AAKAH A+K -> A hex 473 | displayInstruction(31); 474 | add(SinclairData.a, getMask(), SinclairData.a, 1 /* hex */ ); 475 | SinclairData.cc = 0; 476 | //SinclairData.ccMeaning = ''; 477 | } 478 | break; 479 | case 28: 480 | if (SinclairData.sinclair) 481 | { // SCKB C-K -> B 482 | displayInstruction(32); 483 | sub(SinclairData.c, getMask(), SinclairData.b); 484 | } 485 | else 486 | { // SAKAH A-K -> A hex 487 | displayInstruction(33); 488 | sub(SinclairData.a, getMask(), SinclairData.a, 1 /* hex */ ); 489 | SinclairData.cc = 0; 490 | //SinclairData.ccMeaning = ''; 491 | } 492 | break; 493 | case 29: // ACKC: C+K -> C 494 | displayInstruction(34); 495 | add(SinclairData.c, getMask(), SinclairData.c); 496 | break; 497 | case 30: 498 | if (SinclairData.sinclair) 499 | { // AABC A+B -> C 500 | displayInstruction(35); 501 | add(SinclairData.a, SinclairData.b, SinclairData.c); 502 | break; 503 | } 504 | case 31: 505 | if (SinclairData.sinclair) 506 | { // ACBC C+B -> C 507 | displayInstruction(36); 508 | add(SinclairData.c, SinclairData.b, SinclairData.c); 509 | break; 510 | } 511 | default: 512 | //bad instruction 513 | //alert('Bad instruction ' + instruction); 514 | displayInstruction(37); 515 | break; 516 | } 517 | } 518 | else if ((instruction >> 8) == 5) 519 | { 520 | // Flag instruction 521 | byte maskBits = instruction & 0xf; 522 | switch (opcode) 523 | { 524 | case 16: // NOP 525 | displayInstruction(38); 526 | break; 527 | case 17: // WAITDK: wait for display key 528 | SinclairData.display = 0; 529 | if (SinclairData.keyPressed == DK) 530 | { 531 | // Jump 532 | displayInstruction(39); 533 | nextAddress = instruction & 0x1ff; 534 | } 535 | else 536 | { 537 | // Hold address until DK pressed 538 | displayInstruction(40); 539 | nextAddress = SinclairData.address; 540 | } 541 | break; 542 | case 18: // WAITNO: wait for key or address register overflow 543 | if (SinclairData.keyStrobe) 544 | { 545 | // Jump 546 | displayInstruction(41); 547 | nextAddress = instruction & 0x1ff; 548 | } 549 | else 550 | { 551 | // Hold address until key pressed or address overflow (TODO) 552 | displayInstruction(42); 553 | nextAddress = SinclairData.address; 554 | } 555 | break; 556 | case 19: // SFB: set flag B 557 | displayInstruction(43); 558 | writeFlag(SinclairData.bf, 1); 559 | break; 560 | case 20: // SFA: set flag A 561 | displayInstruction(44); 562 | writeFlag(SinclairData.af, 1); 563 | break; 564 | case 21: // SYNC (SYNCH): hold address until end of D10 565 | displayInstruction(45); 566 | if (SinclairData.dActive != 10) 567 | { 568 | nextAddress = SinclairData.address; 569 | } 570 | SinclairData.cc = 0; 571 | //SinclairData.ccMeaning = ''; 572 | break; 573 | case 22: // SCAN (SCANNO): wait for key 574 | SinclairData.display = 1; // Reset display power off latch 575 | if (SinclairData.keyStrobe) 576 | { 577 | displayInstruction(46); 578 | SinclairData.cc = 1; 579 | //SinclairData.ccMeaning = 'key'; 580 | } 581 | else 582 | { 583 | displayInstruction(47); 584 | SinclairData.cc = 0; 585 | //SinclairData.ccMeaning = 'no key'; 586 | if (SinclairData.dActive != 10) 587 | { 588 | // Hold address until end of D10 589 | nextAddress = SinclairData.address; 590 | } 591 | } 592 | break; 593 | case 23: // ZFB: zero flag B 594 | displayInstruction(48); 595 | writeFlag(SinclairData.bf, 0); 596 | break; 597 | case 24: // ZFA: zero flag A 598 | displayInstruction(49); 599 | writeFlag(SinclairData.af, 0); 600 | break; 601 | case 25: // TFB: test flag B 602 | displayInstruction(50); 603 | testFlag(SinclairData.bf); 604 | break; 605 | case 26: // TFA: test flag A 606 | displayInstruction(51); 607 | testFlag(SinclairData.af); 608 | break; 609 | case 27: // FFB: flip flag B 610 | displayInstruction(52); 611 | writeFlag(SinclairData.bf, -1 /* flip */ ); 612 | break; 613 | case 28: // FFA: flip flag A 614 | displayInstruction(67); 615 | writeFlag(SinclairData.af, -1 /* flip */ ); 616 | break; 617 | case 29: // CF: compare flags 618 | displayInstruction(53); 619 | compareFlags(SinclairData.af, SinclairData.bf); 620 | break; 621 | case 30: // NOP 622 | displayInstruction(54); 623 | break; 624 | case 31: // EXF: exchange flags 625 | displayInstruction(55); 626 | exchange(SinclairData.af, SinclairData.bf); 627 | break; 628 | default: 629 | //bad instruction 630 | //alert('Bad instruction ' + instruction); 631 | displayInstruction(56); 632 | break; 633 | } 634 | } 635 | else if (classBits == 0) 636 | { 637 | // jump if reset: BIU, BIZ, BIGE, BINC, BIE, BET 638 | displayInstruction(57); 639 | if (SinclairData.cc == 0) 640 | { 641 | displayInstruction(58); 642 | nextAddress = instruction & 0x1ff; 643 | } 644 | SinclairData.cc = 0; // Clear after jump 645 | //SinclairData.ccMeaning = ''; 646 | } 647 | else if (classBits == 1) 648 | { 649 | // jump if set: BID, BIO, BILT, BIC, BINE 650 | displayInstruction(59); 651 | if (SinclairData.cc == 1) 652 | { 653 | displayInstruction(60); 654 | nextAddress = instruction & 0x1ff; 655 | } 656 | SinclairData.cc = 0; // Clear after jump 657 | //SinclairData.ccMeaning = ''; 658 | } 659 | else if ((instruction >> 7) == 8) 660 | { 661 | // Jump if key down on KO (BKO) 662 | displayInstruction(61); 663 | if (SinclairData.keyStrobe == KO) 664 | { 665 | SinclairData.display = 0; 666 | displayInstruction(62); 667 | nextAddress = instruction & 0x1ff; 668 | } 669 | SinclairData.cc = 0; // Clear after jump 670 | //SinclairData.ccMeaning = ''; 671 | } 672 | else if ((instruction >> 7) == 9) 673 | { 674 | // Jump if key down on KP (BKP) 675 | displayInstruction(63); 676 | if (SinclairData.keyStrobe == KP) 677 | { 678 | SinclairData.display = 0; 679 | displayInstruction(64); 680 | nextAddress = instruction & 0x1ff; 681 | } 682 | SinclairData.cc = 0; // Clear after jump 683 | //SinclairData.ccMeaning = ''; 684 | } 685 | else 686 | { 687 | displayInstruction(65); 688 | //bad instruction 689 | //alert('Bad instruction code ' + instruction); 690 | } 691 | SinclairData.address = nextAddress; 692 | // Put the mask for the next instruction in the model for display 693 | //SinclairData.mask = getMask(); 694 | getMask(); 695 | // Update D state 696 | updateD(); 697 | 698 | displayInstruction(68); // if printing is enabled, do a println after executing a line of code 699 | 700 | byte exectime = micros() - entrytime; 701 | 702 | if (exectime < SinclairData.steptime) 703 | { 704 | delayMicroseconds(SinclairData.steptime - exectime); 705 | } 706 | } 707 | -------------------------------------------------------------------------------- /SinclairScientific1/DisAssembler.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | 12 | void displayArray(signed char src1[]) { 13 | for (byte i = 0; i < 11; i++) 14 | { 15 | Serial.print((int)src1[i]); 16 | Serial.print(F(" ")); 17 | } 18 | Serial.println(F("")); 19 | } 20 | 21 | //rename this function: void displayInstruction(byte dummy) 22 | //to DISABLE instruction viaualization in step() 23 | void displayInstruction(byte dummy) 24 | { 25 | 26 | } 27 | 28 | //rename this function: void displayInstruction(byte dummy) 29 | //to ENABLE instruction viaualization in step() 30 | void displayInstruction1(byte instructionid) 31 | { 32 | Serial.print(F(" (")); 33 | Serial.print(instructionid); 34 | Serial.print(F(":")); 35 | switch (instructionid) 36 | { 37 | case 1: 38 | Serial.print(F("AABA: A+B -> A")); 39 | break; 40 | 41 | case 2: 42 | Serial.print(F("AABA: A+B -> A")); 43 | break; 44 | 45 | case 3: 46 | Serial.print(F("AAKC: A+K -> C")); 47 | break; 48 | 49 | case 4: 50 | Serial.print(F("ACBB C+B -> B")); 51 | break; 52 | 53 | case 5: 54 | Serial.print(F("ABOA: B -> A")); 55 | break; 56 | 57 | case 6: 58 | Serial.print(F("ABOC: B -> C")); 59 | break; 60 | 61 | case 7: 62 | Serial.print(F("ACKA: C+K -> A")); 63 | break; 64 | 65 | case 8: 66 | Serial.print(F("AKCB: C+K -> B")); 67 | break; 68 | 69 | case 9: 70 | Serial.print(F("SABA: A-B -> A")); 71 | break; 72 | 73 | case 10: 74 | Serial.print(F("SABC: A-B -> C")); 75 | break; 76 | 77 | case 11: 78 | Serial.print(F("SAKA: A-K -> A")); 79 | break; 80 | 81 | case 12: 82 | Serial.print(F("SCBC: C-B -> C")); 83 | break; 84 | 85 | case 13: 86 | Serial.print(F("SCKC: C-K -> C")); 87 | break; 88 | 89 | case 14: 90 | Serial.print(F("CAB: compare A-B")); 91 | break; 92 | 93 | case 15: 94 | Serial.print(F("CAK: compare A-K")); 95 | break; 96 | 97 | case 16: 98 | Serial.print(F("CCB: compare C-B")); 99 | break; 100 | 101 | case 17: 102 | Serial.print(F("CCK: compare C-K")); 103 | break; 104 | 105 | case 18: 106 | Serial.print(F("AKA: K -> A")); 107 | break; 108 | 109 | case 19: 110 | Serial.print(F("AKB: K -> B")); 111 | break; 112 | 113 | case 20: 114 | Serial.print(F("AKC: K -> C")); 115 | break; 116 | 117 | case 21: 118 | Serial.print(F("EXAB: exchange A and B")); 119 | break; 120 | 121 | case 22: 122 | Serial.print(F("SLLA: shift A left")); 123 | break; 124 | 125 | case 23: 126 | Serial.print(F("SLLB: shift B left")); 127 | break; 128 | 129 | case 24: 130 | Serial.print(F("SLLC: shift C left")); 131 | break; 132 | 133 | case 25: 134 | Serial.print(F("SRLA: shift A right")); 135 | break; 136 | 137 | case 26: 138 | Serial.print(F("SRLB: shift B right")); 139 | break; 140 | 141 | case 27: 142 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (ADVANCE)")); 143 | break; 144 | 145 | case 28: 146 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (HOLD)")); 147 | break; 148 | 149 | case 29: 150 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (D10 FALLTHROUGH)")); 151 | break; 152 | 153 | case 30: 154 | Serial.print(F("SCBA C-B -> A")); 155 | break; 156 | 157 | case 31: 158 | Serial.print(F("AAKAH A+K -> A hex")); 159 | break; 160 | 161 | case 32: 162 | Serial.print(F("SCKB C-K -> B")); 163 | break; 164 | 165 | case 33: 166 | Serial.print(F("SAKAH A-K -> A hex")); 167 | break; 168 | 169 | case 34: 170 | Serial.print(F("ACKC: C+K -> C")); 171 | break; 172 | 173 | case 35: 174 | Serial.print(F("AABC A+B -> C")); 175 | break; 176 | 177 | case 36: 178 | Serial.print(F("ACBC C+B -> C")); 179 | break; 180 | 181 | case 37: 182 | Serial.print(F("Bad instruction")); 183 | break; 184 | 185 | case 38: 186 | Serial.print(F("NOP")); 187 | break; 188 | 189 | case 39: 190 | Serial.print(F("WAITDK: wait for display key DK (JUMP)")); 191 | break; 192 | 193 | case 40: 194 | Serial.print(F("WAITDK: wait for display key DK (HOLD)")); 195 | break; 196 | 197 | case 41: 198 | Serial.print(F("WAITNO: wait for key or address register overflow (JUMP)")); 199 | break; 200 | 201 | case 42: 202 | Serial.print(F("WAITNO: wait for key or address register overflow (HOLDING)")); 203 | break; 204 | 205 | case 43: 206 | Serial.print(F("SFB: set flag B")); 207 | break; 208 | 209 | case 44: 210 | Serial.print(F("SFA: set flag A")); 211 | break; 212 | 213 | case 45: 214 | Serial.print(F("SYNC (SYNCH): hold address until end of D10")); 215 | break; 216 | 217 | case 46: 218 | Serial.print(F("SCAN (SCANNO): wait for key (KEY)")); 219 | break; 220 | 221 | case 47: 222 | Serial.print(F("SCAN (SCANNO): wait for key (NO KEY)")); 223 | break; 224 | 225 | case 48: 226 | Serial.print(F("ZFB: zero flag B")); 227 | break; 228 | 229 | case 49: 230 | Serial.print(F("ZFA: zero flag A")); 231 | break; 232 | 233 | case 50: 234 | Serial.print(F("TFB: test flag B")); 235 | break; 236 | 237 | case 51: 238 | Serial.print(F("TFA: test flag A")); 239 | break; 240 | 241 | case 52: 242 | Serial.print(F("FFB: flip flag B")); 243 | break; 244 | 245 | case 53: 246 | Serial.print(F("CF: compare flags")); 247 | break; 248 | 249 | case 54: 250 | Serial.print(F("NOP")); 251 | break; 252 | 253 | case 55: 254 | Serial.print(F("EXF: exchange flags")); 255 | break; 256 | 257 | case 56: 258 | Serial.print(F("Bad instruction")); 259 | break; 260 | 261 | case 57: 262 | Serial.print(F("Jump if reset: BIU, BIZ, BIGE, BINC, BIE, BET ")); 263 | break; 264 | 265 | case 58: 266 | Serial.print(F("JUMP IF RESET TAKEN")); 267 | break; 268 | 269 | case 59: 270 | Serial.print(F("Jump if set: BID, BIO, BILT, BIC, BINE")); 271 | break; 272 | 273 | case 60: 274 | Serial.print(F("JUMP IF SET TAKEN")); 275 | break; 276 | 277 | case 61: 278 | Serial.print(F("Jump if key down on KO (BKO)")); 279 | break; 280 | 281 | case 62: 282 | Serial.print(F("JUMP IF KEY DOWN ON KO TAKEN")); 283 | break; 284 | 285 | case 63: 286 | Serial.print(F("Jump if key down on KP (BKP)")); 287 | break; 288 | 289 | case 64: 290 | Serial.print(F("JUMP IF KEY DOWN ON KP TAKEN")); 291 | break; 292 | 293 | case 65: 294 | Serial.print(F("Bad instruction code")); 295 | break; 296 | 297 | case 66: 298 | Serial.print(F("SRLC: shift C right")); 299 | break; 300 | 301 | case 67: 302 | Serial.print(F("FFA: flip flag A")); 303 | break; 304 | 305 | case 68: 306 | Serial.println(F("")); // print end of line at end of void step() 307 | break; 308 | 309 | default: 310 | Serial.print(F("UNKNOWN INSTRUCTION ID")); 311 | break; 312 | } 313 | Serial.print(F(")")); 314 | } 315 | -------------------------------------------------------------------------------- /SinclairScientific1/DisplayAndKeys.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // tedious but fast, most lines compile to 1 instruction. 12 | // this whole file compiles to less than 1KB of code 13 | // 14 | 15 | GPIO KeyRowB; 16 | GPIO KeyRowC; 17 | GPIO KeyRowA; 18 | 19 | GPIO SegmentA; 20 | GPIO SegmentB; 21 | GPIO SegmentC; 22 | GPIO SegmentD; 23 | GPIO SegmentE; 24 | GPIO SegmentF; 25 | GPIO SegmentG; 26 | GPIO SegmentDP; 27 | 28 | GPIO Digit1; 29 | GPIO Digit2; 30 | GPIO Digit3; 31 | GPIO Digit4; 32 | GPIO Digit5; 33 | GPIO Digit6; 34 | GPIO Digit7; 35 | GPIO Digit8; 36 | GPIO Digit9; 37 | 38 | void allSegmentOutput() { 39 | SegmentA.output(); 40 | SegmentB.output(); 41 | SegmentC.output(); 42 | SegmentD.output(); 43 | SegmentE.output(); 44 | SegmentF.output(); 45 | SegmentG.output(); 46 | SegmentDP.output(); 47 | } 48 | 49 | void allSegmentOff() { 50 | SegmentA.high(); 51 | SegmentB.high(); 52 | SegmentC.high(); 53 | SegmentD.high(); 54 | SegmentE.high(); 55 | SegmentF.high(); 56 | SegmentG.high(); 57 | SegmentDP.high(); 58 | } 59 | 60 | void allSegmentInput() { 61 | SegmentA.input(); 62 | SegmentB.input(); 63 | SegmentC.input(); 64 | SegmentD.input(); 65 | SegmentE.input(); 66 | SegmentF.input(); 67 | SegmentG.input(); 68 | SegmentDP.input(); 69 | 70 | SegmentA.high(); 71 | SegmentB.high(); 72 | SegmentC.high(); 73 | SegmentD.high(); 74 | SegmentE.high(); 75 | SegmentF.high(); 76 | SegmentG.high(); 77 | SegmentDP.high(); 78 | } 79 | 80 | void allDigitOutput() { 81 | Digit1.output(); 82 | Digit2.output(); 83 | Digit3.output(); 84 | Digit4.output(); 85 | Digit5.output(); 86 | Digit6.output(); 87 | Digit7.output(); 88 | Digit8.output(); 89 | Digit9.output(); 90 | } 91 | 92 | void allDigitOff() { 93 | Digit1.low(); 94 | Digit2.low(); 95 | Digit3.low(); 96 | Digit4.low(); 97 | Digit5.low(); 98 | Digit6.low(); 99 | Digit7.low(); 100 | Digit8.low(); 101 | Digit9.low(); 102 | } 103 | 104 | void allKeyRowOff() { 105 | KeyRowA.input(); 106 | KeyRowB.input(); 107 | KeyRowC.input(); 108 | 109 | KeyRowA.low(); 110 | KeyRowB.low(); 111 | KeyRowC.low(); 112 | } 113 | 114 | void allKeyRowIdle() { 115 | KeyRowA.output(); 116 | KeyRowB.output(); 117 | KeyRowC.output(); 118 | 119 | KeyRowA.high(); 120 | KeyRowB.high(); 121 | KeyRowC.high(); 122 | } 123 | 124 | byte outputDigit(signed char digit, bool decimalpoint = false) { 125 | 126 | byte segmentslit = 0; 127 | 128 | allDigitOff(); 129 | 130 | if (decimalpoint) 131 | { 132 | segmentslit++; 133 | SegmentDP.low(); 134 | } 135 | else 136 | { 137 | SegmentDP.high(); 138 | } 139 | 140 | switch (digit) { 141 | case 0: 142 | SegmentA.low(); 143 | SegmentB.low(); 144 | SegmentC.low(); 145 | SegmentD.low(); 146 | SegmentE.low(); 147 | SegmentF.low(); 148 | SegmentG.high(); 149 | segmentslit += 6; 150 | break; 151 | case 1: 152 | SegmentA.high(); 153 | SegmentB.low(); 154 | SegmentC.low(); 155 | SegmentD.high(); 156 | SegmentE.high(); 157 | SegmentF.high(); 158 | SegmentG.high(); 159 | segmentslit += 2; 160 | break; 161 | case 2: 162 | SegmentA.low(); 163 | SegmentB.low(); 164 | SegmentC.high(); 165 | SegmentD.low(); 166 | SegmentE.low(); 167 | SegmentF.high(); 168 | SegmentG.low(); 169 | segmentslit += 5; 170 | break; 171 | case 3: 172 | SegmentA.low(); 173 | SegmentB.low(); 174 | SegmentC.low(); 175 | SegmentD.low(); 176 | SegmentE.high(); 177 | SegmentF.high(); 178 | SegmentG.low(); 179 | segmentslit += 5; 180 | break; 181 | case 4: 182 | SegmentA.high(); 183 | SegmentB.low(); 184 | SegmentC.low(); 185 | SegmentD.high(); 186 | SegmentE.high(); 187 | SegmentF.low(); 188 | SegmentG.low(); 189 | segmentslit += 4; 190 | break; 191 | case 5: 192 | SegmentA.low(); 193 | SegmentB.high(); 194 | SegmentC.low(); 195 | SegmentD.low(); 196 | SegmentE.high(); 197 | SegmentF.low(); 198 | SegmentG.low(); 199 | segmentslit += 5; 200 | break; 201 | case 6: 202 | SegmentA.low(); 203 | SegmentB.high(); 204 | SegmentC.low(); 205 | SegmentD.low(); 206 | SegmentE.low(); 207 | SegmentF.low(); 208 | SegmentG.low(); 209 | segmentslit += 6; 210 | break; 211 | case 7: 212 | SegmentA.low(); 213 | SegmentB.low(); 214 | SegmentC.low(); 215 | SegmentD.high(); 216 | SegmentE.high(); 217 | SegmentF.high(); 218 | SegmentG.high(); 219 | segmentslit += 3; 220 | break; 221 | case 8: 222 | SegmentA.low(); 223 | SegmentB.low(); 224 | SegmentC.low(); 225 | SegmentD.low(); 226 | SegmentE.low(); 227 | SegmentF.low(); 228 | SegmentG.low(); 229 | segmentslit += 7; 230 | break; 231 | case 9: 232 | SegmentA.low(); 233 | SegmentB.low(); 234 | SegmentC.low(); 235 | SegmentD.high(); 236 | SegmentE.high(); 237 | SegmentF.low(); 238 | SegmentG.low(); 239 | break; 240 | segmentslit += 5; 241 | case 10: 242 | SegmentA.high(); 243 | SegmentB.high(); 244 | SegmentC.high(); 245 | SegmentD.high(); 246 | SegmentE.high(); 247 | SegmentF.high(); 248 | SegmentG.low(); 249 | segmentslit += 1; 250 | break; 251 | case 99: 252 | SegmentA.high(); 253 | SegmentB.high(); 254 | SegmentC.high(); 255 | SegmentD.high(); 256 | SegmentE.high(); 257 | SegmentF.high(); 258 | SegmentG.high(); 259 | break; 260 | default: 261 | allSegmentOff(); 262 | break; 263 | } 264 | 265 | return segmentslit; 266 | } 267 | 268 | void selectDigit(byte digit) { 269 | 270 | allDigitOff(); 271 | 272 | switch (digit) { 273 | case 0: 274 | Digit1.high(); 275 | break; 276 | case 1: 277 | Digit2.high(); 278 | break; 279 | case 2: 280 | Digit3.high(); 281 | break; 282 | case 3: 283 | Digit4.high(); 284 | break; 285 | case 4: 286 | Digit5.high(); 287 | break; 288 | case 5: 289 | Digit6.high(); 290 | break; 291 | case 6: 292 | Digit7.high(); 293 | break; 294 | case 7: 295 | Digit8.high(); 296 | break; 297 | case 8: 298 | Digit9.high(); 299 | break; 300 | } 301 | } 302 | 303 | // this function is called by the display routines to show a digit for 2ms before moving to the next one. 304 | // can do useful work here... instead of delay(); 305 | 306 | void backgroundWork() { 307 | 308 | } 309 | 310 | void display() { 311 | 312 | bool dp = false; 313 | 314 | for (byte i = 0; i < 9; i++) { 315 | 316 | //SINCLAIR behavior: turn decimal point on automatically at fixed position 317 | if (i == 1) { 318 | dp = true; 319 | } 320 | else 321 | { 322 | dp = false; 323 | } 324 | 325 | outputDigit(digits[i], dp); 326 | selectDigit(i); 327 | 328 | backgroundWork(); 329 | } 330 | } 331 | 332 | 333 | void displaySelfTest(bool longtest = false) { 334 | 335 | char c = 0; 336 | 337 | //kitt stuff 338 | byte pos = 0; 339 | byte p1 = 0; 340 | byte p2 = 0; 341 | char sign = 0; 342 | 343 | bool kitt = false; 344 | 345 | // show 8.8.8.8.8.8.8.8.8. 346 | for (byte t = 0; t < 75; t++) { 347 | for (byte j = 0; j < 9; j++) { 348 | c = readKey(); 349 | if (c == '8') { 350 | longtest = true; 351 | } 352 | if ((longtest == true ) && (c == '2')) { 353 | kitt = true; 354 | } 355 | if (c == 'C') { 356 | goto exitfn; 357 | } 358 | outputDigit(8, true); 359 | selectDigit(j); 360 | delay(2); 361 | } 362 | } 363 | 364 | if (kitt) { 365 | goto kitt; 366 | } 367 | 368 | if (!longtest) { 369 | goto exitfn; 370 | } 371 | 372 | // cycle through 0..9 - on all digits at the same time 373 | for (byte i = 0; i < 12; i++) { 374 | for (byte t = 0; t < 100; t++) { 375 | for (byte j = 0; j < 9; j++) { 376 | if (readKey() == 'C') { 377 | goto exitfn; 378 | } 379 | outputDigit((i != 11) ? i : 99, (i == 11)); 380 | selectDigit(j); 381 | delay(2); 382 | } 383 | } 384 | } 385 | 386 | // show 12345678 387 | for (byte t = 0; t < 100; t++) { 388 | for (byte j = 0; j < 9; j++) { 389 | if (readKey() == 'C') { 390 | goto exitfn; 391 | } 392 | outputDigit(j, false); 393 | selectDigit(j); 394 | delay(2); 395 | } 396 | } 397 | 398 | // move the dot from left to right on 12345678 399 | for (byte i = 0; i < 9; i++) { 400 | for (byte t = 0; t < 100; t++) { 401 | for (byte j = 0; j < 9; j++) { 402 | if (readKey() == 'C') { 403 | goto exitfn; 404 | } 405 | outputDigit(j, i == j); 406 | selectDigit(j); 407 | delay(2); 408 | } 409 | } 410 | } 411 | 412 | // move dot from right to left on blank display 413 | for (byte j = 9; j > 0; j--) { 414 | for (byte t = 0; t < 100; t++) { 415 | for (byte i = 0; i < 9; i++) { 416 | if (readKey() == 'C') { 417 | goto exitfn; 418 | } 419 | outputDigit(99, i == (j - 1)); 420 | selectDigit(i); 421 | delay(2); 422 | } 423 | } 424 | } 425 | 426 | // cycle through 0..9 - on each digit individually 427 | for (byte j = 0; j < 9; j++) { 428 | for (byte i = 0; i < 12; i++) { 429 | for (byte t = 0; t < 20; t++) { 430 | for (byte k = 0; k < 9; k++) { 431 | if (readKey() == 'C') { 432 | goto exitfn; 433 | } 434 | outputDigit( ((k == j) && (i != 11)) ? i : 99, ((k == j) && (i == 11))); 435 | selectDigit(k); 436 | delay(2); 437 | } 438 | } 439 | } 440 | } 441 | 442 | goto exitfn; 443 | 444 | kitt: 445 | 446 | for (byte i = 0; i < 249; i++) 447 | { 448 | p2 = p1; 449 | p1 = pos; 450 | pos += sign; 451 | 452 | if (pos == 0) { 453 | sign = 1; 454 | } 455 | 456 | if (pos == 8) { 457 | sign = -1; 458 | } 459 | 460 | for (byte t = 0; t < 25; t++) { 461 | for (byte j = 0; j < 9; j++) { 462 | c = readKey(); 463 | if (c == 'C') { 464 | goto exitfn; 465 | } 466 | outputDigit(10, false); 467 | selectDigit(j); 468 | if (j == pos) 469 | { 470 | delayMicroseconds(1000); 471 | } else if (j == p1) 472 | { 473 | delayMicroseconds(500); 474 | } else if (j == p2) 475 | { 476 | delayMicroseconds(100); 477 | } else 478 | delayMicroseconds(20); 479 | } 480 | } 481 | } 482 | 483 | exitfn: 484 | allSegmentOff(); 485 | allDigitOff(); 486 | } 487 | 488 | 489 | const char PrintableKeys[19] = "12+E0v-378X654/9^C"; 490 | 491 | byte readKey() { 492 | 493 | byte key = 0; 494 | 495 | allDigitOff(); 496 | allSegmentInput(); 497 | 498 | for (byte i = 0; i < 3; i++) 499 | { 500 | allKeyRowIdle(); 501 | switch (i) { 502 | case 0: 503 | KeyRowA.low(); 504 | delayMicroseconds(2); //for reliability on first read instruction, allow for .low on line before to settle 505 | if (!SegmentA.read()) key = 1; 506 | if (!SegmentB.read()) key = 2; 507 | if (!SegmentC.read()) key = 3; 508 | if (!SegmentD.read()) key = 4; 509 | if (!SegmentE.read()) key = 5; 510 | if (!SegmentF.read()) key = 6; 511 | if (!SegmentG.read()) key = 7; 512 | if (!SegmentDP.read()) key = 8; 513 | break; 514 | case 1: 515 | KeyRowB.low(); 516 | delayMicroseconds(2); 517 | if (!SegmentA.read()) key = 9; 518 | if (!SegmentB.read()) key = 10; 519 | if (!SegmentC.read()) key = 11; 520 | if (!SegmentD.read()) key = 12; 521 | if (!SegmentE.read()) key = 13; 522 | if (!SegmentF.read()) key = 14; 523 | if (!SegmentG.read()) key = 15; 524 | if (!SegmentDP.read()) key = 16; 525 | break; 526 | case 2: 527 | KeyRowC.low(); 528 | delayMicroseconds(2); 529 | if (!SegmentA.read()) key = 17; 530 | if (!SegmentB.read()) key = 18; 531 | break; 532 | } 533 | } 534 | 535 | allKeyRowOff(); 536 | allSegmentOutput(); 537 | 538 | if (key) 539 | { 540 | key = PrintableKeys[key - 1]; 541 | } 542 | 543 | return key; 544 | } 545 | -------------------------------------------------------------------------------- /SinclairScientific1/SinclairScientific1.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // To test the up/down buttons press: 12 | // C up X up / up + up - 5.4105-01 13 | // C dn X dn / dn + dn - 4.4200-01 14 | // 15 | // Sinclair Scientific Glitches implemented: 16 | // dot stays on when display turns off, dot gets brighter 17 | // pressing 0 makes dot brighter, other keys not so much 18 | // pressing a key glitches the number displayed, 19 | // pressing 7 glitches more numbers than pressing 1 20 | // pressing C dims the display 21 | // 22 | // Glitches not implemented: 23 | // 1 is brighter than 0 24 | // - in mantissa is brighter 25 | // 26 | // Other Features: 27 | // press 0 when powering up to display short display self test. 28 | // press 8 when short display self test is showing for longer self test, press C to exit. 29 | // press 1 when powering up for slower than normal speed, display stays lit while computing a result. 30 | // press 2 when powering up for normal speed with display on. 31 | // press 3 when powering up for faster speed with display on. 32 | // 33 | // Notes: 34 | // Step() runs all instructions in 148uS, if an instruction is faster, it delays until 148us passes. 35 | // Adjusted refresh delay in updateDisplay() so execution time of ArcCos 0.0001 matches real hardware. 36 | // step() function get called every 340uS for an effective clock speed of 2.9kHz 37 | // 38 | // uses this fast pin library: 39 | // https://github.com/mikaelpatel/Arduino-GPIO 40 | // 41 | 42 | #include "GPIO.h" 43 | #include 44 | 45 | extern void allSegmentOutput(); 46 | extern void allSegmentOff(); 47 | extern void allSegmentInput(); 48 | extern void allDigitOutput(); 49 | extern void allDigitOff(); 50 | extern void allKeyRowOff(); 51 | extern void allKeyRowIdle(); 52 | extern byte outputDigit(signed char, bool dp = false); 53 | extern void selectDigit(byte); 54 | extern void display(); 55 | extern void displaySelfTest(bool longtest = false); 56 | extern byte readKey(); 57 | 58 | extern void step(); 59 | 60 | //These are the 320 instructions that performed all the tasks on the Sinclair Scientific. 61 | //the rest of this program is to execute and interface these instructions to todays hardware 62 | 63 | const unsigned int objectCode[] PROGMEM = { 64 | 1408, 1392, 1792, 1824, 1860, 1808, 1360, 1376, 65 | 518, 1319, 1360, 1376, 9, 1360, 1908, 1072, 66 | 1083, 1075, 1121, 1129, 1073, 1069, 1051, 1840, 67 | 1955, 1840, 516, 1425, 552, 1430, 33, 1792, 68 | 1398, 1631, 1920, 1683, 34, 2003, 1540, 4, 69 | 1399, 1858, 1872, 1538, 4, 1329, 1335, 4, 70 | 1349, 1347, 4, 1443, 676, 1431, 57, 1559, 71 | 4, 1553, 59, 1443, 677, 1839, 1632, 2018, 72 | 65, 2023, 1719, 72, 1730, 71, 1840, 1666, 73 | 1751, 587, 1840, 1754, 78, 1840, 1718, 594, 74 | 1924, 78, 2017, 1713, 89, 1540, 130, 1844, 75 | 1841, 1652, 597, 130, 1730, 95, 1849, 1650, 76 | 114, 1443, 675, 1355, 1345, 130, 1409, 1559, 77 | 105, 1443, 750, 1839, 1632, 1844, 2023, 1719, 78 | 92, 1538, 1537, 116, 1451, 1796, 791, 1908, 79 | 1781, 637, 1722, 1540, 120, 1940, 1786, 119, 80 | 1445, 820, 1754, 512, 1747, 145, 1860, 1751, 81 | 142, 1686, 141, 1799, 1798, 1686, 1558, 132, 82 | 132, 1908, 1751, 662, 1686, 1686, 1558, 152, 83 | 1441, 614, 1392, 1334, 1408, 1750, 161, 1559, 84 | 159, 1568, 4, 1351, 1355, 1686, 681, 1908, 85 | 165, 1801, 1689, 1824, 1445, 180, 1447, 179, 86 | 1568, 1819, 185, 1565, 1820, 1924, 2011, 1693, 87 | 738, 1888, 1888, 1888, 2012, 1696, 1936, 1936, 88 | 1936, 1872, 1872, 1872, 2012, 1584, 1724, 1920, 89 | 1920, 1920, 1445, 183, 1561, 1447, 210, 1779, 90 | 697, 220, 1451, 727, 1732, 185, 220, 1754, 91 | 1844, 1764, 1844, 185, 1904, 1904, 1904, 1904, 92 | 1904, 130, 1447, 233, 1616, 1600, 1808, 1411, 93 | 100, 1451, 236, 1632, 1840, 130, 1445, 251, 94 | 1750, 760, 1751, 759, 1908, 1686, 240, 1860, 95 | 1794, 1864, 1864, 1824, 2003, 1636, 1924, 1924, 96 | 1700, 1431, 823, 2009, 1787, 253, 1993, 2036, 97 | 1723, 1920, 1920, 1920, 1920, 1588, 1844, 1445, 98 | 814, 1600, 1479, 1447, 765, 1572, 1796, 1806, 99 | 1764, 797, 1700, 1562, 280, 1571, 803, 1860, 100 | 1631, 1892, 280, 1807, 1443, 808, 130, 10, 101 | 1572, 1796, 1904, 1904, 1794, 61, 1572, 1796, 102 | 1803, 1652, 1844, 117, 1908, 1335, 251, 1693, 103 | 253, 1565, 1860, 1563, 2036, 1844, 1411, 790 104 | }; 105 | 106 | #define NUMBER_OF_MASKS 16 107 | #define MASK_LENGTH 12 108 | 109 | const char masks [][MASK_LENGTH] PROGMEM = { 110 | {"00000000000"}, // M0 111 | {"5 "}, // M1 112 | {" 00 "}, // M2 113 | {" 1 "}, // M3 114 | {" 0000000"}, // M4 115 | {" 1"}, // M5 116 | {" 01 "}, // M6 117 | {" 5 "}, // M7 118 | {"000000 "}, // M8 119 | {"0001 "}, // M9 120 | {" 0000001"}, // M10 121 | {" 1 "}, // M11 122 | {" 00005 "}, // M12 123 | {" 00001 "}, // M13 124 | {" 4 "}, // M14 125 | {" 0 "}, // M15 126 | }; 127 | 128 | // OPS WITH K: 1:AAKA 2:AAKC 5:ACKA 6:ACKB 9: 11: 129 | //const unsigned long LISTOPSWITHK = 000111100000001111010101001100110b; 130 | const unsigned long LISTOPSWITHK = 1007135334; 131 | 132 | signed char digits[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 133 | 134 | // keysKN and keysKO must match PrintableKeys on DisplayAndKeys.ino 135 | //const char PrintableKeys[19] = "12+E0v-378X654/9^C"; 136 | 137 | const char keysKN[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', 0}; 138 | const char keysKO[10] = {'C', 'v', '+', '-', '/', 'X', '^', 'E', '0', 0}; 139 | //const char keysKP[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 140 | 141 | // to use with keyStrobe 142 | #define KN 1 143 | #define KO 2 144 | #define KP 3 145 | 146 | // to use with keyPressed 147 | #define DK 1 148 | 149 | // ORIGINAL SINCLAIR DATA 150 | struct SinclairData_t 151 | { 152 | boolean sinclair = true; 153 | 154 | // Important: Array order matches display order, not bit order. 155 | // I.e. a[0] is high-order digit S10, a[10] is low-order digit S0. 156 | signed char a[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register A 157 | signed char b[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register B 158 | signed char c[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register C 159 | signed char af[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 160 | signed char bf[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 161 | 162 | signed char d[11] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 163 | 164 | byte dActive = 1; // Currently active D value 1-11. d[dActive-1] == 0 165 | signed char cc = 0; 166 | byte keyPressed = 0; 167 | 168 | byte keyStrobe = 0; // 'KO' or 'KP' if a keyboard input line is active, i.e. dActive and keyPressed match in the key matrix 169 | unsigned int address = 0; // PROGRAM COUNTER 170 | byte display = 1; // Flag for display on 171 | char mask[MASK_LENGTH]; 172 | 173 | byte showwork = 0; // when 0, display flag controls LED, when 1, LED always on 174 | byte speed = 1; // 0 - slow 1 - normal 2 - fast 175 | byte steptime = 148; // the step() function will take this many microseconds to execute, uses a delay to eat remaining time if it finishes faster. 176 | } SinclairData; 177 | 178 | boolean resetinprogress = false; 179 | 180 | void updateDisplay() 181 | { 182 | byte displayon = SinclairData.display + SinclairData.showwork; 183 | 184 | bool dp = false; // needed for logic below, keep initialized to false 185 | bool dp1; 186 | bool dp2; 187 | 188 | byte showdigit; 189 | byte digitoff = 99; 190 | byte digiton = 99; 191 | byte digitpos = 0; 192 | 193 | switch (SinclairData.dActive) 194 | { 195 | case 1: 196 | digiton = (SinclairData.a[0] == 0) ? 99 : 10; 197 | break; 198 | 199 | case 2: 200 | digiton = SinclairData.a[4]; 201 | break; 202 | 203 | case 3: 204 | digiton = SinclairData.a[5]; 205 | break; 206 | 207 | case 4: 208 | digiton = SinclairData.a[6]; 209 | break; 210 | 211 | case 5: 212 | digiton = SinclairData.a[7]; 213 | break; 214 | 215 | case 6: 216 | digiton = SinclairData.a[8]; 217 | break; 218 | 219 | case 7: 220 | digiton = (SinclairData.a[1] == 0) ? 99 : 10; 221 | break; 222 | 223 | case 8: 224 | digiton = SinclairData.a[2]; 225 | break; 226 | 227 | case 9: 228 | digiton = SinclairData.a[3]; 229 | break; 230 | } 231 | 232 | dp2 = !dp; // !false 233 | 234 | // SINCLAIR behavior: turn decimal point on automatically at fixed position 235 | // dot stays on whether the rest of the display is on or off 236 | // this logic attempts to make optimizer do variable copy on dp1 = dp2 and dp1 = dp below. 237 | if (SinclairData.dActive == 2) { 238 | dp1 = dp2; // true 239 | } 240 | else 241 | { 242 | dp1 = dp; // false 243 | } 244 | 245 | // attempt to have this block take same time to run whether a digit or blank is displayed 246 | if (displayon) { 247 | showdigit = digiton; 248 | digitpos = SinclairData.dActive - 1; 249 | dp = dp1; // true or false depending on digit shown 250 | } 251 | else { 252 | // SINCLAIR behavior: dot goes brighter when display is off. 253 | showdigit = digitoff; 254 | digitpos = 1; 255 | dp = dp2; // always true 256 | } 257 | 258 | byte segmentslit; 259 | segmentslit = outputDigit(showdigit, dp); 260 | //delayMicroseconds(segmentslit << 2); 261 | //delay(segmentslit); 262 | selectDigit(digitpos); 263 | 264 | // adjust case 1 for the following: 265 | // Sin 1 takes about 7.3 seconds. 266 | // Arccos .2 takes about 13.6 seconds 267 | // Arctan 1 takes about 6.6 seconds 268 | // Arccos of a very small value (e.g. 0.0001) goes into an almost-infinite loop and takes 1 minute, 38 seconds to complete 269 | 270 | if (resetinprogress == false) 271 | { 272 | switch (SinclairData.speed) 273 | { 274 | case 0: 275 | delayMicroseconds(2000); 276 | break; 277 | case 1: 278 | delayMicroseconds(155); 279 | break; 280 | case 2: 281 | delayMicroseconds(50); 282 | break; 283 | } 284 | } 285 | else 286 | { 287 | // SINCLAIR behavior: makes the display dim when C is pressed, SinclairData.steptime must be reduced below 148 for this delay to take effect. 288 | delayMicroseconds(50); 289 | } 290 | } 291 | 292 | void setup() 293 | { 294 | // put your setup code here, to run once: 295 | 296 | //Serial.begin(250000); // Cannot even call this function if displays are enabled. conflicts with use of D0 and D1, see UseSoftwareSerial.ino 297 | 298 | allSegmentOff(); 299 | allSegmentOutput(); 300 | 301 | allDigitOff(); 302 | allDigitOutput(); 303 | 304 | char key = readKey(); 305 | SinclairData.showwork = 1; //takes effect only on 1,2,3 306 | 307 | switch (key) 308 | { 309 | case '0': 310 | displaySelfTest(); 311 | SinclairData.speed = 1; 312 | SinclairData.showwork = 0; //normal speed, display off during calculations 313 | break; 314 | case '1': 315 | SinclairData.speed = 0; //slow, display on 316 | break; 317 | case '2': 318 | SinclairData.speed = 1; //normal, display on 319 | break; 320 | case '3': 321 | SinclairData.speed = 2; //fast, display on 322 | break; 323 | default: 324 | SinclairData.speed = 1; 325 | SinclairData.showwork = 0; //normal speed, display off during calculations 326 | break; 327 | } 328 | } 329 | 330 | // timing the loop function using the micros() function, finds it executes every 340uS, for a 2.9kHz clock speed 331 | 332 | void loop() 333 | { 334 | // put your main code here, to run repeatedly: 335 | 336 | char key = readKey(); 337 | 338 | SinclairData.keyStrobe = 0; 339 | 340 | if (key != 0) 341 | { 342 | byte dActive = SinclairData.dActive - 1; 343 | 344 | if (key == keysKN[dActive]) { 345 | SinclairData.keyStrobe = KN; 346 | } 347 | 348 | if (key == keysKO[dActive]) { 349 | SinclairData.keyStrobe = KO; 350 | } 351 | 352 | if (key == 'C') { 353 | if (resetinprogress == false) { 354 | SinclairData.address = 0; 355 | SinclairData.keyStrobe = 0; 356 | SinclairData.dActive = 1; 357 | SinclairData.steptime = 10; // speed up step() function so updateDisplay() function can control brightness via smaller delayMicrososeconds() 358 | resetinprogress = true; 359 | } 360 | } 361 | } 362 | 363 | updateDisplay(); 364 | step(); 365 | 366 | if (resetinprogress) 367 | { 368 | if (SinclairData.address > 8) { 369 | resetinprogress = false; 370 | SinclairData.steptime = 148; 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /SinclairScientific1/UseSoftwareSerial.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // SoftwareSerial is needed instead of the hardware Serial since D0/D1 are used to select digits 0 and 1 12 | // 13 | 14 | SoftwareSerial mySerial(0, 1); // RX, TX 15 | 16 | void outputlong(unsigned long v) 17 | { 18 | // set the data rate for the SoftwareSerial port 19 | mySerial.begin(57600); 20 | 21 | mySerial.print("---"); //this will be corrupted 22 | 23 | mySerial.print("v:"); 24 | mySerial.println(v); 25 | } 26 | 27 | void outputlong(unsigned long v1, unsigned long v2) 28 | { 29 | // set the data rate for the SoftwareSerial port 30 | mySerial.begin(57600); 31 | 32 | mySerial.println("---"); //this will be corrupted 33 | 34 | mySerial.print("v1:"); 35 | mySerial.println(v1); 36 | 37 | mySerial.print("v2:"); 38 | mySerial.println(v2); 39 | } 40 | -------------------------------------------------------------------------------- /SinclairScientific1/libraries/Arduino-GPIO-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduinoenigma/ArduinoNanoSinclairScientificCalculator/f6fb9eae2af7c7aea903687984f963b3e953347f/SinclairScientific1/libraries/Arduino-GPIO-master.zip -------------------------------------------------------------------------------- /SinclairScientific5/CPU.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // BUG: play with .display = 0 to see what ia the absolute minimum that works 12 | // 13 | 14 | boolean opsWithK(byte opcode) 15 | { 16 | return (LISTOPSWITHK & (1UL << opcode)); 17 | } 18 | 19 | unsigned int getInstruction(unsigned int PC) 20 | { 21 | return pgm_read_word_near(objectCode + PC); 22 | } 23 | 24 | byte getMaskNum() 25 | { 26 | return getInstruction(SinclairData.address) & 0x0f; 27 | } 28 | 29 | char *getMask() 30 | { 31 | unsigned int instruction = getInstruction(SinclairData.address); 32 | byte classBits = instruction >> 9; 33 | byte opcode = (instruction >> 4) & 0x1f; 34 | 35 | if (classBits == 3 || (classBits == 2 && opcode > 18 && opcode != 21 && opcode != 22)) 36 | { 37 | byte maskno = getMaskNum(); 38 | 39 | for (byte i = 0; i <= 10; i++) 40 | { 41 | char maskdigit = pgm_read_byte_near(masks[maskno] + i); 42 | 43 | if (maskdigit == ' ') 44 | { 45 | SinclairData.mask[i] = maskdigit; 46 | } 47 | else if (classBits == 3 && opsWithK(opcode)) 48 | { 49 | // Register instruction 50 | SinclairData.mask[i] = maskdigit - '0';; 51 | } 52 | else 53 | { 54 | SinclairData.mask[i] = '*'; 55 | } 56 | } 57 | } 58 | else 59 | { 60 | SinclairData.mask[0] = 0; 61 | } 62 | 63 | return SinclairData.mask; 64 | } 65 | 66 | void add(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 67 | { 68 | byte carry = 0; 69 | getMask(); 70 | for (signed char i = 10; i >= 0; i--) 71 | { 72 | if (SinclairData.mask[i] == ' ') 73 | { 74 | // masked out 75 | // continue; 76 | } 77 | else 78 | { 79 | signed char result = src1[i] + src2[i] + carry; 80 | if (!hex && result >= 10) 81 | { 82 | result -= 10; 83 | carry = 1; 84 | } 85 | else if (hex && result >= 16) 86 | { 87 | result -= 16; 88 | carry = 1; 89 | } 90 | else 91 | { 92 | carry = 0; 93 | } 94 | dst[i] = result; 95 | } 96 | } 97 | if (carry) 98 | { 99 | SinclairData.cc = carry; 100 | //SinclairData.ccMeaning = carry ? 'overflow' : 'no overflow'; 101 | } 102 | } 103 | 104 | void sub(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 105 | { 106 | byte borrow = 0; 107 | getMask(); 108 | for (signed char i = 10; i >= 0; i--) 109 | { 110 | if (SinclairData.mask[i] == ' ') 111 | { 112 | // masked out 113 | // continue; 114 | } 115 | else 116 | { 117 | signed char result = src1[i] - src2[i] - borrow; 118 | if (result < 0) 119 | { 120 | result += hex ? 16 : 10; 121 | borrow = 1; 122 | } 123 | else 124 | { 125 | borrow = 0; 126 | } 127 | dst[i] = result; 128 | } 129 | } 130 | if (borrow) 131 | { 132 | SinclairData.cc = borrow; 133 | //SinclairData.ccMeaning = borrow ? 'borrow' : 'no borrow'; 134 | } 135 | } 136 | 137 | void compare(signed char src1[], signed char src2[]) 138 | { 139 | signed char tmp[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 140 | 141 | sub(src1, src2, tmp); 142 | // Compare sets condition if not borrow 143 | //SinclairData.ccMeaning = SinclairData.cc ? "less than" : "not less than"; 144 | } 145 | 146 | void copy(signed char src[], signed char dst[]) 147 | { 148 | getMask(); 149 | for (signed char i = 10; i >= 0; i--) 150 | { 151 | if (SinclairData.mask[i] == ' ') 152 | { 153 | // masked out 154 | // continue; 155 | } 156 | else 157 | { 158 | dst[i] = src[i]; 159 | } 160 | } 161 | } 162 | 163 | void sll(signed char src[]) 164 | { 165 | getMask(); 166 | signed char digit = 0; 167 | for (signed char i = 10; i >= 0; i--) 168 | { 169 | if (SinclairData.mask[i] == ' ') 170 | { 171 | // masked out 172 | // continue; 173 | } 174 | else 175 | { 176 | signed char newdigit = src[i]; 177 | src[i] = digit; 178 | digit = newdigit; 179 | } 180 | } 181 | } 182 | 183 | void srl(signed char src[]) 184 | { 185 | getMask(); 186 | signed char digit = 0; 187 | for (signed char i = 0; i <= 10; i++) 188 | { 189 | if (SinclairData.mask[i] == ' ') 190 | { 191 | // masked out 192 | // continue; 193 | } 194 | else 195 | { 196 | signed char newdigit = src[i]; 197 | src[i] = digit; 198 | digit = newdigit; 199 | } 200 | } 201 | } 202 | 203 | void writeFlag(signed char dest[], signed char val) 204 | { 205 | getMask(); 206 | for (signed char i = 10; i >= 0; i--) 207 | { 208 | if (SinclairData.mask[i] == ' ') 209 | { 210 | // masked out 211 | // continue; 212 | } 213 | else 214 | { 215 | // Flip dst if val == -1, otherwise set to val 216 | dest[i] = (val < 0) ? (1 - dest[i]) : val; 217 | } 218 | } 219 | } 220 | 221 | void compareFlags(signed char src1[], signed char src2[]) 222 | { 223 | signed char cc = 0; 224 | getMask(); 225 | for (signed char i = 10; i >= 0; i--) 226 | { 227 | if (SinclairData.mask[i] == ' ') 228 | { 229 | // masked out 230 | // continue; 231 | } 232 | else 233 | { 234 | if (src1[i] != src2[i]) 235 | { 236 | cc = 1; 237 | } 238 | } 239 | } 240 | if (cc) 241 | { 242 | SinclairData.cc = 1; 243 | //SinclairData.ccMeaning = 'flags not equal'; 244 | } 245 | } 246 | 247 | void exchange(signed char src1[], signed char src2[]) 248 | { 249 | getMask(); 250 | for (signed char i = 10; i >= 0; i--) 251 | { 252 | if (SinclairData.mask[i] == ' ') 253 | { 254 | // masked out 255 | // continue; 256 | } 257 | else 258 | { 259 | signed char t = src1[i]; 260 | src1[i] = src2[i]; 261 | src2[i] = t; 262 | } 263 | } 264 | } 265 | 266 | void testFlag(signed char src[]) 267 | { 268 | signed char cc = 0; 269 | getMask(); 270 | for (signed char i = 10; i >= 0; i--) 271 | { 272 | if (SinclairData.mask[i] == ' ') 273 | { 274 | // masked out 275 | // continue; 276 | } 277 | else 278 | { 279 | if (src[i]) 280 | { 281 | cc = 1; 282 | } 283 | } 284 | } 285 | /* Only update cc if bit set */ 286 | if (cc) 287 | { 288 | SinclairData.cc = cc; 289 | //SinclairData.ccMeaning = 'flag set'; 290 | } 291 | } 292 | 293 | void updateD() 294 | { 295 | for (signed char i = 10; i >= 0; i--) 296 | { 297 | SinclairData.d[i] = 1; 298 | } 299 | 300 | SinclairData.dActive += 1; 301 | if (SinclairData.dActive > 10) 302 | { 303 | SinclairData.dActive = 1; 304 | } 305 | SinclairData.d[SinclairData.dActive - 1] = 0; 306 | } 307 | 308 | void step() 309 | { 310 | //Serial.print(F("addr:")); 311 | //Serial.print(SinclairData.address); 312 | 313 | unsigned long entrytime = micros(); 314 | 315 | unsigned int instruction = getInstruction(SinclairData.address); 316 | byte classBits = instruction >> 9; 317 | byte opcode = (instruction >> 4) & 0x1f; 318 | unsigned int nextAddress = SinclairData.address + 1; 319 | 320 | //Serial.print(F(" data:")); 321 | //Serial.print(opcode); 322 | //Serial.print(F(" classbits:")); 323 | //Serial.print(classBits); 324 | 325 | if (classBits == 3) 326 | { 327 | // Register instruction 328 | byte maskBits = instruction & 0xf; 329 | switch (opcode) 330 | { 331 | case 0: // AABA: A+B -> A 332 | displayInstruction(1); 333 | add(SinclairData.a, SinclairData.b, SinclairData.a); 334 | break; 335 | case 1: // AAKA: A+K -> A 336 | displayInstruction(2); 337 | add(SinclairData.a, getMask(), SinclairData.a); 338 | break; 339 | case 2: // AAKC: A+K -> C 340 | displayInstruction(3); 341 | add(SinclairData.a, getMask(), SinclairData.c); 342 | break; 343 | case 3: 344 | if (SinclairData.sinclair) 345 | { // ACBB C+B -> B 346 | displayInstruction(4); 347 | add(SinclairData.c, SinclairData.b, SinclairData.b); 348 | } 349 | else 350 | { // ABOA: B -> A 351 | displayInstruction(5); 352 | copy(SinclairData.b, SinclairData.a); 353 | } 354 | break; 355 | case 4: // ABOC: B -> C 356 | displayInstruction(6); 357 | copy(SinclairData.b, SinclairData.c); 358 | break; 359 | case 5: // ACKA: C+K -> A 360 | displayInstruction(7); 361 | add(SinclairData.c, getMask(), SinclairData.a); 362 | break; 363 | case 6: // AKCB: C+K -> B 364 | displayInstruction(8); 365 | add(SinclairData.c, getMask(), SinclairData.b); 366 | break; 367 | case 7: // SABA: A-B -> A 368 | displayInstruction(9); 369 | sub(SinclairData.a, SinclairData.b, SinclairData.a); 370 | break; 371 | case 8: // SABC: A-B -> C 372 | displayInstruction(10); 373 | sub(SinclairData.a, SinclairData.b, SinclairData.c); 374 | break; 375 | case 9: // SAKA: A-K -> A 376 | displayInstruction(11); 377 | sub(SinclairData.a, getMask(), SinclairData.a); 378 | break; 379 | case 10: // SCBC: C-B -> C 380 | displayInstruction(12); 381 | sub(SinclairData.c, SinclairData.b, SinclairData.c); 382 | break; 383 | case 11: // SCKC: C-K -> C 384 | displayInstruction(13); 385 | sub(SinclairData.c, getMask(), SinclairData.c); 386 | break; 387 | case 12: // CAB: compare A-B 388 | displayInstruction(14); 389 | compare(SinclairData.a, SinclairData.b); 390 | break; 391 | case 13: // CAK: compare A-K 392 | displayInstruction(15); 393 | compare(SinclairData.a, getMask()); 394 | break; 395 | case 14: // CCB: compare C-B 396 | displayInstruction(16); 397 | compare(SinclairData.c, SinclairData.b); 398 | break; 399 | case 15: // CCK: compare C-K 400 | displayInstruction(17); 401 | compare(SinclairData.c, getMask()); 402 | break; 403 | case 16: // AKA: K -> A 404 | displayInstruction(18); 405 | copy(getMask(), SinclairData.a); 406 | break; 407 | case 17: // AKB: K -> B 408 | displayInstruction(19); 409 | copy(getMask(), SinclairData.b); 410 | break; 411 | case 18: // AKC: K -> C 412 | displayInstruction(20); 413 | copy(getMask(), SinclairData.c); 414 | break; 415 | case 19: // EXAB: exchange A and B 416 | displayInstruction(21); 417 | exchange(SinclairData.a, SinclairData.b); 418 | break; 419 | case 20: // SLLA: shift A left 420 | displayInstruction(22); 421 | sll(SinclairData.a); 422 | break; 423 | case 21: // SLLB: shift B left 424 | displayInstruction(23); 425 | sll(SinclairData.b); 426 | break; 427 | case 22: // SLLC: shift C left 428 | displayInstruction(24); 429 | sll(SinclairData.c); 430 | break; 431 | case 23: // SRLA: shift A right 432 | displayInstruction(25); 433 | srl(SinclairData.a); 434 | break; 435 | case 24: // SRLB: shift B right 436 | displayInstruction(26); 437 | srl(SinclairData.b); 438 | break; 439 | case 25: // SRLC: shift C right 440 | displayInstruction(66); 441 | srl(SinclairData.c); 442 | break; 443 | case 26: // AKCN: A+K -> A until key down on N or D11 [sic] 444 | // Patent says sets condition if key down, but real behavior 445 | // is to set condition if addition overflows (i.e. no key down) 446 | //SinclairData.display = 0; //comment this line to glitch the display when a number key is pressed (SINCLAIR behavior: actual hardware behavior) 447 | add(SinclairData.a, getMask(), SinclairData.a); 448 | if (SinclairData.keyStrobeKN) 449 | { 450 | displayInstruction(27); 451 | // Advance to next instruction 452 | } 453 | else if (SinclairData.dActive != 10) 454 | { 455 | displayInstruction(28); 456 | // Hold at current instruction and continue scan 457 | nextAddress = SinclairData.address; 458 | } 459 | else 460 | { 461 | displayInstruction(29); 462 | // For state d10, fall through 463 | } 464 | break; 465 | case 27: 466 | if (SinclairData.sinclair) 467 | { // SCBA C-B -> A 468 | displayInstruction(30); 469 | sub(SinclairData.c, SinclairData.b, SinclairData.a); 470 | } 471 | else 472 | { // AAKAH A+K -> A hex 473 | displayInstruction(31); 474 | add(SinclairData.a, getMask(), SinclairData.a, 1 /* hex */ ); 475 | SinclairData.cc = 0; 476 | //SinclairData.ccMeaning = ''; 477 | } 478 | break; 479 | case 28: 480 | if (SinclairData.sinclair) 481 | { // SCKB C-K -> B 482 | displayInstruction(32); 483 | sub(SinclairData.c, getMask(), SinclairData.b); 484 | } 485 | else 486 | { // SAKAH A-K -> A hex 487 | displayInstruction(33); 488 | sub(SinclairData.a, getMask(), SinclairData.a, 1 /* hex */ ); 489 | SinclairData.cc = 0; 490 | //SinclairData.ccMeaning = ''; 491 | } 492 | break; 493 | case 29: // ACKC: C+K -> C 494 | displayInstruction(34); 495 | add(SinclairData.c, getMask(), SinclairData.c); 496 | break; 497 | case 30: 498 | if (SinclairData.sinclair) 499 | { // AABC A+B -> C 500 | displayInstruction(35); 501 | add(SinclairData.a, SinclairData.b, SinclairData.c); 502 | break; 503 | } 504 | case 31: 505 | if (SinclairData.sinclair) 506 | { // ACBC C+B -> C 507 | displayInstruction(36); 508 | add(SinclairData.c, SinclairData.b, SinclairData.c); 509 | break; 510 | } 511 | default: 512 | //bad instruction 513 | //alert('Bad instruction ' + instruction); 514 | displayInstruction(37); 515 | break; 516 | } 517 | } 518 | else if ((instruction >> 8) == 5) 519 | { 520 | // Flag instruction 521 | byte maskBits = instruction & 0xf; 522 | switch (opcode) 523 | { 524 | case 16: // NOP 525 | displayInstruction(38); 526 | break; 527 | case 17: // WAITDK: wait for display key 528 | SinclairData.display = 0; 529 | if (SinclairData.keyPressed == DK) 530 | { 531 | // Jump 532 | displayInstruction(39); 533 | nextAddress = instruction & 0x1ff; 534 | } 535 | else 536 | { 537 | // Hold address until DK pressed 538 | displayInstruction(40); 539 | nextAddress = SinclairData.address; 540 | } 541 | break; 542 | case 18: // WAITNO: wait for key or address register overflow 543 | if (SinclairData.keyStrobeKO || SinclairData.keyStrobeKN || SinclairData.keyStrobeKP) 544 | { 545 | // Jump 546 | displayInstruction(41); 547 | nextAddress = instruction & 0x1ff; 548 | } 549 | else 550 | { 551 | // Hold address until key pressed or address overflow (TODO) 552 | displayInstruction(42); 553 | nextAddress = SinclairData.address; 554 | } 555 | break; 556 | case 19: // SFB: set flag B 557 | displayInstruction(43); 558 | writeFlag(SinclairData.bf, 1); 559 | break; 560 | case 20: // SFA: set flag A 561 | displayInstruction(44); 562 | writeFlag(SinclairData.af, 1); 563 | break; 564 | case 21: // SYNC (SYNCH): hold address until end of D10 565 | displayInstruction(45); 566 | if (SinclairData.dActive != 10) 567 | { 568 | nextAddress = SinclairData.address; 569 | } 570 | SinclairData.cc = 0; 571 | //SinclairData.ccMeaning = ''; 572 | break; 573 | case 22: // SCAN (SCANNO): wait for key 574 | SinclairData.display = 1; // Reset display power off latch 575 | if (SinclairData.keyStrobeKO || SinclairData.keyStrobeKN || SinclairData.keyStrobeKP) 576 | { 577 | displayInstruction(46); 578 | SinclairData.cc = 1; 579 | //SinclairData.ccMeaning = 'key'; 580 | } 581 | else 582 | { 583 | displayInstruction(47); 584 | SinclairData.cc = 0; 585 | //SinclairData.ccMeaning = 'no key'; 586 | if (SinclairData.dActive != 10) 587 | { 588 | // Hold address until end of D10 589 | nextAddress = SinclairData.address; 590 | } 591 | } 592 | break; 593 | case 23: // ZFB: zero flag B 594 | displayInstruction(48); 595 | writeFlag(SinclairData.bf, 0); 596 | break; 597 | case 24: // ZFA: zero flag A 598 | displayInstruction(49); 599 | writeFlag(SinclairData.af, 0); 600 | break; 601 | case 25: // TFB: test flag B 602 | displayInstruction(50); 603 | testFlag(SinclairData.bf); 604 | break; 605 | case 26: // TFA: test flag A 606 | displayInstruction(51); 607 | testFlag(SinclairData.af); 608 | break; 609 | case 27: // FFB: flip flag B 610 | displayInstruction(52); 611 | writeFlag(SinclairData.bf, -1 /* flip */ ); 612 | break; 613 | case 28: // FFA: flip flag A 614 | displayInstruction(67); 615 | writeFlag(SinclairData.af, -1 /* flip */ ); 616 | break; 617 | case 29: // CF: compare flags 618 | displayInstruction(53); 619 | compareFlags(SinclairData.af, SinclairData.bf); 620 | break; 621 | case 30: // NOP 622 | displayInstruction(54); 623 | break; 624 | case 31: // EXF: exchange flags 625 | displayInstruction(55); 626 | exchange(SinclairData.af, SinclairData.bf); 627 | break; 628 | default: 629 | //bad instruction 630 | //alert('Bad instruction ' + instruction); 631 | displayInstruction(56); 632 | break; 633 | } 634 | } 635 | else if (classBits == 0) 636 | { 637 | // jump if reset: BIU, BIZ, BIGE, BINC, BIE, BET 638 | displayInstruction(57); 639 | if (SinclairData.cc == 0) 640 | { 641 | displayInstruction(58); 642 | nextAddress = instruction & 0x1ff; 643 | } 644 | SinclairData.cc = 0; // Clear after jump 645 | //SinclairData.ccMeaning = ''; 646 | } 647 | else if (classBits == 1) 648 | { 649 | // jump if set: BID, BIO, BILT, BIC, BINE 650 | displayInstruction(59); 651 | if (SinclairData.cc == 1) 652 | { 653 | displayInstruction(60); 654 | nextAddress = instruction & 0x1ff; 655 | } 656 | SinclairData.cc = 0; // Clear after jump 657 | //SinclairData.ccMeaning = ''; 658 | } 659 | else if ((instruction >> 7) == 8) 660 | { 661 | // Jump if key down on KO (BKO) 662 | displayInstruction(61); 663 | if (SinclairData.keyStrobeKO) 664 | { 665 | SinclairData.display = 0; 666 | displayInstruction(62); 667 | nextAddress = instruction & 0x1ff; 668 | } 669 | SinclairData.cc = 0; // Clear after jump 670 | //SinclairData.ccMeaning = ''; 671 | } 672 | else if ((instruction >> 7) == 9) 673 | { 674 | // Jump if key down on KP (BKP) 675 | displayInstruction(63); 676 | if (SinclairData.keyStrobeKP) 677 | { 678 | SinclairData.display = 0; 679 | displayInstruction(64); 680 | nextAddress = instruction & 0x1ff; 681 | } 682 | SinclairData.cc = 0; // Clear after jump 683 | //SinclairData.ccMeaning = ''; 684 | } 685 | else 686 | { 687 | displayInstruction(65); 688 | //bad instruction 689 | //alert('Bad instruction code ' + instruction); 690 | } 691 | SinclairData.address = nextAddress; 692 | // Put the mask for the next instruction in the model for display 693 | //SinclairData.mask = getMask(); 694 | getMask(); 695 | // Update D state 696 | updateD(); 697 | 698 | displayInstruction(68); // if printing is enabled, do a println after executing a line of code 699 | 700 | byte exectime = micros() - entrytime; 701 | 702 | if (exectime < SinclairData.steptime) 703 | { 704 | delayMicroseconds(SinclairData.steptime - exectime); 705 | } 706 | } 707 | -------------------------------------------------------------------------------- /SinclairScientific5/Disassembler.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | 12 | void displayArray(signed char src1[]) { 13 | for (byte i = 0; i < 11; i++) 14 | { 15 | Serial.print((int)src1[i]); 16 | Serial.print(F(" ")); 17 | } 18 | Serial.println(F("")); 19 | } 20 | 21 | //rename this function: void displayInstruction(byte dummy) 22 | //to DISABLE instruction viaualization in step() 23 | void displayInstruction(byte dummy) 24 | { 25 | 26 | } 27 | 28 | //rename this function: void displayInstruction(byte dummy) 29 | //to ENABLE instruction viaualization in step() 30 | void displayInstruction1(byte instructionid) 31 | { 32 | Serial.print(F(" (")); 33 | Serial.print(instructionid); 34 | Serial.print(F(":")); 35 | switch (instructionid) 36 | { 37 | case 1: 38 | Serial.print(F("AABA: A+B -> A")); 39 | break; 40 | 41 | case 2: 42 | Serial.print(F("AABA: A+B -> A")); 43 | break; 44 | 45 | case 3: 46 | Serial.print(F("AAKC: A+K -> C")); 47 | break; 48 | 49 | case 4: 50 | Serial.print(F("ACBB C+B -> B")); 51 | break; 52 | 53 | case 5: 54 | Serial.print(F("ABOA: B -> A")); 55 | break; 56 | 57 | case 6: 58 | Serial.print(F("ABOC: B -> C")); 59 | break; 60 | 61 | case 7: 62 | Serial.print(F("ACKA: C+K -> A")); 63 | break; 64 | 65 | case 8: 66 | Serial.print(F("AKCB: C+K -> B")); 67 | break; 68 | 69 | case 9: 70 | Serial.print(F("SABA: A-B -> A")); 71 | break; 72 | 73 | case 10: 74 | Serial.print(F("SABC: A-B -> C")); 75 | break; 76 | 77 | case 11: 78 | Serial.print(F("SAKA: A-K -> A")); 79 | break; 80 | 81 | case 12: 82 | Serial.print(F("SCBC: C-B -> C")); 83 | break; 84 | 85 | case 13: 86 | Serial.print(F("SCKC: C-K -> C")); 87 | break; 88 | 89 | case 14: 90 | Serial.print(F("CAB: compare A-B")); 91 | break; 92 | 93 | case 15: 94 | Serial.print(F("CAK: compare A-K")); 95 | break; 96 | 97 | case 16: 98 | Serial.print(F("CCB: compare C-B")); 99 | break; 100 | 101 | case 17: 102 | Serial.print(F("CCK: compare C-K")); 103 | break; 104 | 105 | case 18: 106 | Serial.print(F("AKA: K -> A")); 107 | break; 108 | 109 | case 19: 110 | Serial.print(F("AKB: K -> B")); 111 | break; 112 | 113 | case 20: 114 | Serial.print(F("AKC: K -> C")); 115 | break; 116 | 117 | case 21: 118 | Serial.print(F("EXAB: exchange A and B")); 119 | break; 120 | 121 | case 22: 122 | Serial.print(F("SLLA: shift A left")); 123 | break; 124 | 125 | case 23: 126 | Serial.print(F("SLLB: shift B left")); 127 | break; 128 | 129 | case 24: 130 | Serial.print(F("SLLC: shift C left")); 131 | break; 132 | 133 | case 25: 134 | Serial.print(F("SRLA: shift A right")); 135 | break; 136 | 137 | case 26: 138 | Serial.print(F("SRLB: shift B right")); 139 | break; 140 | 141 | case 27: 142 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (ADVANCE)")); 143 | break; 144 | 145 | case 28: 146 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (HOLD)")); 147 | break; 148 | 149 | case 29: 150 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (D10 FALLTHROUGH)")); 151 | break; 152 | 153 | case 30: 154 | Serial.print(F("SCBA C-B -> A")); 155 | break; 156 | 157 | case 31: 158 | Serial.print(F("AAKAH A+K -> A hex")); 159 | break; 160 | 161 | case 32: 162 | Serial.print(F("SCKB C-K -> B")); 163 | break; 164 | 165 | case 33: 166 | Serial.print(F("SAKAH A-K -> A hex")); 167 | break; 168 | 169 | case 34: 170 | Serial.print(F("ACKC: C+K -> C")); 171 | break; 172 | 173 | case 35: 174 | Serial.print(F("AABC A+B -> C")); 175 | break; 176 | 177 | case 36: 178 | Serial.print(F("ACBC C+B -> C")); 179 | break; 180 | 181 | case 37: 182 | Serial.print(F("Bad instruction")); 183 | break; 184 | 185 | case 38: 186 | Serial.print(F("NOP")); 187 | break; 188 | 189 | case 39: 190 | Serial.print(F("WAITDK: wait for display key DK (JUMP)")); 191 | break; 192 | 193 | case 40: 194 | Serial.print(F("WAITDK: wait for display key DK (HOLD)")); 195 | break; 196 | 197 | case 41: 198 | Serial.print(F("WAITNO: wait for key or address register overflow (JUMP)")); 199 | break; 200 | 201 | case 42: 202 | Serial.print(F("WAITNO: wait for key or address register overflow (HOLDING)")); 203 | break; 204 | 205 | case 43: 206 | Serial.print(F("SFB: set flag B")); 207 | break; 208 | 209 | case 44: 210 | Serial.print(F("SFA: set flag A")); 211 | break; 212 | 213 | case 45: 214 | Serial.print(F("SYNC (SYNCH): hold address until end of D10")); 215 | break; 216 | 217 | case 46: 218 | Serial.print(F("SCAN (SCANNO): wait for key (KEY)")); 219 | break; 220 | 221 | case 47: 222 | Serial.print(F("SCAN (SCANNO): wait for key (NO KEY)")); 223 | break; 224 | 225 | case 48: 226 | Serial.print(F("ZFB: zero flag B")); 227 | break; 228 | 229 | case 49: 230 | Serial.print(F("ZFA: zero flag A")); 231 | break; 232 | 233 | case 50: 234 | Serial.print(F("TFB: test flag B")); 235 | break; 236 | 237 | case 51: 238 | Serial.print(F("TFA: test flag A")); 239 | break; 240 | 241 | case 52: 242 | Serial.print(F("FFB: flip flag B")); 243 | break; 244 | 245 | case 53: 246 | Serial.print(F("CF: compare flags")); 247 | break; 248 | 249 | case 54: 250 | Serial.print(F("NOP")); 251 | break; 252 | 253 | case 55: 254 | Serial.print(F("EXF: exchange flags")); 255 | break; 256 | 257 | case 56: 258 | Serial.print(F("Bad instruction")); 259 | break; 260 | 261 | case 57: 262 | Serial.print(F("Jump if reset: BIU, BIZ, BIGE, BINC, BIE, BET ")); 263 | break; 264 | 265 | case 58: 266 | Serial.print(F("JUMP IF RESET TAKEN")); 267 | break; 268 | 269 | case 59: 270 | Serial.print(F("Jump if set: BID, BIO, BILT, BIC, BINE")); 271 | break; 272 | 273 | case 60: 274 | Serial.print(F("JUMP IF SET TAKEN")); 275 | break; 276 | 277 | case 61: 278 | Serial.print(F("Jump if key down on KO (BKO)")); 279 | break; 280 | 281 | case 62: 282 | Serial.print(F("JUMP IF KEY DOWN ON KO TAKEN")); 283 | break; 284 | 285 | case 63: 286 | Serial.print(F("Jump if key down on KP (BKP)")); 287 | break; 288 | 289 | case 64: 290 | Serial.print(F("JUMP IF KEY DOWN ON KP TAKEN")); 291 | break; 292 | 293 | case 65: 294 | Serial.print(F("Bad instruction code")); 295 | break; 296 | 297 | case 66: 298 | Serial.print(F("SRLC: shift C right")); 299 | break; 300 | 301 | case 67: 302 | Serial.print(F("FFA: flip flag A")); 303 | break; 304 | 305 | case 68: 306 | Serial.println(F("")); // print end of line at end of void step() 307 | break; 308 | 309 | default: 310 | Serial.print(F("UNKNOWN INSTRUCTION ID")); 311 | break; 312 | } 313 | Serial.print(F(")")); 314 | } 315 | -------------------------------------------------------------------------------- /SinclairScientific5/DisplayAndKeys.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // tedious but fast, most lines compile to 1 instruction. 12 | // this whole file compiles to less than 1KB of code 13 | // 14 | 15 | // 16 | // A0 = D14 17 | // A1 = D15 18 | // A2 = D16 19 | // A3 = D17 20 | // A4 = D18 21 | // A5 = D19 22 | // A6 = D20 23 | // A7 = D21 24 | // 25 | 26 | GPIO SegmentA; 27 | GPIO SegmentB; 28 | GPIO SegmentC; 29 | GPIO SegmentD; 30 | GPIO SegmentE; 31 | GPIO SegmentF; 32 | GPIO SegmentG; 33 | GPIO SegmentDP; 34 | 35 | GPIO Digit1; 36 | GPIO Digit2; 37 | GPIO Digit3; 38 | GPIO Digit4; 39 | GPIO Digit5; 40 | GPIO Digit6; 41 | GPIO Digit7; 42 | GPIO Digit8; 43 | GPIO Digit9; 44 | 45 | // http://forum.arduino.cc/index.php?topic=6549.msg51570#msg51570 46 | // defines for setting and clearing register bits 47 | #ifndef cbi 48 | #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 49 | #endif 50 | #ifndef sbi 51 | #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) 52 | #endif 53 | 54 | void setupFastADC() 55 | { 56 | // http://forum.arduino.cc/index.php?topic=6549.msg51570#msg51570 57 | // set prescale to 16 58 | sbi(ADCSRA, ADPS2) ; 59 | cbi(ADCSRA, ADPS1) ; 60 | cbi(ADCSRA, ADPS0) ; 61 | } 62 | 63 | void allSegmentOutput() { 64 | SegmentA.output(); 65 | SegmentB.output(); 66 | SegmentC.output(); 67 | SegmentD.output(); 68 | SegmentE.output(); 69 | SegmentF.output(); 70 | SegmentG.output(); 71 | SegmentDP.output(); 72 | } 73 | 74 | void allSegmentOff() { 75 | SegmentA.high(); 76 | SegmentB.high(); 77 | SegmentC.high(); 78 | SegmentD.high(); 79 | SegmentE.high(); 80 | SegmentF.high(); 81 | SegmentG.high(); 82 | SegmentDP.high(); 83 | } 84 | 85 | void allSegmentInput() { 86 | SegmentA.input(); 87 | SegmentB.input(); 88 | SegmentC.input(); 89 | SegmentD.input(); 90 | SegmentE.input(); 91 | SegmentF.input(); 92 | SegmentG.input(); 93 | SegmentDP.input(); 94 | 95 | SegmentA.high(); 96 | SegmentB.high(); 97 | SegmentC.high(); 98 | SegmentD.high(); 99 | SegmentE.high(); 100 | SegmentF.high(); 101 | SegmentG.high(); 102 | SegmentDP.high(); 103 | } 104 | 105 | void allDigitOutput() { 106 | Digit1.output(); 107 | Digit2.output(); 108 | Digit3.output(); 109 | Digit4.output(); 110 | Digit5.output(); 111 | Digit6.output(); 112 | Digit7.output(); 113 | Digit8.output(); 114 | Digit9.output(); 115 | } 116 | 117 | void allDigitInput() { 118 | Digit1.input(); 119 | Digit2.input(); 120 | Digit3.input(); 121 | Digit4.input(); 122 | Digit5.input(); 123 | Digit6.input(); 124 | Digit7.input(); 125 | Digit8.input(); 126 | Digit9.input(); 127 | } 128 | 129 | void allDigitOff() { 130 | Digit1.low(); 131 | Digit2.low(); 132 | Digit3.low(); 133 | Digit4.low(); 134 | Digit5.low(); 135 | Digit6.low(); 136 | Digit7.low(); 137 | Digit8.low(); 138 | Digit9.low(); 139 | } 140 | 141 | byte outputDigit(signed char digit, bool decimalpoint = false) { 142 | 143 | byte segmentslit = 0; 144 | 145 | allDigitOff(); 146 | 147 | if (decimalpoint) 148 | { 149 | segmentslit++; 150 | SegmentDP.low(); 151 | } 152 | else 153 | { 154 | SegmentDP.high(); 155 | } 156 | 157 | switch (digit) { 158 | case 0: 159 | SegmentA.low(); 160 | SegmentB.low(); 161 | SegmentC.low(); 162 | SegmentD.low(); 163 | SegmentE.low(); 164 | SegmentF.low(); 165 | SegmentG.high(); 166 | segmentslit += 6; 167 | break; 168 | case 1: 169 | SegmentA.high(); 170 | SegmentB.low(); 171 | SegmentC.low(); 172 | SegmentD.high(); 173 | SegmentE.high(); 174 | SegmentF.high(); 175 | SegmentG.high(); 176 | segmentslit += 2; 177 | break; 178 | case 2: 179 | SegmentA.low(); 180 | SegmentB.low(); 181 | SegmentC.high(); 182 | SegmentD.low(); 183 | SegmentE.low(); 184 | SegmentF.high(); 185 | SegmentG.low(); 186 | segmentslit += 5; 187 | break; 188 | case 3: 189 | SegmentA.low(); 190 | SegmentB.low(); 191 | SegmentC.low(); 192 | SegmentD.low(); 193 | SegmentE.high(); 194 | SegmentF.high(); 195 | SegmentG.low(); 196 | segmentslit += 5; 197 | break; 198 | case 4: 199 | SegmentA.high(); 200 | SegmentB.low(); 201 | SegmentC.low(); 202 | SegmentD.high(); 203 | SegmentE.high(); 204 | SegmentF.low(); 205 | SegmentG.low(); 206 | segmentslit += 4; 207 | break; 208 | case 5: 209 | SegmentA.low(); 210 | SegmentB.high(); 211 | SegmentC.low(); 212 | SegmentD.low(); 213 | SegmentE.high(); 214 | SegmentF.low(); 215 | SegmentG.low(); 216 | segmentslit += 5; 217 | break; 218 | case 6: 219 | SegmentA.low(); 220 | SegmentB.high(); 221 | SegmentC.low(); 222 | SegmentD.low(); 223 | SegmentE.low(); 224 | SegmentF.low(); 225 | SegmentG.low(); 226 | segmentslit += 6; 227 | break; 228 | case 7: 229 | SegmentA.low(); 230 | SegmentB.low(); 231 | SegmentC.low(); 232 | SegmentD.high(); 233 | SegmentE.high(); 234 | SegmentF.high(); 235 | SegmentG.high(); 236 | segmentslit += 3; 237 | break; 238 | case 8: 239 | SegmentA.low(); 240 | SegmentB.low(); 241 | SegmentC.low(); 242 | SegmentD.low(); 243 | SegmentE.low(); 244 | SegmentF.low(); 245 | SegmentG.low(); 246 | segmentslit += 7; 247 | break; 248 | case 9: 249 | SegmentA.low(); 250 | SegmentB.low(); 251 | SegmentC.low(); 252 | SegmentD.high(); 253 | SegmentE.high(); 254 | SegmentF.low(); 255 | SegmentG.low(); 256 | break; 257 | segmentslit += 5; 258 | case 10: 259 | SegmentA.high(); 260 | SegmentB.high(); 261 | SegmentC.high(); 262 | SegmentD.high(); 263 | SegmentE.high(); 264 | SegmentF.high(); 265 | SegmentG.low(); 266 | segmentslit += 1; 267 | break; 268 | case 99: 269 | SegmentA.high(); 270 | SegmentB.high(); 271 | SegmentC.high(); 272 | SegmentD.high(); 273 | SegmentE.high(); 274 | SegmentF.high(); 275 | SegmentG.high(); 276 | break; 277 | default: 278 | allSegmentOff(); 279 | break; 280 | } 281 | 282 | return segmentslit; 283 | } 284 | 285 | byte lastSelectDigit = 0; 286 | 287 | void selectDigit(byte digit) { 288 | 289 | lastSelectDigit = digit; 290 | 291 | allDigitOff(); 292 | allDigitInput(); 293 | 294 | switch (digit) { 295 | case 1: 296 | Digit1.high(); 297 | Digit1.output(); 298 | break; 299 | case 2: 300 | Digit2.high(); 301 | Digit2.output(); 302 | break; 303 | case 3: 304 | Digit3.high(); 305 | Digit3.output(); 306 | break; 307 | case 4: 308 | Digit4.high(); 309 | Digit4.output(); 310 | break; 311 | case 5: 312 | Digit5.high(); 313 | Digit5.output(); 314 | break; 315 | case 6: 316 | Digit6.high(); 317 | Digit6.output(); 318 | break; 319 | case 7: 320 | Digit7.high(); 321 | Digit7.output(); 322 | break; 323 | case 8: 324 | Digit8.high(); 325 | Digit8.output(); 326 | break; 327 | case 9: 328 | Digit9.high(); 329 | Digit9.output(); 330 | break; 331 | } 332 | } 333 | 334 | void SinclairLogo() { 335 | 336 | int showtime = 1; 337 | 338 | for (byte t = 0; t < 14; t++) { 339 | if (t > 2) 340 | { 341 | showtime++; 342 | } 343 | if (t > 4) 344 | { 345 | showtime++; 346 | } 347 | for (int s = 0; s < 400; s++) { 348 | delayMicroseconds(120); // this delay is more important than the hyperoptimized logic below. must have off time. 349 | for (byte digit = 0; digit < 9; digit++) { 350 | switch (digit) { 351 | case 0: // i (comment out pins that do not change, low is on, high is off) 352 | SegmentA.high(); 353 | SegmentB.high(); 354 | //SegmentC.low(); 355 | SegmentD.high(); 356 | SegmentE.high(); 357 | //SegmentF.high(); 358 | SegmentG.high(); 359 | Digit2.high(); 360 | Digit2.output(); 361 | if (t > 0) { 362 | delayMicroseconds(showtime); 363 | } 364 | Digit2.low(); 365 | Digit2.input(); 366 | break; 367 | case 1: // i 368 | //SegmentA.high(); 369 | //SegmentB.high(); 370 | //SegmentC.low(); 371 | //SegmentD.high(); 372 | //SegmentE.high(); 373 | //SegmentF.high(); 374 | //SegmentG.high(); 375 | Digit7.high(); 376 | Digit7.output(); 377 | if (t > 0) { 378 | delayMicroseconds(showtime); 379 | } 380 | Digit7.low(); 381 | Digit7.input(); 382 | break; 383 | case 2: // l 384 | //SegmentA.high(); 385 | SegmentB.low(); 386 | //SegmentC.low(); 387 | //SegmentD.high(); 388 | //SegmentE.high(); 389 | //SegmentF.high(); 390 | //SegmentG.high(); 391 | Digit5.high(); 392 | Digit5.output(); 393 | if (t > 0) { 394 | delayMicroseconds(showtime); 395 | } 396 | Digit5.low(); 397 | Digit5.input(); 398 | break; 399 | case 3: // n 400 | SegmentA.low(); 401 | //SegmentB.low(); 402 | //SegmentC.low(); 403 | //SegmentD.high(); 404 | SegmentE.low(); 405 | SegmentF.low(); 406 | //SegmentG.high(); 407 | Digit3.high(); 408 | Digit3.output(); 409 | if (t > 0) { 410 | delayMicroseconds(showtime); 411 | } 412 | Digit3.low(); 413 | Digit3.input(); 414 | break; 415 | case 4: // r 416 | //SegmentA.low(); 417 | SegmentB.high(); 418 | SegmentC.high(); 419 | //SegmentD.high(); 420 | //SegmentE.low(); 421 | //SegmentF.low(); 422 | //SegmentG.high(); 423 | Digit8.high(); 424 | Digit8.output(); 425 | if (t > 0) { 426 | delayMicroseconds(showtime); 427 | } 428 | Digit8.low(); 429 | Digit9.input(); 430 | break; 431 | case 5: // c 432 | //SegmentA.low(); 433 | //SegmentB.high(); 434 | //SegmentC.high(); 435 | SegmentD.low(); 436 | //SegmentE.low(); 437 | //SegmentF.low(); 438 | //SegmentG.high(); 439 | Digit4.high(); 440 | Digit4.output(); 441 | if (t > 0) { 442 | delayMicroseconds(showtime); 443 | } 444 | Digit4.low(); 445 | Digit4.input(); 446 | break; 447 | case 6: // S 448 | //SegmentA.low(); //low is on 449 | //SegmentB.high(); 450 | SegmentC.low(); 451 | //SegmentD.low(); 452 | SegmentE.high(); 453 | //SegmentF.low(); 454 | SegmentG.low(); 455 | Digit1.high(); 456 | Digit1.output(); 457 | if (t > 0) { 458 | delayMicroseconds(showtime); 459 | } 460 | Digit1.low(); 461 | Digit1.input(); 462 | break; 463 | case 7: // a 464 | //SegmentA.low(); 465 | SegmentB.low(); 466 | //SegmentC.low(); 467 | //SegmentD.low(); 468 | SegmentE.low(); 469 | SegmentF.high(); 470 | //SegmentG.low(); 471 | Digit6.high(); 472 | Digit6.output(); 473 | if (t > 0) { 474 | delayMicroseconds(showtime); 475 | } 476 | Digit6.low(); 477 | Digit6.input(); 478 | break; 479 | } 480 | } 481 | } 482 | } 483 | 484 | exitsinclair: 485 | allSegmentOff(); 486 | allDigitOff(); 487 | 488 | } 489 | 490 | 491 | void displaySelfTest(bool longtest = false) { 492 | 493 | char c = 0; 494 | 495 | //kitt stuff 496 | byte pos = 0; 497 | byte p1 = 0; 498 | byte p2 = 0; 499 | char sign = 0; 500 | 501 | bool kitt = false; 502 | 503 | // show 8.8.8.8.8.8.8.8.8. 504 | for (byte t = 0; t < 75; t++) { 505 | for (byte j = 0; j < 9; j++) { 506 | outputDigit(8, true); 507 | selectDigit(j + 1); 508 | c = readKey(); 509 | if (c == '8') { 510 | longtest = true; 511 | } 512 | if ((longtest == true ) && (c == '2')) { 513 | kitt = true; 514 | } 515 | if (c == 'C') { 516 | goto exitfn; 517 | } 518 | delay(2); 519 | } 520 | } 521 | 522 | if (kitt) { 523 | goto kitt; 524 | } 525 | 526 | if (!longtest) { 527 | goto exitfn; 528 | } 529 | 530 | // cycle through 0..9 - on all digits at the same time 531 | for (byte i = 0; i < 12; i++) { 532 | for (byte t = 0; t < 100; t++) { 533 | for (byte j = 0; j < 9; j++) { 534 | outputDigit((i != 11) ? i : 99, (i == 11)); 535 | selectDigit(j + 1); 536 | if (readKey() == 'C') { 537 | goto exitfn; 538 | } 539 | delay(2); 540 | } 541 | } 542 | } 543 | 544 | // show 12345678 545 | for (byte t = 0; t < 100; t++) { 546 | for (byte j = 0; j < 9; j++) { 547 | outputDigit(j, false); 548 | selectDigit(j + 1); 549 | if (readKey() == 'C') { 550 | goto exitfn; 551 | } 552 | delay(2); 553 | } 554 | } 555 | 556 | // move the dot from left to right on 12345678 557 | for (byte i = 0; i < 9; i++) { 558 | for (byte t = 0; t < 100; t++) { 559 | for (byte j = 0; j < 9; j++) { 560 | outputDigit(j, i == j); 561 | selectDigit(j + 1); 562 | if (readKey() == 'C') { 563 | goto exitfn; 564 | } 565 | delay(2); 566 | } 567 | } 568 | } 569 | 570 | // move dot from right to left on blank display 571 | for (byte j = 9; j > 0; j--) { 572 | for (byte t = 0; t < 100; t++) { 573 | for (byte i = 0; i < 9; i++) { 574 | outputDigit(99, i == (j - 1)); 575 | selectDigit(i + 1); 576 | if (readKey() == 'C') { 577 | goto exitfn; 578 | } 579 | delay(2); 580 | } 581 | } 582 | } 583 | 584 | // cycle through 0..9 - on each digit individually 585 | for (byte j = 0; j < 9; j++) { 586 | for (byte i = 0; i < 12; i++) { 587 | for (byte t = 0; t < 20; t++) { 588 | for (byte k = 0; k < 9; k++) { 589 | outputDigit( ((k == j) && (i != 11)) ? i : 99, ((k == j) && (i == 11))); 590 | selectDigit(k + 1); 591 | if (readKey() == 'C') { 592 | goto exitfn; 593 | } 594 | delay(2); 595 | } 596 | } 597 | } 598 | } 599 | 600 | goto exitfn; 601 | 602 | kitt: 603 | 604 | for (byte i = 0; i < 249; i++) 605 | { 606 | p2 = p1; 607 | p1 = pos; 608 | pos += sign; 609 | 610 | if (pos == 0) { 611 | sign = 1; 612 | } 613 | 614 | if (pos == 8) { 615 | sign = -1; 616 | } 617 | 618 | for (byte t = 0; t < 25; t++) { 619 | for (byte j = 0; j < 9; j++) { 620 | c = readKey(); 621 | if (c == 'C') { 622 | goto exitfn; 623 | } 624 | outputDigit(10, false); 625 | selectDigit(j + 1); 626 | if (j == pos) 627 | { 628 | delayMicroseconds(1000); 629 | } else if (j == p1) 630 | { 631 | delayMicroseconds(500); 632 | } else if (j == p2) 633 | { 634 | delayMicroseconds(100); 635 | } else 636 | delayMicroseconds(20); 637 | } 638 | } 639 | } 640 | 641 | exitfn: 642 | allSegmentOff(); 643 | allDigitOff(); 644 | 645 | } 646 | 647 | //const char PrintableKeys[19] = "12+E0v-378X654/9^C"; 648 | 649 | const char KeysKN[10] = "156789234"; 650 | const char KeysKO[10] = "C/*^E0v+-"; 651 | 652 | byte readKey() { 653 | 654 | byte key = 0; 655 | 656 | if (analogRead(7) > 100) 657 | { 658 | SinclairData.keyStrobeKN = 1; 659 | key = KeysKN[lastSelectDigit - 1]; 660 | } 661 | else 662 | { 663 | SinclairData.keyStrobeKN = 0; 664 | } 665 | 666 | if (analogRead(6) > 100) 667 | { 668 | SinclairData.keyStrobeKO = 1; 669 | key = KeysKO[lastSelectDigit - 1]; 670 | } 671 | else 672 | { 673 | SinclairData.keyStrobeKO = 0; 674 | } 675 | 676 | // ensure C has priority 677 | if ((lastSelectDigit == 1) && (SinclairData.keyStrobeKO)) 678 | { 679 | key = 'C'; 680 | } 681 | 682 | /* 683 | Serial.print(lastSelectDigit); 684 | Serial.print(SinclairData.dActive); 685 | Serial.print(SinclairData.keyStrobeKN); 686 | Serial.print(SinclairData.keyStrobeKO); 687 | Serial.print(key); 688 | Serial.println(""); 689 | */ 690 | 691 | return key; 692 | } 693 | 694 | //since reakKey only reads the active column, this function sweeps through all columns and calls readKey 695 | byte readKeys() 696 | { 697 | byte k; 698 | byte key = 0; 699 | 700 | outputDigit(99); 701 | for (byte i = 1; i < 10; i++) 702 | { 703 | selectDigit(i); 704 | k = readKey(); 705 | 706 | if (k) 707 | { 708 | key = k; 709 | } 710 | 711 | } 712 | 713 | return key; 714 | } 715 | -------------------------------------------------------------------------------- /SinclairScientific5/SinclairScientific5.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 8 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 9 | // @arduinoenigma 2018 10 | // 11 | // To test the up/down buttons press: 12 | // C up X up / up + up - 5.4105-01 13 | // C dn X dn / dn + dn - 4.4200-01 14 | // 15 | // Sinclair Scientific Glitches implemented: 16 | // dot stays on when display turns off, dot gets brighter 17 | // pressing 0 makes dot brighter, other keys not so much 18 | // pressing a key glitches the number displayed, 19 | // pressing 7 glitches more numbers than pressing 1 20 | // pressing C dims the display 21 | // 22 | // Glitches not implemented: 23 | // 1 is brighter than 0 24 | // - in mantissa is brighter 25 | // 26 | // Other Features: 27 | // press 0 when powering up to display short display self test. 28 | // press 8 when short display self test is showing for longer self test, press C to exit. 29 | // press 1 when powering up for slower than normal speed, display stays lit while computing a result. 30 | // press 2 when powering up for normal speed with display on. 31 | // press 3 when powering up for faster speed with display on. 32 | // 33 | // Notes: 34 | // Step() runs all instructions in 148uS, if an instruction is faster, it delays until 148us passes. 35 | // Adjusted refresh delay in updateDisplay() so execution time of ArcCos 0.0001 matches real hardware. 36 | // step() function get called every 340uS for an effective clock speed of 2.9kHz 37 | // 38 | // uses this fast pin library: 39 | // https://github.com/mikaelpatel/Arduino-GPIO 40 | // 41 | 42 | #include "GPIO.h" 43 | 44 | extern void setupFastADC(); 45 | extern void allSegmentOutput(); 46 | extern void allSegmentOff(); 47 | extern void allSegmentInput(); 48 | extern void allDigitOutput(); 49 | extern void allDigitOff(); 50 | extern void allKeyRowOff(); 51 | extern void allKeyRowIdle(); 52 | extern byte outputDigit(signed char, bool dp = false); 53 | extern void selectDigit(byte); 54 | extern void display(); 55 | extern void displaySelfTest(bool longtest = false); 56 | extern byte readKey(); 57 | extern byte readKeys(); 58 | 59 | extern void step(); 60 | 61 | //These are the 320 instructions that performed all the tasks on the Sinclair Scientific. 62 | //the rest of this program is to execute and interface these instructions to todays hardware 63 | 64 | const unsigned int objectCode[] PROGMEM = { 65 | 1408, 1392, 1792, 1824, 1860, 1808, 1360, 1376, 66 | 518, 1319, 1360, 1376, 9, 1360, 1908, 1072, 67 | 1083, 1075, 1121, 1129, 1073, 1069, 1051, 1840, 68 | 1955, 1840, 516, 1425, 552, 1430, 33, 1792, 69 | 1398, 1631, 1920, 1683, 34, 2003, 1540, 4, 70 | 1399, 1858, 1872, 1538, 4, 1329, 1335, 4, 71 | 1349, 1347, 4, 1443, 676, 1431, 57, 1559, 72 | 4, 1553, 59, 1443, 677, 1839, 1632, 2018, 73 | 65, 2023, 1719, 72, 1730, 71, 1840, 1666, 74 | 1751, 587, 1840, 1754, 78, 1840, 1718, 594, 75 | 1924, 78, 2017, 1713, 89, 1540, 130, 1844, 76 | 1841, 1652, 597, 130, 1730, 95, 1849, 1650, 77 | 114, 1443, 675, 1355, 1345, 130, 1409, 1559, 78 | 105, 1443, 750, 1839, 1632, 1844, 2023, 1719, 79 | 92, 1538, 1537, 116, 1451, 1796, 791, 1908, 80 | 1781, 637, 1722, 1540, 120, 1940, 1786, 119, 81 | 1445, 820, 1754, 512, 1747, 145, 1860, 1751, 82 | 142, 1686, 141, 1799, 1798, 1686, 1558, 132, 83 | 132, 1908, 1751, 662, 1686, 1686, 1558, 152, 84 | 1441, 614, 1392, 1334, 1408, 1750, 161, 1559, 85 | 159, 1568, 4, 1351, 1355, 1686, 681, 1908, 86 | 165, 1801, 1689, 1824, 1445, 180, 1447, 179, 87 | 1568, 1819, 185, 1565, 1820, 1924, 2011, 1693, 88 | 738, 1888, 1888, 1888, 2012, 1696, 1936, 1936, 89 | 1936, 1872, 1872, 1872, 2012, 1584, 1724, 1920, 90 | 1920, 1920, 1445, 183, 1561, 1447, 210, 1779, 91 | 697, 220, 1451, 727, 1732, 185, 220, 1754, 92 | 1844, 1764, 1844, 185, 1904, 1904, 1904, 1904, 93 | 1904, 130, 1447, 233, 1616, 1600, 1808, 1411, 94 | 100, 1451, 236, 1632, 1840, 130, 1445, 251, 95 | 1750, 760, 1751, 759, 1908, 1686, 240, 1860, 96 | 1794, 1864, 1864, 1824, 2003, 1636, 1924, 1924, 97 | 1700, 1431, 823, 2009, 1787, 253, 1993, 2036, 98 | 1723, 1920, 1920, 1920, 1920, 1588, 1844, 1445, 99 | 814, 1600, 1479, 1447, 765, 1572, 1796, 1806, 100 | 1764, 797, 1700, 1562, 280, 1571, 803, 1860, 101 | 1631, 1892, 280, 1807, 1443, 808, 130, 10, 102 | 1572, 1796, 1904, 1904, 1794, 61, 1572, 1796, 103 | 1803, 1652, 1844, 117, 1908, 1335, 251, 1693, 104 | 253, 1565, 1860, 1563, 2036, 1844, 1411, 790 105 | }; 106 | 107 | #define NUMBER_OF_MASKS 16 108 | #define MASK_LENGTH 12 109 | 110 | const char masks [][MASK_LENGTH] PROGMEM = { 111 | {"00000000000"}, // M0 112 | {"5 "}, // M1 113 | {" 00 "}, // M2 114 | {" 1 "}, // M3 115 | {" 0000000"}, // M4 116 | {" 1"}, // M5 117 | {" 01 "}, // M6 118 | {" 5 "}, // M7 119 | {"000000 "}, // M8 120 | {"0001 "}, // M9 121 | {" 0000001"}, // M10 122 | {" 1 "}, // M11 123 | {" 00005 "}, // M12 124 | {" 00001 "}, // M13 125 | {" 4 "}, // M14 126 | {" 0 "}, // M15 127 | }; 128 | 129 | // OPS WITH K: 1:AAKA 2:AAKC 5:ACKA 6:ACKB 9: 11: 130 | //const unsigned long LISTOPSWITHK = 000111100000001111010101001100110b; 131 | const unsigned long LISTOPSWITHK = 1007135334; 132 | 133 | signed char digits[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 134 | 135 | // keysKN and keysKO must match PrintableKeys on DisplayAndKeys.ino 136 | //const char PrintableKeys[19] = "12+E0v-378X654/9^C"; 137 | 138 | const char keysKN[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', 0}; 139 | const char keysKO[10] = {'C', 'v', '+', '-', '/', 'X', '^', 'E', '0', 0}; 140 | //const char keysKP[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 141 | 142 | // to use with keyPressed 143 | #define DK 1 144 | 145 | // ORIGINAL SINCLAIR DATA 146 | struct SinclairData_t 147 | { 148 | boolean sinclair = true; 149 | 150 | // Important: Array order matches display order, not bit order. 151 | // I.e. a[0] is high-order digit S10, a[10] is low-order digit S0. 152 | signed char a[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register A 153 | signed char b[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register B 154 | signed char c[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register C 155 | signed char af[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 156 | signed char bf[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 157 | 158 | signed char d[11] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 159 | 160 | byte dActive = 1; // Currently active D value 1-11. d[dActive-1] == 0 161 | signed char cc = 0; 162 | byte keyPressed = 0; 163 | 164 | byte keyStrobeKN = 0; // 165 | byte keyStrobeKO = 0; // ' 166 | byte keyStrobeKP = 0; // ' 167 | 168 | unsigned int address = 0; // PROGRAM COUNTER 169 | byte display = 1; // Flag for display on 170 | char mask[MASK_LENGTH]; 171 | 172 | byte showwork = 0; // when 0, display flag controls LED, when 1, LED always on 173 | byte speed = 1; // 0 - slow 1 - normal 2 - fast 174 | byte steptime = 148; // the step() function will take this many microseconds to execute, uses a delay to eat remaining time if it finishes faster. 175 | 176 | bool resetinprogress = false; 177 | } SinclairData; 178 | 179 | const byte DisplayOrder[9] = {1, 7, 8, 9, 2, 3, 4, 5, 6}; 180 | 181 | void updateDisplay() 182 | { 183 | byte displayon = SinclairData.display + SinclairData.showwork; 184 | 185 | bool dp = false; // needed for logic below, keep initialized to false 186 | bool dp1; 187 | bool dp2; 188 | 189 | byte showdigit; 190 | byte digitoff = 99; 191 | byte digiton = 99; 192 | byte digitpos = 0; 193 | 194 | digitpos = DisplayOrder[SinclairData.dActive - 1]; 195 | 196 | switch (digitpos) 197 | { 198 | case 1: 199 | digiton = (SinclairData.a[0] == 0) ? 99 : 10; 200 | break; 201 | 202 | case 2: 203 | digiton = SinclairData.a[4]; 204 | break; 205 | 206 | case 3: 207 | digiton = SinclairData.a[5]; 208 | break; 209 | 210 | case 4: 211 | digiton = SinclairData.a[6]; 212 | break; 213 | 214 | case 5: 215 | digiton = SinclairData.a[7]; 216 | break; 217 | 218 | case 6: 219 | digiton = SinclairData.a[8]; 220 | break; 221 | 222 | case 7: 223 | digiton = (SinclairData.a[1] == 0) ? 99 : 10; 224 | break; 225 | 226 | case 8: 227 | digiton = SinclairData.a[2]; 228 | break; 229 | 230 | case 9: 231 | digiton = SinclairData.a[3]; 232 | break; 233 | } 234 | 235 | dp2 = !dp; // !false 236 | 237 | // SINCLAIR behavior: turn decimal point on automatically at fixed position 238 | // dot stays on whether the rest of the display is on or off 239 | // this logic attempts to make optimizer do variable copy on dp1 = dp2 and dp1 = dp below. 240 | if (digitpos == 2) { 241 | dp1 = dp2; // true 242 | } 243 | else 244 | { 245 | dp1 = dp; // false 246 | } 247 | 248 | // attempt to have this block take same time to run whether a digit or blank is displayed 249 | if (displayon) { 250 | showdigit = digiton; 251 | dp = dp1; // true or false depending on digit shown 252 | } 253 | else { 254 | // SINCLAIR behavior: dot goes brighter when display is off. 255 | showdigit = digitoff; 256 | if (digitpos > 2) // allow position 1 to be read for C and position 2 to display dot 257 | { 258 | digitpos = 2; 259 | dp = dp2; // always true 260 | } 261 | } 262 | 263 | byte segmentslit; 264 | segmentslit = outputDigit(showdigit, dp); 265 | //delayMicroseconds(segmentslit << 2); 266 | //delay(segmentslit); 267 | selectDigit(digitpos); 268 | 269 | // adjust case 1 for the following: 270 | // Sin 1 takes about 7.3 seconds. 271 | // Arccos .2 takes about 13.6 seconds 272 | // Arctan 1 takes about 6.6 seconds 273 | // Arccos of a very small value (e.g. 0.0001) goes into an almost-infinite loop and takes 1 minute, 38 seconds to complete 274 | 275 | if (SinclairData.resetinprogress == false) 276 | { 277 | switch (SinclairData.speed) 278 | { 279 | case 0: 280 | delayMicroseconds(2000); 281 | break; 282 | case 1: 283 | delayMicroseconds(140); 284 | break; 285 | case 2: 286 | delayMicroseconds(50); 287 | break; 288 | } 289 | } 290 | else 291 | { 292 | // SINCLAIR behavior: makes the display dim when C is pressed, SinclairData.steptime must be reduced below 148 for this delay to take effect. 293 | //delayMicroseconds(5); 294 | } 295 | } 296 | 297 | void setup() 298 | { 299 | // put your setup code here, to run once: 300 | 301 | Serial.begin(250000); 302 | 303 | setupFastADC(); 304 | 305 | allSegmentOff(); 306 | allSegmentOutput(); 307 | 308 | allDigitOff(); 309 | allDigitOutput(); 310 | 311 | SinclairLogo(); 312 | 313 | char key = readKeys(); 314 | SinclairData.showwork = 1; //takes effect only on 1,2,3 315 | 316 | switch (key) 317 | { 318 | case '0': 319 | displaySelfTest(); 320 | SinclairData.speed = 1; 321 | SinclairData.showwork = 0; //normal speed, display off during calculations 322 | break; 323 | case '1': 324 | SinclairData.speed = 0; //slow, display on 325 | break; 326 | case '2': 327 | SinclairData.speed = 1; //normal, display on 328 | break; 329 | case '3': 330 | SinclairData.speed = 2; //fast, display on 331 | break; 332 | default: 333 | SinclairData.speed = 1; 334 | SinclairData.showwork = 0; //normal speed, display off during calculations 335 | break; 336 | } 337 | } 338 | 339 | // timing the loop function using the micros() function, finds it executes every 340uS, for a 2.9kHz clock speed 340 | 341 | void loop() 342 | { 343 | // put your main code here, to run repeatedly: 344 | char key; 345 | 346 | step(); 347 | 348 | updateDisplay(); 349 | 350 | key = readKey(); 351 | 352 | if (SinclairData.resetinprogress) 353 | { 354 | if (SinclairData.address > 8) { 355 | SinclairData.resetinprogress = false; 356 | SinclairData.steptime = 148; 357 | } 358 | else 359 | { 360 | //key = readKey(); 361 | } 362 | } 363 | 364 | if (key == 'C') { 365 | if (SinclairData.resetinprogress == false) { 366 | SinclairData.address = 0; 367 | SinclairData.keyStrobeKN = 0; 368 | SinclairData.keyStrobeKO = 0; 369 | SinclairData.keyStrobeKP = 0; 370 | SinclairData.dActive = 1; 371 | SinclairData.steptime = 5; // speed up step() function so updateDisplay() function can control brightness via smaller delayMicrososeconds() 372 | SinclairData.resetinprogress = true; 373 | } 374 | } 375 | 376 | } 377 | -------------------------------------------------------------------------------- /SinclairScientificExperiments/SinclairEnduranceTimer: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct timeEvent_t 5 | { 6 | byte init; 7 | byte event; 8 | unsigned long time; 9 | } 10 | TimeEvent; 11 | 12 | int eventaddress = 0; 13 | 14 | bool ison = false; 15 | int val = 1024; 16 | 17 | void halt() 18 | { 19 | Serial.println(F("halting")); 20 | 21 | do 22 | { 23 | 24 | } while (true); 25 | } 26 | 27 | void clearEEPROM() 28 | { 29 | int i; 30 | 31 | Serial.println(F("clearing EEPROM")); 32 | 33 | for (i = 0; i < 1024; i++) 34 | { 35 | EEPROM.write(i, 255); 36 | } 37 | 38 | eventaddress = 0; 39 | 40 | Serial.println(F("done")); 41 | 42 | // uncomment to halt program and allow to start recording records on a clear eeprom 43 | halt(); //comment to start recording logs after clearing eeprom 44 | } 45 | 46 | 47 | void readEvents() 48 | { 49 | int evaddress = eventaddress; 50 | int nextevent = 0; 51 | 52 | Serial.println(F("reading stored logs")); 53 | 54 | do 55 | { 56 | eeprom_read_block((void*)&TimeEvent, (void*)evaddress, sizeof(timeEvent_t)); 57 | 58 | if (TimeEvent.init == 42) 59 | { 60 | Serial.print(F("event found at: ")); 61 | Serial.println(evaddress); 62 | nextevent = evaddress + sizeof(timeEvent_t); 63 | if (TimeEvent.event == 0) { 64 | Serial.print(F("off at ")); 65 | } 66 | if (TimeEvent.event == 1) { 67 | Serial.print(F("on at ")); 68 | } 69 | Serial.println(TimeEvent.time); 70 | evaddress += sizeof(timeEvent_t); 71 | } 72 | else 73 | { 74 | evaddress++; 75 | } 76 | } while (evaddress < 1000); 77 | 78 | eventaddress = nextevent; 79 | 80 | Serial.println(F("done")); 81 | } 82 | 83 | 84 | void writeEvent() 85 | { 86 | if (eventaddress < 1000) 87 | { 88 | eeprom_write_block((void*)&TimeEvent, (void*)eventaddress, sizeof(timeEvent_t)); 89 | Serial.print(F("written at: ")); 90 | Serial.println(eventaddress); 91 | eventaddress += sizeof(timeEvent_t); 92 | } 93 | else 94 | { 95 | Serial.println(F("event log full")); 96 | } 97 | } 98 | 99 | 100 | void setup() { 101 | Serial.begin(9600); 102 | 103 | Serial.print(F("time event structure is: ")); 104 | Serial.print(sizeof(timeEvent_t)); 105 | Serial.println(F(" bytes long")); 106 | 107 | readEvents(); 108 | 109 | //clearEEPROM(); // comment to preserve prior execution logs 110 | 111 | Serial.println(F("ready to record events")); 112 | } 113 | 114 | 115 | void loop() { 116 | int sensorValue; 117 | 118 | // read the value from the sensor: 119 | sensorValue = analogRead(A0); 120 | 121 | val = (val * 4.0 + sensorValue ) / 5.0; 122 | 123 | /* 124 | Serial.print(sensorValue); 125 | Serial.print(" "); 126 | Serial.println(val); 127 | */ 128 | 129 | if ((val < 950) && (ison == false)) 130 | { 131 | TimeEvent.init = 42; 132 | TimeEvent.event = 1; 133 | TimeEvent.time = millis(); 134 | writeEvent(); 135 | 136 | ison = true; 137 | Serial.print(F("on at ")); 138 | Serial.println(TimeEvent.time); 139 | } 140 | 141 | if ((val > 1000) && (ison == true)) 142 | { 143 | TimeEvent.init = 42; 144 | TimeEvent.event = 0; 145 | TimeEvent.time = millis(); 146 | writeEvent(); 147 | 148 | ison = false; 149 | Serial.print(F("off at ")); 150 | Serial.println(TimeEvent.time); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /SinclairScientificExperiments/SinclairScientificExperiments.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // 8 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 9 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 10 | // @arduinoenigma 2018 11 | 12 | unsigned int const objectCode[] PROGMEM = { 13 | 1408, 1392, 1792, 1824, 1860, 1808, 1360, 1376, 14 | 518, 1319, 1360, 1376, 9, 1360, 1908, 1072, 15 | 1083, 1075, 1121, 1129, 1073, 1069, 1051, 1840, 16 | 1955, 1840, 516, 1425, 552, 1430, 33, 1792, 17 | 1398, 1631, 1920, 1683, 34, 2003, 1540, 4, 18 | 1399, 1858, 1872, 1538, 4, 1329, 1335, 4, 19 | 1349, 1347, 4, 1443, 676, 1431, 57, 1559, 20 | 4, 1553, 59, 1443, 677, 1839, 1632, 2018, 21 | 65, 2023, 1719, 72, 1730, 71, 1840, 1666, 22 | 1751, 587, 1840, 1754, 78, 1840, 1718, 594, 23 | 1924, 78, 2017, 1713, 89, 1540, 130, 1844, 24 | 1841, 1652, 597, 130, 1730, 95, 1849, 1650, 25 | 114, 1443, 675, 1355, 1345, 130, 1409, 1559, 26 | 105, 1443, 750, 1839, 1632, 1844, 2023, 1719, 27 | 92, 1538, 1537, 116, 1451, 1796, 791, 1908, 28 | 1781, 637, 1722, 1540, 120, 1940, 1786, 119, 29 | 1445, 820, 1754, 512, 1747, 145, 1860, 1751, 30 | 142, 1686, 141, 1799, 1798, 1686, 1558, 132, 31 | 132, 1908, 1751, 662, 1686, 1686, 1558, 152, 32 | 1441, 614, 1392, 1334, 1408, 1750, 161, 1559, 33 | 159, 1568, 4, 1351, 1355, 1686, 681, 1908, 34 | 165, 1801, 1689, 1824, 1445, 180, 1447, 179, 35 | 1568, 1819, 185, 1565, 1820, 1924, 2011, 1693, 36 | 738, 1888, 1888, 1888, 2012, 1696, 1936, 1936, 37 | 1936, 1872, 1872, 1872, 2012, 1584, 1724, 1920, 38 | 1920, 1920, 1445, 183, 1561, 1447, 210, 1779, 39 | 697, 220, 1451, 727, 1732, 185, 220, 1754, 40 | 1844, 1764, 1844, 185, 1904, 1904, 1904, 1904, 41 | 1904, 130, 1447, 233, 1616, 1600, 1808, 1411, 42 | 100, 1451, 236, 1632, 1840, 130, 1445, 251, 43 | 1750, 760, 1751, 759, 1908, 1686, 240, 1860, 44 | 1794, 1864, 1864, 1824, 2003, 1636, 1924, 1924, 45 | 1700, 1431, 823, 2009, 1787, 253, 1993, 2036, 46 | 1723, 1920, 1920, 1920, 1920, 1588, 1844, 1445, 47 | 814, 1600, 1479, 1447, 765, 1572, 1796, 1806, 48 | 1764, 797, 1700, 1562, 280, 1571, 803, 1860, 49 | 1631, 1892, 280, 1807, 1443, 808, 130, 10, 50 | 1572, 1796, 1904, 1904, 1794, 61, 1572, 1796, 51 | 1803, 1652, 1844, 117, 1908, 1335, 251, 1693, 52 | 253, 1565, 1860, 1563, 2036, 1844, 1411, 790 53 | }; 54 | 55 | #define NUMBER_OF_MASKS 16 56 | #define MASK_LENGTH 12 57 | 58 | const char masks [][MASK_LENGTH] PROGMEM = { 59 | {"00000000000"}, // M0 60 | {"5 "}, // M1 61 | {" 00 "}, // M2 62 | {" 1 "}, // M3 63 | {" 0000000"}, // M4 64 | {" 1"}, // M5 65 | {" 01 "}, // M6 66 | {" 5 "}, // M7 67 | {"000000 "}, // M8 68 | {"0001 "}, // M9 69 | {" 0000001"}, // M10 70 | {" 1 "}, // M11 71 | {" 00005 "}, // M12 72 | {" 00001 "}, // M13 73 | {" 4 "}, // M14 74 | {" 0 "}, // M15 75 | }; 76 | 77 | //OPS WITH K: 1:AAKA 2:AAKC 5:ACKA 6:ACKB 9: 11: 78 | //const unsigned long LISTOPSWITHK = 000111100000001111010101001100110b; 79 | const unsigned long LISTOPSWITHK = 1007135334; 80 | 81 | //ORIGINAL SINCLAIR DATA 82 | 83 | //this.rom = objectCode; 84 | //this.sinclair = sinclair; 85 | // Important: Array order matches display order, not bit order. 86 | // I.e. a[0] is high-order digit S10, a[10] is low-order digit S0. 87 | //this.a = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Register A 88 | //this.b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Register B 89 | //this.c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Register C 90 | //this.af = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Flags A 91 | //this.bf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Flags B 92 | // The states are called D1 to D10. The last bit is never set 93 | // d is sort of a combination of the 10-bit digit scan register and the 11-bit D-scan register. 94 | // In reality, all 11 bits of the D-scan register are used, but at varying S cycles. 95 | // The digit scan register is clocked at S9 phase 3. Thus 11 shifts of D-scan take the same time 96 | // as 10 shifts of digit scan. 97 | //this.d = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; // D scan register, d[0] low for D1 98 | // Currently active D value 1-11. d[dActive-1] == 0 99 | //this.dActive = 1; 100 | //this.cc = 0; 101 | //this.ccMeaning = ''; 102 | //this.keyPressed = null; 103 | // 'KO' or 'KP' if a keyboard input line is active, i.e. dActive and keyPressed match in the key matrix 104 | //this.keyStrobe = 0; 105 | //this.address = 0; 106 | //this.display = 1; // Flag for display on 107 | //this.mask = null; 108 | //this.showDisplayScan = 0; 109 | //this.idle = 0; // 1 if in idle loop 110 | //this.speed = 'auto'; // Speed for updates 111 | //this.fastStep = 1; // 1 makes SYNC, etc take 1 cycle 112 | //this.power = 1; // On 113 | 114 | struct SinclairData_t 115 | { 116 | boolean sinclair = true; 117 | 118 | // Important: Array order matches display order, not bit order. 119 | // I.e. a[0] is high-order digit S10, a[10] is low-order digit S0. 120 | signed char a[11] = {8, 0, 0, 2, 0, 0, 4, 0, 0, 0, 6}; // Register A 121 | signed char b[11] = {1, 0, 0, 3, 0, 0, 5, 0, 0, 0, 5}; // Register B 122 | signed char c[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register C 123 | signed char af[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 124 | signed char bf[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 125 | 126 | signed char d[11] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 127 | 128 | // Currently active D value 1-11. d[dActive-1] == 0 129 | byte dActive = 1; 130 | signed char cc = 0; 131 | char ccMeaning[12]; 132 | 133 | byte keyPressed = 0; 134 | // 'KO' or 'KP' if a keyboard input line is active, i.e. dActive and keyPressed match in the key matrix 135 | byte keyStrobe = 0; 136 | unsigned int address = 0; // PROGRAM COUNTER 137 | byte display = 1; // Flag for display on 138 | char mask[MASK_LENGTH]; 139 | byte showDisplayScan = 0; 140 | byte idle = 0; // 1 if in idle loop 141 | byte speed = 0; // Speed for updates 142 | byte fastStep = 1; // 1 makes SYNC, etc take 1 cycle 143 | byte power = 1; // On 144 | } SinclairData; 145 | 146 | boolean opsWithK(byte opcode) { 147 | return (LISTOPSWITHK & (1UL << opcode)); 148 | } 149 | 150 | unsigned int getInstruction(unsigned int PC) { 151 | 152 | return pgm_read_word_near(objectCode + PC); 153 | } 154 | 155 | byte getMaskNum() { 156 | 157 | return getInstruction(SinclairData.address) & 0x0f; 158 | } 159 | 160 | void getMask() { 161 | 162 | unsigned int instruction = getInstruction(SinclairData.address); 163 | byte classBits = instruction >> 9; 164 | byte opcode = (instruction >> 4) & 0x1f; 165 | 166 | if (classBits == 3 || (classBits == 2 && opcode > 18 && opcode != 21 && opcode != 22)) 167 | { 168 | byte maskno = getMaskNum(); 169 | 170 | for (byte i = 0; i <= 10; i++) 171 | { 172 | char maskdigit = pgm_read_byte_near(masks[maskno] + i); 173 | 174 | if (maskdigit == ' ') 175 | { 176 | SinclairData.mask[i] = maskdigit; 177 | } 178 | else if (classBits == 3 && opsWithK(opcode)) 179 | { 180 | // Register instruction 181 | SinclairData.mask[i] = maskdigit; 182 | } 183 | else 184 | { 185 | SinclairData.mask[i] = '*'; 186 | } 187 | } 188 | } 189 | else 190 | { 191 | SinclairData.mask[0] = 0; 192 | } 193 | } 194 | 195 | void add(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 196 | { 197 | byte carry = 0; 198 | getMask(); 199 | for (signed char i = 10; i >= 0; i--) 200 | { 201 | if (SinclairData.mask[i] == ' ') 202 | { 203 | // masked out 204 | // continue; 205 | } 206 | else 207 | { 208 | signed char result = src1[i] + src2[i] + carry; 209 | if (!hex && result >= 10) 210 | { 211 | result -= 10; 212 | carry = 1; 213 | } 214 | else if (hex && result >= 16) 215 | { 216 | result -= 16; 217 | carry = 1; 218 | } 219 | else 220 | { 221 | carry = 0; 222 | } 223 | dst[i] = result; 224 | } 225 | } 226 | if (carry) 227 | { 228 | SinclairData.cc = carry; 229 | //this.model.ccMeaning = carry ? 'overflow' : 'no overflow'; 230 | } 231 | } 232 | 233 | void sub(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 234 | { 235 | byte borrow = 0; 236 | getMask(); 237 | for (signed char i = 10; i >= 0; i--) 238 | { 239 | if (SinclairData.mask[i] == ' ') 240 | { 241 | // masked out 242 | // continue; 243 | } 244 | else 245 | { 246 | signed char result = src1[i] - src2[i] - borrow; 247 | if (result < 0) 248 | { 249 | result += hex ? 16 : 10; 250 | borrow = 1; 251 | } 252 | else 253 | { 254 | borrow = 0; 255 | } 256 | dst[i] = result; 257 | } 258 | } 259 | if (borrow) 260 | { 261 | SinclairData.cc = borrow; 262 | //this.model.ccMeaning = borrow ? 'borrow' : 'no borrow'; 263 | } 264 | } 265 | 266 | void compare(signed char src1[], signed char src2[]) 267 | { 268 | signed char tmp[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 269 | 270 | sub(src1, src2, tmp); 271 | // Compare sets condition if not borrow 272 | //this.model.ccMeaning = this.model.cc ? "less than" : "not less than"; 273 | } 274 | 275 | void copy(signed char src[], signed char dst[]) 276 | { 277 | getMask(); 278 | for (signed char i = 10; i >= 0; i--) 279 | { 280 | if (SinclairData.mask[i] == ' ') 281 | { 282 | // masked out 283 | // continue; 284 | } 285 | else 286 | { 287 | dst[i] = src[i]; 288 | } 289 | } 290 | } 291 | 292 | void all(signed char src[]) 293 | { 294 | getMask(); 295 | signed char digit = 0; 296 | for (signed char i = 10; i >= 0; i--) 297 | { 298 | if (SinclairData.mask[i] == ' ') 299 | { 300 | // masked out 301 | // continue; 302 | } 303 | else 304 | { 305 | signed char newdigit = src[i]; 306 | src[i] = digit; 307 | digit = newdigit; 308 | } 309 | } 310 | } 311 | 312 | void srl(signed char src[]) 313 | { 314 | getMask(); 315 | signed char digit = 0; 316 | for (signed char i = 0; i <= 10; i++) 317 | { 318 | if (SinclairData.mask[i] == ' ') 319 | { 320 | // masked out 321 | // continue; 322 | } 323 | else 324 | { 325 | signed char newdigit = src[i]; 326 | src[i] = digit; 327 | digit = newdigit; 328 | } 329 | } 330 | } 331 | 332 | void writeFlag(signed char dest[], signed char val) 333 | { 334 | getMask(); 335 | for (signed char i = 10; i >= 0; i--) 336 | { 337 | if (SinclairData.mask[i] == ' ') 338 | { 339 | // masked out 340 | // continue; 341 | } 342 | else 343 | { 344 | // Flip dst if val == -1, otherwise set to val 345 | dest[i] = (val < 0) ? (1 - dest[i]) : val; 346 | } 347 | } 348 | } 349 | 350 | void compareFlags(signed char src1[], signed char src2[]) 351 | { 352 | signed char cc = 0; 353 | getMask(); 354 | for (signed char i = 10; i >= 0; i--) 355 | { 356 | if (SinclairData.mask[i] == ' ') 357 | { 358 | // masked out 359 | // continue; 360 | } 361 | else 362 | { 363 | if (src1[i] != src2[i]) 364 | { 365 | cc = 1; 366 | } 367 | } 368 | } 369 | if (cc) 370 | { 371 | SinclairData.cc = 1; 372 | //this.model.ccMeaning = 'flags not equal'; 373 | } 374 | } 375 | 376 | void exchange(signed char src1[], signed char src2[]) 377 | { 378 | getMask(); 379 | for (signed char i = 10; i >= 0; i--) 380 | { 381 | if (SinclairData.mask[i] == ' ') 382 | { 383 | // masked out 384 | // continue; 385 | } 386 | else 387 | { 388 | signed char t = src1[i]; 389 | src1[i] = src2[i]; 390 | src2[i] = t; 391 | } 392 | } 393 | } 394 | 395 | void testFlag(signed char src[]) 396 | { 397 | signed char cc = 0; 398 | getMask(); 399 | for (signed char i = 10; i >= 0; i--) 400 | { 401 | if (SinclairData.mask[i] == ' ') 402 | { 403 | // masked out 404 | // continue; 405 | } 406 | else 407 | { 408 | if (src[i]) 409 | { 410 | cc = 1; 411 | } 412 | } 413 | } 414 | /* Only update cc if bit set */ 415 | if (cc) 416 | { 417 | SinclairData.cc = cc; 418 | //this.model.ccMeaning = 'flag set'; 419 | } 420 | } 421 | 422 | void updateD() 423 | { 424 | for (signed char i = 10; i >= 0; i--) 425 | { 426 | SinclairData.d[i] = 1; 427 | } 428 | 429 | SinclairData.dActive += 1; 430 | if (SinclairData.dActive > 10) 431 | { 432 | SinclairData.dActive = 1; 433 | } 434 | SinclairData.d[SinclairData.dActive - 1] = 0; 435 | } 436 | 437 | 438 | void setup() { 439 | // put your setup code here, to run once: 440 | 441 | Serial.begin(9600); 442 | 443 | display(SinclairData.a); 444 | display(SinclairData.b); 445 | add(SinclairData.a, SinclairData.b, SinclairData.a); 446 | display(SinclairData.a); 447 | Serial.println(F("")); 448 | 449 | //sub(SinclairData.a, SinclairData.b, SinclairData.a); 450 | //display(SinclairData.a); 451 | 452 | display(SinclairData.b); 453 | display(SinclairData.a); 454 | sub(SinclairData.b, SinclairData.a, SinclairData.a); 455 | display(SinclairData.a); 456 | Serial.println(F("")); 457 | 458 | SinclairData.cc = 0; 459 | compare(SinclairData.b, SinclairData.a); 460 | if (SinclairData.cc) Serial.println(F("less than")); else Serial.println(F("not less than")); 461 | 462 | SinclairData.cc = 0; 463 | compare(SinclairData.a, SinclairData.b); 464 | if (SinclairData.cc) Serial.println(F("less than")); else Serial.println(F("not less than")); 465 | 466 | } 467 | 468 | void loop() { 469 | // put your main code here, to run repeatedly: 470 | 471 | } 472 | 473 | void display(signed char src1[]) { 474 | for (byte i = 0; i < 11; i++) 475 | { 476 | Serial.print((int)src1[i]); 477 | Serial.print(F(" ")); 478 | } 479 | Serial.println(F("")); 480 | } 481 | -------------------------------------------------------------------------------- /SinclairScientificExperiments/SinclairScientificKeyBoardTest.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "GPIO.h" 3 | 4 | // 5 | // A0 = D14 6 | // A1 = D15 7 | // A2 = D16 8 | // A3 = D17 9 | // A4 = D18 10 | // A5 = D19 11 | // A6 = D20 12 | // A7 = D21 13 | // 14 | 15 | GPIO SegmentA; 16 | GPIO SegmentB; 17 | GPIO SegmentC; 18 | GPIO SegmentD; 19 | GPIO SegmentE; 20 | GPIO SegmentF; 21 | GPIO SegmentG; 22 | GPIO SegmentDP; 23 | 24 | GPIO Digit1; 25 | GPIO Digit2; 26 | GPIO Digit3; 27 | GPIO Digit4; 28 | GPIO Digit5; 29 | GPIO Digit6; 30 | GPIO Digit7; 31 | GPIO Digit8; 32 | GPIO Digit9; 33 | 34 | void allSegmentOutput() { 35 | SegmentA.output(); 36 | SegmentB.output(); 37 | SegmentC.output(); 38 | SegmentD.output(); 39 | SegmentE.output(); 40 | SegmentF.output(); 41 | SegmentG.output(); 42 | SegmentDP.output(); 43 | } 44 | 45 | void allSegmentInput() { 46 | SegmentA.input(); 47 | SegmentB.input(); 48 | SegmentC.input(); 49 | SegmentD.input(); 50 | SegmentE.input(); 51 | SegmentF.input(); 52 | SegmentG.input(); 53 | SegmentDP.input(); 54 | } 55 | 56 | void allDigitOutput() { 57 | Digit1.output(); 58 | Digit2.output(); 59 | Digit3.output(); 60 | Digit4.output(); 61 | Digit5.output(); 62 | Digit6.output(); 63 | Digit7.output(); 64 | Digit8.output(); 65 | Digit9.output(); 66 | } 67 | 68 | void allDigitInput() { 69 | Digit1.input(); 70 | Digit2.input(); 71 | Digit3.input(); 72 | Digit4.input(); 73 | Digit5.input(); 74 | Digit6.input(); 75 | Digit7.input(); 76 | Digit8.input(); 77 | Digit9.input(); 78 | } 79 | 80 | void allSegmentOffCA() { 81 | SegmentA.high(); 82 | SegmentB.high(); 83 | SegmentC.high(); 84 | SegmentD.high(); 85 | SegmentE.high(); 86 | SegmentF.high(); 87 | SegmentG.high(); 88 | SegmentDP.high(); 89 | } 90 | 91 | void allSegmentOffCC() { 92 | SegmentA.low(); 93 | SegmentB.low(); 94 | SegmentC.low(); 95 | SegmentD.low(); 96 | SegmentE.low(); 97 | SegmentF.low(); 98 | SegmentG.low(); 99 | SegmentDP.low(); 100 | } 101 | 102 | void allDigitOffCA() { 103 | Digit1.low(); 104 | Digit2.low(); 105 | Digit3.low(); 106 | Digit4.low(); 107 | Digit5.low(); 108 | Digit6.low(); 109 | Digit7.low(); 110 | Digit8.low(); 111 | Digit9.low(); 112 | } 113 | 114 | void allDigitOffCC() { 115 | Digit1.high(); 116 | Digit2.high(); 117 | Digit3.high(); 118 | Digit4.high(); 119 | Digit5.high(); 120 | Digit6.high(); 121 | Digit7.high(); 122 | Digit8.high(); 123 | Digit9.high(); 124 | } 125 | 126 | byte outputDigitCC(signed char digit, bool decimalpoint = false) { 127 | 128 | byte segmentslit = 0; 129 | 130 | allDigitOffCC(); 131 | 132 | if (decimalpoint) 133 | { 134 | segmentslit++; 135 | SegmentDP.high(); 136 | } 137 | else 138 | { 139 | SegmentDP.low(); 140 | } 141 | 142 | switch (digit) { 143 | case 0: 144 | SegmentA.high(); 145 | SegmentB.high(); 146 | SegmentC.high(); 147 | SegmentD.high(); 148 | SegmentE.high(); 149 | SegmentF.high(); 150 | SegmentG.low(); 151 | segmentslit += 6; 152 | break; 153 | case 1: 154 | SegmentA.low(); 155 | SegmentB.high(); 156 | SegmentC.high(); 157 | SegmentD.low(); 158 | SegmentE.low(); 159 | SegmentF.low(); 160 | SegmentG.low(); 161 | segmentslit += 2; 162 | break; 163 | case 2: 164 | SegmentA.high(); 165 | SegmentB.high(); 166 | SegmentC.low(); 167 | SegmentD.high(); 168 | SegmentE.high(); 169 | SegmentF.low(); 170 | SegmentG.high(); 171 | segmentslit += 5; 172 | break; 173 | case 3: 174 | SegmentA.high(); 175 | SegmentB.high(); 176 | SegmentC.high(); 177 | SegmentD.high(); 178 | SegmentE.low(); 179 | SegmentF.low(); 180 | SegmentG.high(); 181 | segmentslit += 5; 182 | break; 183 | case 4: 184 | SegmentA.low(); 185 | SegmentB.high(); 186 | SegmentC.high(); 187 | SegmentD.low(); 188 | SegmentE.low(); 189 | SegmentF.high(); 190 | SegmentG.high(); 191 | segmentslit += 4; 192 | break; 193 | case 5: 194 | SegmentA.high(); 195 | SegmentB.low(); 196 | SegmentC.high(); 197 | SegmentD.high(); 198 | SegmentE.low(); 199 | SegmentF.high(); 200 | SegmentG.high(); 201 | segmentslit += 5; 202 | break; 203 | case 6: 204 | SegmentA.high(); 205 | SegmentB.low(); 206 | SegmentC.high(); 207 | SegmentD.high(); 208 | SegmentE.high(); 209 | SegmentF.high(); 210 | SegmentG.high(); 211 | segmentslit += 6; 212 | break; 213 | case 7: 214 | SegmentA.high(); 215 | SegmentB.high(); 216 | SegmentC.high(); 217 | SegmentD.low(); 218 | SegmentE.low(); 219 | SegmentF.low(); 220 | SegmentG.low(); 221 | segmentslit += 3; 222 | break; 223 | case 8: 224 | SegmentA.high(); 225 | SegmentB.high(); 226 | SegmentC.high(); 227 | SegmentD.high(); 228 | SegmentE.high(); 229 | SegmentF.high(); 230 | SegmentG.high(); 231 | segmentslit += 7; 232 | break; 233 | case 9: 234 | SegmentA.high(); 235 | SegmentB.high(); 236 | SegmentC.high(); 237 | SegmentD.low(); 238 | SegmentE.low(); 239 | SegmentF.high(); 240 | SegmentG.high(); 241 | break; 242 | segmentslit += 5; 243 | case 10: 244 | SegmentA.low(); 245 | SegmentB.low(); 246 | SegmentC.low(); 247 | SegmentD.low(); 248 | SegmentE.low(); 249 | SegmentF.low(); 250 | SegmentG.high(); 251 | segmentslit += 1; 252 | break; 253 | case 99: 254 | SegmentA.low(); 255 | SegmentB.low(); 256 | SegmentC.low(); 257 | SegmentD.low(); 258 | SegmentE.low(); 259 | SegmentF.low(); 260 | SegmentG.low(); 261 | break; 262 | default: 263 | allSegmentOffCC(); 264 | break; 265 | } 266 | 267 | return segmentslit; 268 | } 269 | 270 | byte outputDigitCA(signed char digit, bool decimalpoint = false) { 271 | 272 | byte segmentslit = 0; 273 | 274 | allDigitOffCA(); 275 | 276 | if (decimalpoint) 277 | { 278 | segmentslit++; 279 | SegmentDP.low(); 280 | } 281 | else 282 | { 283 | SegmentDP.high(); 284 | } 285 | 286 | switch (digit) { 287 | case 0: 288 | SegmentA.low(); 289 | SegmentB.low(); 290 | SegmentC.low(); 291 | SegmentD.low(); 292 | SegmentE.low(); 293 | SegmentF.low(); 294 | SegmentG.high(); 295 | segmentslit += 6; 296 | break; 297 | case 1: 298 | SegmentA.high(); 299 | SegmentB.low(); 300 | SegmentC.low(); 301 | SegmentD.high(); 302 | SegmentE.high(); 303 | SegmentF.high(); 304 | SegmentG.high(); 305 | segmentslit += 2; 306 | break; 307 | case 2: 308 | SegmentA.low(); 309 | SegmentB.low(); 310 | SegmentC.high(); 311 | SegmentD.low(); 312 | SegmentE.low(); 313 | SegmentF.high(); 314 | SegmentG.low(); 315 | segmentslit += 5; 316 | break; 317 | case 3: 318 | SegmentA.low(); 319 | SegmentB.low(); 320 | SegmentC.low(); 321 | SegmentD.low(); 322 | SegmentE.high(); 323 | SegmentF.high(); 324 | SegmentG.low(); 325 | segmentslit += 5; 326 | break; 327 | case 4: 328 | SegmentA.high(); 329 | SegmentB.low(); 330 | SegmentC.low(); 331 | SegmentD.high(); 332 | SegmentE.high(); 333 | SegmentF.low(); 334 | SegmentG.low(); 335 | segmentslit += 4; 336 | break; 337 | case 5: 338 | SegmentA.low(); 339 | SegmentB.high(); 340 | SegmentC.low(); 341 | SegmentD.low(); 342 | SegmentE.high(); 343 | SegmentF.low(); 344 | SegmentG.low(); 345 | segmentslit += 5; 346 | break; 347 | case 6: 348 | SegmentA.low(); 349 | SegmentB.high(); 350 | SegmentC.low(); 351 | SegmentD.low(); 352 | SegmentE.low(); 353 | SegmentF.low(); 354 | SegmentG.low(); 355 | segmentslit += 6; 356 | break; 357 | case 7: 358 | SegmentA.low(); 359 | SegmentB.low(); 360 | SegmentC.low(); 361 | SegmentD.high(); 362 | SegmentE.high(); 363 | SegmentF.high(); 364 | SegmentG.high(); 365 | segmentslit += 3; 366 | break; 367 | case 8: 368 | SegmentA.low(); 369 | SegmentB.low(); 370 | SegmentC.low(); 371 | SegmentD.low(); 372 | SegmentE.low(); 373 | SegmentF.low(); 374 | SegmentG.low(); 375 | segmentslit += 7; 376 | break; 377 | case 9: 378 | SegmentA.low(); 379 | SegmentB.low(); 380 | SegmentC.low(); 381 | SegmentD.high(); 382 | SegmentE.high(); 383 | SegmentF.low(); 384 | SegmentG.low(); 385 | break; 386 | segmentslit += 5; 387 | case 10: 388 | SegmentA.high(); 389 | SegmentB.high(); 390 | SegmentC.high(); 391 | SegmentD.high(); 392 | SegmentE.high(); 393 | SegmentF.high(); 394 | SegmentG.low(); 395 | segmentslit += 1; 396 | break; 397 | case 99: 398 | SegmentA.high(); 399 | SegmentB.high(); 400 | SegmentC.high(); 401 | SegmentD.high(); 402 | SegmentE.high(); 403 | SegmentF.high(); 404 | SegmentG.high(); 405 | break; 406 | default: 407 | allSegmentOffCA(); 408 | break; 409 | } 410 | 411 | return segmentslit; 412 | } 413 | 414 | byte lastDigit = 0; 415 | 416 | void selectDigitCC(byte digit) { 417 | 418 | allDigitOffCC(); 419 | 420 | lastDigit = digit; 421 | 422 | switch (digit) { 423 | case 0: 424 | Digit1.low(); 425 | break; 426 | case 1: 427 | Digit2.low(); 428 | break; 429 | case 2: 430 | Digit3.low(); 431 | break; 432 | case 3: 433 | Digit4.low(); 434 | break; 435 | case 4: 436 | Digit5.low(); 437 | break; 438 | case 5: 439 | Digit6.low(); 440 | break; 441 | case 6: 442 | Digit7.low(); 443 | break; 444 | case 7: 445 | Digit8.low(); 446 | break; 447 | case 8: 448 | Digit9.low(); 449 | break; 450 | } 451 | } 452 | 453 | void selectDigitCA(byte digit) { 454 | 455 | allDigitOffCA(); 456 | 457 | lastDigit = digit; 458 | 459 | switch (digit) { 460 | case 0: 461 | Digit1.high(); 462 | break; 463 | case 1: 464 | Digit2.high(); 465 | break; 466 | case 2: 467 | Digit3.high(); 468 | break; 469 | case 3: 470 | Digit4.high(); 471 | break; 472 | case 4: 473 | Digit5.high(); 474 | break; 475 | case 5: 476 | Digit6.high(); 477 | break; 478 | case 6: 479 | Digit7.high(); 480 | break; 481 | case 7: 482 | Digit8.high(); 483 | break; 484 | case 8: 485 | Digit9.high(); 486 | break; 487 | } 488 | } 489 | 490 | // 0..38 is 0 491 | // 900 to 1000 is 1 492 | void testKNKO() 493 | { 494 | Serial.print("d:"); 495 | Serial.print(lastDigit); 496 | Serial.print(" kn:"); 497 | Serial.print(analogRead(6)); 498 | Serial.print(" ko:"); 499 | Serial.println(analogRead(7)); 500 | } 501 | 502 | // http://forum.arduino.cc/index.php?topic=6549.msg51570#msg51570 503 | // defines for setting and clearing register bits 504 | #ifndef cbi 505 | #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 506 | #endif 507 | #ifndef sbi 508 | #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) 509 | #endif 510 | 511 | void setupFastADC() 512 | { 513 | // http://forum.arduino.cc/index.php?topic=6549.msg51570#msg51570 514 | // set prescale to 16 515 | sbi(ADCSRA, ADPS2) ; 516 | cbi(ADCSRA, ADPS1) ; 517 | cbi(ADCSRA, ADPS0) ; 518 | } 519 | 520 | void testADC() { 521 | int start ; 522 | int i ; 523 | 524 | Serial.print("ADCTEST: ") ; 525 | start = millis() ; 526 | for (i = 0 ; i < 1000 ; i++) 527 | analogRead(6) ; 528 | Serial.print(millis() - start) ; 529 | Serial.println(" msec (1000 calls)") ; 530 | } 531 | 532 | void setup() { 533 | // put your setup code here, to run once: 534 | 535 | Serial.begin(250000); 536 | 537 | setupFastADC(); 538 | testADC(); 539 | 540 | allSegmentOffCC(); 541 | allSegmentOutput(); 542 | 543 | allDigitOffCC(); 544 | allDigitOutput(); 545 | 546 | /* 547 | for (byte i = 0 ; i < 10 ; i++) 548 | { 549 | Serial.println(i); 550 | outputDigit(i); 551 | selectDigit(0); 552 | delay(3000); 553 | } 554 | */ 555 | } 556 | 557 | byte a = 99; 558 | byte b = 1; 559 | byte c = 0; 560 | 561 | int t = 0; 562 | 563 | byte incd(byte now) 564 | { 565 | byte next = 1; 566 | switch (now) { 567 | case 0: 568 | next = 5; 569 | break; 570 | case 1: 571 | next = 0; 572 | break; 573 | case 5: 574 | next = 99; 575 | break; 576 | case 99: 577 | next = 1; 578 | break; 579 | } 580 | return next; 581 | } 582 | 583 | void loop() { 584 | // put your main code here, to run repeatedly: 585 | 586 | unsigned long entrytime = micros(); 587 | 588 | outputDigitCC(a); 589 | selectDigitCC(0); 590 | testKNKO(); 591 | delay(2); 592 | outputDigitCC(b); 593 | selectDigitCC(1); 594 | testKNKO(); 595 | delay(2); 596 | outputDigitCC(c); 597 | selectDigitCC(2); 598 | testKNKO(); 599 | delay(2); 600 | 601 | unsigned long exittime = micros(); 602 | 603 | outputDigitCC(99); 604 | 605 | Serial.print("exec: "); 606 | Serial.println(exittime - entrytime); 607 | 608 | t++; 609 | if (t > 500) 610 | { 611 | t = 0; 612 | a = incd(a); 613 | b = incd(b); 614 | c = incd(c); 615 | } 616 | 617 | } 618 | -------------------------------------------------------------------------------- /SinclairScientificExperiments2/CPU.ino: -------------------------------------------------------------------------------- 1 | boolean opsWithK(byte opcode) { 2 | return (LISTOPSWITHK & (1UL << opcode)); 3 | } 4 | 5 | unsigned int getInstruction(unsigned int PC) { 6 | 7 | return pgm_read_word_near(objectCode + PC); 8 | } 9 | 10 | byte getMaskNum() { 11 | 12 | return getInstruction(SinclairData.address) & 0x0f; 13 | } 14 | 15 | char *getMask() { 16 | 17 | unsigned int instruction = getInstruction(SinclairData.address); 18 | byte classBits = instruction >> 9; 19 | byte opcode = (instruction >> 4) & 0x1f; 20 | 21 | if (classBits == 3 || (classBits == 2 && opcode > 18 && opcode != 21 && opcode != 22)) 22 | { 23 | byte maskno = getMaskNum(); 24 | 25 | for (byte i = 0; i <= 10; i++) 26 | { 27 | char maskdigit = pgm_read_byte_near(masks[maskno] + i); 28 | 29 | if (maskdigit == ' ') 30 | { 31 | SinclairData.mask[i] = maskdigit; 32 | } 33 | else if (classBits == 3 && opsWithK(opcode)) 34 | { 35 | // Register instruction 36 | SinclairData.mask[i] = maskdigit - '0';; 37 | } 38 | else 39 | { 40 | SinclairData.mask[i] = '*'; 41 | } 42 | } 43 | } 44 | else 45 | { 46 | SinclairData.mask[0] = 0; 47 | } 48 | 49 | return SinclairData.mask; 50 | } 51 | 52 | void add(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 53 | { 54 | byte carry = 0; 55 | getMask(); 56 | for (signed char i = 10; i >= 0; i--) 57 | { 58 | if (SinclairData.mask[i] == ' ') 59 | { 60 | // masked out 61 | // continue; 62 | } 63 | else 64 | { 65 | signed char result = src1[i] + src2[i] + carry; 66 | if (!hex && result >= 10) 67 | { 68 | result -= 10; 69 | carry = 1; 70 | } 71 | else if (hex && result >= 16) 72 | { 73 | result -= 16; 74 | carry = 1; 75 | } 76 | else 77 | { 78 | carry = 0; 79 | } 80 | dst[i] = result; 81 | } 82 | } 83 | if (carry) 84 | { 85 | SinclairData.cc = carry; 86 | //SinclairData.ccMeaning = carry ? 'overflow' : 'no overflow'; 87 | } 88 | } 89 | 90 | void sub(signed char src1[], signed char src2[], signed char dst[], bool hex = false) 91 | { 92 | byte borrow = 0; 93 | getMask(); 94 | for (signed char i = 10; i >= 0; i--) 95 | { 96 | if (SinclairData.mask[i] == ' ') 97 | { 98 | // masked out 99 | // continue; 100 | } 101 | else 102 | { 103 | signed char result = src1[i] - src2[i] - borrow; 104 | if (result < 0) 105 | { 106 | result += hex ? 16 : 10; 107 | borrow = 1; 108 | } 109 | else 110 | { 111 | borrow = 0; 112 | } 113 | dst[i] = result; 114 | } 115 | } 116 | if (borrow) 117 | { 118 | SinclairData.cc = borrow; 119 | //SinclairData.ccMeaning = borrow ? 'borrow' : 'no borrow'; 120 | } 121 | } 122 | 123 | void compare(signed char src1[], signed char src2[]) 124 | { 125 | signed char tmp[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 126 | 127 | sub(src1, src2, tmp); 128 | // Compare sets condition if not borrow 129 | //SinclairData.ccMeaning = SinclairData.cc ? "less than" : "not less than"; 130 | } 131 | 132 | void copy(signed char src[], signed char dst[]) 133 | { 134 | getMask(); 135 | for (signed char i = 10; i >= 0; i--) 136 | { 137 | if (SinclairData.mask[i] == ' ') 138 | { 139 | // masked out 140 | // continue; 141 | } 142 | else 143 | { 144 | dst[i] = src[i]; 145 | } 146 | } 147 | } 148 | 149 | void sll(signed char src[]) 150 | { 151 | getMask(); 152 | signed char digit = 0; 153 | for (signed char i = 10; i >= 0; i--) 154 | { 155 | if (SinclairData.mask[i] == ' ') 156 | { 157 | // masked out 158 | // continue; 159 | } 160 | else 161 | { 162 | signed char newdigit = src[i]; 163 | src[i] = digit; 164 | digit = newdigit; 165 | } 166 | } 167 | } 168 | 169 | void srl(signed char src[]) 170 | { 171 | getMask(); 172 | signed char digit = 0; 173 | for (signed char i = 0; i <= 10; i++) 174 | { 175 | if (SinclairData.mask[i] == ' ') 176 | { 177 | // masked out 178 | // continue; 179 | } 180 | else 181 | { 182 | signed char newdigit = src[i]; 183 | src[i] = digit; 184 | digit = newdigit; 185 | } 186 | } 187 | } 188 | 189 | void writeFlag(signed char dest[], signed char val) 190 | { 191 | getMask(); 192 | for (signed char i = 10; i >= 0; i--) 193 | { 194 | if (SinclairData.mask[i] == ' ') 195 | { 196 | // masked out 197 | // continue; 198 | } 199 | else 200 | { 201 | // Flip dst if val == -1, otherwise set to val 202 | dest[i] = (val < 0) ? (1 - dest[i]) : val; 203 | } 204 | } 205 | } 206 | 207 | void compareFlags(signed char src1[], signed char src2[]) 208 | { 209 | signed char cc = 0; 210 | getMask(); 211 | for (signed char i = 10; i >= 0; i--) 212 | { 213 | if (SinclairData.mask[i] == ' ') 214 | { 215 | // masked out 216 | // continue; 217 | } 218 | else 219 | { 220 | if (src1[i] != src2[i]) 221 | { 222 | cc = 1; 223 | } 224 | } 225 | } 226 | if (cc) 227 | { 228 | SinclairData.cc = 1; 229 | //SinclairData.ccMeaning = 'flags not equal'; 230 | } 231 | } 232 | 233 | void exchange(signed char src1[], signed char src2[]) 234 | { 235 | getMask(); 236 | for (signed char i = 10; i >= 0; i--) 237 | { 238 | if (SinclairData.mask[i] == ' ') 239 | { 240 | // masked out 241 | // continue; 242 | } 243 | else 244 | { 245 | signed char t = src1[i]; 246 | src1[i] = src2[i]; 247 | src2[i] = t; 248 | } 249 | } 250 | } 251 | 252 | void testFlag(signed char src[]) 253 | { 254 | signed char cc = 0; 255 | getMask(); 256 | for (signed char i = 10; i >= 0; i--) 257 | { 258 | if (SinclairData.mask[i] == ' ') 259 | { 260 | // masked out 261 | // continue; 262 | } 263 | else 264 | { 265 | if (src[i]) 266 | { 267 | cc = 1; 268 | } 269 | } 270 | } 271 | /* Only update cc if bit set */ 272 | if (cc) 273 | { 274 | SinclairData.cc = cc; 275 | //SinclairData.ccMeaning = 'flag set'; 276 | } 277 | } 278 | 279 | void updateD() 280 | { 281 | for (signed char i = 10; i >= 0; i--) 282 | { 283 | SinclairData.d[i] = 1; 284 | } 285 | 286 | SinclairData.dActive += 1; 287 | if (SinclairData.dActive > 10) 288 | { 289 | SinclairData.dActive = 1; 290 | } 291 | SinclairData.d[SinclairData.dActive - 1] = 0; 292 | } 293 | 294 | void step() 295 | { 296 | //Serial.print(F("addr:")); 297 | //Serial.print(SinclairData.address); 298 | 299 | unsigned int instruction = getInstruction(SinclairData.address); 300 | byte classBits = instruction >> 9; 301 | byte opcode = (instruction >> 4) & 0x1f; 302 | unsigned int nextAddress = SinclairData.address + 1; 303 | 304 | //Serial.print(F(" data:")); 305 | //Serial.print(opcode); 306 | //Serial.print(F(" classbits:")); 307 | //Serial.print(classBits); 308 | 309 | if (classBits == 3) 310 | { 311 | // Register instruction 312 | byte maskBits = instruction & 0xf; 313 | switch (opcode) 314 | { 315 | case 0: // AABA: A+B -> A 316 | displayInstruction(1); 317 | add(SinclairData.a, SinclairData.b, SinclairData.a); 318 | break; 319 | case 1: // AAKA: A+K -> A 320 | displayInstruction(2); 321 | add(SinclairData.a, getMask(), SinclairData.a); 322 | break; 323 | case 2: // AAKC: A+K -> C 324 | displayInstruction(3); 325 | add(SinclairData.a, getMask(), SinclairData.c); 326 | break; 327 | case 3: 328 | if (SinclairData.sinclair) 329 | { // ACBB C+B -> B 330 | displayInstruction(4); 331 | add(SinclairData.c, SinclairData.b, SinclairData.b); 332 | } 333 | else 334 | { // ABOA: B -> A 335 | displayInstruction(5); 336 | copy(SinclairData.b, SinclairData.a); 337 | } 338 | break; 339 | case 4: // ABOC: B -> C 340 | displayInstruction(6); 341 | copy(SinclairData.b, SinclairData.c); 342 | break; 343 | case 5: // ACKA: C+K -> A 344 | displayInstruction(7); 345 | add(SinclairData.c, getMask(), SinclairData.a); 346 | break; 347 | case 6: // AKCB: C+K -> B 348 | displayInstruction(8); 349 | add(SinclairData.c, getMask(), SinclairData.b); 350 | break; 351 | case 7: // SABA: A-B -> A 352 | displayInstruction(9); 353 | sub(SinclairData.a, SinclairData.b, SinclairData.a); 354 | break; 355 | case 8: // SABC: A-B -> C 356 | displayInstruction(10); 357 | sub(SinclairData.a, SinclairData.b, SinclairData.c); 358 | break; 359 | case 9: // SAKA: A-K -> A 360 | displayInstruction(11); 361 | sub(SinclairData.a, getMask(), SinclairData.a); 362 | break; 363 | case 10: // SCBC: C-B -> C 364 | displayInstruction(12); 365 | sub(SinclairData.c, SinclairData.b, SinclairData.c); 366 | break; 367 | case 11: // SCKC: C-K -> C 368 | displayInstruction(13); 369 | sub(SinclairData.c, getMask(), SinclairData.c); 370 | break; 371 | case 12: // CAB: compare A-B 372 | displayInstruction(14); 373 | compare(SinclairData.a, SinclairData.b); 374 | break; 375 | case 13: // CAK: compare A-K 376 | displayInstruction(15); 377 | compare(SinclairData.a, getMask()); 378 | break; 379 | case 14: // CCB: compare C-B 380 | displayInstruction(16); 381 | compare(SinclairData.c, SinclairData.b); 382 | break; 383 | case 15: // CCK: compare C-K 384 | displayInstruction(17); 385 | compare(SinclairData.c, getMask()); 386 | break; 387 | case 16: // AKA: K -> A 388 | displayInstruction(18); 389 | copy(getMask(), SinclairData.a); 390 | break; 391 | case 17: // AKB: K -> B 392 | displayInstruction(19); 393 | copy(getMask(), SinclairData.b); 394 | break; 395 | case 18: // AKC: K -> C 396 | displayInstruction(20); 397 | copy(getMask(), SinclairData.c); 398 | break; 399 | case 19: // EXAB: exchange A and B 400 | displayInstruction(21); 401 | exchange(SinclairData.a, SinclairData.b); 402 | break; 403 | case 20: // SLLA: shift A left 404 | displayInstruction(22); 405 | sll(SinclairData.a); 406 | break; 407 | case 21: // SLLB: shift B left 408 | displayInstruction(23); 409 | sll(SinclairData.b); 410 | break; 411 | case 22: // SLLC: shift C left 412 | displayInstruction(24); 413 | sll(SinclairData.c); 414 | break; 415 | case 23: // SRLA: shift A right 416 | displayInstruction(25); 417 | srl(SinclairData.a); 418 | break; 419 | case 24: // SRLB: shift B right 420 | displayInstruction(26); 421 | srl(SinclairData.b); 422 | break; 423 | case 25: // SRLC: shift C right 424 | displayInstruction(66); 425 | srl(SinclairData.c); 426 | break; 427 | case 26: // AKCN: A+K -> A until key down on N or D11 [sic] 428 | // Patent says sets condition if key down, but real behavior 429 | // is to set condition if addition overflows (i.e. no key down) 430 | add(SinclairData.a, getMask(), SinclairData.a); 431 | if (SinclairData.keyStrobe == KN) 432 | { 433 | displayInstruction(27); 434 | // Advance to next instruction 435 | } 436 | else if (SinclairData.dActive != 10) 437 | { 438 | displayInstruction(28); 439 | // Hold at current instruction and continue scan 440 | nextAddress = SinclairData.address; 441 | } 442 | else 443 | { 444 | displayInstruction(29); 445 | // For state d10, fall through 446 | } 447 | break; 448 | case 27: 449 | if (SinclairData.sinclair) 450 | { // SCBA C-B -> A 451 | displayInstruction(30); 452 | sub(SinclairData.c, SinclairData.b, SinclairData.a); 453 | } 454 | else 455 | { // AAKAH A+K -> A hex 456 | displayInstruction(31); 457 | add(SinclairData.a, getMask(), SinclairData.a, 1 /* hex */ ); 458 | SinclairData.cc = 0; 459 | //SinclairData.ccMeaning = ''; 460 | } 461 | break; 462 | case 28: 463 | if (SinclairData.sinclair) 464 | { // SCKB C-K -> B 465 | displayInstruction(32); 466 | sub(SinclairData.c, getMask(), SinclairData.b); 467 | } 468 | else 469 | { // SAKAH A-K -> A hex 470 | displayInstruction(33); 471 | sub(SinclairData.a, getMask(), SinclairData.a, 1 /* hex */ ); 472 | SinclairData.cc = 0; 473 | //SinclairData.ccMeaning = ''; 474 | } 475 | break; 476 | case 29: // ACKC: C+K -> C 477 | displayInstruction(34); 478 | add(SinclairData.c, getMask(), SinclairData.c); 479 | break; 480 | case 30: 481 | if (SinclairData.sinclair) 482 | { // AABC A+B -> C 483 | displayInstruction(35); 484 | add(SinclairData.a, SinclairData.b, SinclairData.c); 485 | break; 486 | } 487 | case 31: 488 | if (SinclairData.sinclair) 489 | { // ACBC C+B -> C 490 | displayInstruction(36); 491 | add(SinclairData.c, SinclairData.b, SinclairData.c); 492 | break; 493 | } 494 | default: 495 | //bad instruction 496 | //alert('Bad instruction ' + instruction); 497 | displayInstruction(37); 498 | break; 499 | } 500 | } 501 | else if ((instruction >> 8) == 5) 502 | { 503 | // Flag instruction 504 | byte maskBits = instruction & 0xf; 505 | switch (opcode) 506 | { 507 | case 16: // NOP 508 | displayInstruction(38); 509 | break; 510 | case 17: // WAITDK: wait for display key 511 | SinclairData.display = 0; 512 | if (SinclairData.keyPressed == DK) 513 | { 514 | // Jump 515 | displayInstruction(39); 516 | nextAddress = instruction & 0x1ff; 517 | } 518 | else 519 | { 520 | // Hold address until DK pressed 521 | displayInstruction(40); 522 | nextAddress = SinclairData.address; 523 | } 524 | break; 525 | case 18: // WAITNO: wait for key or address register overflow 526 | if (SinclairData.keyStrobe) 527 | { 528 | // Jump 529 | displayInstruction(41); 530 | nextAddress = instruction & 0x1ff; 531 | } 532 | else 533 | { 534 | // Hold address until key pressed or address overflow (TODO) 535 | displayInstruction(42); 536 | nextAddress = SinclairData.address; 537 | } 538 | break; 539 | case 19: // SFB: set flag B 540 | displayInstruction(43); 541 | writeFlag(SinclairData.bf, 1); 542 | break; 543 | case 20: // SFA: set flag A 544 | displayInstruction(44); 545 | writeFlag(SinclairData.af, 1); 546 | break; 547 | case 21: // SYNC (SYNCH): hold address until end of D10 548 | displayInstruction(45); 549 | if (SinclairData.dActive != 10) 550 | { 551 | nextAddress = SinclairData.address; 552 | } 553 | SinclairData.cc = 0; 554 | //SinclairData.ccMeaning = ''; 555 | break; 556 | case 22: // SCAN (SCANNO): wait for key 557 | SinclairData.display = 1; // Reset display power off latch 558 | if (SinclairData.keyStrobe) 559 | { 560 | displayInstruction(46); 561 | SinclairData.cc = 1; 562 | //SinclairData.ccMeaning = 'key'; 563 | } 564 | else 565 | { 566 | displayInstruction(47); 567 | SinclairData.cc = 0; 568 | //SinclairData.ccMeaning = 'no key'; 569 | if (SinclairData.dActive != 10) 570 | { 571 | // Hold address until end of D10 572 | nextAddress = SinclairData.address; 573 | } 574 | } 575 | break; 576 | case 23: // ZFB: zero flag B 577 | displayInstruction(48); 578 | writeFlag(SinclairData.bf, 0); 579 | break; 580 | case 24: // ZFA: zero flag A 581 | displayInstruction(49); 582 | writeFlag(SinclairData.af, 0); 583 | break; 584 | case 25: // TFB: test flag B 585 | displayInstruction(50); 586 | testFlag(SinclairData.bf); 587 | break; 588 | case 26: // TFA: test flag A 589 | displayInstruction(51); 590 | testFlag(SinclairData.af); 591 | break; 592 | case 27: // FFB: flip flag B 593 | displayInstruction(52); 594 | writeFlag(SinclairData.bf, -1 /* flip */ ); 595 | break; 596 | case 28: // FFA: flip flag A 597 | displayInstruction(67); 598 | writeFlag(SinclairData.af, -1 /* flip */ ); 599 | break; 600 | case 29: // CF: compare flags 601 | displayInstruction(53); 602 | compareFlags(SinclairData.af, SinclairData.bf); 603 | break; 604 | case 30: // NOP 605 | displayInstruction(54); 606 | break; 607 | case 31: // EXF: exchange flags 608 | displayInstruction(55); 609 | exchange(SinclairData.af, SinclairData.bf); 610 | break; 611 | default: 612 | //bad instruction 613 | //alert('Bad instruction ' + instruction); 614 | displayInstruction(56); 615 | break; 616 | } 617 | } 618 | else if (classBits == 0) 619 | { 620 | // jump if reset: BIU, BIZ, BIGE, BINC, BIE, BET 621 | displayInstruction(57); 622 | if (SinclairData.cc == 0) 623 | { 624 | displayInstruction(58); 625 | nextAddress = instruction & 0x1ff; 626 | } 627 | SinclairData.cc = 0; // Clear after jump 628 | //SinclairData.ccMeaning = ''; 629 | } 630 | else if (classBits == 1) 631 | { 632 | // jump if set: BID, BIO, BILT, BIC, BINE 633 | displayInstruction(59); 634 | if (SinclairData.cc == 1) 635 | { 636 | displayInstruction(60); 637 | nextAddress = instruction & 0x1ff; 638 | } 639 | SinclairData.cc = 0; // Clear after jump 640 | //SinclairData.ccMeaning = ''; 641 | } 642 | else if ((instruction >> 7) == 8) 643 | { 644 | // Jump if key down on KO (BKO) 645 | displayInstruction(61); 646 | if (SinclairData.keyStrobe == KO) 647 | { 648 | displayInstruction(62); 649 | nextAddress = instruction & 0x1ff; 650 | } 651 | SinclairData.cc = 0; // Clear after jump 652 | //SinclairData.ccMeaning = ''; 653 | } 654 | else if ((instruction >> 7) == 9) 655 | { 656 | // Jump if key down on KP (BKP) 657 | displayInstruction(63); 658 | if (SinclairData.keyStrobe == KP) 659 | { 660 | displayInstruction(64); 661 | nextAddress = instruction & 0x1ff; 662 | } 663 | SinclairData.cc = 0; // Clear after jump 664 | //SinclairData.ccMeaning = ''; 665 | } 666 | else 667 | { 668 | displayInstruction(65); 669 | //bad instruction 670 | //alert('Bad instruction code ' + instruction); 671 | } 672 | SinclairData.address = nextAddress; 673 | // Put the mask for the next instruction in the model for display 674 | //SinclairData.mask = getMask(); 675 | getMask(); 676 | // Update D state 677 | updateD(); 678 | 679 | //Serial.println(F("")); 680 | //displayArray(SinclairData.a); 681 | } 682 | -------------------------------------------------------------------------------- /SinclairScientificExperiments2/DisAssembler.ino: -------------------------------------------------------------------------------- 1 | void displayInstruction(byte dummy) 2 | { 3 | 4 | } 5 | 6 | void displayArray(signed char src1[]) { 7 | for (byte i = 0; i < 11; i++) 8 | { 9 | Serial.print((int)src1[i]); 10 | Serial.print(F(" ")); 11 | } 12 | Serial.println(F("")); 13 | } 14 | 15 | void displayInstruction1(byte instructionid) 16 | { 17 | Serial.print(F(" (")); 18 | Serial.print(instructionid); 19 | Serial.print(F(":")); 20 | switch (instructionid) 21 | { 22 | case 1: 23 | Serial.print(F("AABA: A+B -> A")); 24 | break; 25 | 26 | case 2: 27 | Serial.print(F("AABA: A+B -> A")); 28 | break; 29 | 30 | case 3: 31 | Serial.print(F("AAKC: A+K -> C")); 32 | break; 33 | 34 | case 4: 35 | Serial.print(F("ACBB C+B -> B")); 36 | break; 37 | 38 | case 5: 39 | Serial.print(F("ABOA: B -> A")); 40 | break; 41 | 42 | case 6: 43 | Serial.print(F("ABOC: B -> C")); 44 | break; 45 | 46 | case 7: 47 | Serial.print(F("ACKA: C+K -> A")); 48 | break; 49 | 50 | case 8: 51 | Serial.print(F("AKCB: C+K -> B")); 52 | break; 53 | 54 | case 9: 55 | Serial.print(F("SABA: A-B -> A")); 56 | break; 57 | 58 | case 10: 59 | Serial.print(F("SABC: A-B -> C")); 60 | break; 61 | 62 | case 11: 63 | Serial.print(F("SAKA: A-K -> A")); 64 | break; 65 | 66 | case 12: 67 | Serial.print(F("SCBC: C-B -> C")); 68 | break; 69 | 70 | case 13: 71 | Serial.print(F("SCKC: C-K -> C")); 72 | break; 73 | 74 | case 14: 75 | Serial.print(F("CAB: compare A-B")); 76 | break; 77 | 78 | case 15: 79 | Serial.print(F("CAK: compare A-K")); 80 | break; 81 | 82 | case 16: 83 | Serial.print(F("CCB: compare C-B")); 84 | break; 85 | 86 | case 17: 87 | Serial.print(F("CCK: compare C-K")); 88 | break; 89 | 90 | case 18: 91 | Serial.print(F("AKA: K -> A")); 92 | break; 93 | 94 | case 19: 95 | Serial.print(F("AKB: K -> B")); 96 | break; 97 | 98 | case 20: 99 | Serial.print(F("AKC: K -> C")); 100 | break; 101 | 102 | case 21: 103 | Serial.print(F("EXAB: exchange A and B")); 104 | break; 105 | 106 | case 22: 107 | Serial.print(F("SLLA: shift A left")); 108 | break; 109 | 110 | case 23: 111 | Serial.print(F("SLLB: shift B left")); 112 | break; 113 | 114 | case 24: 115 | Serial.print(F("SLLC: shift C left")); 116 | break; 117 | 118 | case 25: 119 | Serial.print(F("SRLA: shift A right")); 120 | break; 121 | 122 | case 26: 123 | Serial.print(F("SRLB: shift B right")); 124 | break; 125 | 126 | case 27: 127 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (ADVANCE)")); 128 | break; 129 | 130 | case 28: 131 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (HOLD)")); 132 | break; 133 | 134 | case 29: 135 | Serial.print(F("AKCN: A+K -> A until key down on N or D11 [sic] (D10 FALLTHROUGH)")); 136 | break; 137 | 138 | case 30: 139 | Serial.print(F("SCBA C-B -> A")); 140 | break; 141 | 142 | case 31: 143 | Serial.print(F("AAKAH A+K -> A hex")); 144 | break; 145 | 146 | case 32: 147 | Serial.print(F("SCKB C-K -> B")); 148 | break; 149 | 150 | case 33: 151 | Serial.print(F("SAKAH A-K -> A hex")); 152 | break; 153 | 154 | case 34: 155 | Serial.print(F("ACKC: C+K -> C")); 156 | break; 157 | 158 | case 35: 159 | Serial.print(F("AABC A+B -> C")); 160 | break; 161 | 162 | case 36: 163 | Serial.print(F("ACBC C+B -> C")); 164 | break; 165 | 166 | case 37: 167 | Serial.print(F("Bad instruction")); 168 | break; 169 | 170 | case 38: 171 | Serial.print(F("NOP")); 172 | break; 173 | 174 | case 39: 175 | Serial.print(F("WAITDK: wait for display key DK (JUMP)")); 176 | break; 177 | 178 | case 40: 179 | Serial.print(F("WAITDK: wait for display key DK (HOLD)")); 180 | break; 181 | 182 | case 41: 183 | Serial.print(F("WAITNO: wait for key or address register overflow (JUMP)")); 184 | break; 185 | 186 | case 42: 187 | Serial.print(F("WAITNO: wait for key or address register overflow (HOLDING)")); 188 | break; 189 | 190 | case 43: 191 | Serial.print(F("SFB: set flag B")); 192 | break; 193 | 194 | case 44: 195 | Serial.print(F("SFA: set flag A")); 196 | break; 197 | 198 | case 45: 199 | Serial.print(F("SYNC (SYNCH): hold address until end of D10")); 200 | break; 201 | 202 | case 46: 203 | Serial.print(F("SCAN (SCANNO): wait for key (KEY)")); 204 | break; 205 | 206 | case 47: 207 | Serial.print(F("SCAN (SCANNO): wait for key (NO KEY)")); 208 | break; 209 | 210 | case 48: 211 | Serial.print(F("ZFB: zero flag B")); 212 | break; 213 | 214 | case 49: 215 | Serial.print(F("ZFA: zero flag A")); 216 | break; 217 | 218 | case 50: 219 | Serial.print(F("TFB: test flag B")); 220 | break; 221 | 222 | case 51: 223 | Serial.print(F("TFA: test flag A")); 224 | break; 225 | 226 | case 52: 227 | Serial.print(F("FFB: flip flag B")); 228 | break; 229 | 230 | case 53: 231 | Serial.print(F("CF: compare flags")); 232 | break; 233 | 234 | case 54: 235 | Serial.print(F("NOP")); 236 | break; 237 | 238 | case 55: 239 | Serial.print(F("EXF: exchange flags")); 240 | break; 241 | 242 | case 56: 243 | Serial.print(F("Bad instruction")); 244 | break; 245 | 246 | case 57: 247 | Serial.print(F("Jump if reset: BIU, BIZ, BIGE, BINC, BIE, BET ")); 248 | break; 249 | 250 | case 58: 251 | Serial.print(F("JUMP IF RESET TAKEN")); 252 | break; 253 | 254 | case 59: 255 | Serial.print(F("Jump if set: BID, BIO, BILT, BIC, BINE")); 256 | break; 257 | 258 | case 60: 259 | Serial.print(F("JUMP IF SET TAKEN")); 260 | break; 261 | 262 | case 61: 263 | Serial.print(F("Jump if key down on KO (BKO)")); 264 | break; 265 | 266 | case 62: 267 | Serial.print(F("JUMP IF KEY DOWN ON KO TAKEN")); 268 | break; 269 | 270 | case 63: 271 | Serial.print(F("Jump if key down on KP (BKP)")); 272 | break; 273 | 274 | case 64: 275 | Serial.print(F("JUMP IF KEY DOWN ON KP TAKEN")); 276 | break; 277 | 278 | case 65: 279 | Serial.print(F("Bad instruction code")); 280 | break; 281 | 282 | case 66: 283 | Serial.print(F("SRLC: shift C right")); 284 | break; 285 | 286 | case 67: 287 | Serial.print(F("FFA: flip flag A")); 288 | break; 289 | 290 | default: 291 | Serial.print(F("UNKNOWN INSTRUCTION ID")); 292 | break; 293 | } 294 | Serial.print(F(")")); 295 | } 296 | -------------------------------------------------------------------------------- /SinclairScientificExperiments2/DisplayAndKeys.ino: -------------------------------------------------------------------------------- 1 | //tedious but fast, must lines compile to 1 instruction. 2 | //this whole file compiles to less than 1KB of code 3 | 4 | void allSegmentOutput() { 5 | SegmentA.output(); 6 | SegmentB.output(); 7 | SegmentC.output(); 8 | SegmentD.output(); 9 | SegmentE.output(); 10 | SegmentF.output(); 11 | SegmentG.output(); 12 | SegmentDP.output(); 13 | } 14 | 15 | void allSegmentOff() { 16 | SegmentA.high(); 17 | SegmentB.high(); 18 | SegmentC.high(); 19 | SegmentD.high(); 20 | SegmentE.high(); 21 | SegmentF.high(); 22 | SegmentG.high(); 23 | SegmentDP.high(); 24 | } 25 | 26 | void allSegmentInput() { 27 | SegmentA.input(); 28 | SegmentB.input(); 29 | SegmentC.input(); 30 | SegmentD.input(); 31 | SegmentE.input(); 32 | SegmentF.input(); 33 | SegmentG.input(); 34 | SegmentDP.input(); 35 | 36 | SegmentA.high(); 37 | SegmentB.high(); 38 | SegmentC.high(); 39 | SegmentD.high(); 40 | SegmentE.high(); 41 | SegmentF.high(); 42 | SegmentG.high(); 43 | SegmentDP.high(); 44 | } 45 | 46 | void allDigitOutput() { 47 | Digit3.output(); 48 | Digit4.output(); 49 | Digit5.output(); 50 | Digit6.output(); 51 | Digit7.output(); 52 | Digit8.output(); 53 | Digit9.output(); 54 | } 55 | 56 | void allDigitOff() { 57 | Digit3.low(); 58 | Digit4.low(); 59 | Digit5.low(); 60 | Digit6.low(); 61 | Digit7.low(); 62 | Digit8.low(); 63 | Digit9.low(); 64 | } 65 | 66 | void allKeyRowOff() { 67 | KeyRowA.input(); 68 | KeyRowB.input(); 69 | KeyRowC.input(); 70 | 71 | KeyRowA.low(); 72 | KeyRowB.low(); 73 | KeyRowC.low(); 74 | } 75 | 76 | void allKeyRowIdle() { 77 | KeyRowA.output(); 78 | KeyRowB.output(); 79 | KeyRowC.output(); 80 | 81 | KeyRowA.high(); 82 | KeyRowB.high(); 83 | KeyRowC.high(); 84 | } 85 | 86 | void outputDigit(signed char digit) { 87 | 88 | //decimal point is turned on automatically by void display() 89 | /* 90 | if (digit < 0) 91 | { 92 | SegmentDP.low(); 93 | digit ^= 255; 94 | digit++; 95 | } 96 | else 97 | { 98 | SegmentDP.high(); 99 | } 100 | */ 101 | 102 | allDigitOff(); 103 | 104 | switch (digit) { 105 | case 0: 106 | SegmentA.low(); 107 | SegmentB.low(); 108 | SegmentC.low(); 109 | SegmentD.low(); 110 | SegmentE.low(); 111 | SegmentF.low(); 112 | SegmentG.high(); 113 | break; 114 | case 1: 115 | SegmentA.high(); 116 | SegmentB.low(); 117 | SegmentC.low(); 118 | SegmentD.high(); 119 | SegmentE.high(); 120 | SegmentF.high(); 121 | SegmentG.high(); 122 | break; 123 | case 2: 124 | SegmentA.low(); 125 | SegmentB.low(); 126 | SegmentC.high(); 127 | SegmentD.low(); 128 | SegmentE.low(); 129 | SegmentF.high(); 130 | SegmentG.low(); 131 | break; 132 | case 3: 133 | SegmentA.low(); 134 | SegmentB.low(); 135 | SegmentC.low(); 136 | SegmentD.low(); 137 | SegmentE.high(); 138 | SegmentF.high(); 139 | SegmentG.low(); 140 | break; 141 | case 4: 142 | SegmentA.high(); 143 | SegmentB.low(); 144 | SegmentC.low(); 145 | SegmentD.high(); 146 | SegmentE.high(); 147 | SegmentF.low(); 148 | SegmentG.low(); 149 | break; 150 | case 5: 151 | SegmentA.low(); 152 | SegmentB.high(); 153 | SegmentC.low(); 154 | SegmentD.low(); 155 | SegmentE.high(); 156 | SegmentF.low(); 157 | SegmentG.low(); 158 | break; 159 | case 6: 160 | SegmentA.low(); 161 | SegmentB.high(); 162 | SegmentC.low(); 163 | SegmentD.low(); 164 | SegmentE.low(); 165 | SegmentF.low(); 166 | SegmentG.low(); 167 | break; 168 | case 7: 169 | SegmentA.low(); 170 | SegmentB.low(); 171 | SegmentC.low(); 172 | SegmentD.high(); 173 | SegmentE.high(); 174 | SegmentF.high(); 175 | SegmentG.high(); 176 | break; 177 | case 8: 178 | SegmentA.low(); 179 | SegmentB.low(); 180 | SegmentC.low(); 181 | SegmentD.low(); 182 | SegmentE.low(); 183 | SegmentF.low(); 184 | SegmentG.low(); 185 | break; 186 | case 9: 187 | SegmentA.low(); 188 | SegmentB.low(); 189 | SegmentC.low(); 190 | SegmentD.high(); 191 | SegmentE.high(); 192 | SegmentF.low(); 193 | SegmentG.low(); 194 | break; 195 | case 10: 196 | SegmentA.high(); 197 | SegmentB.high(); 198 | SegmentC.high(); 199 | SegmentD.high(); 200 | SegmentE.high(); 201 | SegmentF.high(); 202 | SegmentG.low(); 203 | break; 204 | default: 205 | allSegmentOff(); 206 | break; 207 | } 208 | } 209 | 210 | void selectDigit(byte digit) { 211 | 212 | allDigitOff(); 213 | 214 | switch (digit) { 215 | case 0: 216 | break; 217 | case 1: 218 | break; 219 | case 2: 220 | Digit3.high(); 221 | break; 222 | case 3: 223 | Digit4.high(); 224 | break; 225 | case 4: 226 | Digit5.high(); 227 | break; 228 | case 5: 229 | Digit6.high(); 230 | break; 231 | case 6: 232 | Digit7.high(); 233 | break; 234 | case 7: 235 | Digit8.high(); 236 | break; 237 | case 8: 238 | Digit9.high(); 239 | break; 240 | } 241 | } 242 | 243 | void display() { 244 | for (byte i = 0; i < 9; i++) { 245 | outputDigit(digits[i]); 246 | selectDigit(i); 247 | 248 | //SINCLAIR behavior: turn decimal point on automatically at fixed position 249 | if (i == 3) { 250 | SegmentDP.low(); 251 | } 252 | else 253 | { 254 | SegmentDP.high(); 255 | } 256 | 257 | backgroundWork(); 258 | } 259 | } 260 | 261 | char readKey() { 262 | byte key = 0; 263 | 264 | allDigitOff(); 265 | allSegmentInput(); 266 | 267 | for (byte i = 0; i < 3; i++) 268 | { 269 | allKeyRowIdle(); 270 | switch (i) { 271 | case 0: 272 | KeyRowA.low(); 273 | delayMicroseconds(2); //for reliability on first read instruction, allow for .low on line before to settle 274 | if (!SegmentA.read()) key = 1; 275 | if (!SegmentB.read()) key = 2; 276 | if (!SegmentC.read()) key = 3; 277 | if (!SegmentD.read()) key = 4; 278 | if (!SegmentE.read()) key = 5; 279 | if (!SegmentF.read()) key = 6; 280 | if (!SegmentG.read()) key = 7; 281 | if (!SegmentDP.read()) key = 8; 282 | break; 283 | case 1: 284 | KeyRowB.low(); 285 | delayMicroseconds(2); 286 | if (!SegmentA.read()) key = 9; 287 | if (!SegmentB.read()) key = 10; 288 | if (!SegmentC.read()) key = 11; 289 | if (!SegmentD.read()) key = 12; 290 | if (!SegmentE.read()) key = 13; 291 | if (!SegmentF.read()) key = 14; 292 | if (!SegmentG.read()) key = 15; 293 | if (!SegmentDP.read()) key = 16; 294 | break; 295 | case 2: 296 | KeyRowC.low(); 297 | delayMicroseconds(2); 298 | if (!SegmentA.read()) key = 17; 299 | if (!SegmentB.read()) key = 18; 300 | break; 301 | } 302 | } 303 | 304 | allKeyRowOff(); 305 | allSegmentOutput(); 306 | 307 | return key; 308 | } 309 | -------------------------------------------------------------------------------- /SinclairScientificExperiments2/SinclairScientificExperiments2.ino: -------------------------------------------------------------------------------- 1 | // Arduino Port of 2 | // 3 | // TI calculator simulator 4 | // Ken Shirriff, http://righto.com/ti 5 | // Based on patent US3934233 6 | // 7 | // 8 | // The goal of this project is to run the following simulator: http://righto.com/sinclair 9 | // on an arduino nano powered custom pcb resembling the original Sinclair Scientific Calculator 10 | // @arduinoenigma 2018 11 | 12 | // TODO: 13 | // match PrintableKeys to 14 | // SinclairData.keyStrobe -> activate KN KO KP 15 | // SinclairData.keyPressed -> activate DK 16 | 17 | #include "GPIO.h" 18 | 19 | extern void display(); 20 | extern void allKeyRowOff(); 21 | extern void allSegmentOutput(); 22 | extern void allSegmentOff(); 23 | extern void allDigitOutput(); 24 | extern void allDigitOff(); 25 | extern void step(); 26 | extern char readKey(); 27 | 28 | GPIO KeyRowB; 29 | GPIO KeyRowC; 30 | GPIO KeyRowA; 31 | 32 | GPIO SegmentA; 33 | GPIO SegmentB; 34 | GPIO SegmentC; 35 | GPIO SegmentD; 36 | GPIO SegmentE; 37 | GPIO SegmentF; 38 | GPIO SegmentG; 39 | GPIO SegmentDP; 40 | 41 | GPIO Digit3; 42 | GPIO Digit4; 43 | GPIO Digit5; 44 | GPIO Digit6; 45 | GPIO Digit7; 46 | GPIO Digit8; 47 | GPIO Digit9; 48 | 49 | byte display_tmr = 0; 50 | signed char digits[9] = {0, 1, -2, 3, 4, -5, 6, 7, 8}; 51 | char PrintableKeys[19] = "12+E0v-378X654/9^C"; 52 | 53 | const char keysKN[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', 0}; 54 | const char keysKO[10] = {'C', 'v', '+', '-', '/', 'X', '^', 'E', '0', 0}; 55 | //const char keysKP[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 56 | 57 | 58 | unsigned int const objectCode[] PROGMEM = { 59 | 1408, 1392, 1792, 1824, 1860, 1808, 1360, 1376, 60 | 518, 1319, 1360, 1376, 9, 1360, 1908, 1072, 61 | 1083, 1075, 1121, 1129, 1073, 1069, 1051, 1840, 62 | 1955, 1840, 516, 1425, 552, 1430, 33, 1792, 63 | 1398, 1631, 1920, 1683, 34, 2003, 1540, 4, 64 | 1399, 1858, 1872, 1538, 4, 1329, 1335, 4, 65 | 1349, 1347, 4, 1443, 676, 1431, 57, 1559, 66 | 4, 1553, 59, 1443, 677, 1839, 1632, 2018, 67 | 65, 2023, 1719, 72, 1730, 71, 1840, 1666, 68 | 1751, 587, 1840, 1754, 78, 1840, 1718, 594, 69 | 1924, 78, 2017, 1713, 89, 1540, 130, 1844, 70 | 1841, 1652, 597, 130, 1730, 95, 1849, 1650, 71 | 114, 1443, 675, 1355, 1345, 130, 1409, 1559, 72 | 105, 1443, 750, 1839, 1632, 1844, 2023, 1719, 73 | 92, 1538, 1537, 116, 1451, 1796, 791, 1908, 74 | 1781, 637, 1722, 1540, 120, 1940, 1786, 119, 75 | 1445, 820, 1754, 512, 1747, 145, 1860, 1751, 76 | 142, 1686, 141, 1799, 1798, 1686, 1558, 132, 77 | 132, 1908, 1751, 662, 1686, 1686, 1558, 152, 78 | 1441, 614, 1392, 1334, 1408, 1750, 161, 1559, 79 | 159, 1568, 4, 1351, 1355, 1686, 681, 1908, 80 | 165, 1801, 1689, 1824, 1445, 180, 1447, 179, 81 | 1568, 1819, 185, 1565, 1820, 1924, 2011, 1693, 82 | 738, 1888, 1888, 1888, 2012, 1696, 1936, 1936, 83 | 1936, 1872, 1872, 1872, 2012, 1584, 1724, 1920, 84 | 1920, 1920, 1445, 183, 1561, 1447, 210, 1779, 85 | 697, 220, 1451, 727, 1732, 185, 220, 1754, 86 | 1844, 1764, 1844, 185, 1904, 1904, 1904, 1904, 87 | 1904, 130, 1447, 233, 1616, 1600, 1808, 1411, 88 | 100, 1451, 236, 1632, 1840, 130, 1445, 251, 89 | 1750, 760, 1751, 759, 1908, 1686, 240, 1860, 90 | 1794, 1864, 1864, 1824, 2003, 1636, 1924, 1924, 91 | 1700, 1431, 823, 2009, 1787, 253, 1993, 2036, 92 | 1723, 1920, 1920, 1920, 1920, 1588, 1844, 1445, 93 | 814, 1600, 1479, 1447, 765, 1572, 1796, 1806, 94 | 1764, 797, 1700, 1562, 280, 1571, 803, 1860, 95 | 1631, 1892, 280, 1807, 1443, 808, 130, 10, 96 | 1572, 1796, 1904, 1904, 1794, 61, 1572, 1796, 97 | 1803, 1652, 1844, 117, 1908, 1335, 251, 1693, 98 | 253, 1565, 1860, 1563, 2036, 1844, 1411, 790 99 | }; 100 | 101 | #define NUMBER_OF_MASKS 16 102 | #define MASK_LENGTH 12 103 | 104 | const char masks [][MASK_LENGTH] PROGMEM = { 105 | {"00000000000"}, // M0 106 | {"5 "}, // M1 107 | {" 00 "}, // M2 108 | {" 1 "}, // M3 109 | {" 0000000"}, // M4 110 | {" 1"}, // M5 111 | {" 01 "}, // M6 112 | {" 5 "}, // M7 113 | {"000000 "}, // M8 114 | {"0001 "}, // M9 115 | {" 0000001"}, // M10 116 | {" 1 "}, // M11 117 | {" 00005 "}, // M12 118 | {" 00001 "}, // M13 119 | {" 4 "}, // M14 120 | {" 0 "}, // M15 121 | }; 122 | 123 | //OPS WITH K: 1:AAKA 2:AAKC 5:ACKA 6:ACKB 9: 11: 124 | //const unsigned long LISTOPSWITHK = 000111100000001111010101001100110b; 125 | const unsigned long LISTOPSWITHK = 1007135334; 126 | 127 | //ORIGINAL SINCLAIR DATA 128 | 129 | //this.rom = objectCode; 130 | //this.sinclair = sinclair; 131 | // Important: Array order matches display order, not bit order. 132 | // I.e. a[0] is high-order digit S10, a[10] is low-order digit S0. 133 | //this.a = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Register A 134 | //this.b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Register B 135 | //this.c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Register C 136 | //this.af = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Flags A 137 | //this.bf = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // Flags B 138 | // The states are called D1 to D10. The last bit is never set 139 | // d is sort of a combination of the 10-bit digit scan register and the 11-bit D-scan register. 140 | // In reality, all 11 bits of the D-scan register are used, but at varying S cycles. 141 | // The digit scan register is clocked at S9 phase 3. Thus 11 shifts of D-scan take the same time 142 | // as 10 shifts of digit scan. 143 | //this.d = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; // D scan register, d[0] low for D1 144 | // Currently active D value 1-11. d[dActive-1] == 0 145 | //this.dActive = 1; 146 | //this.cc = 0; 147 | //this.ccMeaning = ''; 148 | //this.keyPressed = null; 149 | // 'KO' or 'KP' if a keyboard input line is active, i.e. dActive and keyPressed match in the key matrix 150 | //this.keyStrobe = 0; 151 | //this.address = 0; 152 | //this.display = 1; // Flag for display on 153 | //this.mask = null; 154 | //this.showDisplayScan = 0; 155 | //this.idle = 0; // 1 if in idle loop 156 | //this.speed = 'auto'; // Speed for updates 157 | //this.fastStep = 1; // 1 makes SYNC, etc take 1 cycle 158 | //this.power = 1; // On 159 | 160 | //to use with keyStrobe 161 | #define KN 1 162 | #define KO 2 163 | #define KP 3 164 | 165 | //to use with keyPressed 166 | #define DK 1 167 | 168 | struct SinclairData_t 169 | { 170 | boolean sinclair = true; 171 | 172 | // Important: Array order matches display order, not bit order. 173 | // I.e. a[0] is high-order digit S10, a[10] is low-order digit S0. 174 | signed char a[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register A 175 | signed char b[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register B 176 | signed char c[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Register C 177 | signed char af[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 178 | signed char bf[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Flags A 179 | 180 | signed char d[11] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 181 | 182 | // Currently active D value 1-11. d[dActive-1] == 0 183 | byte dActive = 1; 184 | signed char cc = 0; 185 | char ccMeaning[12]; 186 | 187 | byte keyPressed = 0; 188 | // 'KO' or 'KP' if a keyboard input line is active, i.e. dActive and keyPressed match in the key matrix 189 | byte keyStrobe = 0; 190 | unsigned int address = 0; // PROGRAM COUNTER 191 | byte display = 1; // Flag for display on 192 | char mask[MASK_LENGTH]; 193 | byte showDisplayScan = 0; 194 | byte idle = 0; // 1 if in idle loop 195 | byte speed = 0; // Speed for updates 196 | byte fastStep = 1; // 1 makes SYNC, etc take 1 cycle 197 | byte power = 1; // On 198 | } SinclairData; 199 | 200 | 201 | char key = 0; 202 | char p = 0; 203 | 204 | //this function is called by the display routines to show a digit for 2ms before moving to the next one. 205 | //can do useful work here... 206 | void backgroundWork() { 207 | 208 | delay(2); 209 | 210 | } 211 | 212 | 213 | //TODO: enable display digits 0 and 1, move 2..5 to 0.3 and add digits 7 and 8 214 | void updateDisplay() { 215 | 216 | digits[0] = 0; 217 | digits[1] = 0; 218 | digits[2] = (SinclairData.a[0] == 0) ? 99 : 10; 219 | digits[3] = SinclairData.a[4]; 220 | digits[4] = SinclairData.a[5]; 221 | digits[5] = SinclairData.a[6]; //7 and 8 also when we get digits 0 and 1 working 222 | digits[6] = (SinclairData.a[1] == 0) ? 99 : 10; 223 | digits[7] = SinclairData.a[2]; 224 | digits[8] = SinclairData.a[3]; 225 | 226 | display(); 227 | } 228 | 229 | void setup() { 230 | // put your setup code here, to run once: 231 | 232 | Serial.begin(250000); 233 | 234 | allKeyRowOff(); 235 | 236 | allSegmentOutput(); 237 | allSegmentOff(); 238 | 239 | allDigitOutput(); 240 | allDigitOff(); 241 | 242 | delay(1000); 243 | } 244 | 245 | void loop() { 246 | // put your main code here, to run repeatedly: 247 | 248 | step(); 249 | 250 | updateDisplay(); 251 | 252 | key = readKey(); 253 | 254 | SinclairData.keyStrobe = 0; 255 | 256 | if (key != 0) 257 | { 258 | p = PrintableKeys[key - 1]; 259 | //Serial.println(PrintableKeys[key - 1]); 260 | 261 | if (p == keysKN[SinclairData.dActive - 1]) { 262 | SinclairData.keyStrobe = KN; 263 | //Serial.println(F("KN")); 264 | } 265 | 266 | if (p == keysKO[SinclairData.dActive - 1]) { 267 | SinclairData.keyStrobe = KO; 268 | //Serial.println(F("KP")); 269 | } 270 | 271 | if (p == 'C') { 272 | SinclairData.address = 0; 273 | SinclairData.keyStrobe = 0; 274 | } 275 | } 276 | } 277 | 278 | --------------------------------------------------------------------------------