GamepadCodec Arduino Lib 1.0.1
Loading...
Searching...
No Matches
inputs_detection.ino
1/**
2 * @~English
3 * @file decoder/ble_uno_or_nl_16_module/basic_polling/basic_polling.ino
4 * @example decoder/ble_uno_or_nl_16_module/basic_polling/basic_polling.ino
5 * @brief The demonstration periodically prints the real-time button status and joystick movements of connected controllers
6 * using a basic polling method.
7 * @details This example first sends AT commands to establish a Bluetooth connection to a target device, then continuously
8 * monitors all user inputs. It showcases the detection of three distinct button states:
9 * **pressed** (momentary press), **released** (momentary release), and **holding** (sustained press). It also
10 * monitors the analog joystick axes and prints their values when a significant change beyond a set threshold is
11 * detected, filtering out minor jitter.
12 * @note The `Update()` method must be called as frequently as possible within the main loop without delays to ensure
13 * real-time responsiveness and prevent data packet loss.
14 * @see gamepad::codec::Decoder::Update
15 */
16/**
17 * @~Chinese
18 * @file decoder/ble_uno_or_nl_16_module/basic_polling/basic_polling.ino
19 * @example decoder/ble_uno_or_nl_16_module/basic_polling/basic_polling.ino
20 * @brief 演示通过基本轮询方式定期打印已连接手柄的实时按钮状态与摇杆移动。
21 * @details 本示例先通过 AT 指令与指定的蓝牙设备建立连接,然后持续监控所有用户输入。
22 * 它展示了三种不同的按钮状态检测:**按下**(瞬间按下)、**释放**(瞬间释放)和**持续按住**。
23 * 同时,它监控模拟摇杆轴,当检测到超过设定阈值的显著变化时打印其值,从而过滤微小抖动。
24 * @note 必须在主循环中尽可能频繁地调用 `Update()` 方法,且不得添加延时,以确保实时响应性并防止数据包丢失。
25 * @see gamepad::codec::Decoder::Update
26 */
27#include <SoftwareSerial.h>
28
30
31/**
32 * IMPORTANT:
33 * This using directive is REQUIRED to directly access `Button` and `Axis`.
34 * Without it, you must write the fully qualified names:
35 * gamepad::input::Button::kUp
36 * gamepad::input::Axis::kLeftStickX
37 * Forgetting this line will cause compile errors when using Button or Axis.
38 */
39/**
40 * 重要:
41 * 必须使用该命名空间,否则无法直接访问 `Button` 和 `Axis`。
42 * 如果没有这一行,就必须写成完整限定名:
43 * gamepad::input::Button::kUp
44 * gamepad::input::Axis::kLeftStickX
45 * 忘记引入命名空间会导致编译失败。
46 */
47using namespace gamepad::input;
48
49namespace {
50// Debug serial port pin definitions.
51// Connect external serial port tools to these pins to view debug output.
52// 调试串口引脚定义。
53// 将外部串口工具连接到这些引脚以查看调试输出。
54constexpr uint8_t kDebugSerialRxPin = 6; // RX pin for debug output | 调试输出接收引脚
55constexpr uint8_t kDebugSerialTxPin = 5; // TX pin for debug output | 调试输出发送引脚
56
57// Replace with your target device's Bluetooth device address.
58// 替换为目标设备的 Bluetooth device address。
59const String kBluetoothDeviceAddress = "16:00:00:00:03:05";
60
61// The serial port baud rate for communication with the Bluetooth module (please refer to the module data manual, replace with
62// the corresponding baud rate for the module).
63// 与蓝牙模块通信的串口波特率(请查阅模块数据手册,替换为模块对应的波特率)。
64constexpr uint32_t kBluetoothModuleSerialBaudRate = 115200;
65
66constexpr Button kAllButtons[] = {Button::kUp, Button::kDown, Button::kLeft, Button::kRight, Button::kSquareX,
67 Button::kTriangleY, Button::kCrossA, Button::kCircleB, Button::kL1, Button::kL2,
68 Button::kL3, Button::kR1, Button::kR2, Button::kR3, Button::kSelect,
69 Button::kStart, Button::kHome};
70
71// The serial instance passed in by the decoder here is the same as the one passed in Connect().
72// 此处解码器传入的串口实例,与 Connect() 中传入的为同一个串口实例。
73gamepad::codec::Decoder g_decoder(Serial);
74
75// A soft serial port used for debugging output.
76// 用于调试输出的软串口。
77SoftwareSerial g_debug_serial(kDebugSerialRxPin, kDebugSerialTxPin);
78
79const String ButtonToString(Button button) {
80 switch (button) {
81 case Button::kUp: {
82 return "Up"; // UP button | 上按钮
83 }
84 case Button::kDown: {
85 return "Down"; // DOWN button | 下按钮
86 }
87 case Button::kLeft: {
88 return "Left"; // LEFT button | 左按钮
89 }
90 case Button::kRight: {
91 return "Right"; // RIGHT button | 右按钮
92 }
93 case Button::kSquareX: {
94 return "Square(X)"; // SQUARE or X button | 方形 或者 X 按钮
95 }
96 case Button::kTriangleY: {
97 return "Triangle(Y)"; // TRIANGLE or Y button | 三角 或者 Y 按钮
98 }
99 case Button::kCrossA: {
100 return "Cross(A)"; // CROSS or A button | 叉型 或者 A 按钮
101 }
102 case Button::kCircleB: {
103 return "Circle(B)"; // CIRCLE or B button | 圆形 或者 B 按钮
104 }
105 case Button::kL1: {
106 return "L1"; // L1 button | L1按钮
107 }
108 case Button::kL2: {
109 return "L2"; // L2 button | L2按钮
110 }
111 case Button::kL3: {
112 return "L3"; // L3 button | L3按钮
113 }
114 case Button::kR1: {
115 return "R1"; // R1 button | R1按钮
116 }
117 case Button::kR2: {
118 return "R2"; // R2 button | R2按钮
119 }
120 case Button::kR3: {
121 return "R3"; // R3 button | R3按钮
122 }
123 case Button::kSelect: {
124 return "Select"; // SELECT button | 选择按钮
125 }
126 case Button::kStart: {
127 return "Start"; // START button | 开始按钮
128 }
129 case Button::kHome: {
130 return "Home"; // HOME button | 首页按钮
131 }
132 default: {
133 return {}; // Unknown button returns empty string | 未知按钮返回空字符串
134 }
135 }
136}
137
138/**
139 * @~English
140 * @brief Send AT commands to the Bluetooth module to establish a BLE connection.
141 * @details Executes a sequence of AT commands to configure the Bluetooth module as master and connect to the target device
142 * at the specified MAC address.
143 * Command order: AT+DISCON → AT+RESET → AT+ECHO=0 → AT+ROLE=0 → AT+AUTOCON=0 → AT+CON=<mac>.
144 * @param[in] bluetooth_stream Stream connected to the Bluetooth module.
145 * Note: This must be the same Stream instance that is passed to the decoder
146 * (e.g., Serial), as the decoder reads incoming data from this same serial port.
147 * @param[in] bluetooth_device_address MAC address of the target device in format "XX:XX:XX:XX:XX:XX".
148 */
149/**
150 * @~Chinese
151 * @brief 向蓝牙模块发送 AT 指令以建立 BLE 连接。
152 * @details 依次执行 AT 指令序列,将蓝牙模块配置为主机模式并连接到指定 MAC 地址的目标设备。
153 * 指令顺序:AT+DISCON → AT+RESET → AT+ECHO=0 → AT+ROLE=0 → AT+AUTOCON=0 → AT+CON=<mac>。
154 * @param[in] bluetooth_stream 连接蓝牙模块的 Stream 对象。
155 * 注意:此对象必须与传入解码器的 Stream 是同一个实例(如 Serial),
156 * 因为解码器正是从这同一个串口读取手柄发来的数据。
157 * @param[in] bluetooth_device_address 目标设备的 MAC 地址,格式为 "XX:XX:XX:XX:XX:XX"。
158 */
159void Connect(Stream &bluetooth_stream, const String &bluetooth_device_address) {
160 if (bluetooth_device_address.length() != 17 || bluetooth_device_address[2] != ':' || bluetooth_device_address[5] != ':' ||
161 bluetooth_device_address[8] != ':' || bluetooth_device_address[11] != ':' || bluetooth_device_address[14] != ':') {
162 g_debug_serial.println("Error: Invalid MAC address format. Expected: XX:XX:XX:XX:XX:XX");
163 while (true);
164 }
165
166 g_debug_serial.print("Start to connect ");
167 g_debug_serial.println(kBluetoothDeviceAddress);
168
169 // The module may be in a connected state. Send the disconnection command first to ensure the module is in an unconnected
170 // state.
171 // 模块可能处于连接状态,先发送断开指令,确保模块是未连接状态。
172 bluetooth_stream.println("AT+DISCON");
173 delay(100);
174
175 // Software reset BLE chip, clear all pairing and configuration data.
176 // 软件复位蓝牙芯片,清除所有配对和配置数据。
177 bluetooth_stream.println("AT+RESET");
178 delay(100);
179
180 // Close AT information echo.
181 // 关闭AT信息回显。
182 bluetooth_stream.println("AT+ECHO=0");
183 delay(100);
184
185 // Set the module to host mode so that it can actively connect to the BLE of the slave device.
186 // 设置模块为主机模式,使其能够主动连接从机蓝牙。
187 bluetooth_stream.println("AT+ROLE=0");
188 delay(100);
189
190 // Disable the module's automatic Bluetooth connection mode.
191 // 关闭模块的蓝牙自动连接模式。
192 bluetooth_stream.println("AT+AUTOCON=0");
193 delay(100);
194
195 // Initiate BLE connection with the slave using the specified MAC address.
196 // 使用指定的MAC地址发起与从机蓝牙连接。
197 bluetooth_stream.print("AT+CON=");
198 bluetooth_stream.println(bluetooth_device_address);
199 delay(100);
200
201 g_debug_serial.println("Connected");
202}
203} // namespace
204
205void setup() {
206 g_debug_serial.begin(115200);
207
208 Serial.begin(kBluetoothModuleSerialBaudRate);
209
210 Connect(Serial, kBluetoothDeviceAddress);
211}
212
213void loop() {
214 // ==========================================================================
215 // 🔴 CRITICAL: Call Update() as frequently as possible in loop()
216 // ==========================================================================
217 // • Update() processes incoming Bluetooth packets from the gamepad.
218 // • Any delay(...) or long blocking code WILL cause:
219 // - Packet loss
220 // - Input lag
221 // - Unstable connection
222 //
223 // • For real-time control, call Update() every loop iteration
224 // without any blocking operations
225 //
226 // 🔴【重要】Update() 必须在 loop() 中尽可能高频调用
227 // • Update() 负责处理来自手柄的蓝牙数据包。
228 // • 任何形式的 delay 或阻塞代码都会导致:
229 // - 数据丢失
230 // - 响应延迟
231 // - 连接不稳定
232 //
233 // • 实时控制应用中,必须每轮循环都调用 Update(),不可阻塞
234 // ==========================================================================
235 const Tracker &it = g_decoder.Update();
236 // ==========================================================================
237 // Tracker: Gamepad Input Snapshot & Change Engine
238 // ==========================================================================
239 // • Returned by Update()
240 // • Maintains previous and current input snapshots
241 // • Enables edge detection and delta detection
242 //
243 // Tracker 由 Update() 返回
244 // 内部保存上一帧和当前帧的输入数据
245 // 支持边沿检测和差值检测
246 //
247 // 📚 https://codexpad.github.io/gamepad_input_arduino_lib/
248 // ==========================================================================
249
250 // ==========================================================================
251 // 🟢 Button State Change Detection (Edge-Based)
252 // ==========================================================================
253 // • pressed() → button was just pressed (released → pressed)
254 // • released() → button was just released (pressed → released)
255 // • holding() → button remains pressed across frames
256 //
257 // These APIs are *frame-differential* and rely on Update() frequency.
258 // Ideal for UI navigation, action triggers, and avoiding repeat firing.
259 //
260 // • pressed() → 按钮刚被按下(弹起 → 按下)
261 // • released() → 按钮刚被释放(按下 → 弹起)
262 // • holding() → 按钮在两帧之间持续按下
263 //
264 // 这些接口是“帧间差分”的,依赖于 Update() 的高频调用
265 // 非常适合 UI 导航、动作触发和防止长按连发
266 // ==========================================================================
267 for (auto button : kAllButtons) {
268 if (it.pressed(button)) {
269 g_debug_serial.print("Button ");
270 g_debug_serial.print(ButtonToString(button));
271 g_debug_serial.println(": pressed");
272 } else if (it.released(button)) {
273 g_debug_serial.print("Button ");
274 g_debug_serial.print(ButtonToString(button));
275 g_debug_serial.println(": released");
276 } else if (it.holding(button)) {
277 g_debug_serial.print("Button ");
278 g_debug_serial.print(ButtonToString(button));
279 g_debug_serial.println(": holding");
280 }
281 }
282
283 // ==========================================================================
284 // 🟢 Joystick Axis Change Detection (Threshold-Based Filtering)
285 // ==========================================================================
286 // • AxisChanged() detects significant changes between frames
287 // • Uses a threshold to filter out noise and minor jitter
288 // • Only reports movement when change ≥ threshold
289 //
290 // • AxisChanged() 用于检测摇杆轴值的有效变化
291 // • 使用阈值过滤微小抖动和噪声
292 // • 只有当变化幅度 ≥ 阈值时才视为有效移动
293 // ==========================================================================
294 constexpr uint8_t kAxisValueChangeThreshold = 2;
295
296 if (it.AxisChanged(Axis::kLeftStickX, kAxisValueChangeThreshold) ||
297 it.AxisChanged(Axis::kLeftStickY, kAxisValueChangeThreshold) ||
298 it.AxisChanged(Axis::kRightStickX, kAxisValueChangeThreshold) ||
299 it.AxisChanged(Axis::kRightStickY, kAxisValueChangeThreshold)) {
300 g_debug_serial.print("L(X: ");
301 g_debug_serial.print(it[Axis::kLeftStickX]);
302 g_debug_serial.print(", Y: ");
303 g_debug_serial.print(it[Axis::kLeftStickY]);
304 g_debug_serial.print("), R(X: ");
305 g_debug_serial.print(it[Axis::kRightStickX]);
306 g_debug_serial.print(", Y: ");
307 g_debug_serial.print(it[Axis::kRightStickY]);
308 g_debug_serial.println(")");
309 }
310}
Decoder class responsible for parsing gamepad input frames from a byte stream and updating the state ...