Многие цифровые интерфейсы поддерживают полнодуплексный режим работы. Это когда данные передаются и принимаются одновременно. Такое возможно в интерфейсах RMII, RS232, USB 3.0, PCI Express, SPI, 100BASE-TX, HDMI, 100BASE-FX, UART, SATA Express, 100BaseT1 и I2S.

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

В этом же тексте я написал про то как сделать одновременно и воспроизведение и запись звука, написал как на STM32 настроить I2S-трансивер работать в полнодуплексном (Full Duplex) режиме.

Постановка задачи
Настроить на микроконтроллере STM32F407 одновременное воспроизведение и запись аудио потока на I2S2. При этом мне нужен не просто одновременный запуск I2S2_TX и I2S2_EXT_RX, а так, чтобы DMA канал приема I2S_RX был смещен по фазе относительно DMA канала передачи I2S_TX на 180 градусов (на полпериода). То есть в момент прерывания по половине отправки надо только начинать приём по DMA. Мастером в I2S интерфейсе должен быть микроконтроллер. Slave устройством должен быть аудиокодек WM8731. Использовать DMA каналы с прерываниями. Передавать следует цифровой двухканальный звук на частоте 48kHz. Размер семпла должен составлять 16 бит. Master Clock наружу микроконтроллера не выводить.

Сделать две тестировочные STM32 сборки для проверки работы режима full duplex I2S:
1) цифровое эхо (слуховой аппарат)
2) одновременное воспроизведение с SD карты и запись звука на SD карту.

Процессорное ядро должно работать на максимальных оборотах (168 MHz).

Вот, пожалуй, и всё.

В чем проблема?
Проблема в том, что в STM32 HAL API SDK отсутствует функция запуска I2S full duplex с фазовым сдвигом 180 градусов. Что это значит? Если вызывать стандартную SDK функцию HAL_I2SEx_TransmitReceive_DMA, то чтение и запись будут абсолютно синхронными. А это значит, что DMA прерывания по окончанию отправки и DMA прерывания по окончанию приема будут вступать в гонки. Возникнет race conrition. Аналогичная проблема для DMA прерываний по половине отправки. Очевидно, что для преодоления этой неприятности надо разнести по фазе DMA процессы чтения и записи по I2S. Вот про это и будет данный текст.

Теория

Вы можете спросить:

" Зачем на практике надо делать смещение приема относительно передачи? "

Ответ в том, что благодаря разнесению чтения и записи по фазе на 180 градусов Вы можете реализовать потоковую запись и потоковое чтение в реальном масштабе времени. То есть делать воспроизведение и запись звука одновременно. Это как раз то, что необходимо для работы начиная от радаров с сонарами, и заканчивая УЗИ аппаратами и дефектоскопами. Механизм работы показан на временной диаграмме.

Еще смещение фаз RX и TX нужно для потоковой обработки звука в реальном масштабе времени по схеме Rx-Proc-Tx.

Это используется в музыкальных приборах для создания гитарных звуковых эффектов и в приборах подмены голоса в реальном времени.

Аппаратная часть

Эксперименты я провел на учебно-тренировочной электронной плате DevEBox STM32F4XX_M_V3. Аудиокодек - это всё тот же ASIC WM8731 и PCB модуль на его основе. Надо собрать прототип вот по такой схеме. Следует установить всего 10 перемычек. Вот так, просто и незатейливо:

Микрофон я выбрал aceline AMIC-7. Надо собрать такой прототип.

Так выглядит прототип в натуре.

прототип проигрывателя в натуре.
прототип проигрывателя в натуре.

Реализация

Настройка GPIO

Я выбрал I2S2 трансивер. Вот его GPIO пины.

GPIO

PinFunc

Dir

PinMux

Pull

PB10

I2S2_CLK

out

AF5

Up

PB12

I2S2_WS

out

AF5

Up

PC2

I2S2_RX

in

AF6

Up

PC3

I2S2_TX

out

AF5

Up

Вот так это выглядит в диагностике GPIO.

-->gl
+------+--------+-------+------+------+-------+-------------+
| pad  |  mode  | level | dir  | pull |PinMux |   name      |
+------+--------+-------+------+------+-------+-------------+
|  PC2 |   ALT1 |   H   |   in | Down |   6   |  I2S2_SDEXT |
| PB13 |   ALT1 |   L   |  out | Down |   5   |     I2S2_CK |
| PB12 |   ALT1 |   L   |  out | Down |   5   |     I2S2_WS |
|  PC3 |   ALT1 |   L   |  out | Down |   5   |     I2S2_SD |
| PB10 |   ALT1 |   H   |  out |   Up |   4   |    I2C2_SCL |
| PB11 |   ALT1 |   H   |  io  |   Up |   4   |    I2C2_SDA |
|  PA2 |   ALT1 |   L   |  out |  Air |   7   |   USART2_TX |
|  PA3 |   ALT1 |   H   | in   |   Up |   7   |   USART2_RX |
| PC12 |   ALT1 |   H   |  Out |  Air |  12   |      SD_CLK |
|  PD2 |   ALT1 |   H   |  Out |   Up |  12   |      SD_CMD |
|  PC8 |   ALT1 |   H   |   io |   Up |  12   |       SD_D0 |
|  PC9 |   ALT1 |   H   |   io |   Up |  12   |       SD_D1 |
| PC10 |   ALT1 |   H   |   io |   Up |  12   |       SD_D2 |
| PC11 |   ALT1 |   H   |   io |   Up |  12   |       SD_D3 |
|  PA1 |    Out |   L   |  Out |  Air |   0   |    LedGreem |
|  PA8 |    Out |   L   |  Out |  Air |   0   | I2S2_TX_DMA |
|  PA4 |    Out |   L   |  Out |  Air |   0   | I2S2_RX_DMA |
|  PA0 |     In |   L   |   In | Down |   0   |        WKUP |
+------+--------+-------+------+------+-------+-------------+

Настройка I2S

I2S - это высокоскоростной синхронный полнодуплексный цифровой физический master-slave интерфейс передачи данных. Заточен для двухканальных звуковых семплов (Stereo PCM).

В STM32F4x микроконтроллерах только два полноценных I2S трансивера: I2S2 и I2S3. Каждый из двух I2S трансиверов может работать в режиме полного дуплекса.

Трансивер

Base Address

Тандемный трансивер

I2S1

--

--

I2S2

0x4000 3800

I2S2ext

I2S3

0x4000 3C00

I2S3ext

I2S2ext

0x4000 3400

I2S2

I2S3ext

0x4000 4000

I2S3

Однако для активации режима полного дуплекса надо заставить два отдельных I2S трансивера работать синхронно. В тандеме. Надо в регистрах I2S сконфигурировать вот такую сцепку из двух I2S трансиверов.

После инициализации регистры I2S трансиверов должны принять вот такие значения

Чтобы начать трансляцию семплов надо первым делом запускать именно I2S write и уже внутри прерывания по половине отправки I2xTxHalfCallbackCustom запускать I2S read.


bool i2s_dma_write(uint8_t num, 
                   const uint16_t* const array, 
                   const  uint16_t words) {
    bool res = false;
    LOG_DEBUG(I2S, "I2S_%u,WriteDma,Words:%u", num, words);
    I2sHandle_t *Node = I2sGetNode(num);
    if(Node) {
        HAL_StatusTypeDef ret ;
        Node->pHandle->hdmatx->State = HAL_DMA_STATE_BUSY;
        ret = HAL_DMA_Abort_IT(Node->pHandle->hdmatx);
        Node->pHandle->State = HAL_I2S_STATE_READY;
        Node->pHandle->hdmatx->State = HAL_DMA_STATE_READY;
        ret = HAL_I2S_Transmit_DMA(Node->pHandle, (uint16_t*) array, words);
        if(HAL_OK == ret) {
            res = true;
        } else {
            LOG_DEBUG(I2S, "WrErr:%u %s", ret, HalStatusToStr(ret));
        }
    } else {
        LOG_DEBUG(I2S, "%u NodeErr", num);
    }
    return res;
}

Не наоборот. Если в режиме полного дуплекса запустить I2S read, то API отработает, но DMA прерывания не станут срабатывать.

#include "loopback_audio_isr.h"

#include "wav_player_mcal.h"
#include "i2s_full_duplex.h"
#include "loopback_audio_mcal.h"
#include "dma_channel_const.h"
#include "sound_recorder_mcal.h"

bool i2s_rx_dma_restart(uint8_t dma_node_num,
                        uint8_t i2s_num,
                        uint32_t DstAddress,
                        uint32_t DataLength ) {
    bool res = false;
    DmaChannelConfig_t *DmaChannel = DmaChannelGetConfig(dma_node_num);
    if(DmaChannel) {
        I2S_HandleTypeDef *pI2sHandle = i2s_num_to_handle(i2s_num);
        if(pI2sHandle) {
            HAL_StatusTypeDef ret;
            (void) ret;
            if(pI2sHandle->hdmarx) {
                pI2sHandle->hdmarx->State = HAL_DMA_STATE_BUSY;
                ret = HAL_DMA_Abort_IT(pI2sHandle->hdmarx);
                __HAL_UNLOCK(pI2sHandle->hdmarx);
                pI2sHandle->hdmarx->State = HAL_DMA_STATE_READY;
                pI2sHandle->hdmarx->Init.Direction = DMA_PERIPH_TO_MEMORY;

                /* Set the I2S Rx DMA Half transfer complete callback */
                pI2sHandle->hdmarx->XferHalfCpltCallback = I2S_DMARxHalfCplt;

                /* Set the I2S Rx DMA transfer complete callback */
                pI2sHandle->hdmarx->XferCpltCallback = I2S_DMARxCplt;

                /* Set the DMA error callback */
                pI2sHandle->hdmarx->XferErrorCallback = I2S_DMAError;

                ret = HAL_DMA_Start_IT(pI2sHandle->hdmarx,
                                       (uint32_t) DmaChannel->base_addr_source,
                                       DstAddress, DataLength);
                res = dma_channel_half_move_it_ctrl(DmaChannel->DmaChPad, true);
            }
        }
        res = true;
    }
    return res;
}

bool I2xTxHalfCallbackCustom(I2sHandle_t* const I2s) {
    bool res = false;
    LoopbackAudioHandle_t *Node = LoopbackAudioGetNode(1);
    if (Node) {
        if (false == Node->tx_rx_180) {
            if (Node->rec_need) {
                res = i2s_rx_dma_restart(DMA_CHANNEL_NUM_I2S2_RX, 
                                         I2s->num, 
                                         (uint32_t) Node->RxSampleArray,
                                         Node->sample_array_size);
                if (res) {
                    res = sound_recorder_start_indication(Node->rec_num);
                }
            }
            Node->tx_rx_180 = true;
        }
    }
    return res;
}

bool I2xTxDoneCallbackCustom(I2sHandle_t* const I2s) {
    return true;
}

Настройка DMA

Самым правильным режимом работы I2S трансивера является трансляция и запись звука через прямой доступ к памяти. Это режим direct memory access (DMA). DMA-это такой сопроцессор, который умеет только перемещать данные внутри физической памяти микропроцессора. По сути DMA - это аппаратная реализация функции memcpy() + способность работать с аппаратными подсистемами трансиверов. DMA хорош тем, что при окончании отправки он генерирует прерывание DONE.

Также DMA умеет генерировать прерывание об успешном перемещении половины от запланированного объёма данных (DmaMoveHalf).

Flow

DMA

Steam

Direction

Channel

Mode

Align

FIFO Mode

I2S2_TX

DMA1

Steam_4

MEM->PER

Channel_0

Circ

16bit

off

I2S2_RX

DMA1

Steam_3

PER->MEM

Channel_3

Circ

16bit

off

I2S2_TX поток выбран из таблицы 42. Трансиверу I2S2_TX соответствует DMA1 Stream_4 Channel_0.

Трансиверу I2S2_RX соответствует DMA1 Stream_3 Channel_3.

Пример цифрового эха

C эхо примером все просто. Достаточно запустить i2s_write и начнет работать I2S эхо. Вот на данном наборе регистров работает демо-пример цифрового эха.

Настройки I2S регистров для цифрового эха
1:25-->i2s_diag_ll 2;i2s_diag_ll 4

87.024,+28365,291,I,[I2S],I2S2,Base:0x40003800
87.024,+0,292,W,[I2S],CR2:0x00000003=0000_0000|0000_0000|0000_0000|0000_0011
87.025,+1,293,I,[I2S],RXDMAEN:1
87.025,+0,294,I,[I2S],TXDMAEN:1
87.026,+1,295,I,[I2S],ERRIE:0
87.026,+0,296,I,[I2S],RXNEIE:0
87.027,+1,297,I,[I2S],TXEIE:0
87.027,+0,298,W,[I2S],SPI_SR:0x00000080=0000_0000|0000_0000|0000_0000|1000_0000
87.028,+1,299,I,[I2S],RXNE:0
87.028,+0,300,I,[I2S],TXE:0
87.029,+1,301,I,[I2S],CHSIDE:0
87.029,+0,302,I,[I2S],UDR:0
87.029,+0,303,I,[I2S],OVR:0
87.030,+1,304,I,[I2S],BSY:1
87.031,+1,305,I,[I2S],FRE:0
87.031,+0,306,W,[I2S],SPI_DR:0x0000F9D4=0000_0000|0000_0000|1111_1001|1101_0100
87.032,+1,307,I,[I2S],DR:63956=0xf9d4
87.033,+1,308,W,[I2S],SPI_DR:0x00000E00=0000_0000|0000_0000|0000_1110|0000_0000
87.034,+1,309,I,[I2S],CHLEN:0
87.036,+2,310,I,[I2S],DATLEN:0=16 Bit
87.037,+1,311,I,[I2S],CKPOL:0=ClkPolLow
87.037,+0,312,I,[I2S],I2SSTD:0=I2S
87.039,+2,313,I,[I2S],PCMSYNC:0=Short
87.040,+1,314,I,[I2S],I2SCFG:2=MASTER_TX
87.042,+2,315,I,[I2S],I2SE:1=enabled
87.043,+1,316,I,[I2S],I2SMOD:1=I2S
87.043,+0,317,W,[I2S],I2SPR:0x00000110=0000_0000|0000_0000|0000_0001|0001_0000
87.046,+3,318,I,[I2S],I2SDIV:16
87.046,+0,319,I,[I2S],ODD:1
87.049,+3,320,I,[I2S],MCKOE:0
1:27-->
87.068,+19,321,I,[I2S],I2S4,Base:0x40003400
87.068,+0,322,W,[I2S],CR2:0x00000023=0000_0000|0000_0000|0000_0000|0010_0011
87.069,+1,323,I,[I2S],RXDMAEN:1
87.070,+1,324,I,[I2S],TXDMAEN:1
87.070,+0,325,I,[I2S],ERRIE:1
87.070,+0,326,I,[I2S],RXNEIE:0
87.071,+1,327,I,[I2S],TXEIE:0
87.072,+1,328,W,[I2S],SPI_SR:0x00000086=0000_0000|0000_0000|0000_0000|1000_0110
87.072,+0,329,I,[I2S],RXNE:0
87.073,+1,330,I,[I2S],TXE:1
87.073,+0,331,I,[I2S],CHSIDE:1
87.073,+0,332,I,[I2S],UDR:0
87.074,+1,333,I,[I2S],OVR:0
87.074,+0,334,I,[I2S],BSY:1
87.075,+1,335,I,[I2S],FRE:0
87.075,+0,336,W,[I2S],SPI_DR:0x0000FA2C=0000_0000|0000_0000|1111_1010|0010_1100
87.076,+1,337,I,[I2S],DR:64044=0xfa2c
87.077,+1,338,W,[I2S],SPI_DR:0x00000D00=0000_0000|0000_0000|0000_1101|0000_0000
87.078,+1,339,I,[I2S],CHLEN:0
87.080,+2,340,I,[I2S],DATLEN:0=16 Bit
87.081,+1,341,I,[I2S],CKPOL:0=ClkPolLow
87.081,+0,342,I,[I2S],I2SSTD:0=I2S
87.084,+3,343,I,[I2S],PCMSYNC:0=Short
87.084,+0,344,I,[I2S],I2SCFG:1=SLAVE_RX
87.087,+3,345,I,[I2S],I2SE:1=enabled
87.087,+0,346,I,[I2S],I2SMOD:1=I2S
87.087,+0,347,W,[I2S],I2SPR:0x00000002=0000_0000|0000_0000|0000_0000|0000_0010
87.090,+3,348,I,[I2S],I2SDIV:2
87.091,+1,349,I,[I2S],ODD:0
87.093,+2,350,I,[I2S],MCKOE:0

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

Одновременное воспроизведение и запись звука ( LoopBack Audio )

Особый интерес представляет одновременное воспроизведение и запись звука. У меня уже были два подробных текста про то как записывать и воспроизводить звук на микроконтроллере.

Function

Название монографии

URL

Play

Проигрыватель WAV файлов на STM32

https://habr.com/ru/articles/1051842/

Rec

Диктофон на микроконтроллере (WM8731 + I2S + STM32 + SDIO + FAT16)

https://habr.com/ru/articles/1054896/

Однако ничто не мешает делать Play+Rec одновременно, подобно тому как сантехник и электрик могут работать одновременно в одной квартире. Я собрал отдельную demo прошивку для демонстрации LoopBack Audio. Прошивка скомпонована из ранее отлаженных программных компонентов согласно схеме.

Структура прошивки LoopBack Audio
Структура прошивки LoopBack Audio

Достаточно выполнить в UART2 консоли команду loopback_audio_play_rec_test и Вы получите одновременное воспроизведение с записью звуковой дорожки. По сути произойдет запись того, что было воспроизведено + окружающие шумы.

Тот же вывод в консоль, только в тексте

Одновременное воспроизведение и запись звука
 6.119-->
  6.151-->loopback_audio_play_rec_test 1 track48kHz_5s.wav

36.957,+36279,288,I,[LoopBackAudNum,Ok
36.957,+0,289,I,[LoopBackAudio],test_loopback_audio_rec_play_one():Play:[track48kHz_5s.wav]
36.962,+5,290,I,[WAV],OpenFile:[track48kHz_5s.wav]Ok
36.964,+2,291,I,[WAV],ReadHeader,Ok
36.964,+0,292,I,[WAV],SampleCnt:240000,SampleTime:20.833u s,PlayDir:5.000000 s
36.969,+5,293,I,[WAV],OpenFile:[track48kHz_5s.wav]Ok
36.970,+1,294,I,[WAV],ReadHeader,Ok
36.971,+1,295,I,[WAV],SampleCnt:240000,SampleTime:20.833u s,PlayDir:5.000000 s
36.972,+1,296,I,[WAV],File:,SamFreq:48000 Hz,state:0,state:1,i2s_error_cnt:0,rec_cnt:0,sample_cnt:240000,ErrCnt:0,total_file_size:960044,start_rec_ms:0,stop_rec_ms:0,data_size:960000,Spin:189432,cur_save_size:0,Init:On,
36.974,+2,297,I,[WAV],Duration: 5.00 s,ChunkId:RIFF,ChunkSize:960036 Byte,Format:WAVE,Subchunk1Id:fmt ,Subchunk1Size:16,
AudioFormat:0x0001,NumChannels:2,SampleRate:48000 Hz,ByteRate:192000 Byte,BlockAlign:4 Byte,BitsPerSample:16 bit,Subchunk2Id:data,DataSize:960000 Byt
36.992,+18,298,I,[WavPlayer],ReadHeaderOk,Duration: 5.00 s,ChunkId:RIFF,ChunkSize:960036 Byte,Format:WAVE,Subchunk1Id:fmt ,Subchunk1Size:16,
AudioFormat:0x0001,NumChannels:2,SampleRate:48000 Hz,ByteRate:192000 Byte,BlockAlign:4 Byte,BitsPerSample:16 bit,Subchunk2Id:data,DataSize:960000 Byt
36.993,+1,299,I,[I2S],I2S_2,Set,Operation:Tx
36.994,+1,300,I,[WavPlayer],1,SetAudioFreq:48000 Hz
36.994,+0,301,I,[I2S],I2S_2,Set,AudioFreq:48000 Hz
36.995,+1,302,I,[I2S],I2S_2 init
36.995,+0,303,W,[DmaChannel],N:2=I2S2_TX
36.996,+1,304,W,[Dma],2,DMA1,Stream_4,Init
36.997,+1,305,I,[Dma],Init Id:2 Dma:1 Stream:4 Ok!
36.997,+0,306,W,[DmaChannel],N:3=I2S2_RX
36.998,+1,307,W,[Dma],3,DMA1,Stream_3,Init
37.000,+2,308,I,[Dma],Init Id:3 Dma:1 Stream:3 Ok!
37.000,+0,309,I,[I2S],I2S2 initOk
37.001,+1,310,W,[GPIO],PB12,DrvSel:?,I2S2_WS,Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down,Speed:High,Pin:0,
37.003,+2,311,W,[GPIO],PB13,DrvSel:?, I2S2_CK,Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down,Speed:High,Pin:0,
37.006,+3,312,W,[GPIO],PC3,DrvSel:?,I2S2_SD(TX),Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down,Speed:High,Pin:0,
37.009,+3,313,I,[GPIO],Init,PB15,IN
42.124,+5115,314,I,[I2S],DmaStopOk!
42.124,+0,315,I,[SoundRecorder],I2sDmaStop,Ok
42.128,+4,316,I,[SoundRecorder],CloseFile:[R_4096_UT36965_5000Ms.wav]Ok
42.129,+1,317,I,[SoundRecorder],RecDuration:5086 ms
42.129,+0,318,I,[SoundRecorder],WrOkCnt:235
42.130,+1,319,E,[SoundRecorder],LongSaveErrorCnt:1
42.131,+1,320,I,[SoundRecorder],total_file_size:960044
42.131,+0,321,I,[SoundRecorder],Rec,SN:1
42.132,+1,322,I,[SoundRecorder],FileNAme:R_4096_UT36965_5000Ms.wav
42.132,+0,323,I,[I2S],I2S_2,Init
42.133,+1,324,I,[I2S],I2S_2 init
42.133,+0,325,W,[DmaChannel],N:2=I2S2_TX
42.134,+1,326,W,[Dma],2,DMA1,Stream_4,Init
42.134,+0,327,I,[Dma],Init Id:2 Dma:1 Stream:4 Ok!
42.135,+1,328,W,[DmaChannel],N:3=I2S2_RX
42.135,+0,329,W,[Dma],3,DMA1,Stream_3,Init
42.136,+1,330,I,[Dma],Init Id:3 Dma:1 Stream:3 Ok!
42.137,+1,331,I,[I2S],I2S2 initOk
44.519,+2382,332,I,[WavPlayer],ReadHeaderOk,Duration: 5.00 s,ChunkId:RIFF,ChunkSize:960036 Byte,Format:WAVE,Subchunk1Id:fmt ,Subchunk1Size:16,
AudioFormat:0x0001,NumChannels:2,SampleRate:48000 Hz,ByteRate:192000 Byte,BlockAlign:4 Byte,BitsPerSample:16 bit,Subchunk2Id:data,DataSize:960000 Byt
44.521,+2,333,I,[I2S],I2S_2,Set,Operation:Tx
44.522,+1,334,I,[WavPlayer],1,SetAudioFreq:48000 Hz
44.522,+0,335,I,[I2S],I2S_2,Set,AudioFreq:48000 Hz
44.523,+1,336,I,[I2S],I2S_2 init
44.523,+0,337,W,[DmaChannel],N:2=I2S2_TX
44.524,+1,338,W,[Dma],2,DMA1,Stream_4,Init
44.525,+1,339,I,[Dma],Init Id:2 Dma:1 Stream:4 Ok!
44.525,+0,340,W,[DmaChannel],N:3=I2S2_RX
44.526,+1,341,W,[Dma],3,DMA1,Stream_3,Init
44.528,+2,342,I,[Dma],Init Id:3 Dma:1 Stream:3 Ok!
44.528,+0,343,I,[I2S],I2S2 initOk
44.529,+1,344,W,[GPIO],PB12,DrvSel:?,I2S2_WS,Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down,Speed:High,Pin:0,
44.531,+2,345,W,[GPIO],PB13,DrvSel:?, I2S2_CK,Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down,Speed:High,Pin:0,
44.534,+3,346,W,[GPIO],PC3,DrvSel:?,I2S2_SD(TX),Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down,Speed:High,Pin:0,
44.537,+3,347,I,[GPIO],Init,PB15,IN
49.605,+5068,348,I,[I2S],DmaStopOk!
49.606,+1,349,I,[WavPlayer],I2s,Ok
49.606,+0,350,I,[WavPlayer],CloseFile:[R_4096_UT36965_5000Ms.wav]Ok
49.607,+1,351,E,[WavPlayer],SlowReadError:2
49.607,+0,352,I,[WavPlayer],File:[R_4096_UT36965_5000Ms.wav]
 52.046-->

Вы спросите:

"Для чего нужно одновременное воспроизведение и запись звука?"

Ответ прост. Это нужно для разработки сонаров. Сонар отправляет зондирующий ЛЧМ импульс и тотчас же записывает отраженные эхо сигналы. Далее над принятым сигналом вычисляется свертка с известным зондирующим сигналом (FIR фильтрация) и определяются все расстояния до окружающих предметов вокруг с погрешностью равной длительности одного семпла.

Отладка I2S-DMA Full Duplex.

Для контроля факта смещения фазы I2S приема и передачи я добавил в обработчики I2S-DMA прерываний GPIO функции для индикации событий: пин PA4 отражает поведение I2S2_RX, пин PA8 отражает поведение I2S2_TX.

Flow

GPIO

DMA move half

DMA move complete

I2S2_TX

PA8

Set 3.3V

Set 0V

I2S2_RX

PA4

Set 3.3V

Set 0V

Как видно, на осциллографе в самом деле прием и передача смещены во времени друг относительно друга на 180 градусов.

Данный режим I2S2-DMA корректно работал вот на таких значениях DMA регистров.

То же в тексте

Скрытый текст
 55.662-->dcrm 1 3;dcrm 1 4

81.315,+68877,235,W,[DmaChannel]DMA1_Stream3,CH_0,[(null)], Regs
81.315,+0,236,I,[DmaChannel],Base:0x40026058,Cnt:6
+-----+--------------------+-------+------+------------+------------+-------------------------------------------+
|  N  |        Name        |offset | size |    Addr    |   ValHex   |                  ValBin                   |
+-----+--------------------+-------+------+------------+------------+-------------------------------------------+
|   1 |           DMA_SxCR | 0x000 |    0 | 0x40026058 | 0x06032d1f | 0b0000_0110|0000_0011|0010_1101|0001_1111 |
|   2 |         DMA_SxNDTR | 0x004 |    4 | 0x4002605c | 0x000004fd | 0b0000_0000|0000_0000|0000_0100|1111_1101 |
|   3 |          DMA_SxPAR | 0x008 |    4 | 0x40026060 | 0x4000340c | 0b0100_0000|0000_0000|0011_0100|0000_1100 |
|   4 |         DMA_SxM0AR | 0x00c |    4 | 0x40026064 | 0x20008238 | 0b0010_0000|0000_0000|1000_0010|0011_1000 |
|   5 |         DMA_SxM1AR | 0x010 |    4 | 0x40026068 | 0x00000000 | 0b0000_0000|0000_0000|0000_0000|0000_0000 |
|   6 |          DMA_SxFCR | 0x014 |    4 | 0x4002606c | 0x00000020 | 0b0000_0000|0000_0000|0000_0000|0010_0000 |
+-----+--------------------+-------+------+------------+------------+-------------------------------------------+
1:21-->
81.349,+34,237,W,[DmaChannel],DMA1_Stream4,CH_0,[(null)], Regs
81.350,+1,238,I,[DmaChannel],Base:0x40026070,Cnt:6
+-----+--------------------+-------+------+------------+------------+-------------------------------------------+
|  N  |        Name        |offset | size |    Addr    |   ValHex   |                  ValBin                   |
+-----+--------------------+-------+------+------------+------------+-------------------------------------------+
|   1 |           DMA_SxCR | 0x000 |    0 | 0x40026070 | 0x00032d5f | 0b0000_0000|0000_0011|0010_1101|0101_1111 |
|   2 |         DMA_SxNDTR | 0x004 |    4 | 0x40026074 | 0x0000002a | 0b0000_0000|0000_0000|0000_0000|0010_1010 |
|   3 |          DMA_SxPAR | 0x008 |    4 | 0x40026078 | 0x4000380c | 0b0100_0000|0000_0000|0011_1000|0000_1100 |
|   4 |         DMA_SxM0AR | 0x00c |    4 | 0x4002607c | 0x20008238 | 0b0010_0000|0000_0000|1000_0010|0011_1000 |
|   5 |         DMA_SxM1AR | 0x010 |    4 | 0x40026080 | 0x00000000 | 0b0000_0000|0000_0000|0000_0000|0000_0000 |
|   6 |          DMA_SxFCR | 0x014 |    4 | 0x40026084 | 0x00000000 | 0b0000_0000|0000_0000|0000_0000|0000_0000 |
+-----+--------------------+-------+------+------------+------------+-------------------------------------------+
1:21-->

Итоги

Удалось настроить I2S-трансивер на микроконтроллере STM32F407VG в режим полного дуплекса. Удалось разнести по фазе приём и передачу аудиопотока. Удалось написать прошивку одновременного воспроизведения и записи звука на SD карту.

С настроенным I2S можно делать звуковые эффекты в реальном масштабе времени, приборы для слушания пульса, создавать сонары, дефектоскопы, слуховые аппараты и прочее.

Ссылки

Название

URL

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

https://habr.com/ru/articles/834304/

Разработка диктофона в виде бейджа
@the_bat

https://habr.com/ru/companies/selectel/articles/893164/

Исходники прошивки LoopBack Audio

https://github.com/aabzel/trunk/tree/main/source/projects/dev_ebox_stm32f4x_loopback_audio_gcc_m

Обзор Aппаратного Aудио кодека MAX9860 (2x ADC+DAC)

https://habr.com/ru/articles/758140/

Диктофон на микроконтроллере (WM8731 + I2S + STM32 + SDIO + FAT16)

https://habr.com/ru/articles/1054896/

Бинарь прошивки LoopBack Audio

https://github.com/aabzel/Artifacts/tree/main/dev_ebox_stm32f4x_loopback_audio_gcc_m

Чип AudioСodec(а) WM8731 (или (ADC/DAC)*2 из iPod(а))

https://habr.com/ru/articles/703588/

STM32 - I2S2 - Full Duplex

https://www.eevblog.com/forum/microcontrollers/stm32-i2s2-full-duplex-not-triggering-dma/

Сонар из Диктофона

https://habr.com/ru/articles/868672/

Бинарь demo прошивки I2S эха со смещением фазы

https://github.com/aabzel/Artifacts/tree/main/dev_ebox_stm32f4x_i2s_echo_gcc_m/180degPhaseShift

Вопросы
1) Как в STM32 перезарядить работающий DMA канал? То есть как заставить DMA канал работать с начала? Заново выполнить операцию перемещения данных.

2) Существуют ли микроконтроллеры, где DMA сопроцессор выдает прерывания на на 1/3; 2/3 и 3/3 перемещения массива?

3) Существуют ли в продаже готовые компактные учебные отладочные платы с ARM Cortex M4 микроконтроллером (STM32F4x), каким-нибудь простым аудиокодеком (как WM8731), audio-jack портом для наушников, микрофона (и желательно LineIn), разъемом для карты памяти microSD подключаемой по интерфейсу SDIO (или SPI), LEDом и желательно графическим монохромным дисплеем? Чтобы можно было полноценно прототипировать и отлаживать прошивки диктофонов, сонаров, направленных метрономов, будильников, звуковые эффекты для гитар, передачу и прием бинарных данных модулированным звуком, плейер и т. п..

4) Зачем в I2S трансиверах предусмотрена возможность выводить за корпус микроконтроллера пятый провод Master Clock , если и так есть отдельный провод Bit Clock?

Комментарии (6)


  1. Coder007
    20.07.2026 16:04

    Очень интересное решение!

    А с сетевым трафиком I2S не приходилось работать? С широковещательным пакетом, в полном дуплексе и просто передача звука по tcp ip? От 1 к 1 и от 1 к многим.


    1. aabzel Автор
      20.07.2026 16:04

      Очень интересное решение!

      Спасибо.

      А с сетевым трафиком I2S не приходилось работать? 

      Будучи студентом в 2015 году писал прошивку для софт процессора MicroBlaze, который был сконфигурирован на основе Xilinx FPGA Spartan 6, где происходила передача радарных данных по UDP через LwIP на скорости 470 MBit/s.


  1. sim2q
    20.07.2026 16:04

    Это нужно для разработки сонаров.

    Для аудиопроцессора же!
    Кроссовер, задержки и прочие обработчики...


    1. StarJohn
      20.07.2026 16:04

      Интересно, можно NAM A2 запустить? Но там, вроде как, M7 нужен...


      1. sim2q
        20.07.2026 16:04

        не знаю, я про колонки в основном


    1. aabzel Автор
      20.07.2026 16:04

      Что такое кроссовер в контексте звуковой обработки?