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: AustraliaPosts: 1593 |
@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 KingdomPosts: 3839 |
Hi Bill, 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. 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 I've made a note to do that and throw an ERROR if it isn't. 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 Game*Mite, CMM2 Welcome Tape, Creaky old text adventures |
||||
Turbo46 Guru Joined: 24/12/2017 Location: AustraliaPosts: 1593 |
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 KingdomPosts: 3839 |
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 Game*Mite, CMM2 Welcome Tape, Creaky old text adventures |
||||
Turbo46 Guru Joined: 24/12/2017 Location: AustraliaPosts: 1593 |
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 KingdomPosts: 3839 |
Thanks for your help Bill. Best wishes, Tom Game*Mite, CMM2 Welcome Tape, Creaky old text adventures |
||||
Page 2 of 2 |
Print this page |