Recent Posts

Archives

09/06/09: CoCo Joystick and Audio Sampling by admin, | Category: Programming | 8 comments - (Comments are closed)

CoCo Joystick and Audio Sampling

CoCo Joystick and Audio Sampling

Joystick Sampling

Successive Linear Approximation on the Color Computer
The CoCo uses analog joysticks. A 5 volt signal is sent to the joystick and a smaller voltage is returned depending on the orientation of the stick. The job of reading the joystick is converting that analog voltage to a binary value. During this article it would be advisable to obtain a schematic of the Color Computer. Here is a detailed block diagram that will be good enough for this article. Also, a good understanding of the Peripheral Interface Adaptor will help you get the most out of machine language programming.
Since the CoCo does not have a hardware Analog-To-Digital converter chip, the process it uses is called successive linear approximation.
Here is the process:
  1. Mute the CoCo.
  2. Select a joystick.
  3. Write a zero to the sound register
  4. Compare the sound register to the value from the joystick.
  5. If the sound register is greater than or equal to the joystick value, then you have just discovered the joystick value
  6. Increment sound register.
  7. Sound register only goes to 64, so stop when you hit 65.
  8. Go back to step 4.

Mute the CoCo

During this process you are writing to the sound register. You must mute the CoCo so none of this is audible. The CoCo’s mute switch is controlled by line CB2 of PIA 2. CB2 (like the other 19 lines) is a bidirectional line. This means the line can either output data or input data. For this excersize we need to setup CB2 to output data. We do this by setting bits 4 and 5 of control register B.
        BEGIN LDA $FF23  Get current Control Register B value of PIA 2
        ORA #$30   Set CB2 to be an output. (Set bits 4 and 5.)
Now the status of bit 3 of Control Register B will control the CB2 line. If bit 3 is low the line will be low. If bit 3 is high the line will be high. Setting CB2 low will mute the CoCo.
        ANDA  #$F7   Clear bit 3 - Mute CoCo
        STA   $FF23  Write value back to Control Register B

Select a joystick

There are two josticks with two axes each. This is four different joystick values to read.
Position Joystick0 Right, Horiz1 Right, Vert2 Left, Horiz3 Left, Vert
Four positions can be encoded in 2 bits:
Binary Decimal00 001 110 211 3
There is a four position selector switch inside the CoCo. It is controlled by CA2 and CB2 of PIA 1. CA2 represents the most significant bit of the position, and CB2 represents the least significant bit. These lines will need to be configured as outputs and then the lines themselves will be need to be set to the proper value.
        LDA  $FF01  Get current Control Register A value of PIA 1
        LDB  $FF03  Get current Control Register B value of PIA 1
        ORA  #$30   Set CA2 to be an output. (Set bits 4 and 5 of CRA.)
        ORB  #$30   Set CB2 to be an output. (Set bits 4 and 5 of CRB.)
        ANDA #$F7   Set CA2 low. (Clear bit 3.)
        ORB  #$08   Set CB2 high. (Set bit 3.)
        STA  $FF01  Store value back in CRA.
        STB  $FF03  Store value back in CRA.
CA2 low and CB2 high means we are selecting position 1 which corresponds to the vertical axis of the right joystick.
Write a zero to the sound register

The CoCo’s sound register is 6 bits of side A of PIA 2. Specifically bits 2 thru 7 of the Data Register A. This corresponds to lines PA2 thru PA7. These lines need to be configured as outputs so their signals will go to the digital to analog converter.
        LDA  $FF21   Load Control Register A of PIA 2
        ANDA #$FB    Engage Data Direction Register A. (Clear bit 2.)
        STA  $FF21   Store value back in CRA.
        LDA  $FF20   Load Data Direction Register A of PIA 2.
        ORA  #$FC    Set lines PA2 thru PA7 as output. (Set bits 2 thru 7.)
        STA  $FF20   Store value back in DDRA.
Address $FF20 is both the Data Register A and the Data Direction Register A. It’s function is controller by bit 2 of the Control Register A. So in order to actually write data to the CoCo’s sound register we need to modify the control register so we can access the data register.
        LDA  $FF21   Load Control Register A of PIA 2
        ORA  #$04    Engage Data Register A. (Set bit 2.)
        STA  $FF21   Store value back in CRA.
Clearing bits 2 thru 7 of the Data Register A of PIA 2 will write a zero to the sound register.
        LDB  $FF20   Load Data Register A of PIA 2.
        ANDB #$03    Write a zero to the sound register. (Clear bits 2 thru 7.)

Compare the sound register to the value from the joystick

The comparator chip inside the CoCo has two analog inputs and one digital output. The two inputs are labeled plus and minus. The plus input is connected to the four position switch that connects to the four different joystick axis. The minus input is connected to the output of the digital to analog converted that is controlled by bits 2 thru 7 of side A of PIA 2. The comparator will output a high signal if the plus input is higher than the minus input. Otherwise the comparator will output a low signal. The comparator output is connected to line PA7 of PIA 1. We need to setup this line to be an input.
        LDA  $FF01   Load Control Register A of PIA 1
        ANDA #$FB    Engage Data Direction Register A. (Clear bit 2.)
        STA  $FF01   Store value back in CRA.
        LDA  $FF00   Load Data Direction Register A of PIA 1.
        ANDA #$7F    Set line PA7 to be an input. (Clear bit 7.)
        STA  $FF00   Store value back in DDRA.
Modify the Control Register A to engage the Data Register A at $FF00.
        LDA  $FF01   Load Control Register A of PIA 1
        ORA  #$04    Engage Data Register A. (Set bit 2.)
        STA  $FF01   Store value back in CRA.
Read the output from the comparator.
LOOP    STB  $FF20   Store value in Sound Register
        LDA  $FF00   Bit 7 of register A contains the output information from the comparator.
If bit 7 is high, then we have found the current value.
       BPL DONE   Branch if negative condition is set to label DONE.

Increment sound register

Increment Sound register value by one.
        ADDB #$04   Increment Sound register by one value
Check for a carry. The sound register will overflow after 64 tries.
        BCS  DONE    Branch to label DONE if carry bit set.
        BRA  LOOP    Branch back to the label LOOP
We’re done. The value of the joystick is in the sound register. We need to shift the data over two bits to normalize it to 1 thru 64.
DONE    LSRB         Logical shift right
        LSRB         Logical shift right
Now subtract one to bring it to 0 thru 63.
        DECB
Write value to video memory to see it.
        ORB  #$80    Set bit 7 to turn it into a VDG graphic character
        STA  $500    Put value into middle of the 32 collum screen.
        SWI	     Return to ZBUG.
        END
Colophon

As you can see, reading the joystick is quite involved. It is interesting to note that Color BASIC will attempt to read the joystick value ten times but will immediately return once it gets the same value twice from this process. If all ten tries produce different results, the tenth is returned.
Another quirk is BASIC will sample all four joysticks when joystick zero is read. Reading joysticks 1 thru 3 will return values cached during the sampling done when issuing the JOYSTK(0) command.

8 comments to CoCo Joystick and Audio Sampling

  • TD

    If you start comparison at zero and increase the estimated value by 1 each time you didn’t hit the actual value, it might take up to 64 steps.

    “Successive Linear Approximation” does rather always take 6 steps to “guess” the correct value between 0 and 63. The examples below shall illustrate the method. You continue to subtract or add 2^(6-Step), Step: 1..6, from or to the estimated value which is compared to the actual value, starting with 63. The first comparison (Step=1) is thus always done with 31 (=63-32), assuming the actual value must be “lower or equal” to 63. If the result of the comparison is “lower or equal”, it is subtracted, otherwise added.

    Example 1: Actual value is 22.

    1) Compare with 63 – 32 = 31 => Actual value is lower or equal =>
    2) Compare with 31 – 16 = 15 => Actual value is higher
    3) Compare with 15 + 8 = 23 => Actual value is lower or equal =>
    4) Compare with 23 – 4 = 19 => Actual value is higher =>
    5) Compare with 19 + 2 = 21 => Actual value is higher =>
    6) Compare with 21 + 1 = 22 => Actual value is lower or equal => Value is 22

    Example 2: Actual value is 21.

    1) Compare with 63 – 32 = 31 => Actual value is lower or equal =>
    2) Compare with 31 – 16 = 15 => Actual value is higher
    3) Compare with 15 + 8 = 23 => Actual value is lower or equal =>
    4) Compare with 23 – 4 = 19 => Actual value is higher =>
    5) Compare with 19 + 2 = 21 => Actual value is lower or equal =>
    6) Compare with 21 – 1 = 20 => Actual value is higher => Value is 21

    Example 3: Actual value is 0.

    1) Compare with 63 – 32 = 31 => Actual value is lower or equal =>
    2) Compare with 31 – 16 = 15 => Actual value is lower or equal =>
    3) Compare with 15 – 8 = 7 => Actual value is lower or equal =>
    4) Compare with 7 – 4 = 3 => Actual value is lower or equal =>
    5) Compare with 3 – 2 = 1 => Actual value is lower or equal =>
    6) Compare with 1 – 1 = 0 => Actual value is lower or equal => Value is 0

    Example 4: Actual value is 1.

    1) Compare with 63 – 32 = 31 => Actual value is lower or equal =>
    2) Compare with 31 – 16 = 15 => Actual value is lower or equal =>
    3) Compare with 15 – 8 = 7 => Actual value is lower or equal =>
    4) Compare with 7 – 4 = 3 => Actual value is lower or equal =>
    5) Compare with 3 – 2 = 1 => Actual value is lower or equal =>
    6) Compare with 1 – 1 = 0 => Actual value is higher => Value is 1

    Example 5: Actual value is 32.

    1) Compare with 63 – 32 = 31 => Actual value is higher =>
    2) Compare with 31 + 16 = 47 => Actual value is lower or equal =>
    3) Compare with 47 – 8 = 39 => Actual value is lower or equal =>
    4) Compare with 39 – 4 = 35 => Actual value is lower or equal =>
    5) Compare with 35 – 2 = 33 => Actual value is lower or equal =>
    6) Compare with 33 – 1 = 31 => Actual value is higher => Value is 32

    Example 6: Actual value is 31.

    1) Compare with 63 – 32 = 31 => Actual value is lower or equal =>
    2) Compare with 31 – 16 = 15 => Actual value is higher =>
    3) Compare with 15 + 8 = 23 => Actual value is higher =>
    4) Compare with 23 + 4 = 27 => Actual value is higher =>
    5) Compare with 27 + 2 = 29 => Actual value is higher =>
    6) Compare with 29 + 1 = 30 => Actual value is higher => Value is 31

    Example 7: Actual value is 63.

    1) Compare with 63 – 32 = 31 => Actual value is higher =>
    2) Compare with 31 + 16 = 47 => Actual value is higher =>
    3) Compare with 47 + 8 = 55 => Actual value is higher =>
    4) Compare with 55 + 4 = 59 => Actual value is higher =>
    5) Compare with 59 + 2 = 61 => Actual value is higher =>
    6) Compare with 61 + 1 = 62 => Actual value is higher => Value is 63

  • remz

    Hi guys,

    I am having trouble trying to read joystick values. I took the complete code posted above, make it loop, and here are the results: the joystick axis being read seems to be always horizontal, no matter if the line
    ORB #$08 ;Set CB2 high. (Set bit 3.)
    is set or not. I also think the returned values are from -1 to 62 instead of 0 to 63. It appears just like the last DECB is incorrect. I also changed the output from:
    STA $500 Put value into middle of the 32 collum screen.
    to a scrolling version:
    tfr b,a
    jsr [40962] print the character using stdout hook

    I am running the program under MESS emulation, and I haven’t tried it on my real Coco 3. However, joystick reading should work since the BASIC command JOYSTK() works correctly. Also, included game with Rainbow IDE such as Pac-Dude correctly reads joystick value, both under MESS and on a real Coco too. What I am doing wrong?

    Thanks!

  • remz,
    Even though there are errors and some poor choices of algorithms in the code, it can be made to work. That being said, I’d bet you main problem may be with MESS. You need to be sure that the joysticks are active in MESS before you can get any program to work.
    So, what version of MESS are you using? Knowing the version, I can check to see if the default conditions engage the joysticks and that they work correctly.

    Now for the above code.
    ORB #$80 Set bit 7 to turn it into a VDG graphic character
    STA $500 Put value into middle of the 32 collum screen.
    If this doesn’t look suspect to you, it should. The value to put on the text screen is in regB but regA is stored to the screen.
    This is a bug in the code. There are several things that can be done to optimize the rest of the code but they don’t need to be done.

    Let’s make sure your version of MESS is working correctly before doing anything else.

  • remz

    Robert, you are helpful and a true gentleman. OK here it goes:
    When I’m working with Rainbow IDE, I use a older MESS version 0.106 with a debugger.
    I also have MESS version 0.134. Both shows the same result: the axis being read is horizontal, not vertical as the code should indicate. It’s like the ‘Set CB2 high. (Set bit 3.)’ has absolutely no effect. It also does matter in MESS whether I’m using the keyboard, mouse, or a real PC joystick: all ‘works’ equally.
    Please note that it does seem to be a MESS configuration issue, since if I run a simple BASIC program such as:

    10 ?JOYSTK(0),JOYSTK(1):GOTO 10

    The horizontal and vertical axes both work perfectly.
    And about that ‘STA $500’ bug, I didn’t notice it but as I said earlier, I had replaced the code with

    tfr b,a
    jsr [40962] print the character using stdout hook

    in order to see more helpful information.

  • remz

    oops, sorry: I mean:
    ..Please note that it does *NOT* seem to be a MESS configuration issue…
    (btw: I can’t find how to edit my own comments)

  • I’ve asked Roger about editing facilities but have not yet received and answer.

    OK, if your only problem now is that the above code just reads the x-axis, it is not a problem. The code can’t read both directions because the MUX is set for just X.

    You can easily modify the code to sample both the X and Y axis. Of course the code as written merely changes a byte of the low res text screen rather than reporting an actual value.
    As TD mentioned, it really should be a better algorithm. Also, the RS-232 bit at $FF20 should be made an input so that you don’t need to worry about driving an attached RS-232 device crazy. For normal use, you really don’t want the code to end with an SWI but a RTS to Basic.

    Here is a modification to the LOOP so that both X & Y are sampled and two screen bytes are changed to prove that it works.

    LOOP lda $ff01
    eora #8 alternate between X and Y testing
    sta $ff01
    STB $FF20 Store value in DAC
    LDA $FF00 Check bit 7.
    BPL DONE Branch if bit 7 = 0.
    ADDB #$04 Increment DAC by 1.
    BCC LOOP loop if no overflow
    DONE LSRB Logical shift right
    LSRB Logical shift right
    DECB compensate for extra ADDB
    ORB #$80 Set for VDG graphics character
    * Now decide which byte to change
    lda $ff01 get MUX value
    bita #8 is it X or Y
    beq S1 go to X
    stb $501 this is Y
    bra R1
    S1 STB $500 change low res text screen
    R1 CLRB
    BRA LOOP infinite loop
    END BEGIN

    Of course for actual use, you need to average several readings and return the values to Basic.

  • remz

    THANK YOU robert, it works at last! 🙂
    However, I think there is a bug in your code where your ‘inner loop’ jumps. I have added another LOOP label for the infinite-loop testing. Also, I moved CLRB to the beginning of the loop instead of it being set after one pass. I also change the debug character output to standard printable character from ‘@’ to ‘?’ for easier debugging of the range. This made me discover yet another bug: when the joystick is fully left or bottom, the DAC comparator is 256 (0), which then gets converted to 255 (-1) instead of 63. I fixed the bug by doing DECB before the 2 LSRB.

    ILOOP
    clrb ; set comparator starting at $00
    lda $ff01 ; Load Control Register A of PIA 1
    eora #8 alternate between X and Y testing
    sta $ff01 ; Store value back in CRA.
    LOOP STB $FF20 ; Store value in DAC
    LDA $FF00 ; Check bit 7.
    BPL DONE ; Branch if bit 7 = 0.
    ADDB #$04 ; Increment DAC by 1.
    BCC LOOP ; loop if no overflow

    DONE DECB ; compensate for extra ADDB. This must go BEFORE the two lsr, or else 256 will get converted to 255 instead of 63
    LSRB ; Logical shift right
    LSRB ; Logical shift right
    ADDB #$40 ; Set character as non-inverted
    * Now decide which byte to change
    lda $ff01 ; get MUX value
    bita #8 ; is it X or Y
    beq S1 ; go to X
    stb $501 ; this is Y
    bra R1
    S1 STB $500 ; change low res text screen
    R1 BRA ILOOP ; infinite loop

  • Glad you got it to work. The algorithm is certainly not one I’d use for two reasons. As TD stated, the routine is much too slow and a binary search tree should be used. Also if you want a generic routine that can be used both to read joysticks and to make digital music recordings, it is important that the number of clock cycles to find the answer be the same regardless of incoming value.

    I’ll try paste the routine I’d use although I think the format (tabs) will be lost.

    [code]
    * Generic DAC reading routine.
    * BY Robert Gault

    * Reads both joystick inputs and store X & Y values at offsets
    * 0,1,2, & 3; ROUTINE STARTS AT 4

    * SHOULD BE OFFSET LOADED TO DESIRED ADDRESS, PIC CODE

    * No register values changed

    ORG 0
    RXVAL RMB 1
    RYVAL RMB 1
    LXVAL RMB 1
    LYVAL RMB 1
    START PSHS CC,D,X,Y We don’t want to be interrupted
    ORCC #$50 TURN OFF INTERRUPT RESPONSE
    LDA $FF23 SOUND CONTROL; SAVE AND TURN OFF
    STA <SOUND,PCR
    ANDA #^8 NO SOUND
    STA $FF23
    LDA $FF21
    ANDA #^4 SET FOR DATA DIRECTION
    STA $FF21
    LDB $FF20 GET CURRENT DIRECTIONS
    STB <DAC,PCR
    LDB #%11111100 DAC-OUT, TAPE&RS-232:IN
    STB $FF20
    ORA #4 RESET TO NORMAL
    STA $FF21
    LDA $FF01 MUX VALUES; SAVE AND SET FOR X JSTICK
    STA <PIA0A,PCR
    ANDA #^8 MUX=0
    STA $FF01
    LDA $FF03
    STA <PIA0B,PCR
    ANDA #^8 MUX=0
    STA $FF03 MUX IS NOW 0,0; right X
    LDX #$FF20 POINT TO DAC
    LDY #$FF00 POINT TO COMPARATOR
    BSR READJS
    STA <RXVAL,PCR
    LDA $FF01
    ORA #8 Y DIRECTION
    STA $FF01
    BSR READJS
    STA <RYVAL,PCR
    LDA $FF01
    ANDA #^8 X
    STA $FF01
    LDA $FF03
    ORA #8 STICK #2
    STA $FF03
    BSR READJS
    STA <LXVAL,PCR
    LDA $FF01
    ORA #8 Y
    STA $FF01
    BSR READJS
    STA <LYVAL,PCR
    LDD <PIA0A,PCR
    STA $FF01
    STB $FF03
    LDA $FF21
    ANDA #^4
    STA $FF21
    LDB <DAC,PCR
    STB $FF20
    ORA #4
    STA $FF21
    LDA <SOUND,PCR
    STA $FF23
    PULS CC,D,X,Y,PCR

    * Temporary storage
    PIA0A RMB 1
    PIA0B RMB 1
    DAC RMB 1
    SOUND RMB 1

    * Note that all routes through the READJS routine take the same
    * number of cycles. Indirect reads are faster than Extended and
    * as fast as Direct Page.
    READJS LDA #32*4 =64/2*4 middle DAC value
    STA ,X SET DAC
    LDB ,Y READ COMPARATOR
    BMI A@
    SUBA #16*4
    BRA B@
    A@ ADDA #16*4
    BRN B@
    B@ EQU *

    STA ,X
    LDB ,Y
    BMI A@
    SUBA #8*4
    BRA B@
    A@ ADDA #8*4
    BRN B@
    B@ EQU *

    STA ,X
    LDB ,Y
    BMI A@
    SUBA #4*4
    BRA B@
    A@ ADDA #4*4
    BRN B@
    B@ EQU *

    STA ,X
    LDB ,Y
    BMI A@
    SUBA #2*4
    BRA B@
    A@ ADDA #2*4
    BRN B@
    B@ EQU *

    STA ,X
    LDB ,Y
    BMI A@
    SUBA #1*4
    BRA B@
    A@ ADDA #1*4
    BRN B@
    B@ EQU *

    STA ,X
    LDB ,Y
    BMI A@
    SUBA #2
    BRA B@
    A@ ADDA #2
    BRN B@
    B@ LSRA
    LSRA
    RTS

    END START
    [/code]