← Назад к новостям

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

В этом тексте я покажу, как из подручных материалов на основе микроконтроллера, аудиокодека и SD карты запрограммировать самый настоящий диктофон. Покажу принципы работы звукозаписывающей аппаратуры. Но обо всем по порядку... Постановка задачи Разработать прототип диктофона на основе микроконтроллера STM32F407VG и аудио кодека WM8731. Написать прошивку для записи звука в wav файлы. Звук передавать по интерфейсу I2S2. Аудиокодек конфигурировать по интерфейсу I2C2. Использовать фирменный HAL SDK от производителя микроконтроллера STM. Записанные файлы хранить на SD карте, подключенной по интерфейсу SPIO. Использовать DMA потоки для интерфейса I2S2_RX, SDIO и UART2_TX. Внутри SD карты должна быть файловая система FAT16. В прошивке использовать функции из API файловой системы FatFS. Обеспечить возможность записывать в wav файлы через интерфейс командной строки поверх UART2. Указывать длительность записываемого файла аргументами командной строки. Обеспечить возможность работать со стерео WAV файлом на частоте дискретизации 96kHz, семплами разрешением 16-бит. Вот, пожалуй, и все требования к прототипу диктофона. Аппаратная часть Итак, танцуем от печки... Из чего же я буду собирать диктофон? Основа диктофона - это учебно-тренировочная электронная плата DevEBox STM32F4XX_M_V3 . PCB хороша тем, что она миниатюрна и на ней построен гараж для microSD карты. Вот так она выглядит. DevEBox STM32F4XX_M_V3 Вот блок-схема DevEBox STM32F4XX_M_V3. Аудиокодек - это ASIC WM8731 и модуль на его основе. WM8731 - это по сути 2xADC + 2xDAC с торчащим наружу I2S и с управлением по I2C. Надо учинить вот такой прототип. Следует установить всего 12 перемычек. Вот так, просто и не затейливо: Блок-схема диктофона получается такая В качестве накопителя я выбрал микро SD карту на 128 MByte. Теория цифровой звукозаписи Главная трудность записи звука на микроконтроллерах заключается в том, что в микроконтроллерах нет столько много RAM памяти, чтобы за раз записать привычный нам звуковой файл. Одна песня в WAV формате может занимать 60MByte. Для решения этой проблемы существует классическое решение на основе двух массивов. Суть проста. Пока по I2S записывается в RAM память фрагмент 1, микроконтроллер сохраняет из RAM памяти в FAT16 файл фрагмент 2. Пока по I2S записывается в RAM память фрагмент 2, микроконтроллер сохраняет из RAM памяти в файл фрагмент 1. Таким образом становится возможным непрерывно записывать файлы, размер которых в тысячи раз превышает количество RAM памяти в микроконтроллере. Для контролирования процесса записи внутри прошивки должен работать вот такой простой конечный автомат. Состояние автомата можно переключать прямо внутри обработчика прерываний по DMA каналам. А непосредственную запись в SD карту делать в супер цикле функции main по командам конечного автомата записи. Реализация Настройка GPIO Для работы диктофона надо активировать альтернативные функции на I2S2, I2C2, SDIO, UART2. Плюс добавить несколько GPIO пинов для отладки на логическом анализаторе или осциллографе. --> -->gl +-----+-------+---------+------+------+-----+-----+---------------+ | No | pad | mode | dir | pull |MuxS |MuxG | name | +-----+-------+---------+------+------+-----+-----+---------------+ | 0 | PA8 | Out | Out | Air | 0 | 0 | I2S2_TX_DMA | | 1 | PA4 | Out | Out | Air | 0 | 0 | I2S2_RX_DMA | +-----+-------+---------+------+------+-----+-----+---------------+ | 2 | PB13 | ALT1 | Out | Down | 5 | 5 | I2S2_CK | | 3 | PB12 | ALT1 | Out | Down | 5 | 5 | I2S2_WS | | 4 | PB15 | ALT1 | In | Down | 5 | 5 | I2S2_SD | +-----+-------+---------+------+------+-----+-----+---------------+ | 5 | PB10 | ALT1 | Out | Up | 4 | 4 | I2C2_SCL | | 6 | PB11 | ALT1 | Io | Up | 4 | 4 | I2C2_SDA | +-----+-------+---------+------+------+-----+-----+---------------+ | 7 | PA2 | ALT1 | Out | Air | 7 | 7 | USART2_TX | | 8 | PA3 | ALT1 | In | Up | 7 | 7 | USART2_RX | +-----+-------+---------+------+------+-----+-----+---------------+ | 10 | PC12 | ALT1 | Out | Air | 12 | 12 | SD_CLK | | 11 | PD2 | ALT1 | Out | Up | 12 | 12 | SD_CMD | | 12 | PC8 | ALT1 | Io | Up | 12 | 12 | SD_D0 | | 13 | PC9 | ALT1 | Io | Up | 12 | 12 | SD_D1 | | 14 | PC10 | ALT1 | Io | Up | 12 | 12 | SD_D2 | | 15 | PC11 | ALT1 | Io | Up | 12 | 12 | SD_D3 | +-----+-------+---------+------+------+-----+-----+---------------+ | 9 | PA1 | Out | Out | Air | 0 | 0 | LedGreem | +-----+-------+---------+------+------+-----+-----+---------------+ --> Настройка I2S трансивера Для работы я выбрал I2S2. I2S2 трансивер надо сконфигурировать на режим записи. Микроконтроллер должен выступать в роли мастера. Есть два варианта для пина I2S2_SD: PC3 и PB15. GPIO Dir MCU pin PinMux Connector Connector PC3 out 18 AF5 J3.35 -- PB15 in 54 AF5 J2.8 J4.3 В STM32 каждый отдельный I2S трансивер может либо записывать, либо отправлять звук через пин I2Sx_SD. Поэтому для записи и последующего воспроизведения вам придется пере инициализировать I2S трансивер. Есть и отдельный режим полного дуплекса, но он требует еще одного экземпляра I2S трансивера. Полный дуплекс достигается за счет тандемной работы двух трансиверов, но это тема для отдельной статьи. Запись звука должна происходить на таких настройках GPIO. PinMux GPIO DIR Pull PIN PinMux Connector Connector I2S2_SD PB15 in down 54 AF5 J2.8 J4.3 GPIO PC3 in down 18 0 J3.35 -- I2S2_CK PB13 out down 47 5 J3.13 -- I2S2_WS PB12 Out down 51 5 J3.11 J4.5 Синтез WAV файла Как только поступит запрос на запись надо понять сколько вообще надо семплов звука. Для простоты положим, что в запросе на запись задается продолжительность записи в секундах. Известна частота дискретизации, размер одного семпла, количество каналов. Надо определить сколько памяти потребуется для поля данных. Это можно рассчитать по простой формуле: MemNeed [byte] = SampleFreq [Hz] x SampleSize [byte] x Channels [Num] x RevDuration [s] Зная параметры предстоящей записи надо синтезировать заголовок WAV файла. Этот заголовок прописывается в WAV файл и после этого дается отмашка DMA на пуск I2S_RX . Настройка каналов DMA Записывать звук следует через DMA канал для I2S2_RX. Должен быть включен циклический режим, активированы DMA прерывания, выравнивание 16 бит. Тут надо сразу вспомнить что в STM32 микроконтроллерах I2S - это режим SPI. Поэтому настройки каналов DMA надо тоже копировать от SPI. Для I2S2 настройки каналов DMA получаются такие Flow DMA Stream Channel Mode Direction I2S2_RX DMA1 Stream_3 Channel_0 CIRCULAR PERIP->MEM I2S2_TX DMA1 Stream_4 Channel_0 CIRCULAR MEM->PERIP В коде это выглядит так #define DMA_CHANNEL_I2S_COMMON \ .memory_burst = DMA_BURST_SINGLE, \ .periph_burst = DMA_BURST_SINGLE, \ .aligment_per = DMA_ALIGNMENT_WORD, \ .aligment_mem = DMA_ALIGNMENT_WORD, \ .interrupt_on = true, \ .block_count = 1, \ .valid = true, \ .mode = DMA_MODE_CIRCULAR, \ .fifo = DMA_FIFO_OFF, \ .priority = DMA_PRIOR_VERY_HIGH, #define DMA_CHANNEL_I2S2_RX \ { \ .DmaChPad = { .dma_num = 1, .stream=3, .channel = 0, .name = "I2S2_RX", }, \ DMA_CHANNEL_I2S_COMMON \ .per_inc = DMA_INC_OFF, \ .mem_inc = DMA_INC_ON, \ .dir = DMA_MCAL_DIR_PERIPH_TO_MEMORY, \ .name = "I2S2_RX", \ .num = DMA_CHANNEL_NUM_I2S2_RX, \ .base_addr_source = (uint32_t) fromArray, \ .base_addr_destination = (uint32_t) &(I2S2ext->DR), \ .block_size = (uint32_t) DMA_MEMCPY_SIZE, \ .CallBackHalf = CallBackHalfI2s2Rx, \ .CallBackDone = CallBackDoneI2s2Rx, \ }, Настройка и интерфейса SDIO В экспериментах я работал на частоте тактирования SDIO равной 12 MHz (фактическое значение по осциллографу). Повышение частоты до 24MHz не оказало существенного прироста производительности. SDIO интерфейс надо настроить на работу с DMA каналами. Flow DMA Stream Channel Mode Direction SDIO_RX DMA2 Stream_3 Channel_4 PFCTRL PERIP->MEM SDIO_TX DMA2 Stream_6 Channel_4 PFCTRL MEM->PERIP В коде это выглядит так #ifndef DMA_CHANNEL_SDIO_CONFIG_H #define DMA_CHANNEL_SDIO_CONFIG_H #ifdef __cplusplus extern "C" { #endif #include "std_includes.h" #include "dma_channel_types.h" #include "dma_channel_config.h" #ifndef MIN #define MIN(n, m) (((n) Настройка SD карты Перед установкой SD карты надо проверить что карта в самом деле поддерживает нужную битовую скорость на запись. Для этого есть утилита H2testw.exe . Скачать H2testw можно по ссылке . Утилита H2testw не стирает данные с карты. Эта утилита по частям пишет 1MByte, читает 1MByte и проверяет прочитанное с записанным. И так по всей памяти. утилита H2testw проверяет SD карту Утилита H2testw проверяет SD карту в случае успеха утилита выдает строку Test finished without errors. Тут же есть отчет по битовой скорости записи и чтения. Результат тестирования SD карты положительный. Фактическая скорость получилась 13.8 MByte/s . Это в 37.68 раз превышает необходимую скорость записи звука. Warning: Only 3758 of 3820 MByte tested. Test finished without errors. You can now delete the test files *.h2w or verify them again. Writing speed: 13.8 MByte/s Reading speed: 67.4 MByte/s H2testw v1.4 Однако может попасться и бракованная SD карта. В этом случае утилита выдаст примерно такое сообщение Отрицательный тест SD карты The media is likely to be defective. 951.9 MByte OK (1949692 sectors) 2 KByte DATA LOST (4 sectors) Details:0 KByte overwritten (0 sectors) 0 KByte slightly changed ( Отформатировать SD карту в файловую систему FAT16. Назначить Allication Unit size равным 16 kбайт. Для записи набегающего потока двухканальных 16-битных аудио семплов на частоте дискретизации 96kHz надо чтобы в SDIO , скорость записи превышала 384000 Байт в секунду. Я измерил реальную битовую скорость записи в файловую систему и получил такую справку. SDIO clock, MHz kByte/s Byte/s ratio режим, bit 5 159 162847 0,42 1 16.6 262 269003 0.70 1 12 737 754883 1.9* 4 25 292 299304 0,77 1 Для увеличения скорости записи пришлось принять ряд совсем неочевидных мер: 1) Увеличить частоту ARM Cortex-M4 ядра до максимума: 168MHz. Так системная шина тактирует SDIO трансивер с максимальной частотой. 2) Установить подтяжки к питанию для всех SDIO проводов -->gl sd +-----+-------+--------+-------+------+------+-----+-----+---------+ | No | pad | mode | level | dir | pull |MuxS |MuxG | name | +-----+-------+--------+-------+------+------+-----+-----+---------+ | 3 | PC12 | ALT1 | L | out | Up | 12 | 12 | SD_CLK | | 4 | PD2 | ALT1 | H | out | Up | 12 | 12 | SD_CMD | | 5 | PC8 | ALT1 | H | io | Up | 12 | 12 | SD_D0 | | 6 | PC9 | ALT1 | H | io | Up | 12 | 12 | SD_D1 | | 7 | PC10 | ALT1 | H | io | Up | 12 | 12 | SD_D2 | | 8 | PC11 | ALT1 | H | io | Up | 12 | 12 | SD_D3 | +-----+-------+--------+-------+------+------+-----+-----+---------+ 3) Записывать блоки размером 4k Byte за раз. 4) Перевести SDIO на 4битный режим. 5) Работать в SDIO в режиме DMA. 6) Закомментировать отладочное логирование в коде SDIO операций чтения и записи. 7) Повысить приоритет DMA каналов для SDIO до максимума. 8) Повысить приоритет прерываний для DMA каналов SDIO до максимума. 9) Отформатировать SD карту в файловую систему FAT16. Назначить Allocation Unit size равным 16 kбайт. 10) Увеличил тактирование на SDIO тактировании до 12 MHz ( максимальное 24M Hz). 11) Увеличил стековую RAM память до 16k Byte. После этих мер скорость записи заметно увеличилась и стала доходить до 853 kByte/s. Можно провернуть модульный тест fat_fs_write для измерения фактическое скорости записи в файл. Отладка диктофона Чтобы начать запись надо открыть программу PuTTY, подключиться к главной консоли управления на UART2 и выполнить команду sound_recorder_restc DurationS . Первый аргумент задает продолжительность записи. В CLI это выглядит так То же в виде текста 7:44-->sound_recorder_restc 10 465.426,431,I,[SoundRecorder],Duration,Ok 465.427,432,I,[WAV],File:R_4096_UT462732_10000Ms.wav,state:0,state:3,i2s_error_cnt:0, rec_cnt:4,sample_cnt:960000,ErrCnt:0,total_file_size:3840044, start_rec_ms:462753,stop_rec_ms:459991,data_size:3840000, Spin:6871236,cur_save_size:0,Init:On, 465.429,433,I,[WAV],ChunkId:RIFF,ChunkSize:1920036 Byte,Format:WAVE,Subchunk1Id:fmt , Subchunk1Size:16,AudioFormat:0x0001,NumChannels:2, SampleRate:96000 Hz, ByteRate:384000 Byte,BlockAlign:4 Byte,BitsPerSample:16 bit, Subchunk2Id:data,DataSize:1920000 Byte 465.440,434,I,[I2S],I2S_2,Set,Operation:Rx 465.441,435,I,[I2S],2 init 465.442,436,W,[DmaChannel],N:3=I2S2_RX 465.442,437,W,[Dma],3,DMA1,Stream_3,Init 465.443,438,I,[Dma],Init Id:3 Dma:1 Stream:3 Ok! 465.443,439,I,[I2S],I2S2 initOk 465.444,440,I,[GPIO],Init,PA0,IN 465.444,441,I,[GPIO],Init,PC3,IN 465.445,442,W,[GPIO],PB12,DrvSel:?,I2S2_WS,Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down, Speed:High,Pin:0, 465.446,443,W,[GPIO],PB13,DrvSel:?, I2S2_CK,Dir:Out,Mode:ALT1,Mux:5,LL:H,Pull:Down, Speed:High,Pin:0, 465.447,444,W,[GPIO],PB15,DrvSel:?,I2S2_SDEXT,Dir:In,Mode:ALT1,Mux:5,LL:H,Pull:Down, Speed:High,Pin:0, 465.447,445,I,[SoundRecorder],RecStart... 7:45--> 7:48--> 7:49--> 7:49-->475.277,446,I,[I2S],DmaStopOk! 475.277,447,I,[SoundRecorder],I2sDmaStop,Ok 475.281,448,I,[SoundRecorder],CloseFile:[R_4096_UT465427_10000Ms.wav]Ok 475.281,449,I,[SoundRecorder],RecDuration:9833 ms 475.282,450,I,[SoundRecorder],WrOkCnt:938 475.283,451,I,[SoundRecorder],errorCnt:0 475.283,452,I,[SoundRecorder],WrSzErrorCnt:0 475.283,453,I,[SoundRecorder],longWriteErrorCnt:0 После появления в консоли RecDuration на SD карте кристаллизуется уникальный WAV файл, содержащий запись звуковой дорожки. Этот файл можно проиграть любой мультимедийной утилитой. Например стандартной программой Media Player. Архитектура прошивки получилась вот такой В чем трудность создания диктофона? Сделать прошивку прототипа диктофона оказалось на такой уж тривиальной задачей. От записи к записи возникали всяческие неожиданные загвоздки: 1) Как это не странно, но в FatFS API не всегда отрабатывает функция f_write (). Особенно в режиме FAT32. Иной раз f_write выдает OK и правильную записанную длину , а по факту уже на PC в HexViewer явственно видно, что местами прошивка просто пишет в wav файл вкрапления непрерывных нулей. В случае FAT16 эта ситуация не воспроизводится. 2) Функция f_write при записи сваливается в ошибку FR_INT_ERR. Увеличение стека в два раза частично решило проблему. 3) Функция f_write иной раз может просто заклинить. Возвращает, что записано ноль байт и последующие записи тоже выводят 0 байт. При этом код ошибки возвращается FR_OK. Обычно такое происходит, когда в SD карте просто больше нет свободного места. Поэтому перед созданием файла надо убедиться, что на SD карте есть свободное место. 4) Функция f_write работает относительно быстро только, если записывать блоки по 4kByte. Другие размеры обычно не проходят тест на производительность (384000 Byte/s) и запись в SD карту происходит слишком медленно. В результате происходит срыв потока и запись просто теряет свою непрерывность и смысл. 5) FatFS на файловой системе FAT16 работает стабильнне, чем на FAT32. В случае с FAT32 так и не прошел модульный тест на запись 1MByte с последующей проверкой записанных байт. 6) При включенном цифровом фильтре нижних частот аудиокодек на левый канал выдает помехи. левый канал с помехами 7) По непонятным мне причинам, самая стабильная работа диктофона наблюдается на SD карте размером 128MByte (форматирование FAT16). Даже не знаю откуда у меня такая реликтовая SD карта. 8) В финальной WAV записи на левый канал эпизодически просачиваются вкрапления подозрительных одиночных пиков. Водянистый звуковой сигнал Итог В результате менее мне удалось написать STM32F407-прошивку для I2S-совместимого диктофона в возможностью записывать WAV файлы на SD карту по SDIO с файловой системой FAT16 на основе широко распространенной отладочной платы dev_ebox_stm32f4x и такого же распространенного аудиокодека WM8731. Бинари микропрограммы представлены на github . Исходный код прошивки диктофона тут же . Думаю не требуется перечислять все те возможные приложения, где может потребоваться запись звука. Надеюсь этот текст поможет кому-нибудь добавить возможность звукозаписи в свои проекты. Ссылки Название URL Диктофоны для книги рекордов https://habr.com/ru/articles/94553/ Диктофон и медицина @555www555 https://habr.com/ru/articles/876326/ Проигрыватель WAV файлов на STM32 https://habr.com/ru/articles/1051842/ Обзор диктофона Edic Weeny A110 с функцией «чёрного ящика» @EgorKotkin https://habr.com/ru/articles/467935/ Подключение SD карты по SPI (Капсула памяти) @karenic https://habr.com/ru/articles/974076/ Конечный Aвтомат Аппаратного I2C-Трансивера https://habr.com/ru/articles/856548/ Пуск I2S трансивера на Artery https://habr.com/ru/articles/830184/ Audacity https://www.audacityteam.org/ Запуск I2S Трансивера на Artery [часть 2] (DMA, FSM, PipeLine) https://habr.com/ru/articles/834304/ FatFs - Generic FAT Filesystem Module https://elm-chan.org/fsw/ff/ Сравнение микросхем аудио кодеков https://docs.google.com/spreadsheets/d/1r7tuNrQ8PPfIVcM2FeGpwa1eeWjyq41CD0MGq_Q_VV8/edit?gid=0#gid=0 STM32 - uSD - SDIO 4bit - DMA https://github.com/dtiziano/stm32_uSD_SDIO4bit/tree/main Исходный код прошивки диктофона https://github.com/aabzel/trunk/tree/main/source/projects/dev_ebox_stm32f4x_sound_recorder_gcc_m Чип AudioСodec(а) WM8731 (или (ADC/DAC)*2 из iPod(а)) https://habr.com/ru/articles/703588/ Wave File Format - формат звукового файла WAV https://microsin.net/programming/pc/wav-format.html Разбор I2S трафика в программе Logic 2 https://habr.com/ru/articles/758188/ Цифровой звук на STM32: подключаем аналоговый микрофон через SAI и NAU88C22 @a3x https://habr.com/ru/companies/selectel/articles/892852/ Обзор Aппаратного Aудио кодека MAX9860 (2x ADC+DAC) https://habr.com/ru/articles/758140/ Как Работать с UART на Микроконтроллерах (UART + FIFO = LOG) @danil_12345 https://habr.com/ru/articles/981028/ Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB https://habr.com/ru/articles/673522/ Формат WAV-файлов http://solidstate.karelia.ru/p/tutorial/asni/Glava 2/Index3.htm Отладка STM32 программатором J-LINK по SWD @danil_12345 https://habr.com/ru/articles/995996/ Обзор AудиоКодека NAU8814YG https://habr.com/ru/articles/808499/ dev_ebox_stm32f4x_sound_recorder_gcc_m https://github.com/aabzel/Artifacts/tree/main/dev_ebox_stm32f4x_sound_recorder_gcc_m утилита H2testw.exe для испытания SD карт https://www.softportal.com/getsoft-45617-h2testw-1.html Вопросы --1) Что если реальная звуковая дорожка не совпадает с тем, что указано в заголовке WAV файла? --2) Существует ли формат аудиозаписи, который в бинарном виде хранит такие метаданные как точное время начала записи и геолокацию места, где была инициирована запись звука? --3) Как на Си изменить конкретный массив в середине бинарного файла, зная отступ от начала файла, размер заменяемого фрагмента и новое значение размера массива? В качестве файловой системы используется FatFs. --4) Как измерить реальную поддерживаемую скорость записи данной конкретной microSD карты? --5) Существуют ли в продаже готовые отладочные платы с ARM Cortex M4 микроконтроллером (STM32F4X), каким-н простым аудиокодеком как WM8731, audio-jack портов для наушников, микрофона (и желательно LineIn), гаражом для microSD карты подключаемой по интерфейсу SDIO (или SPI), LEDом и желательно графическим дисплеем? Чтобы можно было полноценно прототипировать и отлаживать прошивки диктофонов и плейеров. --6) Как проверить, что диктофонная запись звука в самом деле непрерывания? Как убедиться, что авваратуна не пропустила 10-30 миллисекунд (или добавила не существовавшие сигналы) ? Только зарегистрированные пользователи могут участвовать в опросе. Войдите , пожалуйста. Вы делали запись звука на основе микроконтроллера? 33.33% да 1 66.67% нет 2 Проголосовали 3 пользователя. Воздержавшихся нет. Только зарегистрированные пользователи могут участвовать в опросе. Войдите , пожалуйста. Вы работали с интерфейсом I2S? 33.33% да 1 66.67% нет 2 Проголосовали 3 пользователя. Воздержавшихся нет. Только зарегистрированные пользователи могут участвовать в опросе. Войдите , пожалуйста. Вы работали с файловой системой FATFS? 100% да 2 0% нет 0 Проголосовали 2 пользователя. Воздержавшихся нет.
📊 Источник: Habr | Оригинал