forked from rogerlinndesign/linnstrument-firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ls_serial.ino
475 lines (377 loc) · 11.4 KB
/
ls_serial.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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
/******************************** ls_serial: LinnStrument Serial **********************************
This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/
or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
***************************************************************************************************
**************************************************************************************************/
// Handshake codes for settings transfer
const char* countDownCode = "5, 4, 3, 2, 1 ...\n";
const byte countDownLength = 18;
const char* linnGoCode = "LinnStruments are go!\n";
const char* ackCode = "ACK\n";
const char* linnStrumentControlCode = "LC\n";
const byte linnStrumentControlLength = 3;
boolean waitingForCommands = false;
enum linnCommands {
ACK = 'a',
CRCCheck = 'c',
CRCWrong = 'w',
CRCOk = 'o',
LightLed = 'l',
SendSingleProject = 'j',
SendProjects = 'p',
RestoreProject = 'q',
RestoreSettings = 'r',
SendSettings = 's'
};
byte codePos = 0;
uint32_t lastSerialMoment = 0;
static PROGMEM prog_uint32_t crc_table[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
uint32_t crc_update(uint32_t crc, uint8_t data) {
uint8_t tbl_idx;
tbl_idx = crc ^ (data >> (0 * 4));
crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);
tbl_idx = crc ^ (data >> (1 * 4));
crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);
return crc;
}
uint32_t crc_byte_array(uint8_t* s, uint8_t size) {
uint32_t crc = ~0L;
for (uint8_t i = 0; i < size; ++i) {
crc = crc_update(crc, *s++);
}
crc = ~crc;
return crc;
}
void handleSerialIO() {
// if no serial data is available, return
if (Serial.available() <= 0) {
return;
}
// get the next byte from the serial bus
byte d = Serial.read();
// check for a recognized command
if (waitingForCommands) {
switch (d) {
case SendSettings:
{
serialSendSettings();
break;
}
case RestoreSettings:
{
serialRestoreSettings();
break;
}
case LightLed:
{
serialLightLed();
break;
}
case SendSingleProject:
{
serialSendSingleProject();
break;
}
case SendProjects:
{
serialSendProjects();
break;
}
case RestoreProject:
{
serialRestoreProject();
break;
}
default:
{
waitingForCommands = false;
break;
}
}
}
// handle readyness countdown state
else {
if (d == countDownCode[codePos]) {
codePos++;
if (codePos == countDownLength) {
codePos = 0;
waitingForCommands = true;
Serial.write(linnGoCode);
}
}
else if (d == linnStrumentControlCode[codePos]) {
codePos++;
if (codePos == linnStrumentControlLength) {
codePos = 0;
waitingForCommands = true;
controlModeActive = true;
clearDisplay();
updateDisplay();
Serial.write(ackCode);
}
}
else {
codePos = 0;
}
}
}
boolean waitForSerialAck() {
if (!serialWaitForMaximumTwoSeconds()) return false;
char ack = Serial.read();
lastSerialMoment = millis();
if (ack != ACK) return false;
return true;
}
boolean waitForSerialCheck() {
if (!serialWaitForMaximumTwoSeconds()) return false;
char ack = Serial.read();
lastSerialMoment = millis();
if (ack != CRCCheck) return false;
return true;
}
char waitForSerialCRC() {
if (!serialWaitForMaximumTwoSeconds()) return 0;
char ack = Serial.read();
lastSerialMoment = millis();
return ack;
}
int negotiateOutgoingCRC(byte* buffer, uint8_t size) {
uint32_t crc = crc_byte_array(buffer, size);
Serial.write((byte*)&crc, sizeof(uint32_t));
char crcresponse = waitForSerialCRC();
if (crcresponse == 0) return -1;
if (crcresponse == CRCWrong) return 0;
return 1;
}
int negotiateIncomingCRC(byte* buffer, uint8_t size) {
Serial.write(CRCCheck);
byte buff_crc[sizeof(uint32_t)];
for (byte k = 0; k < sizeof(uint32_t); ++k) {
if (!serialWaitForMaximumTwoSeconds()) return -1;
buff_crc[k] = Serial.read();
lastSerialMoment = millis();
}
uint32_t remote_crc;
memcpy(&remote_crc, buff_crc, sizeof(uint32_t));
uint32_t local_crc = crc_byte_array(buffer, size);
if (local_crc != remote_crc) {
Serial.write(CRCWrong);
return 0;
}
Serial.write(CRCOk);
return 1;
}
void serialSendSettings() {
Serial.write(ackCode);
clearDisplayImmediately();
delayUsec(1000);
int32_t confSize = sizeof(Configuration);
// send the size of the settings
Serial.write((byte*)&confSize, sizeof(int32_t));
// send the actual settings
const uint8_t batchsize = 96;
byte* src = (byte*)&config;
lastSerialMoment = millis();
while (confSize > 0) {
int actual = min(confSize, batchsize);
Serial.write(src, actual);
if (!waitForSerialCheck()) return;
int crc = negotiateOutgoingCRC(src, actual);
if (crc == -1) return;
else if (crc == 0) continue;
confSize -= actual;
src += actual;
}
Serial.write(ackCode);
}
boolean serialWaitForMaximumTwoSeconds() {
// retry if there's no data available
while (Serial.available() <= 0) {
// timeout after 2s if no data is coming in anymore
if (calcTimeDelta(millis(), lastSerialMoment) > 2000) {
waitingForCommands = false;
return false;
}
}
return true;
}
void serialRestoreSettings() {
Serial.write(ackCode);
clearDisplayImmediately();
delayUsec(1000);
// retrieve the size of the settings
lastSerialMoment = millis();
byte buff1[sizeof(int32_t)];
for (byte i = 0; i < sizeof(int32_t); ++i) {
if (!serialWaitForMaximumTwoSeconds()) return;
buff1[i] = Serial.read();
lastSerialMoment = millis();
}
int32_t settingsSize;
memcpy(&settingsSize, buff1, sizeof(int32_t));
Serial.write(ackCode);
// restore the actual settings
uint32_t projectOffset = SETTINGS_OFFSET;
const uint8_t batchsize = 96;
byte buff2[batchsize];
lastSerialMoment = millis();
int32_t remaining = settingsSize;
while (remaining > 0) {
int actual = min(remaining, batchsize);
for (byte k = 0; k < actual; ++k) {
if (!serialWaitForMaximumTwoSeconds()) return;
buff2[k] = Serial.read();
lastSerialMoment = millis();
}
int crc = negotiateIncomingCRC(buff2, actual);
if (crc == -1) return;
else if (crc == 0) continue;
dueFlashStorage.write(projectOffset, buff2, actual);
remaining -= actual;
projectOffset += actual;
}
boolean settingsApplied = upgradeConfigurationSettings(settingsSize, dueFlashStorage.readAddress(SETTINGS_OFFSET));
// activate the retrieved settings
if (settingsApplied) {
applyConfiguration();
}
// send the acknowledgement of success
Serial.write(ackCode);
delayUsec(1000000);
// Turn off OS upgrade mode
switchSerialMode(false);
// Enable normal playing mode and ensure calibration is fully turned off
if (settingsApplied && Device.calibrated) {
setDisplayMode(displayNormal);
clearLed(0, GLOBAL_SETTINGS_ROW);
storeSettings();
}
// turn on calibration instead if no new settings were applied and default settings are used
else {
setDisplayMode(displayCalibration);
controlButton = GLOBAL_SETTINGS_ROW;
lightLed(0, GLOBAL_SETTINGS_ROW);
}
updateDisplay();
waitingForCommands = false;
}
void serialLightLed() {
lastSerialMoment = millis();
byte buff[3];
for (byte i = 0; i < 3; ++i) {
if (!serialWaitForMaximumTwoSeconds()) return;
// read the next byte of the configuration size
buff[i] = Serial.read();
lastSerialMoment = millis();
}
setLed(buff[0], buff[1], buff[2], cellOn);
updateDisplay();
}
int32_t serialSendProjectSize() {
// send the size of a project
int32_t projectSize = sizeof(SequencerProject);
Serial.write((byte*)&projectSize, sizeof(int32_t));
lastSerialMoment = millis();
return projectSize;
}
void serialSendProjectRaw(int32_t projectSize, byte projectNumber) {
byte marker = dueFlashStorage.read(PROJECTS_OFFSET);
// send the actual settings
const uint8_t batchsize = 96;
byte prjIndex = dueFlashStorage.read(PROJECT_INDEX_OFFSET(marker, projectNumber));
uint32_t projectOffset = PROJECTS_OFFSET + PROJECTS_MARKERS_SIZE + prjIndex * SINGLE_PROJECT_SIZE;
int32_t remaining = projectSize;
byte* src = (byte*)dueFlashStorage.readAddress(projectOffset);
while (remaining > 0) {
int actual = min(remaining, batchsize);
Serial.write(src, actual);
if (!waitForSerialCheck()) return;
int crc = negotiateOutgoingCRC(src, actual);
if (crc == -1) return;
else if (crc == 0) continue;
remaining -= actual;
src += actual;
}
}
void serialSendSingleProject() {
Serial.write(ackCode);
clearDisplayImmediately();
delayUsec(1000);
lastSerialMoment = millis();
if (!serialWaitForMaximumTwoSeconds()) return;
uint8_t projectNumber = Serial.read();
Serial.write(ackCode);
Serial.write(Device.version);
int32_t projectSize = serialSendProjectSize();
serialSendProjectRaw(projectSize, projectNumber);
Serial.write(ackCode);
}
void serialSendProjects() {
Serial.write(ackCode);
clearDisplayImmediately();
delayUsec(1000);
// send the count of projects
Serial.write((byte)MAX_PROJECTS);
int32_t projectSize = serialSendProjectSize();
for (byte p = 0; p < MAX_PROJECTS; ++p) {
serialSendProjectRaw(projectSize, p);
}
Serial.write(ackCode);
}
void serialRestoreProject() {
Serial.write(ackCode);
clearDisplayImmediately();
delayUsec(1000);
lastSerialMoment = millis();
if (!serialWaitForMaximumTwoSeconds()) return;
uint8_t version = Serial.read();
if (version < 9) return;
Serial.write(ackCode);
lastSerialMoment = millis();
// retrieve the size of a project
byte buff1[sizeof(int32_t)];
for (byte i = 0; i < 4; ++i) {
if (!serialWaitForMaximumTwoSeconds()) return;
// read the next byte of the project size
buff1[i] = Serial.read();
lastSerialMoment = millis();
}
int32_t projectSize;
memcpy(&projectSize, buff1, sizeof(int32_t));
if (projectSize != sizeof(SequencerProject)) return;
Serial.write(ackCode);
if (!serialWaitForMaximumTwoSeconds()) return;
uint8_t p = Serial.read();
Serial.write(ackCode);
// write the actual project
byte marker = dueFlashStorage.read(PROJECTS_OFFSET);
byte prjIndex = dueFlashStorage.read(PROJECT_INDEX_OFFSET(marker, p));
uint32_t projectOffset = PROJECTS_OFFSET + PROJECTS_MARKERS_SIZE + prjIndex * SINGLE_PROJECT_SIZE;
const uint8_t batchsize = 96;
byte buff2[batchsize];
lastSerialMoment = millis();
int32_t remaining = projectSize;
while (remaining > 0) {
int actual = min(remaining, batchsize);
for (byte k = 0; k < actual; ++k) {
if (!serialWaitForMaximumTwoSeconds()) return;
buff2[k] = Serial.read();
lastSerialMoment = millis();
}
int crc = negotiateIncomingCRC(buff2, actual);
if (crc == -1) return;
else if (crc == 0) continue;
dueFlashStorage.write(projectOffset, buff2, actual);
remaining -= actual;
projectOffset += actual;
}
// finished
Serial.write(ackCode);
delayUsec(500000);
}