内容简介:Today I’m publishingRead on for that discussion, or jump straight to theI’ve long been involved in the community of people who program for the
Today I’m publishing tihle , a new emulator targeting TI graphing calculators (currently only the 83+, but maybe others later). There’s rather a lot to say about it, but here I will discuss themotivation for a new emulatorandfollowed by technical notes on theandinitial development process.
Read on for that discussion, or jump straight to the project homepage on GitLab which has a live demo that runs in your web browser and other resources including source code.
Motivation
I’ve long been involved in the community of people who program for the TI-83+ series of graphing calculators ; it was on that platform that I got started programming in the first place. These days I play more of an advisory than active role in writing programs, by running much ofCemetechbehind the scenes and providing the occasional input to others. I think calculators are an excellent way to introduce people to programming, since they are readily available devices and not too complex to get started on, while still having enough capability to support experienced developers in doing interesting things (because they are readily accessible embedded systems). I fully credit getting started with programming calculators for having ended up programming embedded systems profesionally.
Unfortunately, the future of programming for TI calculators seems to be in peril. About a month ago, the news came out that new versions of the OS for the TI-84+ CE (the most recent variant of the 83+ featuring a color screen and improved eZ80 processor) will remove support for running native code .
Death of a platform
While this doesn’t necessarily affect older calculators or programs, in the long term it seems to spell doom for calculators as an inroad to developing embedded software in particular. Programs written in the calculators’ dialect of BASIC continue to be accessible and a new Python implementation fills the void somewhat, but they lack in depth- where a user could spend time and effort developing native programs constrained only by the hardware they run on before, in the future users will be limited to only those capabilities provided by TI’s software .
In much the same way that I believe Scratch is a decent introduction to programming but completely hides interesting details and is not seriously used by anybody but those using it a learning tool, I also believe removal of support for running native code will ultimately mean people will no longer have calculators (which are often required school equipment!) as a useful entrypoint to serious programming.
Historical interest
In addition to the loss of a way to introduce people to programming, removing support for native code also effectively throws away a large existing library of programs that stretches back more than 20 years (nicely embodied inticalc.org, which first came online in 1996). While the 84+ CE is a young platform relative to the TI-83+ series as a whole (and is incompatible with earlier software), it already has a rich library of programs created by users that will effectively be lost when they can no longer be run on the hardware they are designed for.
The precedent of effectively destroying the work of community members is troubling, and I am motivated to look for ways to preserve it. In much the same way that there still exist thriving communities around long-obsolete home computers like theApple IIandCommodore 64today, I think it’s worthwhile to try to provide a similar opportunity by preserving the platform into a hostile future by working to give the systems and software ongoing life beyond what their creators envisioned (or perhaps more pointedly, beyond what they decided they could make money from).
As a newer platform than those early home computers that still have active communities, information and resources may be rather easier to come by for these calculators because much of the information was born digital and has always been online. However this is also hazardous to preservation, because if items are readily available online there may not be any replacements available if the original goes away. For instance, TI used to freely provide an SDK for the 83+ , but have more recently made it much more difficult to access . Perhaps even more concerningly, TI’s web site no longer seems to provide any information about the TI-84+ CSE , seeming to deny that it ever existed (though manuals and software are still available if you know where to look).
A calling
Recognizing these concerns about the loss of a valuable resource for beginning programmers and loss of interesting history to the grind of the education-industrial complex,what are we to do?
There exist a number of web sites that document the calculators and offer resources related to them- I’m already involved in that, which is valuable and generally well-preserved onthe Internet Archive. But if the existing resources are useless on current hardware as they largely become when large classes of programs are not runnable, new tools become required: I believe this situation calls for emulation of the calculators, to make the platform accessible to everybody .
The state of the art
There already exist a number of emulators for TI calculators, including:
- WabbitEmu, which is open-source and generally good quality.
- jsTIfied, an emulator available as a web application.
- Virtual TI , one of the earliest calculator emulators to be created.
- PindurTI , which is now defunct but offered some useful capabilities in its day.
- ..and others
While most of these are accurate enough to run most programs, this is not the whole story: all of them require a ROM image to work . The ROM includes the calculator’s operating system and boot code, which implement huge swaths of functionality from keyboard and display handling through implementing floating-point arithmetic and the TI-BASIC interpreter.
TI jealously protect their OS and boot code because from the point of view of their business, the entire value of the calculator is in its software. If an emulator can run the same software on a more general machine, what reason is there for a user to purchase a $100 device from TI?But if we want to preserve community software for emulation, we don’t really care about the same functions that TI are interested in protecting.
Though truly accurate emulation still requires all OS functions be available in the same way, many applications only use a small subset of them. So what options are available for emulating the system software?
OS implementations
- TI’s EOS (TI-OS) is provided with the calculator, and can be downloaded from TI in order to update a calculator. However, they added a clickwrap agreement to the OS in 2013 that attempts to forbid use of the OS for emulation. While that clause is questionably enforceable, nobody (myself included) is very interested in challenging it- mostly because TI has historically tended to turn a blind eye to the distribution of ROMs. Any such distribution in a larger, more public forum increases the risk of calling in the lawyers, so for these and the reasons discussed above, I do not believe it is tenable to use TI’s OS for general-availability emulation.
- Brandon Wilson’sOS2seems most relevant, in that it wants to reimplement TI-OS “but better.” However, it hasn’t been updated in 11 years at this point and seems dead. It’s also dependent on the assembler and tools provided by Zilog, which only run on Windows and are closed-source.
- GlassOS is implemented in C and actually seems useable, but lacks compatibility with any existing software.
- KnightOS is UNIX-like and seems to have a selection of useful software, but again is not compatible with any existing software beyond a small library of ports targeting it.
- Many other people (myself included) have attempted to write OSes, but they don’t merit mention here because they’re of even less practical use.
Boot code
While the calculator OS implements most functionality, the boot code is also relevant- TI’s OS uses some support code from the boot code, and of course the boot code is responsible for handling the system power-up sequence and allowing recovery from a no-OS situation.
The boot code on these calculators is meant to be read-only,and is programmed into the device at the factory. Thus, there is no official source for boot code short of reading it from the memory of a physical calculator and the community consensus tends to be that sharing copies of the boot code would infringe on TI’s copyright.
Ben “FloppusMaximus” Moody at one time published a reimplementation of the calculator boot code called “BootFree,” which seems to have disappeared from the Internet except for the version integrated with WabbitEmu. While it seems BootFree disappeared for reasons related to the addition of the clickwrap agreement to the EOS downloads provided by TI,I don’t think the same reasons apply to emulation of non-EOS software- thus, BootFree seems like a fine resource in pursuit of making emulation available to all.
Design
If the goal is to freely emulate calculator programs, we recognize that while the boot code situation seems compatible with accurate emulation, the current available libre OSes are insufficient . While it is technically feasible to emulate TI’s EOS (the only reasonable choice for emulating the current software library), legal forces make it untenable to use as a base for publicly-available emulation with the goal of allowing anybody to run programs for the platform.
It’s also worth noting that there are two major divisions in kinds of programs that run on these calculators: they can be implemented in the provided BASIC dialect (TI-BASIC), or distributed as machine code for the calculator. TI-OS implements the interpreter for TI-BASIC so both of these can be run on the existing emulators, but because using TI-OS is not an option for this project we need to choose how to approach things. The possibility space of emulation projects forks:
- Build a TI-BASIC interpreter, not necessarily one that runs on an emulated calculator.
- Create a whole-machine emulator that works without TI-OS.
While I’ve explored the feasibility of creating a from-scratch TI-BASIC interpreter in the past, it’s never made much progress and I got rather bogged down in accurate emulation of the calculator’s rather unique decimal floating-point format.I have instead chosen to work on a whole-machine emulator that doesn’t require TI-OS .
A novel emulator: thinking like a gamer
It is interesting to compare emulation of calculator programs to that of game systems (which are also common targets for emulation). In game systems the interest in emulation is almost entirely on the programs (games) that are separate from the system. Similarly, I am currently interested in providing emulation of third-party programs that happen to be run on a calculator.
Game systems tend to have small amounts of platform code that game software might use, which emulators tend to hide the existence of: a tiny minority of emulator users would have the capability to extract the firmware from a game system they own, and though most users probably engage in copyright infringement to obtain copies of game software to run on emulators, the emulators owe their existence to the fact that they don’t contain any copyright-infringing code themselves .
On calculators, emulation of interesting programs with existing emulators requires a copy of the system software which notionally requires access to a physical calculator to obtain. This means the emulators can reliably be considered not to fall afoul of any laws, though the barrier to entry is raised. However, the programs themselves are almost entirely made available freely by their authors- among the hobbyists who write calculator software, the platform is largely open. If an emulator can do without a ROM image of a calculator, it is feasible to freely provide the existing library of software to all comers.
Game system emulators tend to solve their inability to distribute the system firmware through high-level emulation: various operations that may be taken by software can be recognized, and the emulator can implement those operations itself. This approach both avoids any dependency on system firmware for its implementation and can often achieve better performance because the emulator need not faithfully emulate the underlying process- only its side effects are in scope. Though high-level emulation tends to sacrifice accuracy in ways that can cause some emulated software to behave incorrectly, this is often because the emulator must take shortcuts to achieve acceptable performance. When emulating a calculator with a 40-year-old CPU architecture that runs at a few Megahertz, acceptable performance should be much easier to achieve without noticeable shortcuts.
With the precedent of existing emulators that are very faithful to the known hardware behavior and decision that no existing calculator OS is appropriate for the application, tihle is born. The name indicates how important that feature is to its existence: it is a h igh- l evel- e mulator for TI calculators.
Implementation, or: the development log
With a plan in mind, I had to start building something. I could have started with the core of one of the existing emulators, but had a few reasons to instead start from (mostly) scratch:
- Code quality is highly variable, and I’m not familiar with their code. I have no idea what kinds of bugs and pitfalls may exist in them.
- Support for high-level emulation would require an unknown amount of modification.
- Portability is questionable, since they’re mostly implemented in various flavors of C or C++ and typically have Windows as the primary target system.
I’m a big fan of Rust and know that portability of Rust programs is pretty good because the language implementation’s standard library does a pretty good job of abstracting away platform details. In addition, I know the compiler has good support for WebAssembly as a platform, which should make it reasonably easy to make a port that runs in a web browser . Running in a web browser is desirable because it is the most accessible way to allow people to emulate games- see for instance the Emularity , which powers emulation of thousands of programs on the Internet Archive.
Cores
Though I chose to start from scratch with an emulator written in Rust, I still didn’t want to spend the effort to implement CPU emulation from scratch. Since the Z80 is such an old CPU and so widely used, there are a number of existing emulators that I might be able to use the core from. Rust is easy to link against C code, so there are many options to consider. Among the most notable:
- YAZE-AGis very mature and emulates a whole CP/M system.
- Fuse is also very mature and emulates the ZX Spectrum. It also has a reputation for high accuracy thanks in part to its comprehensive test suite.
I eventually settled on a core that’s not part of any major emulator: Manuel Sainz de Baranda y Goñi’s “redcode” core . It’s written in very portable C and designed to be used as a library so it takes very little setup or modification to use in my application. It does depend on a large external header-only library as provided, but I was able to adapt it to be more standalone with some work.
I tend to prefer to use permissive licenses on my software, but the redcode Z80 core is GPL -licensed. Since linking it into my binaries would require my code to also be GPL I would prefer to use a core with a more permissive license, but accepting GPL in exchange for not needing to implement the core myself seems like a fair and expedient trade on balance. In the future I might choose to implement my own core and change the license for tihle, but for the time being it will be copyleft.
Taking emulation up a level
Building the CPU core and getting it to run some code wasn’t terribly hard, which left the core issue of how to implement high-level emulation. The emulator needs to be able to recognize when the CPU needs to “trap” into operations provided by the emulator.
I initially defined traps as simply taking some action on instruction fetch . When the CPU would attempt to read memory at chosen addresses, the emulator would execute a trap handler based on the memory address being read, then return data equivalent to instructions appropriate to the trap- usually just the value C9
for a ret
instruction.
This approach quickly turned out to be problematic as I was testing the emulator. I chose Phoenix as the target program for initial development because it’s a sort of classic calculator game and as a bonus shouldn’t depend too tightly on emulation accuracy- Phoenix has designed to be portable across a number of calculators, so hopefully doesn’t contain many assumptions about the system.
Unfortunately, I quickly ran into problems with the way Phoenix generates random numbers. Its FAST_RANDOM
routine uses some self-modifying code to read values from semi-random memory addresses, mixing the read values through a few shifts and xor
s. The initial address it reads is the CPU reset vector; 0x0000
, which I had also defined as the trap that terminates emulation. To work around this I modified reset detection: instead of simply trapping reads from that address, I made the “terminate” flag instead be controlled by any write to port 255 , which is unused on the calculator. By putting code at the reset vector to write to port 255, we can avoid spurious termination.
It quickly becomes obvious that treating termination separately from other traps doesn’t work well. If the program decides to read a value from 0x0028
for instance (where control flow jumps to execute a system routine), it would cause incorrect actions to be taken which would almost certainly cause the program to begin behaving incorrectly. What’s required is a way to ensure traps are only taken when executing the target code, not if the system happens to read a given memory address as data. It wasn’t an immediate problem, but this will be revisited later.
MirageOS
The initial implementation of the memory subsystem only emulated RAM, not the Flash memory in the calculator that holds the OS. Regular programs only execute out of RAM, so it was easy enough to trap on any access to Flash. This became problematic once emulation of Phoenix reached a certain point, because it turns out I had chosen to use the MirageOS version of Phoenix, meaning there was another dependency on the contents of Flash that I had not expected.MirageOSis a shell implemented as a Flash application; it runs directly from Flash, and provides useful support routines to programs designed to take advantage of it.
This version of Phoenix uses the setupint
routine provided by Mirage to implement its timer interrupt that controls game speed. Since Mirage isn’t open-source, I would have had to either reverse-engineer the details of how that works or simply map Mirage into memory. I opted to do the latter, since Mirage is freely redistributable and it will always be easier to emulate it than faithfully implement its functionality in terms of emulator traps.
Hoisted by my own bugs: a debugging adventure
After adding Mirage to the system, things still weren’t working. I spend some time reverse-engineering setupint
and by painstakingly comparing that with execution traces from the emulator, the flow that routine was taking seemed reasonable. It looked like it was jumping somewhere incorrect on servicing the first interrupt after setting it up! Checking the value of the I
register that specifies the location of the interrupt vector table to the core, it didn’t seem correct- meaning there was a problem with the core. Let’s dive into that adventure (or skip ahead toif you prefer not to learn some about my debugging process):
Mirage installs its interrupt vector table to the block of memory at 0x8B00
, filling it with the value 0x8A
. Because custom interrupts on the 83+ must always use the Z80’s interrupt mode 2 but the calculator is not designed to use this mode, this ensures that all interrupts will vector to 0x8A8A
. Breaking execution at 0x71D2
where it loads the I
register:
ROM:71CE ld hl, 8B00h ; Interrupt vectors go at 8B00 ROM:71D1 ld a, h ROM:71D2 ld i, a ROM:71D4 dec a ; A = 8A ROM:71D5 ld bc, 101h ROM:71D8 rst 28h ; Fill 256 bytes of 8A at 8B00 ROM:71D8 ; --------------------------------------------------------------------------- ROM:71D9 dw 4C33h ; MemSet
As expected, HL
was 0x8B00
and A
should have been 8B
, but instead it had value 0x84
and after the ld i, a
instruction the value of I
hadn’t changed!
Digging deeper, I had to debug the emulator- not just inspect the emulated CPU state. This was actually more difficult than expected, because I had been developing withCLionon Windows, using a Rust toolchain targeting the MSVC tools. CLion only supports debugging with GNU tools however, so I couldn’t use the integrated debugger. I do have a copy of Visual Studio handy however, which works just fine to debug MSVC binaries.
I ended up adding a pause to the emulator on startup so I could launch it through CLion, then switch to Visual Studio to attach the debugger and set breakpoints before unpausing the emulator. This worked quite tolerably, since Visual Studio was still able to make sense of all the debug symbols in the binary so the only tedious part was in getting the debugger attached.
With everything hooked up, I saw the following state in my debugger:
By inspecting the assembly and comparing with the debugger’s view of the locals, I can tell that rcx
points to the emulator object
, and offset 0x40 from that refers to the state of the CPU core. PC
of the core has already been incremented to the next instruction, and the intent seems to be that the value of the emulated A
register will be tansferred through ecx
into I
. However, the value it’s reading is 0, not 0x84
as it should be based on the debugger’s view of the AF
register pair.
The redcode emulator uses some utility types that I reimplemented when I didn’t want to include the whole “Z” library that it wants to use, which include the Z16Bit
union used to represent the 16-bit register pairs. It looks like this:
typedef union { zuint16 value_uint16; #ifdef IS_BIG_ENDIAN // 0 is the low-order byte of these aggregates struct { zuint8 index1; zuint8 index0; } values_uint8; #else struct { zuint8 index0; zuint8 index1; } values_uint8; #endif } Z16Bit;
The intent of this type appears to be in allowing easy access to each byte of a 16-bit value; this could be done just with shifting and masking that presumably the compiler could optimize, but I suppose it was designed to not depend on compilers being efficient. In this case, it seems the program is actually reading the wrong byte of the value; when it contains 0x8400
and we want to read the value of A
, it should take the high byte of the value but is instead taking the low. The macro A
in the source code for the core expands to an access to object.state.af.values_uint8.index0
which should be the high byte of the AF
register pair.
So it turns out I made a mistake in adapting that definition to my needs, and index0
is meant to be the high-order byte of the aggregate value, not the low! Simply swapping the locations of index1
and index0
then rebuilding, the state was then correct:
Not only does A
now have the value we expect from reading the Mirage disassembly, ecx
now does contains 0x8B
and the value is correctly written to I
. Problem solved!
Rethinking traps
With improved confidence in the core’s correctness now, emulation can progress to the point where it’s relevant to resume thinking about how to correctly implement traps such that non-instruction reads from memory won’t spuriously trigger them. With the change in reset handling to trap on writes to a particular port, it makes sense to explore the ways in which traps could better be handled.
It’s likely that there will need to be a number of different traps; certainly for a handful of system routines. This means any trap method must be able to provide a way to identify which trap to execute, but it must only be triggered when executed by the CPU. Some ideas come to mind:
- Write a value to a selected port that’s not otherwise used on the calculator.
- Provide a flag from the core to the emulator for handling memory reads to indicate an instruction fetch rather than any other data read.
- Implement a custom instruction that causes a trap.
Modifying the core to handle memory reads differently has unknown complexity so I didn’t want to do that, and writing a value to a port didn’t seem like a very good choice because it can only accomodate 256 values per chosen port. While I don’t know how many traps will eventually be needed, 256 seems like it may not be enough so I opted to look into creating a custom instruction .
Looking at what instructions are defined in both the Z80 and eZ80 documentation (leaving the door open for eZ80 emulation later), there’s a lot of unused space in the ED-prefixed instructions that isn’t used by either CPU and isn’t known to have useful undocumented effects.
According to Jacco Bot , ED00
- ED3F
and EDC0
- EDFF
all have no discernable effect so are good candidates for custom instructions. Cross referencing with the eZ80 CPU manual (Zilog UM0077, table 109), much of the first block is filled in while the second remains sparsely used.
Choosing semi-arbitrarily, ED25
seems like a nice TRAP
instruction , since 25
is an ASCII ‘%’ which seems like it might “pop” out of a text representation of memory a little bit.
To indicate which trap should be taken, I chose to have the instruction include a 16-bit value which indicates to the emulator which trap is desired. This provides a very large number of possible traps that ought to be sufficient for any future needs.
Implementing this new instruction turned out to be very easy, and I learned a little more about how the core is implemented. I simply needed to add a hook for traps (to call from the C core into my Rust emulator) and add a new function that calls that hook, then insert a pointer to that function in the table of ED
-prefixed instruction handlers.
INSTRUCTION(ED_tihle_trap) { PC += 4; uint16_t trap_no = (READ_8(PC - 2) | (READ_8(PC - 1) << 8)); CYCLES += object->trap(object->context, trap_no); return 0; }
The need for an OS
Now that traps are actually a special instruction, it turns out the system needs some kind of OS image! Without one, there’s no way to trigger traps on calls into OS code. As a quick solution, I implemented a tiny OS image that traps on the major OS entrypoints:
; Trap instruction: ED25 + 16 bit parameter .addinstr TRAP * 25ED 4 NOP #define TRAP_RESET 0 #define TRAP_BCALL 1 #define TRAP_OS_INTERRUPT 2 .seek $0000 trap TRAP_RESET rst 00h .seek $0028 trap TRAP_BCALL ret .seek $0038 trap TRAP_OS_INTERRUPT reti
The important traps here are for calls to OS routines at 0x0028
, and the default interrupt handler at 0x0038
. The handler for TRAP_BCALL
inspects the CPU state to choose what system routine to emulate, so it provides most of the OS functionality that any program needs.
Debugging in pictures
With this work, the general structure of the emulator seems sound. Although I didn’t want to implement an OS to begin with, one is now present; however though its complexity is strictly controlled according to whether I find it easier to implement routines in Z80 or as traps, so this seems reasonable. It was then a matter of implementing and debugging the core functions that Phoenix needs.
For some parts of debugging it was easier to write small programs that exercised only the function(s) that needed debugging; these might be good to promote to unit tests in the future.
Displaying text is not particularly hard, though getting a copy of the bitmap font that the calculator uses was a bit of work. I ended up getting each character as an image, then writing some Python scripts to combine them into a binary blob that can be embedded in the emulator. It nearly worked with only a little work, but I did have to debug some bad computations stemming from getting an 8-bit character value and integer rollover.
My initial traps for the _PutS
system routine failed to emulate the undocumented behavior that it updates HL
to point past the string that is displayed. Text output in the small font also had some problems with updating the screen coordinates to draw at. The vertical lines in the small-font text were apparently an error in my font bitmap data that was easily fixed, though the black line at the top of the screen was a different problem.
My LCD emulationhad some issues to fix around not being in the correct mode initially and failing to update its addresses correctly in some situations. It turns out Phoenix is somewhat unusual among games for the 83+ in that it directly accesses the LCD for all its display operations, rather than delegating to a library function provided by the OS ( _GrBufCopy
) or a shell ( fastcopy
) which makes it a good test of basic LCD driver emulation. The display warping was a simple problem of having the wrong display width parameter in the driver emulation.
With the display looking largely correct, the final major hurdle to something that looks like a game was in generating timer interrupts , which Phoenix uses to control the game speed. This required implementing a few control ports which the CPU uses to enable or disable interrupts and ensuring that interrupts would be triggered at the correct times accurately, but wasn’t too bad although I spent some time vexed by a bug where IRQ flags never got reset so interrupts fired continuously.
Handling input
In order to make the game playable, the last piece of the puzzle is in allowing it to receive input. On a physical calculator this is through a simple matrix keyboard, interfaced directly to the CPU via I/O port 0. Where some complexity comes in is in how the calculator OS provides its input abstractions, which turns out to be important but not well documented.
TI-OS provides a routine for scanning the keyboard, called _GetCSC
. It returns one value, a scan code corresponding to the key (if any) that is currently pressed. The documentation leaves it at that however, when the behavior around multiple keys being pressed or keys being held is also important.
It turns out that _GetCSC
is implemented largely via interrupts; while servicing regular timer interrupts, the OS scans they keyboard for keys that are being pressed, and if any are then stores that value in RAM. _GetCSC
reads that value and clears it. If everything were that simple it would be very easy, but the interrupt handler also debounces some keys; the directional arrows and del key can repeat if held, while others will not. The actual timing is that the first repeat of a held key comes after about 48 interrupts, and every 10 thereafter meaning the first repeat comes after about 250 milliseconds and subsequent ones occur at 20 Hz.
While OS2 contains an implementation of this logic (which seemed overcompilicated until I actually understood how this key repeat works), I opted to implement keyboard scanning as a trap instead. While the keyboard hardware is fully emulated because Phoenix only uses _GetCSC
for a few things and directly interfaces with the keyboard for the rest, the OS timer interrupt calls into the emulator to do keyboard scanning because it’s easier to implement and probably more performant.
With this, I’m rather surprised that Phoenix is playable! There’s certainly more work to be done to improve everything, but “Phoenix is playable” was the goal I set for myself that must be reached before I published the project; so.. here we are.
Current state and future
Currently, I’ve really only tested Phoenix. It’s possible that some other games will work in tihle as it stands, and others might work with only minor changes. There are notable holes that I’d like to fill:
- Programs are loaded directly to the point in memory that they execute from. Programs and data should be loaded into memory as if they were managed normally by TI-OS, which will allow programs that want to be able to load external data (such as level packs) to do so, and also enable saving or loading other data , such as high scores or generated programs.
- Rather than directly launching programs, launch a shell and allow users to interactively select a program to run. (Though a mode to directly launch programs may still be desirable.)
- Improve LCD emulation to support greyscale graphics . Programmers sometimes implement rapid switching between black and white to approximate several shades of grey (4-16 depending on how ambitious they are and how much CPU time is worth dedicating to it for the application), which will require LCD emulation to have some “memory” in order to simulate the slow response time of the calculator LCD.
- Support an on-screen keyboard , useful for touchscreen devices and easier to navigate than pressing keys on a computer keyboard.
- Improved debugging tools , to set breakpoints and more interactively explore the system state. Very useful for debugging new programs that don’t yet run correctly, or for developing new programs.
- Your suggestion here! (Suggestions and feature requests via Gitlab issues or other means are welcome.)
I’m quite happy with the ability to run the emulator in a web browser as it is right now. While I can’t claim much credit for it beyond choosing tools that would support that target,it’s still very gratifying to see everything come together to make emulation so accessible. In the longer term, I’d like to try to get emulation via tihle available on the Internet Archive , likemany DOS games are today.
The “minimal” OS that’s included with the emulator is currently a pretty significant hack that I’d like to clean up some, and in the long term it would be cool to make it a much more complete TI-OS reimplementation - possibly taking in most of OS2, and building on top of Brandon’s work. In an ideal world the high-level parts of tihle might eventually become obsolete (or optional) if the OS implementation got good enough.
Links and further reading
tihle lives on GitLab: https://gitlab.com/taricorp/tihle
There you can read more, download binaries to run tihle yourself (or do so directly in your browser), read the source code as well as contribute: I welcome contributions, suggestions and discussion, especially if they come with code!
You can learn more about the technical details of calculators and programming them atWikiTI, though many of the details can be rather inscrutable without additional explanation or prior knowledge. The official TI-83 Plus SDK documentation can be found on the Internet Archive ; much of its contents are omitted from WikiTI, so it’s a valuable source of basic information. Learn TI-83 Plus Assembly in 28 Days is a decent introduction to the platform, intended for readers with no experience programming in assembly but plenty usable by more advanced readers as well, with its combination of simplified official documentation and community-sourced information.
If you’re looking for discussion or programs,Cemetechis one forum where experienced calculator programmers (and me!) hang out, and are happy to discuss things. There’s also an area to find programs there, butticalc.orgis one of the oldest calculator community sites, and has a huge library of software to browse.
以上所述就是小编给大家介绍的《Tihle: A unique TI calculator emulator》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数学与生活(修订版)
[日] 远山启 / 吕砚山、李诵雪、马杰、莫德举 / 人民邮电出版社 / 2014-10 / 42.00元
数学是高等智慧生物的共有思维,是对真理的探索,对矛盾的怀疑,但它绝非一门晦涩难懂的学问,非应试目的的数学是纯粹而朴实的智慧。《数学与生活》为日本数学教育改革之作,旨在还原被考试扭曲的数学,为读者呈现数学的真正容颜,消除应试教学模式带来的数学恐惧感。 本书既包含了初等数学的基础内容,又包含了微分、积分、微分方程、费马定理、欧拉公式等高等数学的内容。作者运用了多个学科的知识。结合日常生活和东西方......一起来看看 《数学与生活(修订版)》 这本书的介绍吧!