8000 Bluetooth Master HID and musical keyboard example (#2195) · TGralla/arduino-pico@f6d13d2 · GitHub
[go: up one dir, main page]

Skip to content

Commit f6d13d2

Browse files
Bluetooth Master HID and musical keyboard example (earlephilhower#2195)
Adds BluetoothHIDMaster and HIDKeyStream which let the PicoW connect to and use Bluetooth Classic HID devices like keyboards and mice. An example that lets the PicoW use a BT keyboard as a piano is included and shows the use of the new classes.
1 parent f786583 commit f6d13d2

File tree

6 files changed

+1031
-1
lines changed

6 files changed

+1031
-1
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// KeyboardPiano example - Released to the public domain in 2024 by Earle F. Philhower, III
2+
//
3+
// Demonstrates using the BluetoothHIDMaster class to use a Bluetooth keyboard as a
4+
// piano keyboard on the PicoW
5+
//
6+
// Hook up a phono plug to GP0 and GP1 (and GND of course...the 1st 3 pins on the PCB)
7+
// Connect wired earbuds up and connect over BT from your keyboard and play some music.
8+
9+
10+
#include <BluetoothHIDMaster.h>
11+
#include <PWMAudio.h>
12+
13+
// We need the inverse map, borrow from the Keyboard library
14+
#include <HID_Keyboard.h>
15+
extern const uint8_t KeyboardLayout_en_US[128];
16+
17+
BluetoothHIDMaster hid;
18+
PWMAudio pwm;
19+
HIDKeyStream keystream;
20+
21+
22+
int16_t sine[1000]; // One complete precalculated sine wave for oscillator use
23+
void precalculateSine() {
24+
for (int i = 0; i < 1000; i++) {
25+
sine[i] = (int16_t)(2000.0 * sin(i * 2 * 3.14159 / 1000.0)); // Only make amplitude ~1/16 max so we can sum up w/o clipping
26+
}
27+
}
28+
29+
// Simple variable frequency resampling oscillator state
30+
typedef struct {
31+
uint32_t key; // Identifier of which key started this tone
32+
uint32_t pos; // Current sine offset
33+
uint32_t step; // Delta in fixed point 16p16 format
34+
} Oscillator;
35+
Oscillator osc[6]; // Look, ma! 6-note polyphony!
36+
37+
// Quiet down, now!
38+
void silenceOscillators() {
39+
noInterrupts();
40+
for (int i = 0; i < 6; i++) {
41+
osc[i].pos = 0;
42+
osc[i].step = 0;
43+
}
44+
interrupts();
45+
}
46+
47+
// PWM callback, generates sum of online oscillators
48+
void fill() {
49+
int num_samples = pwm.availableForWrite() / 2;
50+
int16_t buff[32 * 2];
51+
52+
while (num_samples > 63) {
53+
// Run in 32 LR sample chunks for speed, less loop overhead
54+
for (int o = 0; o < 32; o++) {
55+
int32_t sum = 0;
56+
for (int i = 0; i < 6; i++) {
57+
if (osc[i].step) {
58+
sum += sine[osc[i].pos >> 16];
59+
osc[i].pos += osc[i].step;
60+
while (osc[i].pos >= 1000 << 16) {
61+
osc[i].pos -= 1000 << 16;
62+
}
63+
}
64+
}
65+
if (sum > 32767) {
66+
sum = 32767;
67+
} else if (sum < -32767) {
68+
sum = -32767;
69+
}
70+
buff[o * 2] = (int16_t) sum;
71+
buff[o * 2 + 1] = (int16_t) sum;
72+
}
73+
pwm.write((const uint8_t *)buff, sizeof(buff));
74+
num_samples -= 64;
75+
}
76+
}
77+
78+
// Mouse callbacks. Could keep track of global mouse position, update a cursor, etc.
79+
void mm(void *cbdata, int dx, int dy, int dw) {
80+
(void) cbdata;
81+
Serial.printf("Mouse: X:%d Y:%d Wheel:%d\n", dx, dy, dw);
82+
}
83+
84+
// Buttons are sent separately from movement
85+
void mb(void *cbdata, int butt, bool down) {
86+
(void) cbdata;
87+
Serial.printf("Mouse: Button %d %s\n", butt, down ? "DOWN" : "UP");
88+
}
89+
90+
// Convert a hertz floating point into a step fixed point 16p16
91+
inline uint32_t stepForHz(float hz) {
92+
const float stepHz = 1000.0 / 44100.0;
93+
const float step = hz * stepHz;
94+
return (uint32_t)(step * 65536.0);
95+
}
96+
97+
uint32_t keyStepMap[128]; // The frequency of any raw HID key
98+
void setupKeyStepMap() {
99+
for (int i = 0; i < 128; i++) {
100+
keyStepMap[i] = 0;
101+
}
102+
// Implements the "standard" PC keyboard to piano setup
103+
// https://ux.stackexchange.com/questions/46669/mapping-piano-keys-to-computer-keyboard
104+
keyStepMap[KeyboardLayout_en_US['a']] = stepForHz(261.6256);
105+
keyStepMap[KeyboardLayout_en_US['w']] = stepForHz(277.1826);
106+
keyStepMap[KeyboardLayout_en_US['s']] = stepForHz(293.6648);
107+
keyStepMap[KeyboardLayout_en_US['e']] = stepForHz(311.1270);
108+
keyStepMap[KeyboardLayout_en_US['d']] = stepForHz(329.6276);
109+
keyStepMap[KeyboardLayout_en_US['f']] = stepForHz(349.2282);
110+
keyStepMap[KeyboardLayout_en_US['t']] = stepForHz(369.9944);
111+
keyStepMap[KeyboardLayout_en_US['g']] = stepForHz(391.9954);
112+
keyStepMap[KeyboardLayout_en_US['y']] = stepForHz(415.3047);
113+
keyStepMap[KeyboardLayout_en_US['h']] = stepForHz(440.0000);
114+
keyStepMap[KeyboardLayout_en_US['u']] = stepForHz(466.1638);
115+
keyStepMap[KeyboardLayout_en_US['j']] = stepForHz(493.8833);
116+
keyStepMap[KeyboardLayout_en_US['k']] = stepForHz(523.2511);
117+
keyStepMap[KeyboardLayout_en_US['o']] = stepForHz(554.3653);
118+
keyStepMap[KeyboardLayout_en_US['l']] = stepForHz(587.3295);
119+
keyStepMap[KeyboardLayout_en_US['p']] = stepForHz(622.2540);
120+
keyStepMap[KeyboardLayout_en_US[';']] = stepForHz(659.2551);
121+
keyStepMap[KeyboardLayout_en_US['\'']] = stepForHz(698.4565);
122+
}
123+
124+
// We get make/break for every key which lets us hold notes while a key is depressed
125+
void kb(void *cbdata, int key) {
126+
bool state = (bool)cbdata;
127+
if (state && key < 128) {
128+
// Starting a new note
129+
for (int i = 0; i < 6; i++) {
130+
if (osc[i].step == 0) {
131+
// This one is free
132+
osc[i].key = key;
133+
osc[i].pos = 0;
134+
osc[i].step = keyStepMap[key];
135+
break;
136+
}
137+
}
138+
} else {
139+
for (int i = 0; i < 6; i++) {
140+
if (osc[i].key == (uint32_t)key) {
141+
osc[i].step = 0;
142+
break;
143+
}
144+
}
145+
}
146+
// The HIDKeyStream object converts a key and state into ASCII. HID key IDs do not map 1:1 to ASCII!
147+
// Write the key and make/break state, then read 1 ASCII char back out.
148+
keystream.write((uint8_t)key);
149+
keystream.write((uint8_t)state);
150+
Serial.printf("Keyboard: %02x %s = '%c'\n", key, state ? "DOWN" : "UP", state ? keystream.read() : '-');
151+
}
152+
153+
154+
// Consumer keys are the special media keys on most modern keyboards (mute/etc.)
155+
void ckb(void *cbdata, int key) {
156+
bool state = (bool)cbdata;
157+
Serial.printf("Consumer: %02x %s\n", key, state ? "DOWN" : "UP");
158+
}
159+
160+
161+
void setup() {
162+
Serial.begin();
163+
delay(3000);
164+
165+
Serial.printf("Starting HID master, put your device in pairing mode now.\n");
166+
167+
// Init the sound generator
168+
precalculateSine();
169+
silenceOscillators();
170+
setupKeyStepMap();
171+
172+
// Setup the HID key to ASCII conversion
173+
keystream.begin();
174+
175+
// Init the PWM audio output
176+
pwm.setStereo(true);
177+
pwm.setBuffers(16, 64);
178+
pwm.onTransmit(fill);
179+
pwm.begin(44100);
180+
181+
// Mouse buttons and movement reported separately
182+
hid.onMouseMove(mm);
183+
hid.onMouseButton(mb);
184+
185+
// We can use the cbData as a flag to see if we're making or breaking a key
186+
hid.onKeyDown(kb, (void *)true);
187+
hid.onKeyUp(kb, (void *)false);
188+
189+
// Consumer keys are the special function ones like "mute" or "home"
190+
hid.onConsumerKeyDown(ckb, (void *)true);
191+
hid.onConsumerKeyUp(ckb, (void *)false);
192+
193+
hid.begin();
194+
195+
hid.connectKeyboard();
196+
// or hid.connectMouse();
197+
}
198+
199+
void loop() {
200+
if (BOOTSEL) {
201+
while (BOOTSEL) {
202+
delay(1);
203+
}
204+
hid.disconnect();
205+
hid.clearPairing();
206+
Serial.printf("Restarting HID master, put your device in pairing mode now.\n");
207+
hid.connectKeyboard();
208+
}
209+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#######################################
2+
# Syntax Coloring Map
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
BluetoothHIDMaster KEYWORD1
10+
HIDKeyStream KEYWORD1
11+
12+
#######################################
13+
# Methods and Functions (KEYWORD2)
14+
#######################################
15+
begin KEYWORD2
16+
end KEYWORD2
17+
18+
scan KEYWORD2
19+
scanAsyncDone KEYWORD2
20+
scanAsyncResult KEYWORD2
21+
22+
connectKeyboard KEYWORD2
23+
connectMouse KEYWORD2
24+
25+
hidConnected KEYWORD2
26+
onMouseMove KEYWORD2
27+
onMouseButton KEYWORD2
28+
onKeyDown KEYWORD2
29+
onKeyUp KEYWORD2
30+
onConsumerKeyDown KEYWORD2
31+
onConsumerKeyUp KEYWORD2
32+
33+
# BTDeviceInfo
34+
deviceClass KEYWORD2
35+
address KEYWORD2
36+
addressString KEYWORD2
37+
rssi KEYWORD2
38+
name KEYWORD2
39+
40+
#######################################
41+
# Constants (LITERAL1)
42+
#######################################
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name=BluetoothHIDMaster
2+
version=1.0
3+
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
4+
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
5+
sentence=Classic Bluetooth HID (Keyboard/Mouse/Joystick) master mode
6+
paragraph=Classic Bluetooth HID (Keyboard/Mouse/Joystick) master mode
7+
category=Communication
8+
url=http://github.com/earlephilhower/arduino-pico
9+
architectures=rp2040
10+
dot_a_linkage=true
11+
depends=BluetoothHCI

0 commit comments

Comments
 (0)
0