-
Notifications
You must be signed in to change notification settings - Fork 0
/
MMBluefruit.ino
338 lines (290 loc) · 12.6 KB
/
MMBluefruit.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
/*********************************************************************
MAKING MUSIC 2019 Program Code -- Modified By Chris Uzokwe for the 2019 Apple Distinguished Educators Workshop
TODO:
1. Continue Testing Sequence Stepping Debounce Method
2. Add functionality to account for rests in automatic sequence
3. Work with swift front end to fix the "parseAndUpdate" function's intake of user data (less string packages = less chance of corruption -- so consolidate)
*********************************************************************/
/*=========================================================================
APPLICATION SETTINGS & LIBRARY INCLUDES*/
#include <Arduino.h>
#include <SPI.h>
#include <Servo.h>
#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_SPI.h"
#include "Adafruit_BluefruitLE_UART.h"
#include "BluefruitConfig.h"
#include "ServoHandler.h"
#if SOFTWARE_SERIAL_AVAILABLE
#include <SoftwareSerial.h>
#endif
//#define FACTORYRESET_ENABLE NaN // Place BLUEFRUIT in a 'known good' state for corrupted board debugging -- removed software capability, but this is still possible through hardware (DFU Pin to GND )
#define MINIMUM_FIRMWARE_VERSION "0.6.6" // Minimum Firmware Version need to run features
#define MODE_LED_BEHAVIOUR "MODE" // LED activity, valid options are
// "DISABLE" or "MODE" or "BLEUART" or
// "HWUART" or "SPI" or "MANUAL"
#define MSGDEBUG 0 // View incoming string messages from Swift Playground (in function "parseAndUpdate") for communication debugging
#define ANLOGDEBUG 0 // Display Servo analog input and digital output for manual servo operation debugging
/* Initialize Bluefruit Object as hardware SPI, Bluefruit is using SCK/MOSI/MISO hardware SPI pins and then user selected CS/IRQ/RST */
Adafruit_BluefruitLE_SPI ble(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST);
/*=========================================================================*/
/* SERVO INIT & IO */
const int pin_pairs = 4;
Servo servos[pin_pairs];
int servo_pins[pin_pairs] = {3,5,6,9}; // servo output pins
int analog_pins[pin_pairs] = {A0,A1,A2,A3}; // controller input pins
ServoHandler servo_obj[pin_pairs]; // Non-physical parameter handler for all servos
bool autoToggle = 0; // Auto Servos on(1)/off(0)
// A small helper
void error(const __FlashStringHelper*err) {
Serial.println(err);
while (1);
}
/**************************************************************************/
/*!@brief Sets up the HW an the BLE module (this function is called
automatically on startup)*/
/**************************************************************************/
void setup(void)
{
Serial.begin(115200);
/* SETUP SERVO PINS */
for (int i = 0; i < pin_pairs; i++) {
servos[i].attach(servo_pins[i]);
}
/*==========================================================================
* SETUP OF THE BLE MODULE WITH DEBUG INFO -- this connection is needed before the program starts
*/
if(Serial){
Serial.println(F("Adafruit Bluefruit Command Mode Example"));
Serial.println(F("---------------------------------------"));
/* Initialise the module */
Serial.print(F("Initialising the Bluefruit LE module: "));
}
if ( !ble.begin(VERBOSE_MODE) ){
error(F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
}
// if(Serial){
// Serial.println( F("OK!") );
// }
// /* Disable command echo from Bluefruit */
// ble.echo(false);
//
// if(Serial){
// Serial.println("Requesting Bluefruit info:");
// ble.info(); // Print Bluefruit information
// Serial.println(F("Please use Adafruit Bluefruit LE app to connect in UART mode"));
// Serial.println(F("Then Enter characters to send to Bluefruit"));
// Serial.println();
// }
// ble.verbose(false); // debug info is a little annoying after this point!
// /* Wait for connection */
while (! ble.isConnected()){
delay(500);
}
// LED Activity command is only supported from 0.6.6 -- check before running program
if ( ble.isVersionAtLeast(MINIMUM_FIRMWARE_VERSION) ){
// Change Mode LED Activity
if(Serial){
Serial.println(F("******************************"));
Serial.println(F("Change LED activity to " MODE_LED_BEHAVIOUR));
}
ble.sendCommandCheckOK("AT+HWModeLED=" MODE_LED_BEHAVIOUR);
if(Serial){
Serial.println(F("******************************"));
}
}
/*===============================================================================================*/
}
/**************************************************************************/
// This loop handles all servo movement, while constantly polling for new parameters from the user.
/**************************************************************************/
void loop(void)
{
for(int i = 0; i < pin_pairs; i++){ // iterate through each instantiated servo object to check its parameters and write to the servo based on its parameters
if(servo_obj[i].control == 1){ // auto
if(autoToggle){ // on/off switch for automated servos
processAutomatic(servo_obj[i], i);
}
}
else if(servo_obj[i].control == 0){ // manual
processManual(servo_obj[i], i);
}
}
digitalWrite(13,LOW); // Servo params are not being written -- indicator off
/*Check for incoming characters from Bluefruit*/
ble.println("AT+BLEUARTRX");
ble.readline();
if (strcmp(ble.buffer, "OK") == 0){
// no data
return;
}
// Some data was found, its in the buffer
#if MSGDEBUG
Serial.print("data found...");
Serial.print(F("[Recv] "));
Serial.println(ble.buffer);
#endif
String mydata = String(ble.buffer); // load data into buffer string to parse
parseAndUpdate(mydata);
ble.waitForOK();
}
/**************************************************************************/
// Writes to specified automatic servo
/**************************************************************************/
void processAutomatic(ServoHandler &servo, int servoNum){
unsigned long elapsed = millis(); // Check total program run time, all servos are globally dependent on this clock
if(servo.mode == 0){
if(elapsed > servo.lastHitTime + servo.returnTime){ //
servos[servoNum].write(servo.positions[0]);
}
}
if(elapsed - servo.lastHitTime > servo.waitTime){
if(servo.mode == 0){
servos[servoNum].write(servo.positions[1]);
}
else{
servos[servoNum].write(servo.positions[servo.seqIdx]);
}
servo.lastHitTime = elapsed;
servo.sequenceStep();
}
}
/**************************************************************************/
// Writes to specified manual servo based on its operating mode (standard, positional, stepping)
/**************************************************************************/
void processManual(ServoHandler &servo, int servoNum){
/***MANUAL CONTROL***/
/*each analog pin is polled so its value can be written to appropriate servo....*/
int analog_value = analogRead(analog_pins[servoNum]); // <-- Breakout into function from here:
#if ANLOGDEBUG
Serial.print("SERVO: ");
Serial.print(servoNum+1);
Serial.print(" | MODE: ");
Serial.print(servo.mode);
Serial.print(" | ANALOGVAL: ");
Serial.println(analog_value);
Serial.print("VALUE WRITTEN: ");
#endif
int servo_angle = map(
analog_value, // Analog value
0, // Minimum analog value
1023, // Maximum analog value
servo.positions[0], // Minimum servo angle
servo.positions[1] // Maximum servo angle
);
/*...STANDARD SERVOS are mapped directly to angles recieved.*/
if(servo.mode == 0){
servos[servoNum].write(servo_angle);
#if ANLOGDEBUG
Serial.println(servo_angle);
#endif
}
/*.....if a servo is a QUANTIZED SERVO, depending on how many individual notes it has, potentiometer pins are locked between those values....*/
else if(servo.mode == 1){
int index = round( ( (float)analog_value/(float)1024.0 ) * (float)(servo.numNotes - 1) );
servos[servoNum].write(servo.positions[index]);
#if ANLOGDEBUG
Serial.println(servo.positions[index]);
#endif
}
/*A Mode of 2 Means every trigger will STEP the servo through its sequence*/
else if(servo.mode == 2){
if(analog_value < 511){
if(servo.stepReady){
servos[servoNum].write(servo.positions[servo.seqIdx]);
servo.stepReady = 0;
servo.sequenceStep();
}
}
else {
servo.stepReady = 1;
}
#if ANLOGDEBUG
Serial.println(servo.positions[servo.seqIdx]);
#endif
}
#if ANLOGDEBUG
Serial.println("-------------------------------------------------------");
#endif // < -- to Here
}
/**************************************************************************/
// decodes recieved strings and updates a servo object's parameters accordingly
/**************************************************************************/
void parseAndUpdate(String mydata){
//get function & params
String myfunc = mydata.substring(0,3);
int uartServo = mydata.substring(3,4).toInt();
mydata.remove(0,4);
int strLength = mydata.length();
#if MSGDEBUG
Serial.print("SERVO: ");
Serial.print(uartServo+1);
Serial.print(" | FUNC: ");
Serial.println(myfunc);
Serial.print("REMAINING STRING: ");
Serial.print(mydata);
Serial.print(" | LENGTH: ");
Serial.println(strLength);
#endif
if(myfunc == "BPM"){
setBPM(mydata.toInt());
#if MSGDEBUG
Serial.print("New BPM: ");
Serial.println(BPM);
#endif
}
else if(myfunc == "AUT"){
autoToggle = mydata.toInt();
#if MSGDEBUG
Serial.print("AUTO VAL: ");
Serial.println(autoToggle);
#endif
}
else if(myfunc == "POS"){
delete [] servo_obj[uartServo].positions;
servo_obj[uartServo].positions = new int [strLength];
servo_obj[uartServo].numNotes = strLength;
for(int i = 0; i < strLength; i++){
servo_obj[uartServo].positions[i] = (byte)mydata[i];
#if MSGDEBUG
Serial.print("Positions: ");
Serial.println(servo_obj[uartServo].positions[i]);
#endif
}
}
else if(myfunc == "TIM"){
delete [] servo_obj[uartServo].sequence;
servo_obj[uartServo].sequence = new int [strLength];
for(int i = 0; i < strLength; i++){
servo_obj[uartServo].sequence[i] = (byte)mydata[i];
#if MSGDEBUG
Serial.print("Servo timing: ");
Serial.println(servo_obj[uartServo].sequence[i]);
#endif
}
}
else if(myfunc == "RET"){
servo_obj[uartServo].returnTime = mydata.toInt();
#if MSGDEBUG
Serial.print("Servo return Time: ");
Serial.println( servo_obj[uartServo].returnTime);
#endif
}
else if(myfunc == "MOD"){
servo_obj[uartServo].mode = mydata.toInt();
#if MSGDEBUG
Serial.print("Servo mode: ");
Serial.println( servo_obj[uartServo].mode);
#endif
}
else if(myfunc == "CON"){
servo_obj[uartServo].control = mydata.toInt();
#if MSGDEBUG
Serial.print("Servo control: ");
Serial.println( servo_obj[uartServo].control);
#endif
}
#if MSGDEBUG
Serial.println("-----------------------------------------------------------------------------");
#endif
}