Sunday, 22 December 2024

Reading the joystick port from VIC20 BASIC

The post I wrote on fixing Autofire in VIC20 Bandits (and Multitron) was originally much longer. Patreon supporters got to see the full version in October, but when I came to post it here on the blog in November, I thought it was too long, so I cut some bits out. I now present the "deleted and extended scenes" from that post.

Let's start with a bit of VIC20 BASIC.

Reading a joystick on a VIC20 from BASIC

To read port A of first 6522 VIA chip in the VIC20, you can use the BASIC command

    PRINT PEEK(37137)

You can write a very simple program to repeatedly print that out

    10 PRINT PEEK(37137)

    20 GOTO 10

If you take a standard VIC20 with nothing attached and do that you will get a value of 126 all the time.

If you have a joystick plugged in, you will still read 126.

If you press fire, the value will change to 94.

If you move up, the value will change to 122.

If you move down, the value will change to 118.

If you move left, the value will change to 110.

If you move right, the value will still read 126.

That's a bit of an unfortunate quirk of the VIC20. For some inexplicable reason, the joystick right input is on a different port, on a different chip, so you have to do

    PRINT PEEK(37152)

Normally that will read 247.

If you move right, the value will still read 247.

But I thought you said....

Yeah, another quirk of the VIC20. That port is normally all outputs, driving the keyboard columns. In order to read it, you have to set it to an input first.

Really? but that's very inconvenient. Yeah, well, I didn't design it.

Instead you need to do something like this

    10 POKE 37154, 127 : PRINT PEEK(37152) : POKE 37154, 255

    20 GOTO 10

That will set the relevant pin to an input, read the value, then set it back to an output so it will not mess up the keyboard scan routine.

Now if you run that it will normally print 247.

If you move right, the value will change to 119.

(although ideally you would not have the PRINT routine there, just read to a value to reduce the time where the pin is not an output)

Given that information, you could write a simple program that reads those ports, and compares against those values.

    IF PEEK(37137) = 94 THEN PRINT "fire!"

However, there are a few problem. If you move up and press fire at the same time, you get a different value, 90, so the test would fail.

A better way is to test the individual bits, so

    IF (32 AND PEEK(37137))=0 THEN PRINT "fire!"

(it is never ideal doing bitwise work in decimal, it is much easier to see in hex or binary, but that's what we're stuck with in BASIC).

10 A = PEEK (37151)

20 POKE 37154,127 : B = PEEK (37152) : POKE 37154,255

30 IF (A AND 4) = 0 THEN PRINT "up"

40 IF (A AND 8) = 0 THEN PRINT "down"

50 IF (A AND 16) = 0 THEN PRINT "left"

60 IF (B AND 128) = 0 THEN PRINT "right"

70 IF (A AND 32) = 0 THEN PRINT "fire"

80 GOTO 10

That seems to work.

But what about the other bits?

There are also some other issues with just comparing values.

If you have a datasette plugged in, the value of 37137 will still read 126.

If you have left the play key pressed after loading, the value will now read 62.

All your comparisons by value will now be broken, bitwise tests will be fine.

The obviously causes an issue for some games, which have a "PLEASE STOP TAPE" message at the start of the game (rather than changing the tests to cope with that)

Something else that affects the value is the IEC bus.

If you have a IEC drive plugged in, the value of 37137 will still read 126 at power on.

If you have accessed that drive, it will now read 127.

Any developers working on a purely tape based system may not have known that.

There was also this long section that was cut where I worked through what was happening in the game Bandits.

There is one more game on the cartridge which has this autofire issue, but I haven't been able to fix that before. Time to have another go.

Fixing Bandits

I wonder if I can fix Bandits now? That seems to just autofire all the time, it does move correctly, but fire is stuck on.

The issue is the same, although the implementation is a lot more complicated.

   lda $9111

   asl

   asl

   asl

   bcs label_b410

   and #$ef

And it goes on

label_b410

   asl $9120

   ror

   ora #$04

OK, I worked through this to find out what is going on. The ASL is arithmetic shift left. That moves the state of the fire button out into the carry. That gets rid of the IEC ATN and cassette sense in the process, but leave the IEC clock and data in the mix (now moved to bits 3 and 4)

It can test the fire status by checking the carry bit. If it is clear (i.e. if fire button pressed), it clears a different bit with an AND instruction. It clears bit 4, and in future testing, that is the bit it now uses for testing fire. (why it does this I don't know, maybe they just wanted the directions together?)

After that it reads VIA#2 Port B and shifts bit 7 of that into carry. The ROR is rotate right, that rolls the carry (joystick right) into the accumulator.

Finally it ORs this with 04, which forces bit 2 high.

The final upshot of all that is the accumulator now contains joystick right, joystick left, joystick down, joystick up, and the reposition joystick fire. It then saves this and uses it for comparisons.

        C 7 6 5 4 3 2 1 0

Read   x A C F L D U d c

         ASL    A C F L D U d c 0

         ASL    C F L D U d c 0 0

         ASL    F L D U d c 0 0 0

         AND EF F L D U f c 0 0 0   if fire f = 0, otherwise d       

       ASL 9120 R L D U f c 0 0 0

         ROR    0 R L D U f c 0 0

         ORA 04 0 R L D U f 1 0 0

On a normal VIC20 with no IEC device attached, that works fine. It assumes that the value of bit 4 part way through shuffling is going to be a 1. It then sets it to 0 if fire is pressed. However, if the value of that pin is already 0, then it will think fire is pressed when it isn't.

That bit happens to contain the value read from the IEC data pin, the top trace in yellow as before.

That is how it looks if you have no IEC device connected. The dips are when the chip is in reset. The drive pins become inputs, and the inputs to the 7406 buffers float high and assert the signals. After reset, the pins are set as outputs. Data and ATN are released, but clock stays asserted as that is the default state for the VIC20. Basically it is putting it's towel down on the deckchair and saying "if anyone is going to talk around here, it is going to be me".

If there is an IEC device present, it sees that and responds appropriately by asserting data to say it is ready to receive.

Bandits seems to interpret that as fire being pressed.

The autofire can score quite well just sitting there, but ideally you would have control.

As with the other games you can avoid this by not having an IEC device attached.

The odd thing with Bandits, is you can also"fix" the problem by pressing the drive reset button. That's unusual, I don't think any of the other games were helped by this.

I tried all sorts of things, including resetting the drive via software before launching the game, but always when it started it would it would end up with the data line held low.

I finally got to the bottom of why, and it wasn't part of the code above that reads the port, it was earlier on in the initialisation........

The story continues in the main post where I then went on to fix the problems in Multitron and Bandits.


Advertisements

The patched versions of Bandits and Multitron are included in the new Penultimate +3 version which also has a built in SD2IEC drive.


Minstrel 2 and 3 kits and PET repair parts are available from my Tindie store..

I will slowly be moving things over there from my SellMyRetro store, so if there is anything that you want, let me know and I'll add it.


Patreon

You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. This also includes access to my Patreon only Discord server for even more regular updates.