“b” будет начата непрерывная передача данных в двоичном формате. Чтобы отключить передачу, надо отправить символ “s”.
4.6.3. Двоичный формат
Каждый пакет содержит идентификатор байта, за которым следуют 19 байтов данных, что в сумме составляет 20 байтов. Идентификатор байта определяет то, как принимающее программное обеспечение или драйвер должны анализировать следующие 19 байтов данных. Плата Ganglion производит выборку биосигналов с частотой 200 Гц. Из-за ограничения передачи пакетов BLE 100 Гц введен протокол дельта-сжатия.
Образцы данных с платы Ganglion имеют 24-битный формат, в первую очередь MSB. Если бы данные не сжимались, то можно было бы разместить только одну выборку в каждом пакете BLE, потому что 24 бита - это всего 3 байта, умноженные на 4 выборки для каждого канала, по 12 байтов, что привело бы к максимальной частоте дискретизации 100 Гц. Верхний наивысший предел согласованной передачи BLE составляет где-то около 100 пакетов в секунду. Поэтому сжатие сделано для того, чтобы получить две выборки на пакет, что дает в реальном времени частоту дискретизации 200 Гц.
4.6.4. 19-битное сжатие
По умолчанию используется 19-разрядное дельта-сжатие. Но далее описано 18-битное дельта-сжатие. Проще всего воспользоваться этой схемой сжатия, представленной на рис. 4.16.
Рис. 4.16. Схема 19-битного дельта-сжатия
95
Предположим, что плата Ganglion подключена к компьютеру, и пользователь хочет начать потоковую передачу данных. Пользователь (или управляющее программное обеспечение) отправляет “b”, и когда плата Ganglion получает “b”, она включает аппаратное обеспечение сбора данных, начинает брать выборки и отправляет данные в 20-байтных пакетах. Самый первый байт каждого пакета – это “Packet ID”, а самый первый отправленный пакет начинается с идентификатора “0x00”. Значение “0x00” указывает управляющему программному обеспечению, что пакет содержит несжатые данные, то есть 4 24-битных значения, занимающих 12 байтов после идентификатора пакета, связанного с каналом 1-4 соответственно. Эти значения являются основными данными платы Ganglion и используются для обеспечения работы алгоритма распаковки на компьютере. Плата Ganglion хранит значения в массиве с именем “lastChannelData[]”. Данный код можно найти в репозитории библиотеки Ganglion (https://github.com/OpenBCI/OpenBCI_Ganglion_Library).
void OpenBCI_Ganglion::compressData19() { int deltas[4];
int even = sampleCounter % 2; for (int i = 0; i < 4; i++) {
deltas[i] = lastChannelData[i] - channelData[i]; // subtract new from old lastChannelData[i] = channelData[i]; // keep track of the previous value bitWrite(deltas[i], 0, (bitRead(deltas[i], 31))); // store the sign bit in bit0
}
if (even == 1) { // pack odd samples first compression_ring[ringBufferLevel][0] = ((deltas[0] & 0x0007F800) >> 11); compression_ring[ringBufferLevel][1] = ((deltas[0] & 0x000007F8) >> 3); compression_ring[ringBufferLevel][2] = ((deltas[0] & 0x00000007) << 5); compression_ring[ringBufferLevel][2] |= ((deltas[1] & 0x0007C000) >> 14); compression_ring[ringBufferLevel][3] = ((deltas[1] & 0x00003FC0) >> 6); compression_ring[ringBufferLevel][4] = ((deltas[1] & 0x0000003F) << 2); compression_ring[ringBufferLevel][4] |= ((deltas[2] & 0x00060000) >> 17); compression_ring[ringBufferLevel][5] = ((deltas[2] & 0x0001FE00) >> 9); compression_ring[ringBufferLevel][6] = ((deltas[2] & 0x000001FE) >> 1); compression_ring[ringBufferLevel][7] = ((deltas[2] & 00000001) << 7); compression_ring[ringBufferLevel][7] |= ((deltas[3] & 0x0007F000) >> 12); compression_ring[ringBufferLevel][8] = ((deltas[3] & 0x00000FF0) >> 4); compression_ring[ringBufferLevel][9] = ((deltas[3] & 0x0000000F) << 4); } else { // pack even samples second compression_ring[ringBufferLevel][9] |= ((deltas[0] & 0x00078000) >> 15); compression_ring[ringBufferLevel][10] = ((deltas[0] & 0x00007F80) >> 7); compression_ring[ringBufferLevel][11] = ((deltas[0] & 0x0000007F) << 1); compression_ring[ringBufferLevel][11] |= ((deltas[1] & 0x00040000) >> 18); compression_ring[ringBufferLevel][12] = ((deltas[1] & 0x0003FC00) >> 10); compression_ring[ringBufferLevel][13] = ((deltas[1] & 0x000003FC) >> 2); compression_ring[ringBufferLevel][14] = ((deltas[1] & 0x00000003) << 6); compression_ring[ringBufferLevel][14] |= ((deltas[2] & 0x0007E000) >> 13); compression_ring[ringBufferLevel][15] = ((deltas[2] & 0x00001FE0) >> 5); compression_ring[ringBufferLevel][16] = ((deltas[2] & 0x0000001F) << 3); compression_ring[ringBufferLevel][16] |= ((deltas[3] & 0x00070000) >> 16); compression_ring[ringBufferLevel][17] = ((deltas[3] & 0x0000FF00) >> 8); compression_ring[ringBufferLevel][18] = (deltas[3] & 0x000000FF); sendCompressedPacket19(); // send on the even packet
}
}
96
Через 5 мсек Ganglion получает еще один набор из 4 выборок данных. Так как предыдущий пакет был пакетом 0, следующий пакет будет 1. В данной схеме сжатия нечетные выборки не отправляются. Вместо этого вычитаются эти новые значения канала из значений, содержащихся в массиве “lastChannelData[]”. Результатом является разница отправляемых данных. В этом случае нельзя отправить все 24 бита, поэтому вместо этого отправляется 19 LSB и разница каждого из них. При этом необходимо отслеживать знак полученной разности (отрицательная или положительная разница). Это сделано путем перемещения знакового бита результата вычитания в бит нулевой позиции. Да, это означает, что теряется один бит разрешения. Значения разницы вставляются в следующие 9,5 байтов пакета BLE. Следующая выборка - это выборка 2, и следует тот же процесс получения дельт (разниц), сохранения знака и передачи получаемых 19-битных значений, но на этот раз после того, как упаковка дельт закончена, полный пакет отправляется на радиопередатчик BLE. Каждый пакет, посланный со сжатием 19 бит, использует “Packet ID” в диапазоне от 101 до 200. Затем плата Ganglion отправляет несжатый пакет со значением 0x00 у “Packet ID”. Это означает, что раз в секунду плата Ganglion отправляет необработанный или несжатый пакет данных. Если по какой-либо причине пакет отбрасывается или возникает какая-то другая проблема с сохранением целостности данных, новый, полностью 100 % реальный несжатый пакет данных находится менее чем в секунде от битого пакета.
Все дельта-сжатия начинаются с необработанного несжатого пакета с байтовым идентификатором “0x00”, то есть 12 байтов несжатых 24-разрядных целочисленных выборок со знаком. Плата Ganglion использует 2D-буфер, вызываемый “compression-ring” для хранения пакетов BLE. Этот буфер имеет размер 101 пакет, что означает, что он может хранить до минуты последней записи данных. Это может быть использовано для возможности повторного вызова сброшенных (битых) пакетов.
18-битное и 19-битное сжатия сохраняют бит со знаком в позиции 0 или LSB. Отрицательные числа всегда присутствуют в позиции 1 или LSB, а положительные числа всегда в позиции 0 или LSB. Если сохранить бит со знаком в MSB, то будет потеряна половину динамического диапазона (плохого), и поместив бит в LSB, будет потеряна номинальная ошибка округления.
4.6.5. 18-битное сжатие
Сжатие 19 бит не оставляет места для дополнительных данных. Если необходимо отправить данные встроенного акселерометра, то необходимо еще более сильно сжать данные. В случае 18-битного сжатия используется 0x00 “Packet ID” для отправки сырых несжатых данных. Каждый следующий пакет использует “Packet ID” от 1 до 100. Таким образом, управляющее программное обеспечение, которое выполняет алгоритм распаковки, может распаковывать
97
каждый пакет без необходимости «знать», в каком состоянии находится система (рис. 4.17).
Рис. 4.17. Схема 18-битного дельта-сжатия
void OpenBCI_Ganglion::compressData18() { int deltas[4];
int even = sampleCounter % 2; for (int i = 0; i < 4; i++) {
deltas[i] = lastChannelData[i] - channelData[i]; // subtract new from old lastChannelData[i] = channelData[i]; // keep track of the previous value bitWrite(deltas[i], 0, (bitRead(deltas[i], 31))); // store the sign bit in bit0
}
if (even == 1) { // pack odd samples first compression_ring[ringBufferLevel][0] = ((deltas[0] & 0x0003FC00) >> 10); compression_ring[ringBufferLevel][1] = ((deltas[0] & 0x000003FC) >> 2); compression_ring[ringBufferLevel][2] = ((deltas[0] & 0x00000003) << 6); compression_ring[ringBufferLevel][2] |= ((deltas[1] & 0x0003F000) >> 12); compression_ring[ringBufferLevel][3] = ((deltas[1] & 0x00000FF0) >> 4); compression_ring[ringBufferLevel][4] = ((deltas[1] & 0x0000000F) << 4); compression_ring[ringBufferLevel][4] |= ((deltas[2] & 0x0003C000) >> 14); compression_ring[ringBufferLevel][5] = ((deltas[2] & 0x00003FC0) >> 6); compression_ring[ringBufferLevel][6] = ((deltas[2] & 0x0000003F) << 2); compression_ring[ringBufferLevel][6] |= ((deltas[3] & 0x00030000) >> 16); compression_ring[ringBufferLevel][7] = ((deltas[3] & 0x0000FF00) >> 8); compression_ring[ringBufferLevel][8] = (deltas[3] & 0x000000FF);
} else { // pack even samples second compression_ring[ringBufferLevel][9] = ((deltas[0] & 0x0003FC00) >> 10); compression_ring[ringBufferLevel][10] = ((deltas[0] & 0x000003FC) >> 2); compression_ring[ringBufferLevel][11] = ((deltas[0] & 0x00000003) << 6); compression_ring[ringBufferLevel][11] |= ((deltas[1] & 0x0003F000) >> 12); compression_ring[ringBufferLevel][12] = ((deltas[1] & 0x00000FF0) >> 4); compression_ring[ringBufferLevel][13] = ((deltas[1] & 0x0000000F) << 4); compression_ring[ringBufferLevel][13] |= ((deltas[2] & 0x0003C000) >> 14); compression_ring[ringBufferLevel][14] = ((deltas[2] & 0x00003FC0) >> 6);
98
compression_ring[ringBufferLevel][15] = ((deltas[2] & 0x0000003F) << 2); compression_ring[ringBufferLevel][15] |= ((deltas[3] & 0x00030000) >> 16); compression_ring[ringBufferLevel][16] = ((deltas[3] & 0x0000FF00) >> 8); compression_ring[ringBufferLevel][17] = (deltas[3] & 0x000000FF);
sendCompressedPacket18(); // send on the even packet
}
}
Когда акселерометр работает, частота дискретизации акселерометра по умолчанию составляет 10 Гц. Ganglion будет получать данные акселерометра всякий раз, когда будет готов, но он будет отправлять только значения с определенным заголовком “Packet ID”.
void OpenBCI_Ganglion::sendCompressedPacket18() { |
|
radioBuffer[0] = ringBufferLevel; |
|
for (int i = 0; i < 18; i++) { |
|
radioBuffer[i+1] = compression_ring[ringBufferLevel][i]; |
|
} |
|
if(useAccel){ |
|
if(ringBufferLevel%10 == 1){ radioBuffer[19] = axisData[0]; } |
// x axis |
if(ringBufferLevel%10 == 2){ radioBuffer[19] = axisData[1]; } |
// y axis |
if(ringBufferLevel%10 == 3){ radioBuffer[19] = axisData[2]; } |
// z axis |
} else if(useAux){ |
|
if(ringBufferLevel%10 == 1){ radioBuffer[19] = auxData[0]; } |
|
if(ringBufferLevel%10 == 2){ radioBuffer[19] = auxData[1]; } |
|
if(ringBufferLevel%10 == 3){ radioBuffer[19] = auxData[2]; } |
|
} |
|
ringBufferLevel++; |
|
if (BLEconnected) { |
|
SimbleeBLE.send(radioBuffer, 20); |
|
} |
|
} |
|
Используется номер, связанный с положением буфера “compression_ring”, чтобы указать, когда необходимо передавать данные значений осей x, y и z. При передаче c использованием 18-битного сжатия пакеты данных акселерометра можно найти в числах, заканчивающихся на 1, 2 и 3 поля “Packet ID”.
Плата Ganglion также способна отправлять “auxData” данные, заданные пользователем, вместо данных акселерометра. Команда “n” в формате ASCII используется для включения акселерометра на плате Ganglion.
4.6.6. Измерение импеданса контакта кожа-электрод
На плате Ganglion имеется аппаратное обеспечение, позволяющее измерить сопротивление контакта электрода с кожей. Программное обеспечение использует управляющие команды “z” в формате ASCII, чтобы начать тестирование полного сопротивления, и команды “Z”, чтобы остановить измерение импеданса. Во время тестирования импеданса поток данных отключается. Плата Ganglion будет посылать значения измеренного импеданса
99