Запуск I2S Трансивера на Artery [часть 2] (DMA FSM, Pipeline)

362d3abf29cf95149a919972f29cd9ec.png

Пролог

В этом тексте вы узнаете, что общего между I2S трансивером и оладьями. Да… Именно так. Зачем программисту микроконтроллеров конвейеры и цифровые фильтры.

В этом тексте изложено как источать звук при помощи I2S DMA.

В чём проблема?

В прошлом тексте было упомянуто, что работа I2S трансивера в режиме прерываний сопряжена с относительно высокой нагрузкой на процессорное ядро. Это вызвано тем, что часто происходят вызовы обработчиков прерываний. Если мы передаём звук с частотой 48kHz, то прерывания будут происходить с частотой 96kHz.

Надо что-то сделать с этой проблемой.

Постановка задачи

Написать firmware приложение, которое будет одновременно и непрерывно записывать ® и непрерывно транслировать (T) данные по I2S2 в микроконтроллере Artery. При этом? всё должно работать в реальном времени. Реализовать cхему R→T.

В переводя на кухонный язык, надо сделать полностью цифровое эхо. Через RAM память.

В качестве задачи со звёздочкой попробовать, ещё тут же в реальном времени слегка видоизменить принятые данные перед отправкой назад в I2S. Пропустить через какой-н цифровой фильтр. То есть реализовать схему Read®→Proc (P)→Transmit (T)

Решение

Итак, танцуем от печки… Решением проблемы является трансляция и запись звука через direct memory access (DMA). DMA-это такой сопроцессор, который умеет только перемещать данные внутри физической памяти микропроцессора. По сути DMA — это аппаратная реализация функции memcpy (). DMA хорош тем, что при окончании отправки он генерирует прерывание DONE. Также DMA умеет генерировать прерывание об успешном перемещении половины от запланированного объёма данных. Вот так.

Передача данных от RAM в SPI

Передача данных от RAM в SPI

У DMA есть 3 режима.

Src

Dist

Пояснение

1

RAM

PHY

Трансляция в периферию

2

PHY

RAM

Запись из периферии

3

RAM

RAM

memcpy ()

4

PHY

PHY

такой режим отсутствует

Очевидно, что мы не может одновременно и записывать и воспроизводить одни и те же PCM данные. Надо их записывать по кусочкам и воспроизводить тоже по кусочкам. Например по 1024 семпла. Один семпл 2 канала. В каждом канале 2 байт (int16_t).

Наивно-интуитивно напрашивается вот такой способ решить проблему цифрового эхо

1a6f7a461c664dcdd8c5f4e11e3721b3.png

Однако это очень плохая идея, так как тут нарушается непрерывность записи звука и нарушается непрерывность воспроизведения звука. Такой способ будет работать только в случае, когда размер записываемой звуковой дорожки составляет всего лишь один семпл! Однако так не решить проблему слишком частых прерываний.

Как же быть?

Чтобы решить эту инженерную проблему надо вспомнить старинную русскую народную логическую задачку.

Как пожарить 3 оладьи за 3 минуты, если каждая сторона жарится по 1 мин., а на сковородку помещается только 2 оладьи ?

А решение у задачи такое. T-top, B-button.

b74f4b27d8c2f41f637f932f1b831e31.png

Это, своего рода, самый первый жизненный опыт применения конвейерной обработки на бытовом уровне. Аналогичный принцип надо применить в обработке цифрового звука I2S. Только тут вместо оладьей будут массивы слов по 514 байт. Вместо сковородки — I2S трансивер. Только и всего…

Вот и получается, что надо поделить временную память на две одинаковые части Low и Hi. Пока записывается L транслировать H, пока записывается H транслировать L.

95a16b5ec799c4b1a706c6f7abd615da.png

Учитывая, что в DMA есть циркулярный режим, то такая обработка будет работать сама по себе бесконечно долго. Её надо лишь завести. Как завести? Если говорить на рабоче- крестьянском языке, то I2S-DMA надо завести «с толкача». Как это выглядит?

1a273493ae3c00d425bc5b70fe69867b.png

Придется в коде прошивки реализовать вот такой простенькие конечный автомат на 4 состояния. У автомата задача, не много не мало, а обеспечить правильный старт и последующую само синхронизацию между передачей и отправкой семплов так, чтобы запись отставала по фазе от воспроизведения на полпериода. Это необходимо для реализации полностью цифрового loopback. Вот граф этого конечного автомата.

6315e5af309078faed43bdd0349ac014.png

Можно выделать массив семплов I2STempData[1024], обнулить его и отправить по DMA в I2S. Как только сработает прерывание по половине отправки включить запись по DMA в этот же массив I2STempData. Далее всё будет происходить на аппаратном уровне само по себе. И цифровое эхо заработает.

Получается, что прерывания происходят каждые 512 семплов. То есть с частотой 93.75 Hz. Каждые 10.6 ms прерывание по DMA. Это в 512 раз лучше чем без DMA. Причем нагрузку можно даже уменьшить ещё, если увеличить размер временного массива с семплами I2STempData.

Тут всё в полном порядке: запись непрерывная, воспроизведение тоже непрерывное. Один лишь минус это запаздывание выходного сигнала на половину длинны временного массива. Насколько велико это запаздывание? Допустим мы работаем на частоте 48kHz. Длительность одного семпла составляет 20.8333us. Сколько времени надо для воспроизведения 512 семплов? Ответ: всего 10.6ms. В принципе всё, что меньше 15ms человеку не заметно.

А как быть, если мы хотим перед отправкой ещё немного подшаманить данные? Тогда появится новая фаза Proc (P). Получится вот такой трёхтактный конвейер.

b5c220eee64985d0df845e30879c3862.png

Однако трёх-тактным конвейером не получится дирижировать, так как обычно DMA подсистемы на ARM Cortex-MCU микроконтроллерах не генерируют прерывания по ½ и 2/3 отправки. Да. Вот так… У нас в распоряжении есть только одно промежуточное прерывание по ½ отправки. К этому приходятся приспосабливаться.

Поэтому придется сделать прозябающий конвейер, который 25% времени ничего не делает. Вот так, четырехтактный конвейер.

e1a079d38a68efa7c9d70be68696d254.png

Это паллиaтивное решение, чисто из-за совместимости с Artery MCU (у STM та же самая ситуация). Теперь мы уже оперируем четвертинками временного массива с семплами. Однако кто отдаст приказ начать обрабатывать четвертинки?

Для этого, как ни крути, но придется запускать аппаратный таймер №8, который генерирует прерывания каждые 21.3ms. Это то самое время, которое уходит на трансляцию временного массива из 1024 семплов на частоте дискретизации 48kHz. Получается, что таймер будет переполняться с частотой 46.875 Hz. А прерывания будут происходить с частотой 93.75 Hz.

93ce7cc36721e1f5a527de5de4eb2e0c.png

Также надо настроить на аппаратном таймере прерывание по сравнению с компаратором на половине периода. Впрямь как в DMA.

07f44a7b9d13affd1589454e90eb8e21.png

На нижнем графике All_Interrupts видно, что прерывания теперь происходят каждые 256 семплов, через каждые 5.3(3)ms. То есть с частотой 187.5 Hz. Это в 256 раз меньшая нагрузка на процессор, в сравнении с тем, когда мы источали I2S трафик по прерываниям вообще без использования DMA. Успех!

Однако кто даст отмашку аппаратному таймеру начать считать? Тут чисто по комбинаторике, как ни крути, существует всего-навсего 4 варианта

№ DMA

Data direction

Event

Action

1

Tx

Half

Timer Counter = 75%

2

Tx

Done

Timer Counter = 25%

3

Rx

Half

Timer Counter = 25%

4

Rx

Done

Timer Counter = 75%

И как видно, ответ никто. В прерываниях по DMA придется просто устанавливать значение аппаратного счетчика в 25% или 75% от периода счета. Это даже хорошо так как постоянно будет происходить синхронизация I2S и аппаратного таймера.

Так мы получим нужную шкалу для запуска процедуры обработки принятых семплов (например цифровое изменение громкости, или цифровая фильтрация). Осталось только понять кто именно даст команду на обработку каждой отдельной четвертинки принятого массива. Вот эта таблица показывает кто будет делать обработку.

5b667135072d35315181c297ba34bb0a.png

С теорией определились. Теперь надо это всё реализовать на электронной плате.

Каков план?

--Подать тактирование на DMA1

--Определить канал1 DMA1 для отправки аудио потока в I2S2

--Определить канал2 DMA1 для приёма аудио потока из I2S2

--Запустить аппаратный таймер 8

--Настроить компаратор на таймере 8 и генерировать прерывание на половине периода.

--Настроить I2S2 на следующие параметры: 16bit, Master I2S, AudioFreq: 48kHz.

Практическая часть

Настало время сделать решающий эксперимент и проверить эту идею на реальном железе. В качестве прототипа я воспользуюсь учебно-треннировочной электронной платой AT-Start-F437. Перед вам двухярусная сборка: AT-Start-F437 + WM8731.

Эта плата стала для меня настоящей электронной партой.

Эта плата стала для меня настоящей электронной партой.

Вот так соединены модуль аудио кодека WM8731 и учебно-треннировочную электронную плату AT-START-F473

Wire

GPIO

PIN MUX

Pull

MCU dir

WM8731 board

I2S2_CK

PС7

5

Down

out

BCLK

I2S2_WS

PB9

5

Up

out

DACLR, ADLRC

I2S2_SD

PC3

4

Up

out

DADAT

I2S2_SDEXT

PC2

6

Up

in

ADDAT

I2S2_MCK

PA3

5

Air

out

--

I2C2_SDA

PF0

4

Up

in_out

SDA

I2C2_SCL

PF1

4

Up

out

SCL

схема подключения модуля с аудиокодеком WM8731.

Учебно-тренировочный комплект для записи и воспроизведения цифрового звука

Учебно-тренировочный комплект для записи и воспроизведения цифрового звука

На уровне SoC (а) AT32F437 надо активировать трансиверы SPI2 на передачу, а I2S2EXT на приём. Плюс включить TMR8

8995c03c1ee7846cea55ca0ad44a268c.png

Тут надо пояснить, что в Artery, чтобы I2S заработал в полном дуплексе надо как бы составить его из двух отдельных одновременно работающих синхронно I2S трансиверов.

#

Аппаратный модуль

Направление

шина

режим

Boundary address

1

SPI2

Передатчик

APB1

MASTER_TX

0×4000 3800

2

I2S2EXT

Приёмник

APB2

SLAVE_RX

0×4001 7800

Стоит заметить, что сброс регистров в SPI2 одновременно сбрасывает регистры в I2S2EXT. Вот такой привет со стороны Artery.

Надо назначить каналам DMA функции I2S2. Вот так.

DMA

Channel

Function

Function

Direction

Data Width

Direction

1

1

13

SPI2_TX

out

HALF_WORD

MEM→PERIPH

1

2

110

I2S2_EXT_RX

in

HALF_WORD

PERIPH→MEM

Обработка принятых семплов

Как мы помним, при частоте дискретизации в 48kHz на обработку одного семпла у нас в распоряжении есть всего лишь 20.83 us. Это значит, что в конвейере на обработку непрерывной порции из 256 семплов у нас будет максимум 5.3ms. На всё. Поэтому необходимо чтобы Си функция в прошивке успела сделать свою DSP обработку за менее чем 5.3ms.

В качестве примера простой аудио обработки я попробовал реализовать искусственный эхо эффект на основе цифрового БИХ фильтра. Вот его цифровая цепь.

f66f0eee86208365d82707394797ced8.png

Все вычисления были для 16-битных семплов. На ARM Cortex-M4F при частоте ядра 250MHz эти 256 16-бит семплов обсчитываются IIR фильтром всего за 1.613 ms. Получается один семпл обрабатывается 6.3 us. Это в 3.3 раза быстрее, чем позволяет deadline. Успех.

Как отлаживать I2S?

Любая разработка начинается только тогда, когда появляются средства отладки. Особенно в I2S так как это hi load интерфейс. Тут мегагерцы в битовой скорости. Как за всем уследить? Как всё проверить? Ответ прост. Надо отлаживать аудио тракт по частям, подобно тому как в математике вычисляют интегралы по частям.

Фаза1: Проверка I2S на стороне MCU

I2S в режиме полного дуплекса можно проверить весьма остроумно. Можно установить перемычку с выхода на вход и включить передачу I2S данных. Это называется loopback Ожидается, что после остановки запишется тот же I2S поток, что и был воспроизведён. Бит в бит.

d209ec5ab1c14221e1f21835dfd48f8f.png

Таким образом можно будет проверить корректность настройки I2S трансивера на стороне микроконтроллера.

Фаза2: Проверка I2S на стороне аудио кодека WM8731

Аналогично можно проверить сам Audio Codec. Надо взять джампер или перемычку и соединить пины ADC-DATA и DAC_DATA. Таким образом получится полностью цифровое I2S — эхо. Бит в бит. Тот же самый loopback.

d043362d8d2e3162437c9518a9b7977e.png

Затем можно попробовать сказать что-н в микрофон и одновременно слушать наушники.

++Если конфиг в кодеке исправен, то вы услышите то, что сказали в реальном времени. Без задержки.

---Если звука нет или появилась какая-то трескотня, то ищите ошибку в конфиге I2C ячеек внутри ASIC аудио кодека.

Вот так. Всего одним проводком вполне реально найти ошибку в конфигах, либо в MCU, либо в кодеке.

Когда заработает I2S Full Duplex режим на осциллографе должна быть вот такая картинка.

опечатка: не I2C2_SCL, а I2S2_CLK

опечатка: не I2C2_SCL, а I2S2_CLK

Фаза 3 Проверка синхронизации

Также можно использовать GPIO для контроля и отладки синхронизации между DMA каналами приёма и отправки. Надо лишь в обработчиках DMA прерываний выполнять вот такие действия:

DMA

Канал

Прерывание

Действие GPIO

GPIO

пояснение

1

1

Half

Установить 3.3V

PB6

Отправка половины

1

2

Half

Установить 3.3V

PB7

Приём половины

1

1

Done

Установить 0V

PB6

Отправка завершена

1

2

Done

Установить 0V

PB7

Приём завершен

Тогда, если всё хорошо, то должна получится вот такая осциллограмма

5c2ca316b2764543ac8f6aa2b26d8c59.png

Как раз DMA отправка и DMA прием смещены друг относительно друга на пол периода. Так и должно быть.

Теперь можно спокойно запустить Timer8 c каналом сравнения №1 для выработки сигналов для запуска обработки принятых звуковых семплов. Тут стоит отметить, что в микроконтроллере Artery каналы сравнения есть не у всех таймеров. У таймера 6 их нет вообще. И у всех таймеров разное количество каналов сравнения. Прерывание по середине счета я сделал на основе аппаратного компаратора, которые есть в таймерах и эти компараторы тоже могут генерировать прерывания.

2762182783a2c38bdc9e6fa117c83de7.png

Как видно, удалось на практике разбить приём I2S 1024 семплов на 4 равных по времени интервала и вырабатывать триггеры на каждую четвертинку. Теперь остается только добавить в обработчики прерываний таймера 8 и DMA каналов код обработки семплов и получится обработка звука в реальном времени.

Достоинства I2S в режиме DMA

++Низкая нагрузка на центральный процессор микроконтроллера. Максимум 2 прерывания на всю передачу данных. Отправка и приём происходят полностью аппаратно. Это открывает дорогу для обработки I2S трафика в реальном времени прямо на MCU.

Недостатки I2S в режиме DMA

--В DMA нет прерываний по ⅓ и 2/3 от переданных данных. Как по мне, дак это печально. Прерывания на 33% 66% работы позволили бы выполнить оптимизацию производительности конвейера типа Read-Proc-Transmitt (R-P-T).

Итоги

Как видите, запуск I2S в режиме DMA это та ещё морока. Это hi-load тема. Надо производить всё предельно аккуратно. Нужно отладить и запустить целый конвейер. Надо разбираться в конечных автоматах. Надо знать как отлаживаться.

Зато DMA это единственно верный способ работы с такими интерфейсами как I2S, UART, SPI и прочее.

Этот текст пример того, что в программировании микроконтроллеров грамотная документация важнее, чем исходный код. По хорошей документации кто угодно напишет код. А имея только код восстановить документацию порой просто нереально.

Полученные результаты открывают дорогу для разработки всяческих приложений обработки аудио данных в реальном времени. Можно накладывать эхо эффект, дисторшн, производить цифровую фильтрацию и прочее.

Мне вот удалось реализовать цифровой эхо эффект на основе IIR фильтра, который работает полностью в реальном времени. Прямо на микроконтроллере.

Надеюсь, что текст поможет кому-н тоже разобраться в I2S и написать интересное звуковое приложение на микроконтроллере.

Словарь

Акронима

Расшифровка

DMA

direct memory access

БИХ

бесконечная импульсная характеристика

IIR

Infinite impulse response

I2S

Inter-Integrated Circuit Sound

R-P-T

Read-Рrocess-Transmitt

R-T

Read-Transmitt

GPIO

General-purpose input/output

Ссылки

© Habrahabr.ru