NEWS, EDITORIALS, REFERENCE
I'm feeling good that I'm back on pace with my posting rhythm. This is my third post in August. Today I just wanted to show off a system feature that I built in last month or so. It's the system status bar, as well as new global, configurable keyboard shortcuts. And this also includes some work on the File and Memory modules to support the status bar.
Let's just dig right in. I was anticipating that this would be a briefer post than some of my others, but it's me after all. I'm very wordy and detailed. So, this may turn out to be longer than I'd originally expected.
You know, it's funny, I've been using a C64 since I was 9 years old, and it never fully dawned on me until just recently the importance of reading the drive's error channel.
As I discussed at some length in the post from earlier this year, KERNAL, File Refs and Services, the C64 itself really doesn't know anything about files or file systems, or anything else about disk management. I argued then that this is a strength, when you consider the nature of the system. The machine is small, with limited RAM and even less ROM. And yet, it can easily make use of modern storage media like CD roms, SD Cards, CF Cards and hard drives. This is because the DOS is entirely offloaded into the device.
The C64 merely opens a connection over the serial bus, and sends commands over the command channel. This allows the C64 to read and write files, and issue commands to the device's DOS to let it handle the nitty details of manipulating the data on the storage medium.
Consequently, though, the C64 itself doesn't know very much about error states in the devices it talks to. There are a couple of conditions it handles. For example, if you try to access a device number that doesn't exist, well, the C64 knows about that. And if you're reading in bytes and you hit the end of the file, the C64 knows about that too. But, imagine that what you really want to do is change a directory, then create and open a file, write some data to it, and close it. To do this, the C64 must send the change directory command, over the command channel, prior to trying to open the file. If all goes well, the file will end up being created inside the subdirectory.
But what happens if things don't go as planned? What happens if, for some reason, the directory doesn't exist? What will happen is that the C64 will issue the change directory command. That will produce an error internal to the drive, about which the C64 knows absolutely nothing. If the C64 then simply proceeds to open/create the file, it will be created in the wrong place. Even worse, if you open with an overwrite flag, who knows, you might accidentally overwrite a file with the same name that's not even supposed to be there, because you're not where you think you are.
There is only one way to deal with this: Read the error channel, religiously.
It's not like this hasn't been known for 40 years, it's right there in the User's Guide. But, when you actually get into the details of programming, somehow things that have always been there but that you've never thought much about suddenly pop up become noticeable.
Taken from 1541 User's Guide at archive.org.
Dear reader and Commodore 64 enthusiast,
You've come here to read one of my high–quality, long–form, weblog posts. Thank you for your interest, time and input. It would be a lonely world without loyal and friendly readers like you.
I'm creating C64 OS and documenting my progress along the way, to give something to you and contribute to the Commodore community. Please consider buying a Commodore Logo Patch or making a small donation, to help me continue to bring you updates, in–depth technical discussions and programming reference. Your generous support is greatly appreciated.
Greg Naçu — C64OS.com
Just a few short years ago, it was almost a total mystery to me how the electronics of NES, SNES and other controllers worked. C64 controllers are easy. There is one wire for every button, plus one wire for ground. You push a button and that button's wire is connected to ground. 5 buttons plus ground is 6 wires. The 5 controller wires lead directly to pins on the CIA chip for either its Port A or Port B. The CIA port pins internally pull up, so every IRQ cycle your program can read them to see if they're low. Any port pin that's pulled low is because the button is depressed connecting it to ground. Simple.
But, how do these controllers with many buttons communicate with the computer? Especially when they have fewer wires but more buttons than a C64 controller. By what magic does this work?
This is gonna get a bit technical. Better buckle up!
How does the NES controller work?
First off, if you don't know much about digital electronics, but you really want to learn more, I recommend reading this: All About Circuits – Textbook – Vol IV – Digital
The whole site is basically free online education. Volume 4 of the textbook walks you through everything you need to know about digital electronics. I learned more about electronics from that tutorial than I had in my whole life up until that point. From there, the large fold–out C64 schematics, that came in the spiral bound C64 Programmers Reference Guide, actually started to make sense. And I began to be able to lookup the spec sheets on the chips to see how they work.
The short answer for how these controllers work, is that they use a shift register. But, to understand what a shift register is, we have to understand a number of more primative components. Let's quickly go over the components that combine together to build up to a shift register.
Alright. I don't want lose my cadence of approximately two posts a month. However, I've got a much needed summer vacation coming up in a few days which cuts this month short. I've got a couple of small news items to cover first.
Whoops, I didn't get this post finished before my vacation. So, this post didn't make it into July.
1) I recently got some more work done on C64 Luggable, and its documentation. The table of contents is now pretty much filled out, and I've published a new section, Front I/O. If you missed the announcement on Twitter and you're following along or interested in my progress on that project you can read about it here.
2) I have started to organize the C64 OS section of this site. It's mostly for technical documentation for programming, but it'll also include user documentation. I haven't written much content for it yet, but I've reorganized it so it is structured very similarly to the documentation for C64 Luggable. Sidebar table of contents, separate page for each section, with forum comments and analytics on each page. This will give me a place to start writing real documentation, that will be final for v1.0.
3) Speaking of v1.0. I'm still quite a ways off, many months I would guess. But I can see glimmerings of light at the end of the tunnel. For a long time, I was just coding and coding and I couldn't even run my code. It only worked in theory, even though a lot had been written. In this last few months things have really come together. The code is working, the system is running and I'm able to see and try out the fruits of my labor. If you've been following on Twitter, I've been able to bang out some utilities fairly quickly. And I've been doing the design work for several more. Finally being able to work on code that is not part of the KERNAL is a huge relief.
4) As if my attention isn't split enough ways already, I've also been putting some thought and time into packaging, and distribution of C64 OS v1.0. I've been doing some research on other OSes, (GEOS 1.0 and 2.0, Wheels, Risc OS, and early versions of Mac OS and Windows) to see what commonalities they have, and what applications and accessories they shipped with. I'm setting realistic targets for a core set of applications, utilities, and extras to include in C64 OS that I can A) actually accomplish, but that B) will make a compelling offering. Along with this, I've been writing the first draft of a User Manual. I'm formatting it for 5.5" x 8.5"1 and looking into reasonable production options. I've also decided to distribute C64 OS on 16 megabyte SD Cards, (sorry, not 1541! and not .D64) which will be affixed to the inside cover the User Manual. I'll have a lot more to say about this in future posts.
Now on to Utilities!
In the last month of so, I've been working on Utilities, how they are loaded and how they are quit. How they load and save their configuration data, and how they efficiently redraw. I want eagerly to write in detail about Utilities and get into the details of how everything comes together around them. However, before I do that, I've got to take a bit of time first to document my plans and work on something a bit more low level. I've been thinking about what exactly to call this lower level system, and I've decide to call it Command and Control.
Command and Control is the flow of execution, the order of event processing, and the execution of processes and timers. I've discussed in other posts already many of the elements that will be covered in this post, but this post will bring them together into a more coherent picture. Also I've done more of the programming legwork since the time when I was just planning. Some ideas have gone by the wayside and others, I've never mentioned before, have crept in. So, no need to go any more meta than that, let's just talk about the details.
Interrupt Service Routine and Events
Whatever the CPU happens to be working on, the details of which we'll get into below, the IRQ is triggered by CIA1 60 times a second. This is the standard time, the KERNAL also configures the system this way at start up. The main system timing is therefore built around 1/60th of a second.
The interrupt service routine makes use of I/O and the KERNAL, and so it is essential that these are mapped in whenever it is time to service the IRQ. When the CPU is interrupted, it hops through an IRQ vector which it finds at the end of memory, $FFFE and $FFFF.1 You don't have much control over the state of the machine when an IRQ goes off. But you do have some. Initially, if I ever needed to patch out the KERNAL I would be very meticulous about calling SEI (SEt Interrupt mask bit), so that IRQs would be ignored. And then after patching the KERNAL back in calling CLI (CLear Interrupt mask bit). Unfortunately, this is not without its issues.
From, Compute!'s Mapping the Commodore 64 and C64c, pg.245
It is the Interrupt Service Routine that reads and updates the position of the mouse cursor. If you disable interrupts for too long, the consequence is that the mouse becomes jerky and unresponsive. It is really a subpar experience.
Well, at the very least, this post has got the right number!1 Although I don't know if its content will live up to the hype of the number, I do have some fun stuff to show today. We'll be looking at the menu system in C64 OS, seeing it in action, and doing a comparison to GEOS. It's just as a friendly comparison, since GEOS is the quintessential OS-with-menus that people think about on the C64. So, how do they stack up?
The menu system was something I started designing a long time ago. In my head it was something I knew I wanted to have, and I spent a good deal of time pondering how it could all come together. Consequently, the menu system has been mentioned many times and is the subject of at least two posts from early last year. Recursion and C64 OS's Menu UI (3/3) from March 1st, 2017 when I did a technical deep dive on writing recursive code in 6502 ASM. And then again on May 9th 2017, in Pointers in Practice, Menus, which was a programming theory post about pointers and how they can be used in creating hierarchical data structures.
I'll be the first to admit that I'm apparently a slow coder. In the earliest days it was a huge hill for me to climb to understand how to do anything in assembly language. And I went through many stages of reforming the code in order to be able to sustain large project development coding natively on the C64 itself. Additionally, I've really had to educate myself about each new aspect of the computer as I've gone. Learning about how the CPU works, and the VIC modes, and how memory is mapped, and how the CIAs can be programmed, and how to use interrupts, etc. It's been quite a trip.
But it has all come to fruition. I had the menus actually functioning and doing what I wanted them to starting maybe 3 or 4 months ago. Since then I've been adjusting them, and experimenting with what I can do with them in the context of writing a real world application, or the new C64 OS utilities. This has lead to the addition of numerous small features and behavioral tweaks to get them to where they are now. I'm finally ready to show them in action, and I'm really eager to get into a technical discussion about how they work and all that you can do with them. So let's dig in.
I feel like I'm late to the party. When I started the Commodore 8 Bit Buyer's Guide, one of the things I knew I had to do was have a special feature section and put the 1541 Ultimate II+ top front and center.
It seemed like everyone was talking about it, and raving about how great it is. Sight unseen, I took a couple of quotes from people on Twitter, did my research online, and wrote a Buyer's Guide feature about this contraption, built and brought to us by Gideon's Logic Architectures. In that My Take, which is in essence a mini–review, I described the 1541UII+ (as it is frequently referred to for short) as the The Jesus Device. And that epithet is well earned, but there are a couple of small caveats.
Gideon's Logic Architectures — Webstore
Almost five months ago, Christmas 2017 was over, but daddy needed a little retro pick me up. At the very start of the year, January 2nd, 2018, I placed an order so I could finally get my hands on one and join the crowd. When I placed my order, they were temporarily on back order waiting for a new batch of stock to come in.
I didn't have to wait too long, just a hair over 6 weeks from time of order to the day it arrived, on February 16. And that time included the delay for getting the device back into stock. I tweeted out the day it arrived. As you can see, I got one of the white ones.
Reading other people's thoughts and reviews and some online documentation is one thing, but there is nothing quite like having something in your hands. The very first thing I noticed when I took it out of the package was the professional quality of the enclosure. It doesn't just look svelt and smooth, it feels really solid in your hand. The seams and cutouts are totally custom for the hardware features of this uber–cartridge. The buttons have a great clicky feel, and that's important, because you're gonna be pushing those buttons a lot.
There is so much to talk about with the 1541 Ultimate, it's hard to know where to start. So, I guess I'll start at the top, the unboxing. Then I'll work my way into exactly what it is this device is and what it can do. And I'll link to the reviews of others along the way. A lot has already been said and demonstrated, and I don't want to just repeat the work of others.
In this post we're going to talk about mouse tracking and the difference between proportionality, speed and acceleration.
I'm a big fan of acceleration for reasons that we'll get into shortly, including a video demonstration. But I didn't realize until fairly recently, when I started doing research on the topic, that mouse acceleration is actually a very contentious issue, particularly amongst PC gamers.1 But it's also a sticking point for switchers from PC to Mac (or vice versa) because the default acceleration curves are different between the two operating systems. This can lead to your muscle memory making wrong predictions about how the mouse cursor will move on the screen based on how you move the mouse around on the table. It's such an annoyance for some people that there are numerous third party utilities for both platforms that allow you to override the system defaults and customize the mouse movement much more finely.
Most C64 users back in the day had a joystick, or two or ten. But only a small number of those same users also had a mouse. I was one of the former for at least several years. My first C64 was a second–hand C64c, with a 1541-II, an EPYX Fastload cartridge and an original copy of GEOS v1.2 included in (and advertised on) the box. I also had several joysticks and no mouse.
When I discovered GEOS, it was my first experience with a graphical user interface and it positively captivated me. It is probably the reason I am still obsessed with operating systems 28 years later. GEOS comes with loadable drivers for different input devices, so it supports either a joystick or a mouse.
What is a Directional Input Device?
A C64 joystick is a directional input device. It essentially consists of 5 digital (on/off) buttons. Up/down/left/right and fire. If you press the left-button and hold it down, then every 60th of a second the computer uses the same CIA chip it uses to scan the keyboard to notice that the button is depressed. But from scan to scan there is fundamentally no change of input, it just notices that the button is still held down. Given this unchanging, digitally ON state, how far should the computer move the on–screen cursor at each 60th of a second?
This is a very short, practical, follow–up to my post earlier in the week about my final direction with how to implement timers in C64 OS.
I implemented the input module a long time ago, it was one of the very first modules of code I wrote for C64 OS. It is sort of a combination of mouse and keyboard driver, and maintains queues of the three broad categories of input events: Mouse Events, Keyboard Command Events and Printable Key Events. You can read all about this, from my post The Event Model, which was published in January 2017.
The mouse events consist of mostly mouse primitives, this excludes drag and drop events, for example. It also excludes boundary crossing events such as mouseover and mouseout. So the events that it supports are:
- left down
- left tracking
- left up
- right down
- right up
Additionally, I considered to be of sufficient primitiveness, left click, left double click and right click. These are ever so slightly more than purely primitive, because a click is a not a state of a button, but rather it is a sequence of button states with a temporal aspect. If you mouse down and then later mouse up, but too much time has passed, a click will not be generated. In order to really get it right, the whole thing turned out to be more complicated then I was anticipating.
I love learning. And let me tell you, working on a big project that pushes your boundaries is just one learning experience after another. I've got quite a bit of code in C64 OS now, and so after I've been working on some areas for a while and neglecting others, I sometimes go back to code I wrote months ago, look it over and say:
What the hell was I thinking back then? Greg Naçu — 2018
I'll give you a quick example.
I probably “finished” writing the majority of the code in the screen module what feels like a year ago. The screen module implements the main event loop, fetches events from the event queues and distributes them to screen layers. The code also tracks screen layers and lets you push them and pop them to and from the screen layer stack, among other things. I looked over that code, and realized the way I was doing the pushing and popping, the way I was tracking the screen layers and accessing them, it was almost insane. In the intervening months I've just learned and improved so much that when I go back and look at my old variable naming conventions, code formatting, and comment style, they look horrible.
Not to mention the way it actually works. I've done lots of silly amateur stuff, like, loading the accumulator and then immediately comparing it to zero with CMP before doing a branch on equal to zero. In case you're unaware, that's pretty dumb. Loading the accumulator accordingly sets the Zero flag automatically. Comparing to 0, in this case, is just busy work.
Another example can be found in technique. There are two ways you can make a table of pointers. You can make a single array of 2–byte elements. But if you do this, your byte–index into the table has to increment and decrement by two to track the array offset. And to get the high byte of one of the pointers, for example, you have to set the index to: (array offset)*2 + 1. Alternatively, you can split the pointers into two arrays of 1–byte elements, a table for lo bytes and a table for hi bytes. To move through these tables you just increment and decrement the index by one. To get the high byte, (if the X index register is already set,) it's just LDA HIBYTES,X. And to get the low byte the index stays exactly the same, and it's just LDA LOBYTES,X. And so on and so forth.
Live and learn, right? Learning the practicals of programming is like learning to play pool. You can know all about geometry, but if you don't have that muscle memory and instinctive experience, your pool game is gonna suck.
We're back. This is a quick post to discuss a cool little programming technique I came up with recently. It's a "Switch" Macro. Let's just hop right in.
If you've ever written code in a high level language you're familiar with the usual flow control statements. You have if, else and else if statements, a few types of loop, for, while and do while, and a few others.1 One of the things that tripped me up when starting to learn 6502 is how to replicate some of these fairly mundane flow mechanisms.
For example, in an if–statement you can use logical operators to chain numerous checks that together determine if the code inside that block should be executed or not. But how do you do that sort of thing in 6502 when you only have branch operations on single conditions? Well, you have to do it very manually. When conditions are ANDed together you check each condition one at a time, and if any one of them is not true you use it to branch past the whole block. And you do the opposite for ORing conditions. You check a condition, and if it's true you branch past the condition checking and straight into the block of code. If you make it past all the condition checking and nothing branched you into the code, then you have a default branch at the bottom that skips the block of code.
Here's an example of 3 conditions ANDed together:
And here's an example of 3 conditions ORed together:
Note how there is a bit of a shortcut on the last branch of the OR conditions. Because it's the last one being checked, if it doesn't pass you can branch to skip, otherwise flow will fall through to the code.
Above are previews of the 10 most recent posts.
Below are titles of the 5 next most recent posts.