
Многие цифровые интерфейсы поддерживают полнодуплексный режим работы. Это когда данные передаются и принимаются одновременно. Такое возможно в интерфейсах 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 |
|
Rec |
Диктофон на микроконтроллере (WM8731 + I2S + STM32 + SDIO + FAT16) |
Однако ничто не мешает делать Play+Rec одновременно, подобно тому как сантехник и электрик могут работать одновременно в одной квартире. Я собрал отдельную demo прошивку для демонстрации 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) |
|
Разработка диктофона в виде бейджа |
|
Исходники прошивки LoopBack Audio |
https://github.com/aabzel/trunk/tree/main/source/projects/dev_ebox_stm32f4x_loopback_audio_gcc_m |
Обзор Aппаратного Aудио кодека MAX9860 (2x ADC+DAC) |
|
Диктофон на микроконтроллере (WM8731 + I2S + STM32 + SDIO + FAT16) |
|
Бинарь прошивки LoopBack Audio |
https://github.com/aabzel/Artifacts/tree/main/dev_ebox_stm32f4x_loopback_audio_gcc_m |
Чип AudioСodec(а) WM8731 (или (ADC/DAC)*2 из iPod(а)) |
|
STM32 - I2S2 - Full Duplex |
https://www.eevblog.com/forum/microcontrollers/stm32-i2s2-full-duplex-not-triggering-dma/ |
Сонар из Диктофона |
|
Бинарь 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)

sim2q
20.07.2026 16:04Это нужно для разработки сонаров.
Для аудиопроцессора же!
Кроссовер, задержки и прочие обработчики...
Coder007
Очень интересное решение!
А с сетевым трафиком I2S не приходилось работать? С широковещательным пакетом, в полном дуплексе и просто передача звука по tcp ip? От 1 к 1 и от 1 к многим.
aabzel Автор
Спасибо.
Будучи студентом в 2015 году писал прошивку для софт процессора MicroBlaze, который был сконфигурирован на основе Xilinx FPGA Spartan 6, где происходила передача радарных данных по UDP через LwIP на скорости 470 MBit/s.