Home
JAQForum Ver 20.06
Log In or Join  
Active Topics
Local Time 01:15 06 Dec 2022 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : PicoMiteVGA/CMM2: Controller API

     Page 2 of 2    
Author Message
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1120
Posted: 06:56am 16 Nov 2022
Copy link to clipboard 
Print this post

@Tom, Playing with your drivers and I realized that you don't check that the Classic is actually present and open.

Can I suggest that you test that (X) returns &HA4200101 so you know that it is a Classic and that the instruction:

If Not is_open% Then Controller Classic Open 3 : is_open% = 1

actually worked? If there is not one plugged in it will crash. Unless I am missing something again.

I'm not sure how you can pass that an open failed back to the game program when using a SUB though.

Bill
Keep safe. Live long and prosper.
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2830
Posted: 01:07pm 16 Nov 2022
Copy link to clipboard 
Print this post

Hi Bill,

  Turbo46 said  Regarding the [Wii Classic] controller, I was finding these, they have the same plug but are called 'Pro'. I don't know if they are compatible with the CMM2 or not.


I very strongly suspect they are compatible, I found this regarding the Pro on the Interweb:

"It's the same thing as the original, except that it's slightly wider, has grips, and the cord has been moved to the top. It's been described as being more comfortable. It works on all games."

If it doesn't return the same device code I will be flabbergasted.

  Turbo46 said  @Tom, Playing with your drivers and I realized that you don't check that the Classic is actually present and open.


When I get around to documenting the library the recommendation will be that the driver ctrl.OPEN calls are enclosed within ON ERROR IGNORE and ON ERROR ABORT commands, so to expand upon my earlier example:

driver$ = "wii_classic_3"

' *** Beginning of additions ******************************
On Error Ignore
Call driver$, ctrl.OPEN
On Error Abort
If Mm.ErrNo <> 0 Then Error "Could not open controller"
' *** End of additions ************************************

Do While Not game_over%
 ' Game loop logic.
 ...

 ' Read controller.
 Call cntr$, result%

 ' Do something (e.g. SELECT CASE) with driver independent result%.
 ...
Loop
Call driver$, ctrl.CLOSE


  Turbo46 said  Can I suggest that you test that (X) returns &HA4200101 so you know that it is a Classic ...


I've made a note to do that and throw an ERROR if it isn't.

  Turbo46 said  ... and that the instruction:

If Not is_open% Then Controller Classic Open 3 : is_open% = 1

actually worked?


My current intention is to continue to allow any error reported to propagate to the caller.

Note that there are other options for error handling, this is just the one I'm leaning towards at the moment.

e.g. I could say that the driver has to catch the error and then propagate it out by setting x% and/or a global variable, so the wii classic driver would contain:
Select Case x%
  Case ctrl.OPEN
    If Not is_open% Then
      On Error Skip 1
      Controller Classic Open 3
      If Mm.ErrorNo <> 0 Then
        x% = -999 ' signal an error
        ctrl.err$ = "Could not open controller"
        Exit Sub
      EndIf
    EndIf
    is_open% = 1


But I don't see you gain much other than increased complexity in the driver, you would also then need to call the driver with a variable rather than a constant, e.g.
x% = ctrl.OPEN
Call ctrl$, x%
If x% <> ctrl.OPEN Then Error(ctrl.err$)


TBH I could wax lyrical about the limitations of error handling in MMBasic (and "Street BASIC" in general) and argue that what we really need is more structured error handling, perhaps something like:
Try
 ' do stuff that might ERROR
 ' if that happens skip commands until reach CATCH
Catch
 If Mm.ErrNo = 32 Then
   Print "Error 32"
 Else
   Error Throw ' rethrow the error up to the next TRY/CATCH
               ' or exit program if there isn't one.
 EndIf
End Try


But for compatibility you'd want it on every modern MMBasic implementation and (ignoring the "no remaining language tokens" argument) even I am not so foolhardy as to bring it up with Peter. Perhaps one-day I might add it to MMB4L for kicks and giggles.

Best wishes,

Tom
Edited 2022-11-17 00:46 by thwill
CMM2 Welcome Tape, Creaky old text adventures
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1120
Posted: 10:49pm 16 Nov 2022
Copy link to clipboard 
Print this post

Thanks Tom, I didn't think that you would want to use a global, I must try to understand the CALL command and function.

I'll wait to see what you come up with in the meantime I'll stick with the SUB and FUNCTION method for any games I mess with.

Bill
Keep safe. Live long and prosper.
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2830
Posted: 11:09am 17 Nov 2022
Copy link to clipboard 
Print this post

Hey Bill,

Can you give this a spin with your Nunchuk connected to the 3 different I2C channels ?

Also try with the alternative "wii_any" and "wii_classic" drivers, see lines 333-335.

' Transpiled on 17-11-2022 11:11:48

' Copyright (c) 2022 Thomas Hugo Williams
' License MIT <https://opensource.org/licenses/MIT>
' For CMM2 running MMBasic 5.07.02b6

Option Base 0
Option Default None
Option Explicit On

' BEGIN:     #Include "ctrl.ipp" -----------------------------------------------
' Copyright (c) 2022 Thomas Hugo Williams
' License MIT <https://opensource.org/licenses/MIT>
'
' MMBasic Controller Library
'
' Preprocessor flag CMM2 defined
' Preprocessor flag CTRL_USE_KEYDOWN defined

Const ctrl.VERSION = 902  ' 0.9.2

' Button values as returned by controller driver subroutines.
Const ctrl.R      = &h01
Const ctrl.START  = &h02
Const ctrl.HOME   = &h04
Const ctrl.SELECT = &h08
Const ctrl.L      = &h10
Const ctrl.DOWN   = &h20
Const ctrl.RIGHT  = &h40
Const ctrl.UP     = &h80
Const ctrl.LEFT   = &h100
Const ctrl.ZR     = &h200
Const ctrl.X      = &h400
Const ctrl.A      = &h800
Const ctrl.Y      = &h1000
Const ctrl.B      = &h2000
Const ctrl.ZL     = &h4000

Const ctrl.OPEN  = -1
Const ctrl.CLOSE = -2
Const ctrl.SOFT_CLOSE = -3

' The NES standard specifies a 12 micro-second pulse, but all the controllers
' I've tested work with 1 micro-second, and possibly less.
Const ctrl.PULSE = 0.001 ' 1 micro-second

' When a key is down the corresponding byte of this 256-byte map is set,
' when the key is up then it is unset.
'
' Note that when using INKEY$ (as opposed to the CMM2 'KEYDOWN' function or
' the PicoMiteVGA 'ON PS2' command) to read the keyboard we cannot detect
' keyup events and instead automatically clear a byte after it is read.
Dim ctrl.key_map%(31 + Mm.Info(Option Base))

' Timer number configured for reading the KEYDOWN state on the CMM2.
Dim ctrl.tick_nbr%

' Initialises keyboard reading.
'
' @param  period%  CMM2 only - interval to read KEYDOWN state, default 40 ms.
' @param  nbr%     CMM2 only - timer nbr to read KEYDOWN state, default 4.
Sub ctrl.init_keys(period%, nbr%)
 ctrl.term_keys()
 ctrl.tick_nbr% = Choice(nbr% = 0, 4, nbr%)
 SetTick Choice(period% = 0, 40, period%), ctrl.on_tick(), ctrl.tick_nbr%
End Sub

' Note there is little point in calling KeyDown(0) to determine the number of
' keys that are down, hardware limitations mean it's unlikely ever to be > 4
' and if a given key isn't down it just returns 0 so we harmlessly set that
' byte in the key map.
Sub ctrl.on_tick()
 Memory Set Peek(VarAddr ctrl.key_map%()), 0, 256
 Poke Var ctrl.key_map%(), KeyDown(1), 1
 Poke Var ctrl.key_map%(), KeyDown(2), 1
 Poke Var ctrl.key_map%(), KeyDown(3), 1
 Poke Var ctrl.key_map%(), KeyDown(4), 1
End Sub

' Terminates keyboard reading.
Sub ctrl.term_keys()
 If ctrl.tick_nbr% <> 0 Then SetTick 0, 0, ctrl.tick_nbr%
 Memory Set Peek(VarAddr ctrl.key_map%()), 0, 256
 Do While Inkey$ <> "" : Loop
End Sub

Function ctrl.keydown%(i%)
 ctrl.keydown% = Peek(Var ctrl.key_map%(), i%)
End Function

Function ctrl.poll_multiple$(ctrls$(), mask%, duration%)
 Local expires% = Choice(duration%, Timer + duration%, &h7FFFFFFFFFFFFFFF), i%
 Do
   For i% = Bound(ctrls$(), 0) To Bound(ctrls$(), 1)
     If ctrl.poll_single%(ctrls$(i%), mask%) Then
       ctrl.poll_multiple$ = ctrls$(i%)
       Exit Do
     EndIf
   Next
 Loop While Timer < expires%
End Function

' Opens, polls (for a maximum of 5ms) and closes a controller.
'
' @param  ctrl$  controller driver function.
' @param  mask%  bit mask to match against.
' @return        1 if any of the bits in the mask match what is read from the
'                controller, otherwise 0.
Function ctrl.poll_single%(ctrl$, mask%)
 On Error Ignore
 Call ctrl$, ctrl.OPEN
 If Mm.ErrNo = 0 Then
   Local key%, t% = Timer + 5
   Do
     Call ctrl$, key%
     If key% And mask% Then
       ctrl.poll_single% = 1
       ' Wait for user to release key.
       Do While key% : Pause 5 : Call ctrl$, key% : Loop
       Exit Do
     EndIf
   Loop While Timer < t%
   Call ctrl$, ctrl.SOFT_CLOSE
 EndIf
 On Error Abort
End Function

' Reads the keyboard as if it were a controller.
'
' Note that the PicoMite has no KEYDOWN function so we are limited to
' reading a single keypress from the input buffer and cannot handle multiple
' simultaneous keys or properly handle a key being pressed and not released.
Sub keys_cursor(x%)
 If x% < 0 Then Exit Sub
 x% =    ctrl.keydown%(32)  * ctrl.A
 Inc x%, ctrl.keydown%(128) * ctrl.UP
 Inc x%, ctrl.keydown%(129) * ctrl.DOWN
 Inc x%, ctrl.keydown%(130) * ctrl.LEFT
 Inc x%, ctrl.keydown%(131) * ctrl.RIGHT
End Sub

' Atari joystick port on CMM2 Deluxe G2.
Sub atari_dx(x%)
 Select Case x%
   Case Is >= 0
     x% =    Not Pin(32) * ctrl.A
     Inc x%, Not Pin(33) * ctrl.B
     Inc x%, Not Pin(35) * ctrl.UP
     Inc x%, Not Pin(36) * ctrl.DOWN
     Inc x%, Not Pin(38) * ctrl.LEFT
     Inc x%, Not Pin(40) * ctrl.RIGHT
     Exit Sub
   Case ctrl.OPEN
     SetPin 32, Din, PullUp
     SetPin 33, Din, PullUp
     SetPin 35, Din, PullUp
     SetPin 36, Din, PullUp
     SetPin 38, Din, PullUp
     SetPin 40, Din, PullUp
 End Select
End Sub

' NES gamepad attached USING ADAPTER to Atari joystick port on CMM2 Deluxe G2.
'
' IMPORTANT! the adapter is required to swap the Male DB9 (CMM2) +5V supply on
' Pin 7 to Pin 6 on the Female DB9 (Gamepad).
'
'   Pin 38: Latch, Pin 40: Clock, Pin 36: Data
Sub nes_dx(x%)
 Select Case x%
   Case Is >= 0
     Pulse 38, ctrl.PULSE
     x% =    Not Pin(36) * ctrl.A      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.B      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.SELECT : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.START  : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.UP     : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.DOWN   : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.LEFT   : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.RIGHT  : Pulse 40, ctrl.PULSE
     Exit Sub
   Case ctrl.OPEN
     SetPin 38, Dout
     SetPin 40, Dout
     SetPin 36, Din
     Pin(38) = 0
     Pin(40) = 0
 End Select
End Sub

' SNES gamepad attached USING ADAPTER to Atari joystick port on CMM2 Deluxe G2.
'
' IMPORTANT! the adapter is required to swap the Male DB9 (CMM2) +5V supply on
' Pin 7 to Pin 6 on the Female DB9 (Gamepad).
'
'   Pin 38: Latch, Pin 40: Clock, Pin 36: Data
Sub snes_dx(x%)
 Select Case x%
   Case Is >= 0
     Pulse 38, ctrl.PULSE
     x% =    Not Pin(36) * ctrl.B      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.Y      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.SELECT : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.START  : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.UP     : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.DOWN   : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.LEFT   : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.RIGHT  : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.A      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.X      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.L      : Pulse 40, ctrl.PULSE
     Inc x%, Not Pin(36) * ctrl.R      : Pulse 40, ctrl.PULSE
     Exit Sub
   Case ctrl.OPEN
     nes_dx(ctrl.OPEN)
 End Select
End Sub

Sub wii_internal(i2c%, x%, type%)
 Static is_open%(3)

 If x% >= 0 Then
   Select Case is_open%(i2c%)
     Case &hA4200101
       x% = Classic(B, i2c%)
       If x% = &h7FFF Then x% = 0 ' Ignore this glitch.
     Case &hA4200000
       x% =    Nunchuk(Z,  i2c%) * ctrl.A
       Inc x%, Nunchuk(C,  i2c%) * ctrl.B
       Inc x%, (Nunchuk(JY, i2c%) > 170) * ctrl.UP
       Inc x%, (Nunchuk(JY, i2c%) < 90)  * ctrl.DOWN
       Inc x%, (Nunchuk(JX, i2c%) < 90)  * ctrl.LEFT
       Inc x%, (Nunchuk(JX, i2c%) > 170) * ctrl.RIGHT
   End Select
   Exit Sub
 EndIf

 Select Case x%
   Case ctrl.OPEN
     If is_open%(i2c%) Then Exit Sub
     Controller Nunchuk Open i2c%
     If Mm.ErrNo Then
       Error "Not connected"
       Exit Sub
     EndIf
     is_open%(i2c%) = Nunchuk(T, i2c%)
     Select Case is_open%(i2c%)
       Case &hA4200000
         If Not(type% And &h01) Then
           Controller Nunchuk Close i2c%
           is_open%(i2c%) = 0
           Error "Nunchuck controller not supported"
         EndIf
       Case &hA4200101
         Controller Nunchuk Close i2c%
         If Not(type% And &h10) Then
           is_open%(i2c%) = 0
           Error "Classic controller not supported"
         Else
           Controller Classic Open i2c%
         EndIf
       Case Else
         Controller Nunchuck Close i2c%
         is_open%(i2c%) = 0
         Error "Unrecognised controller"
     End Select
   Case ctrl.CLOSE
     Select Case is_open%(i2c%)
       Case &hA4200000
         Controller Nunchuk Close i2c%
       Case &hA4200101
         Controller Classic Close i2c%
     End Select
     is_open%(i2c%) = 0
   Case ctrl.SOFT_CLOSE
     ' Do nothing
 End Select
End Sub

' Wii Nunchuk OR Classic gamepad on I2C1.
Sub wii_any_1(x%)
 wii_internal(1, x%, &h11)
End Sub

' Wii Nunchuk OR Classic gamepad on I2C2.
Sub wii_any_2(x%)
 wii_internal(2, x%, &h11)
End Sub

' Wii Nunchuk OR Classic gamepad on I2C3.
Sub wii_any_3(x%)
 wii_internal(3, x%, &h11)
End Sub

' Wii Classic gamepad on I2C1.
Sub wii_classic_1(x%)
 wii_internal(1, x%, &h10)
End Sub

' Wii Classic gamepad on I2C2.
Sub wii_classic_2(x%)
 wii_internal(2, x%, &h10)
End Sub

' Wii Classic gamepad on I2C3.
Sub wii_classic_3(x%)
 wii_internal(3, x%, &h10)
End Sub

' Wii Nunchuk on I2C1.
Sub wii_nunchuk_1(x%)
 wii_internal(1, x%, &h01)
End Sub

' Wii Nunchuk on I2C2.
Sub wii_nunchuk_2(x%)
 wii_internal(2, x%, &h01)
End Sub

' Wii Nunchuk on I2C3.
Sub wii_nunchuk_3(x%)
 wii_internal(3, x%, &h01)
End Sub

' END:       #Include "ctrl.ipp" -----------------------------------------------
Mode 1
Cls

Option Break 4
On Key 3, on_break

Dim bits%
Dim driver$(3) = ("", "wii_nunchuk_1", "wii_nunchuk_2", "wii_nunchuk_3")
' Dim driver$(3) = ("", "wii_classic_1", "wii_classic_2", "wii_classic_3")
' Dim driver$(3) = ("", "wii_any_1", "wii_any_2", "wii_any_3")
Dim err$(3)
Dim i%
Dim out$(3)

Print "Nunchuck Controller Test"

For i% = 1 To 3
 On Error Ignore
 Call driver$(i%), ctrl.OPEN
 If Mm.ErrNo <> 0 Then err$(i%) = Mid$(Mm.ErrMsg$, InStr(Mm.ErrMsg$, ":") + 2)
 out$(i%) = err$(i%)
 On Error Abort
Next

Do
 Print @(0, 20) "I2C1: " + rpad$(out$(1), 40)
 Print "I2C2: " + rpad$(out$(2), 40)
 Print "I2C3: " + rpad$(out$(3), 40)

 For i% = 1 To 3
   If err$(i%) = "" Then
     Call driver$(i%), bits%
     out$(i%) = ctrl_bits_to_string$(bits%)
   EndIf
 Next
Loop

end_program()

Sub on_break()
 Option Break 3
 end_program()
End Sub

Sub end_program()
 For i% = 1 To 3
   Call driver$(i%), ctrl.CLOSE
 Next
 End
End SUb

' Gets a string representation of bits read from a controller.
'
' @param bits%  controller state returned by controller read function.
Function ctrl_bits_to_string$(bits%)
 Static BUTTONS$(14) = ("R","Start","Home","Select","L","Down","Right","Up","Left","ZR","X","A","Y","B","ZL")

 If bits% = 0 Then
   ctrl_bits_to_string$ = "No buttons down"
   Exit Function
 EndIf

 ctrl_bits_to_string$ = Str$(bits%) + " = "
 Local count%, i%, s$
 For i% = 0 To Bound(BUTTONS$(), 1)
   If bits% And 2^i% Then
     s$ = BUTTONS$(i%)
     If count% > 0 Then Cat ctrl_bits_to_string$, ", "
     Cat ctrl_bits_to_string$, s$
     Inc count%
   EndIf
 Next
End Function

' Gets a string padded to a given width with spaces to the right.
'
' @param s$  the string.
' @param w%  the width.
' @return    the padded string.
'            If Len(s$) > w% then returns the unpadded string.
Function rpad$(s$, w%)
 rpad$ = s$
 If Len(s$) < w% Then rpad$ = s$ + Space$(w% - Len(s$))
End Function


Thanks,

Tom
Edited 2022-11-18 03:58 by thwill
CMM2 Welcome Tape, Creaky old text adventures
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1120
Posted: 03:20am 18 Nov 2022
Copy link to clipboard 
Print this post



That was perfect Tom.

The Nunchuk controller finds the Nunchuk in port 1, 2 or 3 and reports "no buttons down" and fhen detects button A or B (Z or C) down, all directions up, down, right, left and and any combination of them and the buttons when operated at the same time.

Wii_classic finds the controller connected but reports "Nunchuck controller not supported"

Wii_any works exactly the same as the Nunchuk controller.

Not only that but it works with 1, 2 or 3 at the same time! (I have two working Nunchuks and a third with a faulty pot).

Bill
Keep safe. Live long and prosper.
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 2830
Posted: 10:20am 18 Nov 2022
Copy link to clipboard 
Print this post

  Turbo46 said  That was perfect Tom.


Thanks for your help Bill.

Best wishes,

Tom
CMM2 Welcome Tape, Creaky old text adventures
 
     Page 2 of 2    
Print this page


To reply to this topic, you need to log in.

© JAQ Software 2022