Совместить две такие популярные у разработчиков электронных устройств платформы, как Android и Arduino, порой бывает не так уж и просто. Зачастую в некоторых случаях нужен способ с низкой задержкой отправки данных с телефона Android на микроконтроллер Arduino, и USB On-The-Go (OTG) не подходит для этого по некоторым причинам.
Но сегодня почти у всех телефонов есть разъем для наушников, который может создавать произвольные формы сигнала. Итак, давайте посмотрим, можем ли мы послать последовательный сигнал из аудио порта?
Асинхронный последовательный протокол работает с заданной скоростью передачи (обычно 9600, 57600 или 115200 бод), которая определяет, насколько широк каждый импульс. Данные отправляются в «фреймах», которые включают начальный бит, 5-9 бит данных, дополнительный бит четности, 1-2 стоповых бита. Он асинхронный, поэтому, когда ничего не отправляется, линия удерживается в высоком логическом уровне до следующего кадра данных.
Чтобы отправить сигнал, мы используем цифровой аналоговый преобразователь (ЦАП). Он может формировать 44100 выборок в секунду (44,1 кГц), что гарантирует, что он может воссоздать весь слышимый частотный диапазон. На практике ЦАП обычно используют в таких случаях на частоте 48 кГц.
Чтобы создать правильный сигнал, мы можем использовать Android NDK и библиотеку Oboe (https://github.com/google/oboe) для высокопроизводительного аудио. С 8-битными фрагментами данных доступно только 256 уникальных фреймов, поэтому их можно предварительно создать и сохранить в таблице. Ниже приведен код для создания волновой таблицы. С комментариями он должен быть достаточно ясным; для каждого образца аудиосигнала мы вычисляем, какой бит кадра выводится, и устанавливаем звук на ± MAXIMUM_AMPLITUDE_VALUE. Для 16-битного PCM это ±32768.
// Количество бит в каждом байтовом кадре (стартовые биты, стоповые биты, данные)
int byte_frame_bits = DATA_BITS + START_BITS + STOP_BITS;
// Количество выборок на бит UART
float bit_width = sample_rate / BAUD_RATE;
// Количество выборок на каждый байтовый кадр
frame_width = std::ceil(byte_frame_bits * bit_width);
// Заполнять байты таблицы
for (int byte = 0; byte < bytes_wavetable.size(); byte++) {
auto& wave = bytes_wavetable[byte];
wave.resize(frame_width);
for (int i = 0; i < frame_width; i++) {
int bit_id = std::floor((float) i / bit_width - START_BITS);
int16_t to_send;
// стартовые биты
if (bit_id < 0) {
to_send = -MAXIMUM_AMPLITUDE_VALUE;
}
// биты данных
else if (bit_id < DATA_BITS && bit_id >= 0) {
// 0/1 бит для отправки
uint8_t bit_send = (byte >> bit_id) & (0x01);
// 0/1 расширен до минимальной или максимальной амплитуды
to_send = ((2 * bit_send) - 1) * MAXIMUM_AMPLITUDE_VALUE;
}
// стоп-бит и удержание линии в высоком лог.уровне, то есть нет данных
else {
to_send = MAXIMUM_AMPLITUDE_VALUE;
}
wave[i] = to_send;
}
}
Библиотека Oboe позволяет вам устанавливать обратный вызов всякий раз, когда нужны новые аудиоданные (AudioStreamCallback :: onAudioReady ()), поэтому мы можем скопировать кадры из таблицы в выходной аудио-буфер, или если нет данных для отправки, заполним буфер высокими значениями. Теперь мы можем просто отправить наш сигнал через аудиопорт на 24 килообод (для создания квадратной волны нужны две выборки). Но не стоит торопиться. Существует две проблемы. Во-первых, выход находится в диапазоне ± 1,5 В; нам нужно 0-5 В. Во-вторых, аудио ЦАП фактически не выводит выборки напрямую. Он пытается воссоздать дискретизированный аудиосигнал, поэтому он будет делать некоторую интерполяцию.
Ниже представлена осциллограмма, показывающая, как выглядит необработанный сигнал из ЦАП (зеленый), и что бы мы хотели получить (оранжевый). Обратите внимание, что два сигнала имеют различное вертикальное масштабирование и смещение.
С помощью операционного усилителя мы можем создать схему компаратора, которая будет выводить 0 В, когда сигнал отрицательный, и +5 В, когда сигнал положительный.
Входной аудиосигнал представлен V2, и выход появляется на терминале OUT усилителя. Схема является неинвертирующим компаратором с положительной обратной связью (что приводит к гистерезису для устойчивости вблизи нулевой точки кроссовера). Резисторы R3 и R4 образуют делитель напряжения, чтобы сформировать опорное напряжение 2.5 В. Затем входящий аудиосигнал смещается на + 2.5 В путем подключения звукового заземления к эталонному 2.5 В. Поэтому сравнение смещенного входа с 2.5 В эквивалентно сравнению с 0 В. Выход теперь в диапазоне 0-5 В, потому что операционный усилитель питается от источника питания 5 В, а его выход – rail-to-rail. Не забудьте подключить заземление источника питания к земле.
© digitrode.ru