├── LICENSE ├── img ├── bluepad32-arduino-logo.png ├── bluepad32-how-does-it-work.png └── bluepad32-how-does-it-work.xcf ├── .clang-format ├── src ├── Gamepad.cpp ├── Gamepad.h ├── GamepadProperties.h ├── constants.h ├── ControllerProperties.h ├── utility │ ├── wl_types.h │ ├── wl_definitions.h │ ├── debug.h │ ├── spi_drv.h │ ├── wifi_spi.h │ └── spi_drv.cpp ├── ControllerData.h ├── Bluepad32.h ├── Controller.cpp ├── Controller.h └── Bluepad32.cpp ├── library.properties ├── examples ├── Tools │ ├── CheckFirmwareVersion │ │ └── CheckFirmwareVersion.ino │ └── SerialNINAPassthrough │ │ └── SerialNINAPassthrough.ino ├── Gamepad_OLD_API │ └── Gamepad_OLD_API.ino └── Controller │ └── Controller.ino ├── CHANGELOG ├── keywords.txt └── README.adoc /LICENSE: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: Apache 2.0 or LGPL-2.1-or-later 2 | -------------------------------------------------------------------------------- /img/bluepad32-arduino-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoquesada/bluepad32-arduino/HEAD/img/bluepad32-arduino-logo.png -------------------------------------------------------------------------------- /img/bluepad32-how-does-it-work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoquesada/bluepad32-arduino/HEAD/img/bluepad32-how-does-it-work.png -------------------------------------------------------------------------------- /img/bluepad32-how-does-it-work.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoquesada/bluepad32-arduino/HEAD/img/bluepad32-how-does-it-work.xcf -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Defines the Chromium style for automatic reformatting. 2 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | BasedOnStyle: Google 4 | -------------------------------------------------------------------------------- /src/Gamepad.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2023, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | // Empty on purpose. 5 | // Instead, see ArduinoController.cpp -------------------------------------------------------------------------------- /src/Gamepad.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2023, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_GAMEPAD_H 5 | #define BP32_GAMEPAD_H 6 | 7 | // 8 | // Deprecated. Use Controller.h instead 9 | // 10 | 11 | #include "Controller.h" 12 | 13 | using Gamepad = Controller; 14 | using GamepadPtr = ControllerPtr; 15 | 16 | #endif // BP32_GAMEPAD_H 17 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Bluepad32 for NINA-W10 boards 2 | version=1.3.5 3 | author=Ricardo Quesada 4 | maintainer=Ricardo Quesada 5 | sentence=Enables gamepad support for NINA-based boards like the Nano RP2040 Connect, MKR WiFi 1010, MKR VIDOR 4000, UNO WiFi Rev.2 and Nano 33 IoT. 6 | paragraph=With this library you can use any Bluetooth gamepad like DualSense, DualShock4, Nintendo Switch, Android gamepads, Xbox One S, etc. Bluetooth mice are also supported. 7 | category=Communication 8 | url=http://github.com/ricardoquesada/bluepad32-arduino 9 | architectures=* 10 | includes=Bluepad32.h 11 | -------------------------------------------------------------------------------- /src/GamepadProperties.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2022, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_GAMEPAD_PROPERTIES_H 5 | #define BP32_GAMEPAD_PROPERTIES_H 6 | 7 | // 8 | // Deprecated. Use ControllerProperties.h instead 9 | // 10 | 11 | #include "ControllerProperties.h" 12 | 13 | enum { 14 | GAMEPAD_PROPERTY_FLAG_RUMBLE = CONTROLLER_PROPERTY_FLAG_RUMBLE, 15 | GAMEPAD_PROPERTY_FLAG_PLAYER_LEDS = CONTROLLER_PROPERTY_FLAG_PLAYER_LEDS, 16 | GAMEPAD_PROPERTY_FLAG_PLAYER_LIGHTBAR = 17 | CONTROLLER_PROPERTY_FLAG_PLAYER_LIGHTBAR, 18 | 19 | GAMEPAD_PROPERTY_FLAG_GAMEPAD = CONTROLLER_PROPERTY_FLAG_GAMEPAD, 20 | GAMEPAD_PROPERTY_FLAG_MOUSE = CONTROLLER_PROPERTY_FLAG_MOUSE, 21 | GAMEPAD_PROPERTY_FLAG_KEYBOARD = CONTROLLER_PROPERTY_FLAG_KEYBOARD, 22 | }; 23 | 24 | using GamepadProperties = ControllerProperties; 25 | using GamepadPropertiesPtr = ControllerPropertiesPtr; 26 | 27 | #endif // BP32_GAMEPAD_PROPERTIES_H 28 | -------------------------------------------------------------------------------- /examples/Tools/CheckFirmwareVersion/CheckFirmwareVersion.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This example checks if the firmware loaded on the NINA module 3 | * is updated. 4 | * 5 | * Supported on boards with NINA W10x like: 6 | * - Arduino MKR WiFi 1010, 7 | * - UNO WiFi Rev.2, 8 | * - Nano RP2040 Connect, 9 | * - Nano 33 IoT, 10 | * - etc. 11 | * 12 | * Created 17 October 2018 by Riccardo Rosario Rizzo 13 | * This code is in the public domain. 14 | */ 15 | #include 16 | 17 | void setup() { 18 | // Initialize serial 19 | Serial.begin(9600); 20 | while (!Serial) { 21 | ; // wait for serial port to connect. 22 | } 23 | 24 | // Print a welcome message 25 | Serial.println("Bluepdad32 firmware check."); 26 | Serial.println(); 27 | 28 | // Print firmware version on the module 29 | String fv = BP32.firmwareVersion(); 30 | String latestFv; 31 | Serial.print("Firmware version installed: "); 32 | Serial.println(fv); 33 | } 34 | 35 | void loop() { 36 | // do nothing 37 | } 38 | -------------------------------------------------------------------------------- /src/constants.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2022, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_CONSTANTS_H 5 | #define BP32_CONSTANTS_H 6 | 7 | enum { 8 | // Bluepad32 specific commands. 9 | BP32_GET_PROTOCOL_VERSION = 0x00, 10 | BP32_GET_GAMEPADS_DATA = 0x01, 11 | BP32_SET_GAMEPAD_PLAYERS_LED = 0x02, 12 | BP32_SET_GAMEPAD_COLOR_LED = 0x03, 13 | BP32_SET_GAMEPAD_RUMBLE = 0x04, 14 | BP32_FORGET_BLUETOOTH_KEYS = 0x05, 15 | BP32_GET_GAMEPAD_PROPERTIES = 0x06, 16 | BP32_ENABLE_BLUETOOTH_CONNECTIONS = 0x07, 17 | BP32_DISCONNECT_GAMEPAD = 0x08, 18 | BP32_GET_CONTROLLERS_DATA = 0x09, 19 | 20 | // From 0x10-0x7f are defined in wifi_spi.h 21 | }; 22 | 23 | // Possible answers when the request doesn't need an answer, like in "set_xxx" 24 | enum { 25 | BP32_RESPONSE_ERROR = 0, 26 | BP32_RESPONSE_OK = 1, 27 | }; 28 | 29 | enum { 30 | BP32_PROTOCOL_VERSION_HI = 0x01, 31 | BP32_PROTOCOL_VERSION_LO = 0x03, 32 | }; 33 | 34 | #define BP32_MAX_CONTROLLERS 4 35 | #define BP32_MAX_GAMEPADS BP32_MAX_CONTROLLERS 36 | 37 | #endif // BP32_CONSTANTS_H -------------------------------------------------------------------------------- /src/ControllerProperties.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2023, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_CONTROLLER_PROPERTIES_H 5 | #define BP32_CONTROLLER_PROPERTIES_H 6 | 7 | // Must match nina_controller_properties_t defined here: 8 | // https://github.com/ricardoquesada/bluepad32/blob/main/src/components/bluepad32/platform/uni_platform_nina.c 9 | 10 | enum { 11 | CONTROLLER_PROPERTY_FLAG_RUMBLE = 1UL << 0, 12 | CONTROLLER_PROPERTY_FLAG_PLAYER_LEDS = 1UL << 1, 13 | CONTROLLER_PROPERTY_FLAG_PLAYER_LIGHTBAR = 1UL << 2, 14 | 15 | CONTROLLER_PROPERTY_FLAG_GAMEPAD = 1UL << 13, 16 | CONTROLLER_PROPERTY_FLAG_MOUSE = 1UL << 14, 17 | CONTROLLER_PROPERTY_FLAG_KEYBOARD = 1UL << 15, 18 | }; 19 | 20 | struct ControllerProperties { 21 | uint8_t idx; // Device index 22 | uint8_t btaddr[6]; // BT Addr 23 | uint8_t type; // model: copy from nina_gamepad_t 24 | uint8_t subtype; // subtype. E.g: Wii Remote 2nd version 25 | uint16_t vendor_id; // VID 26 | uint16_t product_id; // PID 27 | uint16_t flags; // Features like Rumble, LEDs, etc. 28 | } __attribute__((packed)); 29 | 30 | typedef ControllerProperties* ControllerPropertiesPtr; 31 | 32 | #endif // BP32_CONTROLLER_PROPERTIES_H 33 | -------------------------------------------------------------------------------- /src/utility/wl_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | wl_types.h - Library for Arduino Wifi shield. 3 | Copyright (c) 2018 Arduino SA. All rights reserved. 4 | Copyright (c) 2011-2014 Arduino. All right reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | /* 21 | * wl_types.h 22 | * 23 | * Created on: Jul 30, 2010 24 | * Author: dlafauci 25 | */ 26 | 27 | #ifndef _WL_TYPES_H_ 28 | #define _WL_TYPES_H_ 29 | 30 | #include 31 | 32 | typedef enum { 33 | WL_FAILURE = -1, 34 | WL_SUCCESS = 1, 35 | } wl_error_code_t; 36 | 37 | /* Authentication modes */ 38 | enum wl_auth_mode { 39 | AUTH_MODE_INVALID, 40 | AUTH_MODE_AUTO, 41 | AUTH_MODE_OPEN_SYSTEM, 42 | AUTH_MODE_SHARED_KEY, 43 | AUTH_MODE_WPA, 44 | AUTH_MODE_WPA2, 45 | AUTH_MODE_WPA_PSK, 46 | AUTH_MODE_WPA2_PSK 47 | }; 48 | 49 | typedef enum { 50 | WL_PING_DEST_UNREACHABLE = -1, 51 | WL_PING_TIMEOUT = -2, 52 | WL_PING_UNKNOWN_HOST = -3, 53 | WL_PING_ERROR = -4 54 | } wl_ping_result_t; 55 | 56 | #endif //_WL_TYPES_H_ 57 | -------------------------------------------------------------------------------- /src/ControllerData.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 - 2023, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_CONTROLLER_DATA_H 5 | #define BP32_CONTROLLER_DATA_H 6 | 7 | #include 8 | 9 | // Must match nina_controller_t defined here: 10 | // https://github.com/ricardoquesada/bluepad32/blob/main/src/components/bluepad32/platform/uni_platform_nina.c 11 | 12 | enum { 13 | CONTROLLER_CLASS_NONE, 14 | CONTROLLER_CLASS_GAMEPAD, 15 | CONTROLLER_CLASS_MOUSE, 16 | CONTROLLER_CLASS_KEYBOARD, 17 | CONTROLLER_CLASS_BALANCE_BOARD, 18 | }; 19 | 20 | typedef struct __attribute__((packed)) { 21 | // Usage Page: 0x01 (Generic Desktop Controls) 22 | uint8_t dpad; 23 | int32_t axis_x; 24 | int32_t axis_y; 25 | int32_t axis_rx; 26 | int32_t axis_ry; 27 | 28 | // Usage Page: 0x02 (Sim controls) 29 | int32_t brake; 30 | int32_t throttle; 31 | 32 | // Usage Page: 0x09 (Button) 33 | uint16_t buttons; 34 | 35 | // Misc buttons (from 0x0c (Consumer) and others) 36 | uint8_t misc_buttons; 37 | 38 | int32_t gyro[3]; 39 | int32_t accel[3]; 40 | } nina_gamepad_t; 41 | 42 | typedef struct __attribute__((packed)) { 43 | int32_t delta_x; 44 | int32_t delta_y; 45 | uint8_t buttons; 46 | uint8_t misc_buttons; 47 | int8_t scroll_wheel; 48 | } nina_mouse_t; 49 | 50 | typedef struct __attribute__((packed)) { 51 | uint16_t tr; // Top right 52 | uint16_t br; // Bottom right 53 | uint16_t tl; // Top left 54 | uint16_t bl; // Bottom left 55 | int temperature; // Temperature 56 | } nina_balance_board_t; 57 | 58 | struct ControllerData { 59 | int8_t idx; 60 | // Class of controller: gamepad, mouse, balance, etc. 61 | uint8_t klass; 62 | union { 63 | nina_gamepad_t gamepad; 64 | nina_mouse_t mouse; 65 | nina_balance_board_t balance_board; 66 | }; 67 | uint8_t battery; 68 | } __attribute__((packed)); 69 | 70 | typedef ControllerData* ControllerDataPtr; 71 | 72 | #endif // BP32_CONTROLLER_DATA_H 73 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Bluepad32 1.3.3 - 2024.02.08 2 | * Back to original name "Bluepad32", otherwise I will have to change the entry in the Arduino repository. 3 | 4 | Bluepad32 1.3.2 - 2024.02.08 5 | * Renamed to "Bluepad32 for NINA" to avoid confusion 6 | 7 | Bluepad32 1.3.1 - 2023.04.30 8 | * Add API to read Gyro/Accel 9 | * Update example to show how to do it 10 | 11 | Bluepad32 1.3.0 - 2023.04.16 12 | 13 | * Add "BP32.localBdAddress". Returns BD Address 14 | * Requires Bluepad32 Firmware v3.7.0-rc1 or newer 15 | 16 | Bluepad32 1.2.1 - 2023.01.29 17 | 18 | * Scroll Wheel support for Mouse 19 | * Requires Bluepad32 Firmware v3.6.0-rc1 or newer 20 | 21 | Bluepad32 1.2.0 - 2023.01.16 22 | 23 | * New Controller API that supports gamepads, mice and balance boards 24 | * Battery status is supported 25 | * Requires Bluepad32 Firmware v3.6.0-rc0 or newer 26 | 27 | Bluepad32 1.1.5 - 2023.01.03 28 | 29 | * Gamepad.getProperties() return correct info 30 | 31 | Bluepad32 1.1.4 - 2022.10.16 32 | 33 | * Add Gamepad.disconnect() 34 | Requires Bluepad32 v.3.5.2 or newer 35 | 36 | Bluepad32 1.1.3 - 2022.07.04 37 | 38 | * Update description in BP32.enableNewBluetoothConnections(); 39 | 40 | Bluepad32 1.1.2 - 2022.06.18 41 | 42 | * Add BP32.enableNewBluetoothConnections() 43 | Requires Bluepad32 v3.5-beta1 or newer 44 | * Add additional GAMEPAD_PROPERTY_FLAG_* in GamepadProperties.h: 45 | Useful to identify whether the controller is a gamepad, mouse or keyboard. 46 | 47 | Bluepad32 1.1.1 - 2022.04.18 48 | 49 | * Fix compilation warnings 50 | 51 | Bluepad32 1.1.0 - 2022.04.17 52 | 53 | * Add Gamepad.getProperties() function 54 | Returns btaddress, vid/pid, and other useful properties 55 | 56 | Bluepad32 1.0.0 - 2022.03.21 57 | 58 | * Example: Supports multiple connected gamepads 59 | * SPI Driver: Correctly parses variable-length responses 60 | 61 | Bluepad32 0.99.4 - 2022.02.28 62 | 63 | * Update library properties, CHANGELOG 64 | 65 | Bluepad32 0.99.3 - 2022.02.27 66 | 67 | * Example includes call to forgetBluetoothKeys() 68 | * Remove BLUEPAD_LATEST_FIRMWARE_VERSION since it was outdated 69 | 70 | Bluepad32 0.99.2 - 2022.01.07 71 | 72 | * Compiles Ok in Arduino UNO WiFi Rev.2 73 | 74 | Bluepad32 0.99.0 - 2021.07.17 75 | 76 | * Initial public release 77 | * Supports: 78 | * fetch all gamepad buttons/axis 79 | * allows setting rumble 80 | * allows setting players LED 81 | * allows setting color LED 82 | * Supports the following NINA commands 83 | * check firmware version 84 | * diginal write 85 | * digital read 86 | * pin mode 87 | * set debug mode 88 | -------------------------------------------------------------------------------- /src/utility/wl_definitions.h: -------------------------------------------------------------------------------- 1 | /* 2 | wl_definitions.h - Library for Arduino Wifi shield. 3 | Copyright (c) 2018 Arduino SA. All rights reserved. 4 | Copyright (c) 2011-2014 Arduino. All right reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | /* 21 | * wl_definitions.h 22 | * 23 | * Created on: Mar 6, 2011 24 | * Author: dlafauci 25 | */ 26 | 27 | #ifndef WL_DEFINITIONS_H_ 28 | #define WL_DEFINITIONS_H_ 29 | 30 | // Maximum size of a SSID 31 | #define WL_SSID_MAX_LENGTH 32 32 | // Length of passphrase. Valid lengths are 8-63. 33 | #define WL_WPA_KEY_MAX_LENGTH 63 34 | // Length of key in bytes. Valid values are 5 and 13. 35 | #define WL_WEP_KEY_MAX_LENGTH 13 36 | // Size of a MAC-address or BSSID 37 | #define WL_MAC_ADDR_LENGTH 6 38 | // Size of a MAC-address or BSSID 39 | #define WL_IPV4_LENGTH 4 40 | // Maximum size of a SSID list 41 | #define WL_NETWORKS_LIST_MAXNUM 10 42 | // Maxmium number of socket 43 | #define WIFI_MAX_SOCK_NUM 10 44 | // Socket not available constant 45 | #define SOCK_NOT_AVAIL 255 46 | // Default state value for WiFi state field 47 | #define NA_STATE -1 48 | 49 | typedef enum { 50 | WL_NO_SHIELD = 255, 51 | WL_NO_MODULE = WL_NO_SHIELD, 52 | WL_IDLE_STATUS = 0, 53 | WL_NO_SSID_AVAIL, 54 | WL_SCAN_COMPLETED, 55 | WL_CONNECTED, 56 | WL_CONNECT_FAILED, 57 | WL_CONNECTION_LOST, 58 | WL_DISCONNECTED, 59 | WL_AP_LISTENING, 60 | WL_AP_CONNECTED, 61 | WL_AP_FAILED 62 | } wl_status_t; 63 | 64 | /* Encryption modes */ 65 | enum wl_enc_type { /* Values map to 802.11 encryption suites... */ 66 | ENC_TYPE_WEP = 5, 67 | ENC_TYPE_TKIP = 2, 68 | ENC_TYPE_CCMP = 4, 69 | /* ... except these two, 7 and 8 are reserved in 802.11-2007 70 | */ 71 | ENC_TYPE_NONE = 7, 72 | ENC_TYPE_AUTO = 8, 73 | 74 | ENC_TYPE_UNKNOWN = 255 75 | }; 76 | 77 | #endif /* WL_DEFINITIONS_H_ */ 78 | -------------------------------------------------------------------------------- /src/Bluepad32.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2022, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache 2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_BLUEPAD32_H 5 | #define BP32_BLUEPAD32_H 6 | 7 | #include 8 | 9 | #include "Controller.h" 10 | #include "ControllerData.h" 11 | #include "ControllerProperties.h" 12 | #include "Gamepad.h" 13 | #include "GamepadProperties.h" 14 | #include "constants.h" 15 | 16 | // Using C callbacks since AVR-GCC (needed for Arduino UNO WiFi) doesn't support 17 | // STL 18 | typedef void (*ControllerCallback)(ControllerPtr controller); 19 | using GamepadCallback = ControllerCallback; 20 | 21 | class Bluepad32 { 22 | // This is what the user receives 23 | Controller _controllers[BP32_MAX_CONTROLLERS]; 24 | 25 | // This is used internally by SPI, and then copied into the Controller::State 26 | // of each controller 27 | int _prevConnectedControllers; 28 | 29 | ControllerCallback _onConnect; 30 | ControllerCallback _onDisconnect; 31 | 32 | public: 33 | Bluepad32(); 34 | /* 35 | * Get the firmware version 36 | * result: version as string with this format a.b.c 37 | */ 38 | const char* firmwareVersion(); 39 | void setDebug(uint8_t on); 40 | 41 | // GPIOs: This is from NINA fw, and might be needed on some boards. 42 | // E.g.: Arduino Nano RP2040 Connect requires them to turn on/off the RGB LED, 43 | // that is controlled from the ESP32. 44 | void pinMode(uint8_t pin, uint8_t mode); 45 | int digitalRead(uint8_t pin); 46 | void digitalWrite(uint8_t pin, uint8_t value); 47 | 48 | // Controller 49 | void update(); 50 | 51 | // When a controller connects to the ESP32, the ESP32 stores keys to make it 52 | // easier the reconnection. 53 | // If you want to "forget" (delete) the keys from ESP32, you should call this 54 | // function. 55 | void forgetBluetoothKeys(); 56 | 57 | // Enable / Disable new Bluetooth connections. 58 | // When enabled, the device is put in Discovery mode, and new pairs are 59 | // accepted. When disabled, only devices that have paired before can connect. 60 | // Established connections are not affected. 61 | void enableNewBluetoothConnections(bool enabled); 62 | 63 | // 64 | // Get the local Bluetooth Address. 65 | // return: pointer to uint8_t array with length 6. 66 | // 67 | const uint8_t* localBdAddress(); 68 | 69 | void setup(const ControllerCallback& onConnect, 70 | const ControllerCallback& onDisconnect); 71 | 72 | private: 73 | void checkProtocol(); 74 | 75 | uint8_t _protocolVersionHi; 76 | uint8_t _protocolVersionLow; 77 | 78 | friend class Controller; 79 | }; 80 | 81 | extern Bluepad32 BP32; 82 | 83 | #endif // BP32_BLUEPAD32_H 84 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For WiFiNINA 3 | ####################################### 4 | 5 | ####################################### 6 | # Library (KEYWORD1) 7 | ####################################### 8 | 9 | 10 | Controller KEYWORD1 11 | Gamepad KEYWORD1 12 | Bluepad32 KEYWORD1 13 | BP32 KEYWORD1 14 | 15 | 16 | ####################################### 17 | # Methods and Functions (KEYWORD2) 18 | ####################################### 19 | 20 | # Bluepad32 21 | firmwareVersion KEYWORD2 22 | setDebug KEYWORD2 23 | pinMode KEYWORD2 24 | digitalRead KEYWORD2 25 | digitalWrite KEYWORD2 26 | 27 | # Gamepad 28 | update KEYWORD2 29 | forgetBluetoothKeys KEYWORD2 30 | setup KEYWORD2 31 | a KEYWORD2 32 | b KEYWORD2 33 | x KEYWORD2 34 | y KEYWORD2 35 | l1 KEYWORD2 36 | l2 KEYWORD2 37 | r1 KEYWORD2 38 | r2 KEYWORD2 39 | thumbL KEYWORD2 40 | thumbR KEYWORD2 41 | buttons KEYWORD2 42 | miscButtons KEYWORD2 43 | axisX KEYWORD2 44 | axisY KEYWORD2 45 | axisRX KEYWORD2 46 | axisRY KEYWORD2 47 | brake KEYWORD2 48 | throttle KEYWORD2 49 | miscSystem KEYWORD2 50 | miscBack KEYWORD2 51 | miscHome KEYWORD2 52 | isConnected KEYWORD2 53 | getModel KEYWORD2 54 | setPlayerLEDs KEYWORD2 55 | setColorLED KEYWORD2 56 | setRumble KEYWORD2 57 | 58 | 59 | ####################################### 60 | # Constants (LITERAL1) 61 | ####################################### 62 | 63 | # Controllers 64 | CONTROLLER_TYPE_UnknownSteamController LITERAL1 65 | CONTROLLER_TYPE_SteamController LITERAL1 66 | CONTROLLER_TYPE_SteamControllerV2 LITERAL1 67 | CONTROLLER_TYPE_UnknownNonSteamController LITERAL1 68 | CONTROLLER_TYPE_XBox360Controller LITERAL1 69 | CONTROLLER_TYPE_XBoxOneController LITERAL1 70 | CONTROLLER_TYPE_PS3Controller LITERAL1 71 | CONTROLLER_TYPE_PS4Controller LITERAL1 72 | CONTROLLER_TYPE_WiiController LITERAL1 73 | CONTROLLER_TYPE_AppleController LITERAL1 74 | CONTROLLER_TYPE_AndroidController LITERAL1 75 | CONTROLLER_TYPE_SwitchProController LITERAL1 76 | CONTROLLER_TYPE_SwitchJoyConLeft LITERAL1 77 | CONTROLLER_TYPE_SwitchJoyConRight LITERAL1 78 | CONTROLLER_TYPE_SwitchJoyConPair LITERAL1 79 | CONTROLLER_TYPE_SwitchInputOnlyController LITERAL1 80 | CONTROLLER_TYPE_MobileTouch LITERAL1 81 | CONTROLLER_TYPE_XInputSwitchController LITERAL1 82 | CONTROLLER_TYPE_PS5Controller LITERAL1 83 | CONTROLLER_TYPE_iCadeController LITERAL1 84 | CONTROLLER_TYPE_SmartTVRemoteController LITERAL1 85 | CONTROLLER_TYPE_EightBitdoController LITERAL1 86 | CONTROLLER_TYPE_GenericController LITERAL1 87 | CONTROLLER_TYPE_NimbusController LITERAL1 88 | CONTROLLER_TYPE_OUYAController LITERAL1 89 | 90 | # DPAD 91 | DPAD_UP LITERAL1 92 | DPAD_DOWN LITERAL1 93 | DPAD_RIGHT LITERAL1 94 | DPAD_LEFT LITERAL1 95 | 96 | # Buttons 97 | BUTTON_A LITERAL1 98 | BUTTON_B LITERAL1 99 | BUTTON_X LITERAL1 100 | BUTTON_Y LITERAL1 101 | BUTTON_SHOULDER_L LITERAL1 102 | BUTTON_SHOULDER_R LITERAL1 103 | BUTTON_TRIGGER_L LITERAL1 104 | BUTTON_TRIGGER_R LITERAL1 105 | BUTTON_THUMB_L LITERAL1 106 | BUTTON_THUMB_R LITERAL1 107 | 108 | # Misc buttons 109 | MISC_BUTTON_SYSTEM LITERAL1 110 | MISC_BUTTON_BACK LITERAL1 111 | MISC_BUTTON_HOME LITERAL1 112 | -------------------------------------------------------------------------------- /examples/Tools/SerialNINAPassthrough/SerialNINAPassthrough.ino: -------------------------------------------------------------------------------- 1 | /* 2 | SerialNINAPassthrough - Use esptool to flash the u-blox NINA (ESP32) module 3 | Arduino MKR WiFi 1010, Arduino MKR Vidor 4000, and Arduino UNO WiFi Rev.2. 4 | 5 | Copyright (c) 2018 Arduino SA. All rights reserved. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 23 | #include 24 | 25 | unsigned long baud = 119400; 26 | #else 27 | unsigned long baud = 115200; 28 | #endif 29 | 30 | int rts = -1; 31 | int dtr = -1; 32 | 33 | void setup() { 34 | Serial.begin(baud); 35 | 36 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 37 | FPGA.begin(); 38 | #endif 39 | 40 | SerialNina.begin(baud); 41 | 42 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 43 | FPGA.pinMode(FPGA_NINA_GPIO0, OUTPUT); 44 | FPGA.pinMode(FPGA_SPIWIFI_RESET, OUTPUT); 45 | #else 46 | pinMode(NINA_GPIO0, OUTPUT); 47 | pinMode(NINA_RESETN, OUTPUT); 48 | #endif 49 | 50 | #ifdef ARDUINO_AVR_UNO_WIFI_REV2 51 | // manually put the NINA in upload mode 52 | digitalWrite(NINA_GPIO0, LOW); 53 | 54 | digitalWrite(NINA_RESETN, LOW); 55 | delay(100); 56 | digitalWrite(NINA_RESETN, HIGH); 57 | delay(100); 58 | digitalWrite(NINA_RESETN, LOW); 59 | #endif 60 | } 61 | 62 | void loop() { 63 | #ifndef ARDUINO_AVR_UNO_WIFI_REV2 64 | if (rts != Serial.rts()) { 65 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 66 | FPGA.digitalWrite(FPGA_SPIWIFI_RESET, (Serial.rts() == 1) ? LOW : HIGH); 67 | #elif defined(ARDUINO_SAMD_NANO_33_IOT) 68 | digitalWrite(NINA_RESETN, Serial.rts() ? LOW : HIGH); 69 | #else 70 | digitalWrite(NINA_RESETN, Serial.rts()); 71 | #endif 72 | rts = Serial.rts(); 73 | } 74 | 75 | if (dtr != Serial.dtr()) { 76 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 77 | FPGA.digitalWrite(FPGA_NINA_GPIO0, (Serial.dtr() == 1) ? HIGH : LOW); 78 | #else 79 | digitalWrite(NINA_GPIO0, (Serial.dtr() == 0) ? HIGH : LOW); 80 | #endif 81 | dtr = Serial.dtr(); 82 | } 83 | #endif 84 | 85 | if (Serial.available()) { 86 | SerialNina.write(Serial.read()); 87 | } 88 | 89 | if (SerialNina.available()) { 90 | Serial.write(SerialNina.read()); 91 | } 92 | 93 | #ifndef ARDUINO_AVR_UNO_WIFI_REV2 94 | // check if the USB virtual serial wants a new baud rate 95 | if (Serial.baud() != baud) { 96 | rts = -1; 97 | dtr = -1; 98 | 99 | baud = Serial.baud(); 100 | #ifndef ARDUINO_SAMD_MKRVIDOR4000 101 | SerialNina.begin(baud); 102 | #endif 103 | } 104 | #endif 105 | } 106 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | // Define the repository information in these attributes 2 | :repository-owner: ricardoquesasda 3 | :repository-name: bluepad32-arduino 4 | :project-name: Bluepad32 5 | 6 | = {project-name} library for Arduino NINA-W10 boards = 7 | 8 | image::https://img.shields.io/discord/775177861665521725.svg[link=https://discord.gg/r5aMn6Cw5q] 9 | 10 | image::img/bluepad32-arduino-logo.png[logo] 11 | 12 | Enables gamepad support for all NINA-W10 based boards. In particular these boards: 13 | 14 | * https://store.arduino.cc/usa/nano-rp2040-connect-with-headers[Arduino Nano RP2040 Connect] 15 | * https://store.arduino.cc/usa/nano-33-iot[Arduino Nano 33 IoT] 16 | * https://store.arduino.cc/usa/mkr-wifi-1010[Arduino MKR WiFi 1010] 17 | * http://store.arduino.cc/products/arduino-uno-wifi-rev2[Arduino UNO WiFi Rev2] 18 | * https://store.arduino.cc/products/arduino-mkr-vidor-4000[Arduino Arduino MKR Vidor 4000] 19 | 20 | If you are looking for Arduino running on ESP32 boards, check out this other project: 21 | 22 | * https://bluepad32.readthedocs.io/en/latest/plat_arduino/[Arduino + Bluepad32 on ESP32 boards] 23 | 24 | == Supported gamepads == 25 | 26 | image::https://lh3.googleusercontent.com/pw/AM-JKLXpmyDvNXZ_LmlmBSYObRZDhwuY6hHXXBzAicFw1YH1QNSgZrpiPWXZMiPNM0ATgrockqGf5bLsI3fWceJtQQEj2_OroHs1SrxsgmS8Rh4XHlnFolchomsTPVC7o5zi4pXGQkhGEFbinoh3-ub_a4lQIw=-no[gamepads] 27 | 28 | With this library you can use any modern Bluetooth gamepad in Arduino. Some of the supported gamepads, but not limited to, are: 29 | 30 | * Sony family: DualSense (PS5), DualShock 4 (PS4), DualShock 3 (PS3) 31 | * Nintendo family: Switch gamepads, Switch JoyCons, Wii, Wii U 32 | * Xbox Wireless controller 33 | * 8BitDo gamepads 34 | * Android gamepads 35 | * Stadia controller 36 | * Windows gamepads 37 | * and more 38 | 39 | For a complete list, see: https://bluepad32.readthedocs.io/en/latest/supported_gamepads/ 40 | 41 | == How does it work == 42 | 43 | As mentioned above, only boards with the NINA-W10 (ESP32) co-processor are supported. 44 | This is because the project consists of two parts: 45 | 46 | * "Bluepad32 library for Arduino", runs on the main processor: "C" 47 | * "Bluepad32 firmware", runs on the NINA-W10 co-processor: "B" 48 | 49 | image::img/bluepad32-how-does-it-work.png[how does it work] 50 | 51 | 52 | The gamepads (A), using Bluetooth, connect to the NINA-W10 co-processor (B). 53 | 54 | And NINA-W10 (B) sends the gamepad data to the main processor \(C). In this case the 55 | main processor is the RP2040, but it could be different on other boards. As an example, 56 | on the Nano 33 IoT, the main processor is the SAMD 21. 57 | 58 | So, in order to use the library you have to flash the "Bluepad32 firmware" on NINA-W10. 59 | This is a simple step that needs to be done just once, and can be undone at any time. 60 | Info about Bluepad32 firmware is avaiable here: 61 | 62 | * Bluepad32 firmware doc: https://bluepad32.readthedocs.io/en/latest/plat_nina/ 63 | * Download: https://github.com/ricardoquesada/bluepad32/releases 64 | 65 | 66 | == Use cases == 67 | 68 | You can use this library to: 69 | 70 | * Create a video game 71 | * Or control a car / robot 72 | * Or control LEDs, motor, etc. 73 | * Or to draw a curtain, open a window, control a fan, etc. 74 | * etc. 75 | 76 | == License == 77 | 78 | Copyright (c) 2021 Ricardo Quesada 79 | 80 | SPDX-License-Identifier: Apache 2.0 or LGPL-2.1-or-later 81 | 82 | -------------------------------------------------------------------------------- /src/utility/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | debug.h - Library for Arduino Wifi shield. 3 | Copyright (c) 2011-2014 Arduino. All right reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | //*********************************************/ 20 | // 21 | // File: debug.h 22 | // 23 | // Author: dlf (Metodo2 srl) 24 | // 25 | //********************************************/ 26 | 27 | #ifndef Debug_H 28 | #define Debug_H 29 | 30 | #include 31 | #include 32 | 33 | #define PRINT_FILE_LINE() \ 34 | do { \ 35 | Serial.print("["); \ 36 | Serial.print(__FILE__); \ 37 | Serial.print("::"); \ 38 | Serial.print(__LINE__); \ 39 | Serial.print("]"); \ 40 | } while (0); 41 | 42 | #ifdef _DEBUG_ 43 | 44 | #define INFO(format, args...) \ 45 | do { \ 46 | char buf[250]; \ 47 | sprintf(buf, format, args); \ 48 | Serial.println(buf); \ 49 | } while (0); 50 | 51 | #define INFO1(x) \ 52 | do { \ 53 | PRINT_FILE_LINE() Serial.print("-I-"); \ 54 | Serial.println(x); \ 55 | } while (0); 56 | 57 | #define INFO2(x, y) \ 58 | do { \ 59 | PRINT_FILE_LINE() Serial.print("-I-"); \ 60 | Serial.print(x, 16); \ 61 | Serial.print(","); \ 62 | Serial.println(y, 16); \ 63 | } while (0); 64 | 65 | #else 66 | #define INFO1(x) \ 67 | do { \ 68 | } while (0); 69 | #define INFO2(x, y) \ 70 | do { \ 71 | } while (0); 72 | #define INFO(format, args...) \ 73 | do { \ 74 | } while (0); 75 | #endif 76 | 77 | #if 0 78 | #define WARN(args) \ 79 | do { \ 80 | PRINT_FILE_LINE() \ 81 | Serial.print("-W-"); \ 82 | Serial.println(args); \ 83 | } while (0); 84 | #else 85 | #define WARN(args) \ 86 | do { \ 87 | } while (0); 88 | #endif 89 | 90 | #if _DEBUG_SPI_ 91 | #define DBG_PIN2 5 92 | #define DBG_PIN 4 93 | 94 | #define START() digitalWrite(DBG_PIN2, HIGH); 95 | #define END() digitalWrite(DBG_PIN2, LOW); 96 | #define SET_TRIGGER() digitalWrite(DBG_PIN, HIGH); 97 | #define RST_TRIGGER() digitalWrite(DBG_PIN, LOW); 98 | 99 | #define INIT_TRIGGER() \ 100 | pinMode(DBG_PIN, OUTPUT); \ 101 | pinMode(DBG_PIN2, OUTPUT); \ 102 | RST_TRIGGER() 103 | #define TOGGLE_TRIGGER() \ 104 | SET_TRIGGER() \ 105 | delayMicroseconds(2); \ 106 | RST_TRIGGER() 107 | #else 108 | #define START() 109 | #define END() 110 | #define SET_TRIGGER() 111 | #define RST_TRIGGER() 112 | #define INIT_TRIGGER() 113 | #define TOGGLE_TRIGGER() 114 | #endif 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /src/utility/spi_drv.h: -------------------------------------------------------------------------------- 1 | /* 2 | spi_drv.h - Library for Arduino Wifi shield. 3 | Copyright (c) 2018 Arduino SA. All rights reserved. 4 | Copyright (c) 2011-2014 Arduino. All right reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #ifndef SPI_Drv_h 22 | #define SPI_Drv_h 23 | 24 | #include 25 | #include 26 | 27 | #include "utility/wifi_spi.h" 28 | 29 | #define SPI_START_CMD_DELAY 10 30 | 31 | #define NO_LAST_PARAM 0 32 | #define LAST_PARAM 1 33 | 34 | #define DUMMY_DATA 0xFF 35 | 36 | #define WAIT_FOR_SLAVE_SELECT() \ 37 | if (!SpiDrv::initialized) { \ 38 | SpiDrv::begin(); \ 39 | } \ 40 | SpiDrv::waitForSlaveReady(); \ 41 | SpiDrv::spiSlaveSelect(); 42 | 43 | class SpiDrv { 44 | private: 45 | // static bool waitSlaveReady(); 46 | static void waitForSlaveSign(); 47 | static void getParam(uint8_t* param); 48 | 49 | public: 50 | static bool initialized; 51 | 52 | static void begin(); 53 | 54 | static void end(); 55 | 56 | static void spiDriverInit(); 57 | 58 | static void spiSlaveSelect(); 59 | 60 | static void spiSlaveDeselect(); 61 | 62 | static char spiTransfer(volatile char data); 63 | 64 | static void waitForSlaveReady(bool const feed_watchdog = false); 65 | 66 | // static int waitSpiChar(char waitChar, char* readChar); 67 | 68 | static int waitSpiChar(unsigned char waitChar); 69 | 70 | static int readAndCheckChar(char checkChar, char* readChar); 71 | 72 | static char readChar(); 73 | 74 | static int waitResponseParams(uint8_t cmd, uint8_t numParam, tParam* params); 75 | 76 | static int waitResponseCmd(uint8_t cmd, uint8_t numParam, uint8_t* param, 77 | uint8_t* param_len); 78 | 79 | static int waitResponseData8(uint8_t cmd, uint8_t* param, uint8_t* param_len); 80 | 81 | static int waitResponseData16(uint8_t cmd, uint8_t* param, 82 | uint16_t* param_len); 83 | /* 84 | static int waitResponse(uint8_t cmd, tParam* params, uint8_t* numParamRead, 85 | uint8_t maxNumParams); 86 | 87 | static int waitResponse(uint8_t cmd, uint8_t numParam, uint8_t* param, 88 | uint16_t* param_len); 89 | */ 90 | static int waitResponse(uint8_t cmd, uint8_t* numParamRead, uint8_t** params, 91 | uint8_t maxNumParams); 92 | 93 | static void sendParam(uint8_t* param, uint8_t param_len, 94 | uint8_t lastParam = NO_LAST_PARAM); 95 | 96 | static void sendParamNoLen(uint8_t* param, size_t param_len, 97 | uint8_t lastParam = NO_LAST_PARAM); 98 | 99 | static void sendParamLen8(uint8_t param_len); 100 | 101 | static void sendParamLen16(uint16_t param_len); 102 | 103 | static uint8_t readParamLen8(uint8_t* param_len = NULL); 104 | 105 | static uint16_t readParamLen16(uint16_t* param_len = NULL); 106 | 107 | static void sendBuffer(uint8_t* param, uint16_t param_len, 108 | uint8_t lastParam = NO_LAST_PARAM); 109 | 110 | static void sendParam(uint16_t param, uint8_t lastParam = NO_LAST_PARAM); 111 | 112 | static void sendCmd(uint8_t cmd, uint8_t numParam); 113 | 114 | static int available(); 115 | }; 116 | 117 | extern SpiDrv spiDrv; 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /src/utility/wifi_spi.h: -------------------------------------------------------------------------------- 1 | /* 2 | wifi_spi.h - Library for Arduino Wifi shield. 3 | Copyright (c) 2018 Arduino SA. All rights reserved. 4 | Copyright (c) 2011-2014 Arduino. All right reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #ifndef WiFi_Spi_h 22 | #define WiFi_Spi_h 23 | 24 | #include 25 | 26 | #include "utility/wl_definitions.h" 27 | 28 | #define CMD_FLAG 0 29 | #define REPLY_FLAG 1 << 7 30 | #define DATA_FLAG 0x40 31 | 32 | #define WIFI_SPI_ACK 1 33 | #define WIFI_SPI_ERR 0xFF 34 | 35 | #define TIMEOUT_CHAR 1000 36 | 37 | //#define MAX_SOCK_NUM 4 /**< Maxmium number of socket */ 38 | #define NO_SOCKET_AVAIL 255 39 | 40 | #define START_CMD 0xE0 41 | #define END_CMD 0xEE 42 | #define ERR_CMD 0xEF 43 | #define CMD_POS 1 // Position of Command OpCode on SPI stream 44 | #define PARAM_LEN_POS 2 // Position of Param len on SPI stream 45 | 46 | enum { 47 | SET_NET_CMD = 0x10, 48 | SET_PASSPHRASE_CMD = 0x11, 49 | SET_KEY_CMD = 0x12, 50 | // TEST_CMD = 0x13, 51 | SET_IP_CONFIG_CMD = 0x14, 52 | SET_DNS_CONFIG_CMD = 0x15, 53 | SET_HOSTNAME_CMD = 0x16, 54 | SET_POWER_MODE_CMD = 0x17, 55 | SET_AP_NET_CMD = 0x18, 56 | SET_AP_PASSPHRASE_CMD = 0x19, 57 | SET_DEBUG_CMD = 0x1A, 58 | GET_TEMPERATURE_CMD = 0x1B, 59 | GET_REASON_CODE_CMD = 0x1F, 60 | 61 | GET_CONN_STATUS_CMD = 0x20, 62 | GET_IPADDR_CMD = 0x21, 63 | GET_MACADDR_CMD = 0x22, 64 | GET_CURR_SSID_CMD = 0x23, 65 | GET_CURR_BSSID_CMD = 0x24, 66 | GET_CURR_RSSI_CMD = 0x25, 67 | GET_CURR_ENCT_CMD = 0x26, 68 | SCAN_NETWORKS = 0x27, 69 | START_SERVER_TCP_CMD = 0x28, 70 | GET_STATE_TCP_CMD = 0x29, 71 | DATA_SENT_TCP_CMD = 0x2A, 72 | AVAIL_DATA_TCP_CMD = 0x2B, 73 | GET_DATA_TCP_CMD = 0x2C, 74 | START_CLIENT_TCP_CMD = 0x2D, 75 | STOP_CLIENT_TCP_CMD = 0x2E, 76 | GET_CLIENT_STATE_TCP_CMD = 0x2F, 77 | DISCONNECT_CMD = 0x30, 78 | // GET_IDX_SSID_CMD = 0x31, 79 | GET_IDX_RSSI_CMD = 0x32, 80 | GET_IDX_ENCT_CMD = 0x33, 81 | REQ_HOST_BY_NAME_CMD = 0x34, 82 | GET_HOST_BY_NAME_CMD = 0x35, 83 | START_SCAN_NETWORKS = 0x36, 84 | GET_FW_VERSION_CMD = 0x37, 85 | // GET_TEST_CMD = 0x38, 86 | SEND_DATA_UDP_CMD = 0x39, 87 | GET_REMOTE_DATA_CMD = 0x3A, 88 | GET_TIME_CMD = 0x3B, 89 | GET_IDX_BSSID = 0x3C, 90 | GET_IDX_CHANNEL_CMD = 0x3D, 91 | PING_CMD = 0x3E, 92 | GET_SOCKET_CMD = 0x3F, 93 | 94 | // All command with DATA_FLAG 0x40 send a 16bit Len 95 | SET_ENT_CMD = 0x40, 96 | 97 | SEND_DATA_TCP_CMD = 0x44, 98 | GET_DATABUF_TCP_CMD = 0x45, 99 | INSERT_DATABUF_CMD = 0x46, 100 | 101 | // regular format commands 102 | SET_PIN_MODE = 0x50, 103 | SET_DIGITAL_WRITE = 0x51, 104 | SET_ANALOG_WRITE = 0x52, 105 | GET_DIGITAL_READ = 0x53, 106 | GET_ANALOG_READ = 0x54, 107 | 108 | // regular format commands 109 | WRITE_FILE = 0x60, 110 | READ_FILE = 0x61, 111 | DELETE_FILE = 0x62, 112 | EXISTS_FILE = 0x63, 113 | DOWNLOAD_FILE = 0x64, 114 | APPLY_OTA_COMMAND = 0x65, 115 | RENAME_FILE = 0x66, 116 | DOWNLOAD_OTA = 0x67, 117 | }; 118 | 119 | enum wl_tcp_state { 120 | CLOSED = 0, 121 | LISTEN = 1, 122 | SYN_SENT = 2, 123 | SYN_RCVD = 3, 124 | ESTABLISHED = 4, 125 | FIN_WAIT_1 = 5, 126 | FIN_WAIT_2 = 6, 127 | CLOSE_WAIT = 7, 128 | CLOSING = 8, 129 | LAST_ACK = 9, 130 | TIME_WAIT = 10 131 | }; 132 | 133 | enum numParams { 134 | PARAM_NUMS_0, 135 | PARAM_NUMS_1, 136 | PARAM_NUMS_2, 137 | PARAM_NUMS_3, 138 | PARAM_NUMS_4, 139 | PARAM_NUMS_5, 140 | PARAM_NUMS_6, 141 | MAX_PARAM_NUMS 142 | }; 143 | 144 | #define MAX_PARAMS MAX_PARAM_NUMS - 1 145 | #define PARAM_LEN_SIZE 1 146 | 147 | typedef struct __attribute__((__packed__)) { 148 | uint8_t paramLen; 149 | char* param; 150 | } tParam; 151 | 152 | typedef struct __attribute__((__packed__)) { 153 | uint16_t dataLen; 154 | char* data; 155 | } tDataParam; 156 | 157 | typedef struct __attribute__((__packed__)) { 158 | unsigned char cmd; 159 | unsigned char tcmd; 160 | unsigned char nParam; 161 | tParam params[MAX_PARAMS]; 162 | } tSpiMsg; 163 | 164 | typedef struct __attribute__((__packed__)) { 165 | unsigned char cmd; 166 | unsigned char tcmd; 167 | unsigned char nParam; 168 | tDataParam params[MAX_PARAMS]; 169 | } tSpiMsgData; 170 | 171 | typedef struct __attribute__((__packed__)) { 172 | unsigned char cmd; 173 | unsigned char tcmd; 174 | // unsigned char totLen; 175 | unsigned char nParam; 176 | } tSpiHdr; 177 | 178 | typedef struct __attribute__((__packed__)) { 179 | uint8_t paramLen; 180 | uint32_t param; 181 | } tLongParam; 182 | 183 | typedef struct __attribute__((__packed__)) { 184 | uint8_t paramLen; 185 | uint16_t param; 186 | } tIntParam; 187 | 188 | typedef struct __attribute__((__packed__)) { 189 | uint8_t paramLen; 190 | uint8_t param; 191 | } tByteParam; 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /src/Controller.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2021, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #include "Controller.h" 5 | 6 | #include 7 | 8 | #include "Bluepad32.h" 9 | #include "constants.h" 10 | #include "utility/debug.h" 11 | #include "utility/spi_drv.h" 12 | #include "utility/wl_types.h" 13 | 14 | Controller::Controller() : _connected(false), _data(), _properties() { 15 | _data.idx = -1; 16 | _properties.idx = -1; 17 | } 18 | 19 | bool Controller::isConnected() const { return _connected; } 20 | 21 | void Controller::setPlayerLEDs(uint8_t led) const { 22 | if (!isConnected()) { 23 | WARN("gamepad not connected"); 24 | return; 25 | } 26 | 27 | WAIT_FOR_SLAVE_SELECT(); 28 | // Send Command 29 | SpiDrv::sendCmd(BP32_SET_GAMEPAD_PLAYERS_LED, PARAM_NUMS_2); 30 | SpiDrv::sendParam((uint8_t*)&_data.idx, 1, NO_LAST_PARAM); 31 | SpiDrv::sendParam((uint8_t*)&led, 1, LAST_PARAM); 32 | 33 | // pad to multiple of 4 34 | SpiDrv::readChar(); 35 | 36 | SpiDrv::spiSlaveDeselect(); 37 | // Wait the reply elaboration 38 | SpiDrv::waitForSlaveReady(); 39 | SpiDrv::spiSlaveSelect(); 40 | 41 | // Wait for reply 42 | uint8_t data, dataLen; 43 | if (!SpiDrv::waitResponseCmd(BP32_SET_GAMEPAD_PLAYERS_LED, PARAM_NUMS_1, 44 | &data, &dataLen)) { 45 | WARN("error waitResponse"); 46 | } 47 | SpiDrv::spiSlaveDeselect(); 48 | 49 | if (data != BP32_RESPONSE_OK || dataLen != 1) { 50 | WARN("Failed to set players led"); 51 | } 52 | } 53 | 54 | void Controller::setColorLED(uint8_t red, uint8_t green, uint8_t blue) const { 55 | if (!isConnected()) { 56 | WARN("gamepad not connected"); 57 | return; 58 | } 59 | 60 | uint8_t colors[3] = {red, green, blue}; 61 | 62 | WAIT_FOR_SLAVE_SELECT(); 63 | // Send Command 64 | SpiDrv::sendCmd(BP32_SET_GAMEPAD_COLOR_LED, PARAM_NUMS_4); 65 | SpiDrv::sendParam((uint8_t*)&_data.idx, 1, NO_LAST_PARAM); 66 | SpiDrv::sendParam(colors, 3, LAST_PARAM); 67 | 68 | // pad to multiple of 4 69 | SpiDrv::readChar(); 70 | SpiDrv::readChar(); 71 | SpiDrv::readChar(); 72 | 73 | SpiDrv::spiSlaveDeselect(); 74 | // Wait the reply elaboration 75 | SpiDrv::waitForSlaveReady(); 76 | SpiDrv::spiSlaveSelect(); 77 | 78 | // Wait for reply 79 | uint8_t data, dataLen; 80 | if (!SpiDrv::waitResponseCmd(BP32_SET_GAMEPAD_COLOR_LED, PARAM_NUMS_1, &data, 81 | &dataLen)) { 82 | WARN("error waitResponse"); 83 | } 84 | SpiDrv::spiSlaveDeselect(); 85 | 86 | if (data != BP32_RESPONSE_OK || dataLen != 1) { 87 | WARN("Failed to set color led"); 88 | } 89 | } 90 | 91 | void Controller::setRumble(uint8_t force, uint8_t duration) const { 92 | if (!isConnected()) { 93 | WARN("gamepad not connected"); 94 | return; 95 | } 96 | 97 | uint8_t rumble[2] = {force, duration}; 98 | 99 | WAIT_FOR_SLAVE_SELECT(); 100 | // Send Command 101 | SpiDrv::sendCmd(BP32_SET_GAMEPAD_RUMBLE, PARAM_NUMS_3); 102 | SpiDrv::sendParam((uint8_t*)&_data.idx, 1, NO_LAST_PARAM); 103 | SpiDrv::sendParam(rumble, 2, LAST_PARAM); 104 | 105 | // already padded to multiple of 4 106 | 107 | SpiDrv::spiSlaveDeselect(); 108 | // Wait the reply elaboration 109 | SpiDrv::waitForSlaveReady(); 110 | SpiDrv::spiSlaveSelect(); 111 | 112 | // Wait for reply 113 | uint8_t data, dataLen; 114 | if (!SpiDrv::waitResponseCmd(BP32_SET_GAMEPAD_RUMBLE, PARAM_NUMS_1, &data, 115 | &dataLen)) { 116 | WARN("error waitResponse"); 117 | } 118 | 119 | if (data != BP32_RESPONSE_OK || dataLen != 1) { 120 | WARN("Failed to set rumble"); 121 | } 122 | SpiDrv::spiSlaveDeselect(); 123 | } 124 | 125 | void Controller::disconnect() { 126 | if (!isConnected()) { 127 | WARN("gamepad not connected"); 128 | return; 129 | } 130 | 131 | WAIT_FOR_SLAVE_SELECT(); 132 | // Send Command 133 | SpiDrv::sendCmd(BP32_DISCONNECT_GAMEPAD, PARAM_NUMS_1); 134 | SpiDrv::sendParam((uint8_t*)&_data.idx, 1, LAST_PARAM); 135 | 136 | // pad to multiple of 4 137 | SpiDrv::readChar(); 138 | SpiDrv::readChar(); 139 | 140 | SpiDrv::spiSlaveDeselect(); 141 | // Wait the reply elaboration 142 | SpiDrv::waitForSlaveReady(); 143 | SpiDrv::spiSlaveSelect(); 144 | 145 | // Wait for reply 146 | uint8_t data, dataLen; 147 | if (!SpiDrv::waitResponseCmd(BP32_DISCONNECT_GAMEPAD, PARAM_NUMS_1, &data, 148 | &dataLen)) { 149 | WARN("error waitResponse"); 150 | } 151 | SpiDrv::spiSlaveDeselect(); 152 | 153 | if (data != BP32_RESPONSE_OK || dataLen != 1) { 154 | WARN("Failed to disconnect"); 155 | } 156 | } 157 | 158 | // Private functions 159 | 160 | void Controller::onConnected() { 161 | _connected = true; 162 | 163 | // Properties are static, they don't change during the "life of the 164 | // connection". Request them once, at connect time and cache the result. 165 | // FIXME: _properties.idx contains the error code in case it fails. 166 | uint8_t errorCode; 167 | tParam params[] = { 168 | {sizeof(errorCode), (char*)&errorCode}, 169 | {sizeof(_properties), (char*)&_properties}, 170 | }; 171 | 172 | // Requires protocol version 1.1 at least. 173 | if (BP32._protocolVersionHi <= BP32_PROTOCOL_VERSION_HI && 174 | BP32._protocolVersionLow < BP32_PROTOCOL_VERSION_LO) { 175 | WARN("Requires protocol version 1.3. Upgrade ESP32 firmware"); 176 | _properties.idx = -1; 177 | return; 178 | } 179 | 180 | INFO("Requesting properties for idx=%d", _data.idx); 181 | WAIT_FOR_SLAVE_SELECT(); 182 | // Send Command 183 | SpiDrv::sendCmd(BP32_GET_GAMEPAD_PROPERTIES, PARAM_NUMS_1); 184 | SpiDrv::sendParam((uint8_t*)&_data.idx, 1, LAST_PARAM); 185 | 186 | // pad to multiple of 4 187 | SpiDrv::readChar(); 188 | SpiDrv::readChar(); 189 | 190 | SpiDrv::spiSlaveDeselect(); 191 | // Wait the reply elaboration 192 | SpiDrv::waitForSlaveReady(); 193 | SpiDrv::spiSlaveSelect(); 194 | 195 | // Wait for reply 196 | if (!SpiDrv::waitResponseParams(BP32_GET_GAMEPAD_PROPERTIES, PARAM_NUMS_2, 197 | params)) { 198 | WARN("error waitResponseParams"); 199 | _properties.idx = -2; 200 | return; 201 | } 202 | 203 | SpiDrv::spiSlaveDeselect(); 204 | 205 | if (errorCode != BP32_RESPONSE_OK) { 206 | WARN("Failed to get gamepad properties"); 207 | _properties.idx = -3; 208 | } 209 | } 210 | 211 | void Controller::onDisconnected() { 212 | _connected = false; 213 | _properties = {}; 214 | } 215 | -------------------------------------------------------------------------------- /examples/Gamepad_OLD_API/Gamepad_OLD_API.ino: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2022, Ricardo Quesada 2 | // SPDX-License-Identifier: Apache 2.0 or LGPL-2.1-or-later 3 | 4 | /* 5 | * This example shows how to use the Gamepad API. 6 | * 7 | * Supported on boards with NINA W10x. In particular these boards: 8 | * - Arduino MKR WiFi 1010, 9 | * - UNO WiFi Rev.2, 10 | * - Nano RP2040 Connect, 11 | * - Nano 33 IoT, 12 | * - Arduino Arduino MKR Vidor 4000 13 | */ 14 | #include 15 | 16 | // 17 | // README FIRST 18 | // 19 | // This example uses the old (but still supported) API. 20 | // Do not use it for newer projects. 21 | // 22 | // Instead use the new "Controller" API. 23 | // 24 | 25 | GamepadPtr myGamepads[BP32_MAX_GAMEPADS] = {}; 26 | 27 | void setup() { 28 | // Initialize serial 29 | Serial.begin(9600); 30 | while (!Serial) { 31 | // wait for serial port to connect. 32 | // You don't have to do this in your game. This is only for debugging 33 | // purposes, so that you can see the output in the serial console. 34 | ; 35 | } 36 | 37 | String fv = BP32.firmwareVersion(); 38 | Serial.print("Firmware version installed: "); 39 | Serial.println(fv); 40 | 41 | // BP32.pinMode(27, OUTPUT); 42 | // BP32.digitalWrite(27, 0); 43 | 44 | // This call is mandatory. It setups Bluepad32 and creates the callbacks. 45 | BP32.setup(&onConnectedGamepad, &onDisconnectedGamepad); 46 | 47 | // "forgetBluetoothKeys()" should be called when the user performs 48 | // a "device factory reset", or similar. 49 | // Calling "forgetBluetoothKeys" in setup() just as an example. 50 | // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect. 51 | // But might also fix some connection / re-connection issues. 52 | BP32.forgetBluetoothKeys(); 53 | } 54 | 55 | // This callback gets called any time a new gamepad is connected. 56 | // Up to 4 gamepads can be connected at the same time. 57 | void onConnectedGamepad(GamepadPtr gp) { 58 | bool foundEmptySlot = false; 59 | for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 60 | if (myGamepads[i] == nullptr) { 61 | Serial.print("CALLBACK: Gamepad is connected, index="); 62 | Serial.println(i); 63 | myGamepads[i] = gp; 64 | foundEmptySlot = true; 65 | 66 | // Optional, once the gamepad is connected, request further info about the 67 | // gamepad. 68 | GamepadProperties properties = gp->getProperties(); 69 | char buf[80]; 70 | sprintf(buf, 71 | "BTAddr: %02x:%02x:%02x:%02x:%02x:%02x, VID/PID: %04x:%04x, " 72 | "flags: 0x%02x", 73 | properties.btaddr[0], properties.btaddr[1], properties.btaddr[2], 74 | properties.btaddr[3], properties.btaddr[4], properties.btaddr[5], 75 | properties.vendor_id, properties.product_id, properties.flags); 76 | Serial.println(buf); 77 | break; 78 | } 79 | } 80 | if (!foundEmptySlot) { 81 | Serial.println( 82 | "CALLBACK: Gamepad connected, but could not found empty slot"); 83 | } 84 | } 85 | 86 | void onDisconnectedGamepad(GamepadPtr gp) { 87 | bool foundGamepad = false; 88 | 89 | for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 90 | if (myGamepads[i] == gp) { 91 | Serial.print("CALLBACK: Gamepad is disconnected from index="); 92 | Serial.println(i); 93 | myGamepads[i] = nullptr; 94 | foundGamepad = true; 95 | break; 96 | } 97 | } 98 | 99 | if (!foundGamepad) { 100 | Serial.println( 101 | "CALLBACK: Gamepad disconnected, but not found in myGamepads"); 102 | } 103 | } 104 | 105 | void loop() { 106 | // This call fetches all the gamepad info from the NINA (ESP32) module. 107 | // Just call this function in your main loop. 108 | // The gamepad pointers (the ones received in the callbacks) gets updated 109 | // automatically. 110 | BP32.update(); 111 | 112 | // It is safe to always do this before using the gamepad API. 113 | // This guarantees that the gamepad is valid and connected. 114 | for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 115 | GamepadPtr myGamepad = myGamepads[i]; 116 | 117 | if (myGamepad && myGamepad->isConnected()) { 118 | // There are different ways to query whether a button is pressed. 119 | // By query each button individually: 120 | // a(), b(), x(), y(), l1(), etc... 121 | if (myGamepad->a()) { 122 | static int colorIdx = 0; 123 | // Some gamepads like DS4 and DualSense support changing the color LED. 124 | // It is possible to change it by calling: 125 | switch (colorIdx % 3) { 126 | case 0: 127 | // Red 128 | myGamepad->setColorLED(255, 0, 0); 129 | break; 130 | case 1: 131 | // Green 132 | myGamepad->setColorLED(0, 255, 0); 133 | break; 134 | case 2: 135 | // Blue 136 | myGamepad->setColorLED(0, 0, 255); 137 | break; 138 | } 139 | colorIdx++; 140 | } 141 | 142 | if (myGamepad->b()) { 143 | // Turn on the 4 LED. Each bit represents one LED. 144 | static int led = 0; 145 | led++; 146 | // Some gamepads like the DS3, DualSense, Nintendo Wii, Nintendo Switch 147 | // support changing the "Player LEDs": those 4 LEDs that usually 148 | // indicate the "gamepad seat". It is possible to change them by 149 | // calling: 150 | myGamepad->setPlayerLEDs(led & 0x0f); 151 | } 152 | 153 | if (myGamepad->x()) { 154 | // Duration: 255 is ~2 seconds 155 | // force: intensity 156 | // Some gamepads like DS3, DS4, DualSense, Switch, Xbox One S support 157 | // force feedback. 158 | // It is possible to set it by calling: 159 | myGamepad->setRumble(0xc0 /* force */, 0xc0 /* duration */); 160 | } 161 | 162 | if (myGamepad->y()) { 163 | // Disable new gamepad connections 164 | Serial.println("Bluetooth new connections disabled"); 165 | BP32.enableNewBluetoothConnections(false); 166 | 167 | // Serial.println("Gamepad disconnected!"); 168 | // myGamepad->disconnect(); 169 | } 170 | 171 | // Another way to query the buttons, is by calling buttons(), or 172 | // miscButtons() which return a bitmask. 173 | // Some gamepads also have DPAD, axis and more. 174 | char buffer[120]; 175 | snprintf( 176 | buffer, sizeof(buffer) - 1, 177 | "idx=%d, dpad: 0x%02x, buttons: 0x%04x, axis L: %4li, %4li, axis " 178 | "R: %4li, %4li, brake: %4ld, throttle: %4li, misc: 0x%02x", 179 | i, // Gamepad Index 180 | myGamepad->dpad(), // DPAD 181 | myGamepad->buttons(), // bitmask of pressed buttons 182 | myGamepad->axisX(), // (-511 - 512) left X Axis 183 | myGamepad->axisY(), // (-511 - 512) left Y axis 184 | myGamepad->axisRX(), // (-511 - 512) right X axis 185 | myGamepad->axisRY(), // (-511 - 512) right Y axis 186 | myGamepad->brake(), // (0 - 1023): brake button 187 | myGamepad->throttle(), // (0 - 1023): throttle (AKA gas) button 188 | myGamepad->miscButtons() // bitmak of pressed "misc" buttons 189 | ); 190 | Serial.println(buffer); 191 | 192 | // You can query the axis and other properties as well. See Gamepad.h 193 | // For all the available functions. 194 | } 195 | } 196 | 197 | delay(150); 198 | } 199 | -------------------------------------------------------------------------------- /src/Controller.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2023, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #ifndef BP32_CONTROLLER_H 5 | #define BP32_CONTROLLER_H 6 | 7 | #include 8 | #include 9 | 10 | #include "ControllerData.h" 11 | #include "ControllerProperties.h" 12 | 13 | class Controller { 14 | public: 15 | // FIXME: Should not be duplicated. 16 | // Must match values from "uni_hid_device_vendors.h" 17 | enum { 18 | CONTROLLER_TYPE_None = -1, 19 | CONTROLLER_TYPE_Unknown = 0, 20 | 21 | // Steam Controllers 22 | CONTROLLER_TYPE_UnknownSteamController = 1, 23 | CONTROLLER_TYPE_SteamController = 2, 24 | CONTROLLER_TYPE_SteamControllerV2 = 3, 25 | 26 | // Other Controllers 27 | CONTROLLER_TYPE_UnknownNonSteamController = 30, 28 | CONTROLLER_TYPE_XBox360Controller = 31, 29 | CONTROLLER_TYPE_XBoxOneController = 32, 30 | CONTROLLER_TYPE_PS3Controller = 33, 31 | CONTROLLER_TYPE_PS4Controller = 34, 32 | CONTROLLER_TYPE_WiiController = 35, 33 | CONTROLLER_TYPE_AppleController = 36, 34 | CONTROLLER_TYPE_AndroidController = 37, 35 | CONTROLLER_TYPE_SwitchProController = 38, 36 | CONTROLLER_TYPE_SwitchJoyConLeft = 39, 37 | CONTROLLER_TYPE_SwitchJoyConRight = 40, 38 | CONTROLLER_TYPE_SwitchJoyConPair = 41, 39 | CONTROLLER_TYPE_SwitchInputOnlyController = 42, 40 | CONTROLLER_TYPE_MobileTouch = 43, 41 | // Client-side only, used to mark Switch-compatible controllers as 42 | // not supporting Switch controller protocol 43 | CONTROLLER_TYPE_XInputSwitchController = 44, 44 | CONTROLLER_TYPE_PS5Controller = 45, 45 | 46 | // Bluepad32 own extensions 47 | CONTROLLER_TYPE_iCadeController = 50, // (Bluepad32) 48 | CONTROLLER_TYPE_SmartTVRemoteController = 51, // (Bluepad32) 49 | CONTROLLER_TYPE_EightBitdoController = 52, // (Bluepad32) 50 | CONTROLLER_TYPE_GenericController = 53, // (Bluepad32) 51 | CONTROLLER_TYPE_NimbusController = 54, // (Bluepad32) 52 | CONTROLLER_TYPE_OUYAController = 55, // (Bluepad32) 53 | 54 | CONTROLLER_TYPE_LastController, // Don't add game controllers below this 55 | // enumeration - this enumeration can 56 | // change value 57 | 58 | // Keyboards and Mice 59 | CONTROLLER_TYPE_GenericKeyboard = 400, 60 | CONTROLLER_TYPE_GenericMouse = 800, 61 | }; 62 | 63 | // DPAD 64 | enum { 65 | DPAD_UP = 1 << 0, 66 | DPAD_DOWN = 1 << 1, 67 | DPAD_RIGHT = 1 << 2, 68 | DPAD_LEFT = 1 << 3, 69 | }; 70 | 71 | // BUTTON_ are the main gamepad buttons, like X, Y, A, B, etc. 72 | enum { 73 | BUTTON_A = 1 << 0, 74 | BUTTON_B = 1 << 1, 75 | BUTTON_X = 1 << 2, 76 | BUTTON_Y = 1 << 3, 77 | BUTTON_SHOULDER_L = 1 << 4, 78 | BUTTON_SHOULDER_R = 1 << 5, 79 | BUTTON_TRIGGER_L = 1 << 6, 80 | BUTTON_TRIGGER_R = 1 << 7, 81 | BUTTON_THUMB_L = 1 << 8, 82 | BUTTON_THUMB_R = 1 << 9, 83 | }; 84 | 85 | // MISC_BUTTONS_ are buttons that are usually not used in the game, but are 86 | // helpers like "back", "home", etc. 87 | enum { 88 | MISC_BUTTON_SYSTEM = 1 << 0, // AKA: PS, Xbox, etc. 89 | MISC_BUTTON_BACK = 1 << 1, // AKA: Select, Share, - 90 | MISC_BUTTON_HOME = 1 << 2, // AKA: Start, Options, + 91 | }; 92 | 93 | Controller(); 94 | 95 | // 96 | // Gamepad Related 97 | // 98 | 99 | uint8_t dpad() const { return _data.gamepad.dpad; } 100 | 101 | // Axis 102 | int32_t axisX() const { return _data.gamepad.axis_x; } 103 | int32_t axisY() const { return _data.gamepad.axis_y; } 104 | int32_t axisRX() const { return _data.gamepad.axis_rx; } 105 | int32_t axisRY() const { return _data.gamepad.axis_ry; } 106 | 107 | // Brake & Throttle 108 | int32_t brake() const { return _data.gamepad.brake; } 109 | int32_t throttle() const { return _data.gamepad.throttle; } 110 | 111 | // Gyro / Accel 112 | int32_t gyroX() const { return _data.gamepad.gyro[0]; } 113 | int32_t gyroY() const { return _data.gamepad.gyro[1]; } 114 | int32_t gyroZ() const { return _data.gamepad.gyro[2]; } 115 | int32_t accelX() const { return _data.gamepad.accel[0]; } 116 | int32_t accelY() const { return _data.gamepad.accel[1]; } 117 | int32_t accelZ() const { return _data.gamepad.accel[2]; } 118 | 119 | // 120 | // Shared between Mouse & Gamepad 121 | // 122 | 123 | // Returns the state of all buttons. 124 | uint16_t buttons() const { 125 | if (_data.klass == CONTROLLER_CLASS_GAMEPAD) return _data.gamepad.buttons; 126 | if (_data.klass == CONTROLLER_CLASS_MOUSE) return _data.mouse.buttons; 127 | // Not supported in other controllers 128 | return 0; 129 | } 130 | 131 | // Returns the state of all misc buttons. 132 | uint16_t miscButtons() const { 133 | if (_data.klass == CONTROLLER_CLASS_GAMEPAD) 134 | return _data.gamepad.misc_buttons; 135 | if (_data.klass == CONTROLLER_CLASS_MOUSE) return _data.mouse.misc_buttons; 136 | // Not supported in other controllers 137 | return 0; 138 | } 139 | 140 | // To test one button at the time. 141 | bool a() const { return buttons() & BUTTON_A; } 142 | bool b() const { return buttons() & BUTTON_B; } 143 | bool x() const { return buttons() & BUTTON_X; } 144 | bool y() const { return buttons() & BUTTON_Y; } 145 | bool l1() const { return buttons() & BUTTON_SHOULDER_L; } 146 | bool l2() const { return buttons() & BUTTON_TRIGGER_L; } 147 | bool r1() const { return buttons() & BUTTON_SHOULDER_R; } 148 | bool r2() const { return buttons() & BUTTON_TRIGGER_R; } 149 | bool thumbL() const { return buttons() & BUTTON_THUMB_L; } 150 | bool thumbR() const { return buttons() & BUTTON_THUMB_R; } 151 | 152 | // Misc buttons 153 | bool miscSystem() const { return miscButtons() & MISC_BUTTON_SYSTEM; } 154 | bool miscBack() const { return miscButtons() & MISC_BUTTON_BACK; } 155 | bool miscHome() const { return miscButtons() & MISC_BUTTON_HOME; } 156 | 157 | // 158 | // Mouse related 159 | // 160 | int32_t deltaX() const { return _data.mouse.delta_x; } 161 | int32_t deltaY() const { return _data.mouse.delta_y; } 162 | int8_t scrollWheel() const { return _data.mouse.scroll_wheel; } 163 | 164 | // 165 | // Wii Balance Board related 166 | // 167 | uint16_t topLeft() const { return _data.balance_board.tl; } 168 | uint16_t topRight() const { return _data.balance_board.tr; } 169 | uint16_t bottomLeft() const { return _data.balance_board.bl; } 170 | uint16_t bottomRight() const { return _data.balance_board.br; } 171 | int temperature() const { return _data.balance_board.temperature; } 172 | 173 | // 174 | // Shared among all 175 | // 176 | 177 | // 0 = Unknown Battery state 178 | // 1 = Battery Empty 179 | // 255 = Battery full 180 | uint8_t battery() const { return _data.battery; } 181 | 182 | bool isGamepad() const { return _data.klass == CONTROLLER_CLASS_GAMEPAD; } 183 | bool isMouse() const { return _data.klass == CONTROLLER_CLASS_MOUSE; } 184 | bool isBalanceBoard() const { 185 | return _data.klass == CONTROLLER_CLASS_BALANCE_BOARD; 186 | } 187 | int8_t index() const { return _idx; } 188 | 189 | bool isConnected() const; 190 | void disconnect(); 191 | 192 | uint8_t getClass() const { return _data.klass; } 193 | // Returns the controller model. 194 | int getModel() const { return _properties.type; } 195 | String getModelName() const; 196 | ControllerProperties getProperties() const { return _properties; } 197 | 198 | // "Output" functions. 199 | void setPlayerLEDs(uint8_t led) const; 200 | void setColorLED(uint8_t red, uint8_t green, uint8_t blue) const; 201 | void setRumble(uint8_t force, uint8_t duration) const; 202 | 203 | private: 204 | void onConnected(); 205 | void onDisconnected(); 206 | 207 | bool _connected; 208 | // Controller index, from 0 to 3. 209 | int8_t _idx; 210 | ControllerData _data; 211 | ControllerProperties _properties; 212 | 213 | // Delete copy constructor to avoid copying the state by mistake. If so, 214 | // chances are that the controller won't get updated automatically. 215 | Controller(const Controller&) = delete; 216 | 217 | // For converting controller types to names. 218 | struct controllerNames { 219 | int type; 220 | const char* name; 221 | }; 222 | static const struct controllerNames _controllerNames[]; 223 | 224 | // Friends 225 | friend class Bluepad32; 226 | }; 227 | 228 | typedef Controller* ControllerPtr; 229 | 230 | #endif // BP32_CONTROLLER_H 231 | -------------------------------------------------------------------------------- /examples/Controller/Controller.ino: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2023, Ricardo Quesada 2 | // SPDX-License-Identifier: Apache 2.0 or LGPL-2.1-or-later 3 | 4 | /* 5 | * This example shows how to use the Controller API. 6 | * 7 | * Supported on boards with NINA W10x. In particular, these boards: 8 | * - Arduino MKR WiFi 1010, 9 | * - UNO WiFi Rev.2, 10 | * - Nano RP2040 Connect, 11 | * - Nano 33 IoT, 12 | * - Arduino MKR Vidor 4000 13 | */ 14 | #include 15 | 16 | ControllerPtr myControllers[BP32_MAX_CONTROLLERS]; 17 | 18 | // Arduino setup function. Runs in CPU 1 19 | void setup() { 20 | // Initialize serial 21 | Serial.begin(9600); 22 | while (!Serial) { 23 | // Wait for serial port to connect. 24 | // You don't have to do this in your game. This is only for debugging 25 | // purposes, so that you can see the output in the serial console. 26 | ; 27 | } 28 | 29 | String fv = BP32.firmwareVersion(); 30 | Serial.print("Firmware version installed: "); 31 | Serial.println(fv); 32 | 33 | // To get the BD Address (MAC address) call: 34 | const uint8_t* addr = BP32.localBdAddress(); 35 | Serial.print("BD Address: "); 36 | for (int i = 0; i < 6; i++) { 37 | Serial.print(addr[i], HEX); 38 | if (i < 5) 39 | Serial.print(":"); 40 | else 41 | Serial.println(); 42 | } 43 | 44 | // BP32.pinMode(27, OUTPUT); 45 | // BP32.digitalWrite(27, 0); 46 | 47 | // This call is mandatory. It sets up Bluepad32 and creates the callbacks. 48 | BP32.setup(&onConnectedController, &onDisconnectedController); 49 | 50 | // "forgetBluetoothKeys()" should be called when the user performs 51 | // a "device factory reset", or similar. 52 | // Calling "forgetBluetoothKeys" in setup() just as an example. 53 | // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect. 54 | // But it might also fix some connection / re-connection issues. 55 | BP32.forgetBluetoothKeys(); 56 | } 57 | 58 | // This callback gets called any time a new gamepad is connected. 59 | // Up to 4 gamepads can be connected at the same time. 60 | void onConnectedController(ControllerPtr ctl) { 61 | bool foundEmptySlot = false; 62 | for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 63 | if (myControllers[i] == nullptr) { 64 | Serial.print("CALLBACK: Controller is connected, index="); 65 | Serial.println(i); 66 | myControllers[i] = ctl; 67 | foundEmptySlot = true; 68 | 69 | // Optional, once the gamepad is connected, request further info about the 70 | // gamepad. 71 | ControllerProperties properties = ctl->getProperties(); 72 | char buf[80]; 73 | sprintf(buf, 74 | "BTAddr: %02x:%02x:%02x:%02x:%02x:%02x, VID/PID: %04x:%04x, " 75 | "flags: 0x%02x", 76 | properties.btaddr[0], properties.btaddr[1], properties.btaddr[2], 77 | properties.btaddr[3], properties.btaddr[4], properties.btaddr[5], 78 | properties.vendor_id, properties.product_id, properties.flags); 79 | Serial.println(buf); 80 | break; 81 | } 82 | } 83 | if (!foundEmptySlot) { 84 | Serial.println( 85 | "CALLBACK: Controller connected, but could not found empty slot"); 86 | } 87 | } 88 | 89 | void onDisconnectedController(ControllerPtr ctl) { 90 | bool foundGamepad = false; 91 | 92 | for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 93 | if (myControllers[i] == ctl) { 94 | Serial.print("CALLBACK: Controller is disconnected from index="); 95 | Serial.println(i); 96 | myControllers[i] = nullptr; 97 | foundGamepad = true; 98 | break; 99 | } 100 | } 101 | 102 | if (!foundGamepad) { 103 | Serial.println( 104 | "CALLBACK: Controller disconnected, but not found in myControllers"); 105 | } 106 | } 107 | 108 | void processGamepad(ControllerPtr gamepad) { 109 | // There are different ways to query whether a button is pressed. 110 | // By query each button individually: 111 | // a(), b(), x(), y(), l1(), etc... 112 | 113 | if (gamepad->a()) { 114 | static int colorIdx = 0; 115 | // Some gamepads like DS4 and DualSense support changing the color LED. 116 | // It is possible to change it by calling: 117 | switch (colorIdx % 3) { 118 | case 0: 119 | // Red 120 | gamepad->setColorLED(255, 0, 0); 121 | break; 122 | case 1: 123 | // Green 124 | gamepad->setColorLED(0, 255, 0); 125 | break; 126 | case 2: 127 | // Blue 128 | gamepad->setColorLED(0, 0, 255); 129 | break; 130 | } 131 | colorIdx++; 132 | } 133 | 134 | if (gamepad->b()) { 135 | // Turn on the 4 LED. Each bit represents one LED. 136 | static int led = 0; 137 | led++; 138 | // Some gamepads like the DS3, DualSense, Nintendo Wii, Nintendo Switch 139 | // support changing the "Player LEDs": those 4 LEDs that usually indicate 140 | // the "gamepad seat". 141 | // It is possible to change them by calling: 142 | gamepad->setPlayerLEDs(led & 0x0f); 143 | } 144 | 145 | if (gamepad->x()) { 146 | // Duration: 255 is ~2 seconds 147 | // force: intensity 148 | // Some gamepads like DS3, DS4, DualSense, Switch, Xbox One S support 149 | // rumble. 150 | // It is possible to set it by calling: 151 | gamepad->setRumble(0xc0 /* force */, 0xc0 /* duration */); 152 | } 153 | 154 | // Another way to query the buttons, is by calling buttons(), or 155 | // miscButtons() which return a bitmask. 156 | // Some gamepads also have DPAD, axis and more. 157 | char buf[256]; 158 | snprintf(buf, sizeof(buf) - 1, 159 | "idx=%d, dpad: 0x%02x, buttons: 0x%04x, " 160 | "axis L: %4li, %4li, axis R: %4li, %4li, " 161 | "brake: %4ld, throttle: %4li, misc: 0x%02x, " 162 | "gyro x:%6d y:%6d z:%6d, accel x:%6d y:%6d z:%6d, " 163 | "battery: %d", 164 | gamepad->index(), // Gamepad Index 165 | gamepad->dpad(), // DPad 166 | gamepad->buttons(), // bitmask of pressed buttons 167 | gamepad->axisX(), // (-511 - 512) left X Axis 168 | gamepad->axisY(), // (-511 - 512) left Y axis 169 | gamepad->axisRX(), // (-511 - 512) right X axis 170 | gamepad->axisRY(), // (-511 - 512) right Y axis 171 | gamepad->brake(), // (0 - 1023): brake button 172 | gamepad->throttle(), // (0 - 1023): throttle (AKA gas) button 173 | gamepad->miscButtons(), // bitmask of pressed "misc" buttons 174 | gamepad->gyroX(), // Gyro X 175 | gamepad->gyroY(), // Gyro Y 176 | gamepad->gyroZ(), // Gyro Z 177 | gamepad->accelX(), // Accelerometer X 178 | gamepad->accelY(), // Accelerometer Y 179 | gamepad->accelZ(), // Accelerometer Z 180 | gamepad->battery() // 0=Unknown, 1=empty, 255=full 181 | ); 182 | Serial.println(buf); 183 | 184 | // You can query the axis and other properties as well. See 185 | // Controller.h For all the available functions. 186 | } 187 | 188 | void processMouse(ControllerPtr mouse) { 189 | char buf[160]; 190 | sprintf(buf, 191 | "idx=%d, deltaX:%4li, deltaY:%4li, buttons: 0x%04x, misc: 0x%02x, " 192 | "scrollWheel: %d, battery=%d", 193 | mouse->index(), // Controller Index 194 | mouse->deltaX(), // Mouse delta X 195 | mouse->deltaY(), // Mouse delta Y 196 | mouse->buttons(), // bitmask of pressed buttons 197 | mouse->miscButtons(), // bitmask of pressed "misc" buttons 198 | mouse->scrollWheel(), // Direction: 1=up, -1=down, 0=no movement 199 | mouse->battery() // 0=Unk, 1=Empty, 255=full 200 | ); 201 | Serial.println(buf); 202 | } 203 | 204 | void processBalanceBoard(ControllerPtr balance) { 205 | char buf[160]; 206 | sprintf(buf, 207 | "idx=%d, tl:%4i, tr:%4i, bl: %4i, br: %4i, temperature=%d, " 208 | "battery=%d", 209 | balance->index(), // Controller Index 210 | balance->topLeft(), balance->topRight(), balance->bottomLeft(), 211 | balance->bottomRight(), balance->temperature(), 212 | balance->battery() // 0=Unk, 1=Empty, 255=full 213 | ); 214 | Serial.println(buf); 215 | } 216 | 217 | // Arduino loop function. Runs in CPU 1 218 | void loop() { 219 | // This call fetches all the controller info from the NINA (ESP32) module. 220 | // Call this function in your main loop. 221 | // The controllers' pointer (the ones received in the callbacks) gets updated 222 | // automatically. 223 | BP32.update(); 224 | 225 | // It is safe to always do this before using the controller API. 226 | // This guarantees that the controller is valid and connected. 227 | for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) { 228 | ControllerPtr myController = myControllers[i]; 229 | 230 | if (myController && myController->isConnected()) { 231 | if (myController->isGamepad()) 232 | processGamepad(myController); 233 | else if (myController->isMouse()) 234 | processMouse(myController); 235 | else if (myController->isBalanceBoard()) 236 | processBalanceBoard(myController); 237 | } 238 | } 239 | delay(150); 240 | } 241 | -------------------------------------------------------------------------------- /src/Bluepad32.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - 2022, Ricardo Quesada, http://retro.moe 2 | // SPDX-License-Identifier: Apache-2.0 or LGPL-2.1-or-later 3 | 4 | #include "Bluepad32.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "constants.h" 10 | #include "utility/debug.h" 11 | #include "utility/spi_drv.h" 12 | #include "utility/wl_types.h" 13 | 14 | Bluepad32::Bluepad32() 15 | : _controllers(), 16 | _prevConnectedControllers(0), 17 | _onConnect(), 18 | _onDisconnect() {} 19 | 20 | const char* Bluepad32::firmwareVersion() { 21 | // Max size of fwversion. Might not be possible to have an overflow since the 22 | // len is stored in a char. But fragile nonetheless. 23 | static char fwVersion[256]; 24 | 25 | WAIT_FOR_SLAVE_SELECT(); 26 | // Send Command 27 | SpiDrv::sendCmd(GET_FW_VERSION_CMD, PARAM_NUMS_0); 28 | 29 | SpiDrv::spiSlaveDeselect(); 30 | // Wait the reply elaboration 31 | SpiDrv::waitForSlaveReady(); 32 | SpiDrv::spiSlaveSelect(); 33 | 34 | // Wait for reply 35 | uint8_t dataLen; 36 | if (!SpiDrv::waitResponseCmd(GET_FW_VERSION_CMD, PARAM_NUMS_1, 37 | (uint8_t*)fwVersion, &dataLen)) { 38 | WARN("error waitResponse"); 39 | } 40 | SpiDrv::spiSlaveDeselect(); 41 | return fwVersion; 42 | } 43 | 44 | void Bluepad32::pinMode(uint8_t pin, uint8_t mode) { 45 | WAIT_FOR_SLAVE_SELECT(); 46 | // Send Command 47 | SpiDrv::sendCmd(SET_PIN_MODE, PARAM_NUMS_2); 48 | SpiDrv::sendParam((uint8_t*)&pin, 1, NO_LAST_PARAM); 49 | SpiDrv::sendParam((uint8_t*)&mode, 1, LAST_PARAM); 50 | 51 | // pad to multiple of 4 52 | SpiDrv::readChar(); 53 | 54 | SpiDrv::spiSlaveDeselect(); 55 | // Wait the reply elaboration 56 | SpiDrv::waitForSlaveReady(); 57 | SpiDrv::spiSlaveSelect(); 58 | 59 | // Wait for reply 60 | uint8_t data, dataLen; 61 | if (!SpiDrv::waitResponseCmd(SET_PIN_MODE, PARAM_NUMS_1, &data, &dataLen)) { 62 | WARN("error waitResponse"); 63 | } 64 | SpiDrv::spiSlaveDeselect(); 65 | } 66 | 67 | int Bluepad32::digitalRead(uint8_t pin) { 68 | WAIT_FOR_SLAVE_SELECT(); 69 | // Send Command 70 | SpiDrv::sendCmd(GET_DIGITAL_READ, PARAM_NUMS_1); 71 | SpiDrv::sendParam((uint8_t*)&pin, 1, LAST_PARAM); 72 | 73 | // pad to multiple of 4 74 | SpiDrv::readChar(); 75 | SpiDrv::readChar(); 76 | 77 | SpiDrv::spiSlaveDeselect(); 78 | // Wait the reply elaboration 79 | SpiDrv::waitForSlaveReady(); 80 | SpiDrv::spiSlaveSelect(); 81 | 82 | // Wait for reply 83 | uint8_t data, dataLen; 84 | if (!SpiDrv::waitResponseCmd(GET_DIGITAL_READ, PARAM_NUMS_1, &data, 85 | &dataLen)) { 86 | WARN("error waitResponse"); 87 | } 88 | SpiDrv::spiSlaveDeselect(); 89 | 90 | return !!data; 91 | } 92 | 93 | void Bluepad32::setDebug(uint8_t on) { 94 | WAIT_FOR_SLAVE_SELECT(); 95 | 96 | // Send Command 97 | SpiDrv::sendCmd(SET_DEBUG_CMD, PARAM_NUMS_1); 98 | 99 | SpiDrv::sendParam(&on, 1, LAST_PARAM); 100 | 101 | // pad to multiple of 4 102 | SpiDrv::readChar(); 103 | SpiDrv::readChar(); 104 | 105 | SpiDrv::spiSlaveDeselect(); 106 | // Wait the reply elaboration 107 | SpiDrv::waitForSlaveReady(); 108 | SpiDrv::spiSlaveSelect(); 109 | 110 | // Wait for reply 111 | uint8_t data, dataLen; 112 | SpiDrv::waitResponseCmd(SET_DEBUG_CMD, PARAM_NUMS_1, &data, &dataLen); 113 | 114 | SpiDrv::spiSlaveDeselect(); 115 | } 116 | 117 | void Bluepad32::digitalWrite(uint8_t pin, uint8_t value) { 118 | WAIT_FOR_SLAVE_SELECT(); 119 | // Send Command 120 | SpiDrv::sendCmd(SET_DIGITAL_WRITE, PARAM_NUMS_2); 121 | SpiDrv::sendParam((uint8_t*)&pin, 1, NO_LAST_PARAM); 122 | SpiDrv::sendParam((uint8_t*)&value, 1, LAST_PARAM); 123 | 124 | // pad to multiple of 4 125 | SpiDrv::readChar(); 126 | 127 | SpiDrv::spiSlaveDeselect(); 128 | // Wait the reply elaboration 129 | SpiDrv::waitForSlaveReady(); 130 | SpiDrv::spiSlaveSelect(); 131 | 132 | // Wait for reply 133 | uint8_t data, dataLen; 134 | if (!SpiDrv::waitResponseCmd(SET_DIGITAL_WRITE, PARAM_NUMS_1, &data, 135 | &dataLen)) { 136 | WARN("error waitResponse"); 137 | } 138 | SpiDrv::spiSlaveDeselect(); 139 | } 140 | 141 | void Bluepad32::update() { 142 | WAIT_FOR_SLAVE_SELECT(); 143 | // Send Command 144 | // SpiDrv::sendCmd(BP32_GET_GAMEPADS_DATA, PARAM_NUMS_0); 145 | SpiDrv::sendCmd(BP32_GET_CONTROLLERS_DATA, PARAM_NUMS_0); 146 | 147 | SpiDrv::spiSlaveDeselect(); 148 | // Wait the reply elaboration 149 | SpiDrv::waitForSlaveReady(); 150 | SpiDrv::spiSlaveSelect(); 151 | 152 | // Wait for reply 153 | uint8_t totalParams; 154 | 155 | ControllerData currentData[BP32_MAX_CONTROLLERS]; 156 | 157 | SpiDrv::waitResponse(BP32_GET_CONTROLLERS_DATA, &totalParams, 158 | (uint8_t**)currentData, BP32_MAX_CONTROLLERS); 159 | 160 | SpiDrv::spiSlaveDeselect(); 161 | 162 | int connectedControllers = 0; 163 | for (int i = 0; i < totalParams; i++) { 164 | connectedControllers |= (1 << currentData[i].idx); 165 | 166 | // Update controllers state 167 | _controllers[currentData[i].idx]._data = currentData[i]; 168 | } 169 | 170 | // No changes in connected controllers. No need to call onConnected or 171 | // onDisconnected. 172 | if (connectedControllers == _prevConnectedControllers) return; 173 | 174 | // Compare bit by bit, and find which one got connected and which one 175 | // disconnected. 176 | for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) { 177 | int bit = (1 << i); 178 | int current = connectedControllers & bit; 179 | int prev = _prevConnectedControllers & bit; 180 | 181 | // No changes in this controller, skip 182 | if (current == prev) continue; 183 | 184 | if (current) { 185 | _controllers[i].onConnected(); 186 | _onConnect(&_controllers[i]); 187 | INFO("controller connected: %d", i); 188 | } else { 189 | _onDisconnect(&_controllers[i]); 190 | _controllers[i].onDisconnected(); 191 | INFO("controller disconnected: %d", i); 192 | } 193 | } 194 | 195 | _prevConnectedControllers = connectedControllers; 196 | } 197 | 198 | void Bluepad32::forgetBluetoothKeys() { 199 | WAIT_FOR_SLAVE_SELECT(); 200 | // Send Command 201 | SpiDrv::sendCmd(BP32_FORGET_BLUETOOTH_KEYS, PARAM_NUMS_0); 202 | 203 | SpiDrv::spiSlaveDeselect(); 204 | // Wait the reply elaboration 205 | SpiDrv::waitForSlaveReady(); 206 | SpiDrv::spiSlaveSelect(); 207 | 208 | // Wait for reply 209 | uint8_t data, dataLen; 210 | if (!SpiDrv::waitResponseCmd(BP32_FORGET_BLUETOOTH_KEYS, PARAM_NUMS_1, &data, 211 | &dataLen)) { 212 | WARN("error waitResponse"); 213 | } 214 | SpiDrv::spiSlaveDeselect(); 215 | } 216 | 217 | void Bluepad32::enableNewBluetoothConnections(bool enabled) { 218 | WAIT_FOR_SLAVE_SELECT(); 219 | uint8_t en = enabled; 220 | 221 | // Send Command 222 | SpiDrv::sendCmd(BP32_ENABLE_BLUETOOTH_CONNECTIONS, PARAM_NUMS_1); 223 | 224 | SpiDrv::sendParam(&en, 1, LAST_PARAM); 225 | 226 | // pad to multiple of 4 227 | SpiDrv::readChar(); 228 | SpiDrv::readChar(); 229 | 230 | SpiDrv::spiSlaveDeselect(); 231 | // Wait the reply elaboration 232 | SpiDrv::waitForSlaveReady(); 233 | SpiDrv::spiSlaveSelect(); 234 | 235 | // Wait for reply 236 | uint8_t data, dataLen; 237 | SpiDrv::waitResponseCmd(BP32_ENABLE_BLUETOOTH_CONNECTIONS, PARAM_NUMS_1, 238 | &data, &dataLen); 239 | 240 | SpiDrv::spiSlaveDeselect(); 241 | } 242 | 243 | void Bluepad32::setup(const ControllerCallback& onConnect, 244 | const ControllerCallback& onDisconnect) { 245 | _onConnect = onConnect; 246 | _onDisconnect = onDisconnect; 247 | 248 | checkProtocol(); 249 | } 250 | 251 | void Bluepad32::checkProtocol() { 252 | WAIT_FOR_SLAVE_SELECT(); 253 | // Send Command 254 | SpiDrv::sendCmd(BP32_GET_PROTOCOL_VERSION, PARAM_NUMS_0); 255 | 256 | SpiDrv::spiSlaveDeselect(); 257 | // Wait the reply elaboration 258 | SpiDrv::waitForSlaveReady(); 259 | SpiDrv::spiSlaveSelect(); 260 | 261 | // Wait for reply 262 | uint8_t dataLen; 263 | uint8_t data[2]; 264 | if (!SpiDrv::waitResponseCmd(BP32_GET_PROTOCOL_VERSION, PARAM_NUMS_1, data, 265 | &dataLen)) { 266 | WARN("error waitResponse"); 267 | } 268 | 269 | SpiDrv::spiSlaveDeselect(); 270 | 271 | if (dataLen != 2) { 272 | WARN("Invalid dataLen"); 273 | } 274 | INFO("Protocol version: %d.%d", data[0], data[1]); 275 | _protocolVersionHi = data[0]; 276 | _protocolVersionLow = data[1]; 277 | } 278 | 279 | const uint8_t* Bluepad32::localBdAddress() { 280 | static uint8_t bdaddr[6]; 281 | uint8_t dataLen = 0; 282 | 283 | WAIT_FOR_SLAVE_SELECT(); 284 | 285 | // Send Command 286 | SpiDrv::sendCmd(GET_MACADDR_CMD, PARAM_NUMS_1); 287 | 288 | uint8_t _dummy = DUMMY_DATA; 289 | SpiDrv::sendParam(&_dummy, 1, LAST_PARAM); 290 | 291 | // pad to multiple of 4 292 | SpiDrv::readChar(); 293 | SpiDrv::readChar(); 294 | 295 | SpiDrv::spiSlaveDeselect(); 296 | // Wait the reply elaboration 297 | SpiDrv::waitForSlaveReady(); 298 | SpiDrv::spiSlaveSelect(); 299 | 300 | // Wait for reply 301 | SpiDrv::waitResponseCmd(GET_MACADDR_CMD, PARAM_NUMS_1, bdaddr, &dataLen); 302 | 303 | SpiDrv::spiSlaveDeselect(); 304 | 305 | return bdaddr; 306 | } 307 | 308 | Bluepad32 BP32; -------------------------------------------------------------------------------- /src/utility/spi_drv.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | spi_drv.cpp - Library for Arduino Wifi shield. 3 | Copyright (c) 2018 Arduino SA. All rights reserved. 4 | Copyright (c) 2011-2014 Arduino. All right reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #include "utility/spi_drv.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 28 | 29 | // check if a bitstream is already included 30 | #if __has_include() 31 | // yes, so use the existing VidorFPGA include 32 | #include 33 | #else 34 | // otherwise, fallback to VidorPeripherals and it's bitstream 35 | #include 36 | #endif 37 | 38 | #define NINA_GPIO0 FPGA_NINA_GPIO0 39 | #define SPIWIFI_SS FPGA_SPIWIFI_SS 40 | #define SPIWIFI_ACK FPGA_SPIWIFI_ACK 41 | #define SPIWIFI_RESET FPGA_SPIWIFI_RESET 42 | 43 | #define pinMode(pin, mode) FPGA.pinMode(pin, mode) 44 | #define digitalRead(pin) FPGA.digitalRead(pin) 45 | #define digitalWrite(pin, value) FPGA.digitalWrite(pin, value) 46 | #endif 47 | 48 | //#define _DEBUG_ 49 | extern "C" { 50 | #include "utility/debug.h" 51 | } 52 | 53 | static uint8_t SLAVESELECT = 10; // ss 54 | static uint8_t SLAVEREADY = 7; // handshake pin 55 | static uint8_t SLAVERESET = 5; // reset pin 56 | 57 | static bool inverted_reset = false; 58 | 59 | #define DELAY_TRANSFER() 60 | 61 | #ifndef SPIWIFI 62 | #define SPIWIFI SPI 63 | #endif 64 | 65 | #ifndef NINA_GPIOIRQ 66 | #define NINA_GPIOIRQ NINA_GPIO0 67 | #endif 68 | 69 | bool SpiDrv::initialized = false; 70 | 71 | void SpiDrv::begin() { 72 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 73 | FPGA.begin(); 74 | #endif 75 | 76 | #ifdef SPIWIFI_SS 77 | SLAVESELECT = SPIWIFI_SS; 78 | #endif 79 | 80 | #ifdef SPIWIFI_ACK 81 | SLAVEREADY = SPIWIFI_ACK; 82 | #endif 83 | 84 | #ifdef SPIWIFI_RESET 85 | SLAVERESET = (uint8_t)SPIWIFI_RESET; 86 | #endif 87 | 88 | #ifdef ARDUINO_SAMD_MKRVIDOR4000 89 | inverted_reset = false; 90 | #else 91 | if (SLAVERESET > PINS_COUNT) { 92 | inverted_reset = true; 93 | SLAVERESET = ~SLAVERESET; 94 | } 95 | #endif 96 | 97 | pinMode(SLAVESELECT, OUTPUT); 98 | pinMode(SLAVEREADY, INPUT); 99 | pinMode(SLAVERESET, OUTPUT); 100 | pinMode(NINA_GPIO0, OUTPUT); 101 | 102 | digitalWrite(NINA_GPIO0, HIGH); 103 | digitalWrite(SLAVESELECT, HIGH); 104 | digitalWrite(SLAVERESET, inverted_reset ? HIGH : LOW); 105 | delay(10); 106 | digitalWrite(SLAVERESET, inverted_reset ? LOW : HIGH); 107 | delay(750); 108 | 109 | digitalWrite(NINA_GPIO0, LOW); 110 | pinMode(NINA_GPIOIRQ, INPUT); 111 | 112 | SPIWIFI.begin(); 113 | 114 | #ifdef _DEBUG_ 115 | INIT_TRIGGER() 116 | #endif 117 | 118 | initialized = true; 119 | } 120 | 121 | void SpiDrv::end() { 122 | digitalWrite(SLAVERESET, inverted_reset ? HIGH : LOW); 123 | 124 | pinMode(SLAVESELECT, INPUT); 125 | 126 | SPIWIFI.end(); 127 | 128 | initialized = false; 129 | } 130 | 131 | void SpiDrv::spiSlaveSelect() { 132 | SPIWIFI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); 133 | digitalWrite(SLAVESELECT, LOW); 134 | 135 | // wait for up to 5 ms for the NINA to indicate it is not ready for transfer 136 | // the timeout is only needed for the case when the shield or module is not 137 | // present 138 | for (unsigned long start = millis(); 139 | (digitalRead(SLAVEREADY) != HIGH) && (millis() - start) < 5;) 140 | ; 141 | } 142 | 143 | void SpiDrv::spiSlaveDeselect() { 144 | digitalWrite(SLAVESELECT, HIGH); 145 | SPIWIFI.endTransaction(); 146 | } 147 | 148 | char SpiDrv::spiTransfer(volatile char data) { 149 | char result = SPIWIFI.transfer(data); 150 | DELAY_TRANSFER(); 151 | 152 | return result; // return the received byte 153 | } 154 | 155 | int SpiDrv::waitSpiChar(unsigned char waitChar) { 156 | int timeout = TIMEOUT_CHAR; 157 | unsigned char _readChar = 0; 158 | do { 159 | _readChar = readChar(); // get data byte 160 | if (_readChar == ERR_CMD) { 161 | WARN("Err cmd received\n"); 162 | return -1; 163 | } 164 | } while ((timeout-- > 0) && (_readChar != waitChar)); 165 | return (_readChar == waitChar); 166 | } 167 | 168 | int SpiDrv::readAndCheckChar(char checkChar, char* readChar) { 169 | getParam((uint8_t*)readChar); 170 | 171 | return (*readChar == checkChar); 172 | } 173 | 174 | char SpiDrv::readChar() { 175 | uint8_t readChar = 0; 176 | getParam(&readChar); 177 | return readChar; 178 | } 179 | 180 | #define WAIT_START_CMD(x) waitSpiChar(START_CMD) 181 | 182 | #define IF_CHECK_START_CMD(x) \ 183 | if (!WAIT_START_CMD(_data)) { \ 184 | TOGGLE_TRIGGER() \ 185 | WARN("Error waiting START_CMD"); \ 186 | return 0; \ 187 | } else 188 | 189 | #define CHECK_DATA(check, x) \ 190 | if (!readAndCheckChar(check, &x)) { \ 191 | TOGGLE_TRIGGER() \ 192 | WARN("Reply error"); \ 193 | INFO2(check, (uint8_t)x); \ 194 | return 0; \ 195 | } else 196 | 197 | #define waitSlaveReady() (digitalRead(SLAVEREADY) == LOW) 198 | #define waitSlaveSign() (digitalRead(SLAVEREADY) == HIGH) 199 | #define waitSlaveSignalH() \ 200 | while (digitalRead(SLAVEREADY) != HIGH) { \ 201 | } 202 | #define waitSlaveSignalL() \ 203 | while (digitalRead(SLAVEREADY) != LOW) { \ 204 | } 205 | 206 | void SpiDrv::waitForSlaveSign() { 207 | while (!waitSlaveSign()) 208 | ; 209 | } 210 | 211 | void SpiDrv::waitForSlaveReady(bool const feed_watchdog) { 212 | unsigned long const start = millis(); 213 | while (!waitSlaveReady()) { 214 | if (feed_watchdog) { 215 | if ((millis() - start) < 10000) { 216 | // FIXME: Add support for watchdog 217 | // WiFi.feedWatchdog(); 218 | } 219 | } 220 | } 221 | } 222 | 223 | void SpiDrv::getParam(uint8_t* param) { 224 | // Get Params data 225 | *param = spiTransfer(DUMMY_DATA); 226 | DELAY_TRANSFER(); 227 | } 228 | 229 | int SpiDrv::waitResponseCmd(uint8_t cmd, uint8_t numParam, uint8_t* param, 230 | uint8_t* param_len) { 231 | char _data = 0; 232 | int ii = 0; 233 | 234 | IF_CHECK_START_CMD(_data) { 235 | CHECK_DATA(cmd | REPLY_FLAG, _data){}; 236 | 237 | CHECK_DATA(numParam, _data) { 238 | readParamLen8(param_len); 239 | for (ii = 0; ii < (*param_len); ++ii) { 240 | // Get Params data 241 | // param[ii] = spiTransfer(DUMMY_DATA); 242 | getParam(¶m[ii]); 243 | } 244 | } 245 | 246 | readAndCheckChar(END_CMD, &_data); 247 | } 248 | 249 | return 1; 250 | } 251 | /* 252 | int SpiDrv::waitResponse(uint8_t cmd, uint8_t numParam, uint8_t* param, 253 | uint16_t* param_len) 254 | { 255 | char _data = 0; 256 | int i =0, ii = 0; 257 | 258 | IF_CHECK_START_CMD(_data) 259 | { 260 | CHECK_DATA(cmd | REPLY_FLAG, _data){}; 261 | 262 | CHECK_DATA(numParam, _data); 263 | { 264 | readParamLen16(param_len); 265 | for (ii=0; ii<(*param_len); ++ii) 266 | { 267 | // Get Params data 268 | param[ii] = spiTransfer(DUMMY_DATA); 269 | } 270 | } 271 | 272 | readAndCheckChar(END_CMD, &_data); 273 | } 274 | 275 | return 1; 276 | } 277 | */ 278 | 279 | int SpiDrv::waitResponseData16(uint8_t cmd, uint8_t* param, 280 | uint16_t* param_len) { 281 | char _data = 0; 282 | uint16_t ii = 0; 283 | 284 | IF_CHECK_START_CMD(_data) { 285 | CHECK_DATA(cmd | REPLY_FLAG, _data){}; 286 | 287 | uint8_t numParam = readChar(); 288 | if (numParam != 0) { 289 | readParamLen16(param_len); 290 | for (ii = 0; ii < (*param_len); ++ii) { 291 | // Get Params data 292 | param[ii] = spiTransfer(DUMMY_DATA); 293 | } 294 | } 295 | 296 | readAndCheckChar(END_CMD, &_data); 297 | } 298 | 299 | return 1; 300 | } 301 | 302 | int SpiDrv::waitResponseData8(uint8_t cmd, uint8_t* param, uint8_t* param_len) { 303 | char _data = 0; 304 | int ii = 0; 305 | 306 | IF_CHECK_START_CMD(_data) { 307 | CHECK_DATA(cmd | REPLY_FLAG, _data){}; 308 | 309 | uint8_t numParam = readChar(); 310 | if (numParam != 0) { 311 | readParamLen8(param_len); 312 | for (ii = 0; ii < (*param_len); ++ii) { 313 | // Get Params data 314 | param[ii] = spiTransfer(DUMMY_DATA); 315 | } 316 | } 317 | 318 | readAndCheckChar(END_CMD, &_data); 319 | } 320 | 321 | return 1; 322 | } 323 | 324 | int SpiDrv::waitResponseParams(uint8_t cmd, uint8_t numParam, tParam* params) { 325 | char _data = 0; 326 | int i = 0, ii = 0; 327 | 328 | IF_CHECK_START_CMD(_data) { 329 | CHECK_DATA(cmd | REPLY_FLAG, _data){}; 330 | 331 | uint8_t _numParam = readChar(); 332 | if (_numParam != 0) { 333 | for (i = 0; i < _numParam; ++i) { 334 | params[i].paramLen = readParamLen8(); 335 | for (ii = 0; ii < params[i].paramLen; ++ii) { 336 | // Get Params data 337 | params[i].param[ii] = spiTransfer(DUMMY_DATA); 338 | } 339 | } 340 | } else { 341 | WARN("Error numParam == 0"); 342 | return 0; 343 | } 344 | 345 | if (numParam != _numParam) { 346 | WARN("Mismatch numParam"); 347 | return 0; 348 | } 349 | 350 | readAndCheckChar(END_CMD, &_data); 351 | } 352 | return 1; 353 | } 354 | 355 | /* 356 | int SpiDrv::waitResponse(uint8_t cmd, tParam* params, uint8_t* numParamRead, 357 | uint8_t maxNumParams) 358 | { 359 | char _data = 0; 360 | int i =0, ii = 0; 361 | 362 | IF_CHECK_START_CMD(_data) 363 | { 364 | CHECK_DATA(cmd | REPLY_FLAG, _data){}; 365 | 366 | uint8_t numParam = readChar(); 367 | 368 | if (numParam > maxNumParams) 369 | { 370 | numParam = maxNumParams; 371 | } 372 | *numParamRead = numParam; 373 | if (numParam != 0) 374 | { 375 | for (i=0; i maxNumParams) { 409 | numParam = maxNumParams; 410 | } 411 | *numParamRead = numParam; 412 | if (numParam != 0) { 413 | for (i = 0; i < numParam; ++i) { 414 | uint8_t paramLen = readParamLen8(); 415 | for (ii = 0; ii < paramLen; ++ii) { 416 | // Get Params data 417 | data[i * paramLen + ii] = spiTransfer(DUMMY_DATA); 418 | } 419 | } 420 | } else { 421 | WARN("Error numParams == 0"); 422 | readAndCheckChar(END_CMD, &_data); 423 | return 0; 424 | } 425 | readAndCheckChar(END_CMD, &_data); 426 | } 427 | return 1; 428 | } 429 | 430 | void SpiDrv::sendParamNoLen(uint8_t* param, size_t param_len, 431 | uint8_t lastParam) { 432 | size_t i = 0; 433 | // Send Spi paramLen 434 | sendParamLen8(0); 435 | 436 | // Send Spi param data 437 | for (i = 0; i < param_len; ++i) { 438 | spiTransfer(param[i]); 439 | } 440 | 441 | // if lastParam==1 Send Spi END CMD 442 | if (lastParam == 1) spiTransfer(END_CMD); 443 | } 444 | 445 | void SpiDrv::sendParam(uint8_t* param, uint8_t param_len, uint8_t lastParam) { 446 | int i = 0; 447 | // Send Spi paramLen 448 | sendParamLen8(param_len); 449 | 450 | // Send Spi param data 451 | for (i = 0; i < param_len; ++i) { 452 | spiTransfer(param[i]); 453 | } 454 | 455 | // if lastParam==1 Send Spi END CMD 456 | if (lastParam == 1) spiTransfer(END_CMD); 457 | } 458 | 459 | void SpiDrv::sendParamLen8(uint8_t param_len) { 460 | // Send Spi paramLen 461 | spiTransfer(param_len); 462 | } 463 | 464 | void SpiDrv::sendParamLen16(uint16_t param_len) { 465 | // Send Spi paramLen 466 | spiTransfer((uint8_t)((param_len & 0xff00) >> 8)); 467 | spiTransfer((uint8_t)(param_len & 0xff)); 468 | } 469 | 470 | uint8_t SpiDrv::readParamLen8(uint8_t* param_len) { 471 | uint8_t _param_len = spiTransfer(DUMMY_DATA); 472 | if (param_len != NULL) { 473 | *param_len = _param_len; 474 | } 475 | return _param_len; 476 | } 477 | 478 | uint16_t SpiDrv::readParamLen16(uint16_t* param_len) { 479 | uint16_t _param_len = 480 | spiTransfer(DUMMY_DATA) << 8 | (spiTransfer(DUMMY_DATA) & 0xff); 481 | if (param_len != NULL) { 482 | *param_len = _param_len; 483 | } 484 | return _param_len; 485 | } 486 | 487 | void SpiDrv::sendBuffer(uint8_t* param, uint16_t param_len, uint8_t lastParam) { 488 | uint16_t i = 0; 489 | 490 | // Send Spi paramLen 491 | sendParamLen16(param_len); 492 | 493 | // Send Spi param data 494 | for (i = 0; i < param_len; ++i) { 495 | spiTransfer(param[i]); 496 | } 497 | 498 | // if lastParam==1 Send Spi END CMD 499 | if (lastParam == 1) spiTransfer(END_CMD); 500 | } 501 | 502 | void SpiDrv::sendParam(uint16_t param, uint8_t lastParam) { 503 | // Send Spi paramLen 504 | sendParamLen8(2); 505 | 506 | spiTransfer((uint8_t)((param & 0xff00) >> 8)); 507 | spiTransfer((uint8_t)(param & 0xff)); 508 | 509 | // if lastParam==1 Send Spi END CMD 510 | if (lastParam == 1) spiTransfer(END_CMD); 511 | } 512 | 513 | // clang-format off 514 | /* Cmd Struct Message */ 515 | /* _________________________________________________________________________________ */ 516 | /* | START CMD | C/R | CMD |[TOT LEN]| N.PARAM | PARAM LEN | PARAM | .. | END CMD | */ 517 | /* |___________|______|______|_________|_________|___________|________|____|_________| */ 518 | /* | 8 bit | 1bit | 7bit | 8bit | 8bit | 8bit | nbytes | .. | * 8bit | */ 519 | /* |___________|______|______|_________|_________|___________|________|____|_________| */ 520 | // clang-format on 521 | 522 | void SpiDrv::sendCmd(uint8_t cmd, uint8_t numParam) { 523 | // Send Spi START CMD 524 | spiTransfer(START_CMD); 525 | 526 | // Send Spi C + cmd 527 | spiTransfer(cmd & ~(REPLY_FLAG)); 528 | 529 | // Send Spi totLen 530 | // spiTransfer(totLen); 531 | 532 | // Send Spi numParam 533 | spiTransfer(numParam); 534 | 535 | // If numParam == 0 send END CMD 536 | if (numParam == 0) spiTransfer(END_CMD); 537 | } 538 | 539 | int SpiDrv::available() { return (digitalRead(NINA_GPIOIRQ) != LOW); } 540 | 541 | SpiDrv spiDrv; 542 | --------------------------------------------------------------------------------