Files
Gromov_IA 525a1dd90e Описание стенда и быстрый старт
- docs/stand.md: состав стенда, схема и таблица соединений, питание,
  порядок работы, нормальные показания на ПК и на выходе ЦАП, неисправности;
- README: UART через мост BL616 вместо внешнего FT232, быстрый старт,
  структура, команды симуляции через --elab-run (работают и с mcode-сборкой
  GHDL), python вместо py -3, список локальной документации;
- .gitignore: мусор генератора IP Gowin, Python, Windows.
2026-10-02 14:21:07 +03:00

27 KiB
Raw Permalink Blame History

Цифровой микрофон SPH9855 на Tang Primer 25K

Прошивка ПЛИС GW5A-25 принимает поток PDM с микрофона SPH9855LM4H-C, подключённого по линии LVDS (SN65LVDS), приводит его к 24 бит / 12 500 Гц и отдаёт отсчёты сразу в две стороны: кадрами по UART через встроенный мост BL616 на ПК и по I2S на ЦАП PCM5102A, чей аналоговый выход смотрят осциллографом.

MIC_CLK 3.125 МГц -> CIC sinc^3 /50 -> 62.5 кГц -> ФНЧ /5 -> 12 500 Гц ->

  • кадры по 50 отсчётов -> UART 921600 -> BL616 -> USB на ПК;
  • пересчёт в 48 828 Гц -> I2S -> PCM5102A -> осциллограф.

Стенд целиком — состав, схема соединений, питание, порядок включения, нормальные показания и неисправности — описан в docs/stand.md.

Быстрый старт

Сборка — в Gowin IDE: открыть DigMic.gprj, Run All. Битстрим — impl\pnr\DigMic.fs.

Загрузка в ПЛИС через мост BL616 (DirtyJTAG):

openFPGALoader -c dirtyJtag impl\pnr\DigMic.fs

Проверка: светодиод платы часто мигает (10 Гц), а на ПК идёт поток:

python -m pip install -r tools/requirements.txt
python tools/uart_live.py --port COM14

COM14 — порт моста BL616 на рабочем ПК стенда; на другом ПК номер покажет python tools/uart_capture.py --list. Норма: 250 кадров/с, CRC 0, STATUS чисто.

Структура

DigMic.gprj                     проект Gowin, GW5A-LV25MG121NC1/I0, ревизия A
impl/DigMic_process_config.json настройки синтеза и PnR (остальное в impl/ не трекается)
src/                            RTL, ограничения выводов и тайминга, IP-ядро PLL
tb/                             тестбенчи: сквозной, ветвь ЦАП, кнопка
tools/                          скрипты ПК: приём, просмотр, прослушивание, расчёт ФНЧ
docs/stand.md                   описание стенда

Тактирование и частоты

Параметр Значение Обоснование
Системный клок 50 МГц (E2) генератор платы
MIC_CLK 3.125 МГц = 50/16 Normal mode микрофона 1.1...4.8 МГц
CIC sinc^3, децимация 50 до 62.5 кГц, без умножителей, аккумуляторы 18 бит
ФНЧ 221 отвод, децимация 5 до 12 500 Гц
Выходная ЧД 12 500 Гц
UART 921600, 8N1 157 байт на кадр, 250 кадров/с, 39.25 кБ/с (43% канала)
I2S fS 48 828.125 Гц = 50/1024, BCK 64 fS, SCK 12.5 МГц = 256 fS проверено на железе отдельной прошивкой-генератором синуса

Делитель 16 выбран не произвольно: чтобы обойтись без PLL, частота PDM должна быть 50 МГц / N, а чтобы децимация до 12 500 Гц была целой, N обязан делить 4000. Из подходящих делителей 16 даёт самую высокую частоту в пределах Normal mode, то есть наибольшее передискретизирование (250).

Дециматор

Двухступенчатый, потому что одного CIC мало: его нули стоят на кратных 12.5 кГц, и в рабочей полосе он подавил бы сворачиваемые участки только на ~24 дБ. Поэтому CIC снимает поток до 62.5 кГц, а всю избирательность даёт ФНЧ.

Показатель Значение
неравномерность полосы 0...5000 Гц 0.032 дБ
подавление от 6250 Гц 76 дБ
подавление сворачиваемых полос 92 дБ

Спад CIC в полосе не компенсируется отдельным звеном: он задан прямо в желаемой АЧХ при расчёте ФНЧ, поэтому смена порядка CIC требует только перезапуска генератора коэффициентов.

Порядок 3, а не 4, выбран не из соображений АЧХ — характеристики с ним даже лучше — а по ограничению кристалла, см. ниже.

Коэффициенты и разрядности лежат в src/fir_coefs_pkg.vhd, файл генерируется:

python tools/gen_fir_coefs.py

Шкала

Постоянная '1' на выводе данных микрофона даёт полную шкалу 24 бит (код 8 388 596). Отсюда шкала выхода — dBFS, ровно та, в которой микрофон специфицирован. Вольт в тракте нет, V_REF в приёмных скриптах не нужен.

Формат кадра UART

Поле Байт Описание
SYNC 2 A5 5A
FRAME_CNT 2 uint16 BE, инкремент на кадр
STATUS 1 бит0 — переполнение FIFO, бит1 — насыщение дециматора
DATA 150 50 отсчётов x 24 бита, MSB first
CRC16 2 CCITT, poly 0x1021, init 0xFFFF, по FRAME_CNT..DATA

SYNC в CRC не входит: он нужен приёмнику для ресинхронизации до того, как тот начнёт что-либо считать.

UART_TX выведен на C3 — цепь FPGA_UART_TX платы Dock, к встроенному мосту BL616. На мосту стоит заменённая прошивка (отдельный проект BL616): она держит 921600 без потерь и одновременно даёт JTAG, так что загрузка и приём идут по одному кабелю USB-C. Со штатной прошивкой Sipeed UART моста работал плохо, а Gowin Programmer, наоборот, видит только её — с заменённой прошивкой битстрим грузится через openFPGALoader.

Выход на ЦАП

I2S и SCK — те же, что в отдельной прошивке-генераторе синуса, на которой модуль PCM5102A проверялся на железе: тот же i2s_transmitter.v без изменений, те же частоты. Источник отсчётов здесь — выход ФНЧ, те же отсчёты, что уходят в кадры UART, но мимо FIFO.

Частота ЦАП не равна 12 500 Гц и не кратна ей, но обе делятся из одного клока 50 МГц, так что их отношение точное и постоянное: 4000 / 1024 тактов. Поэтому пересчёт обходится без буфера и без подстройки, которые нужны uart_audio.py на ПК. Пересчёт — линейная интерполяция между двумя последними отсчётами, доля берётся из числа тактов с прихода последнего. Простое удержание давало бы ступени по 3–4 повтора с дрожанием на период ЦАП и зеркала 12.5 кГц ± f на −21 дБ (для 1 кГц); с интерполяцией они на −43 дБ. Задержка — один отсчёт, 80 мкс.

Полоса та же, что у тракта, — до 5 кГц. Полная шкала 24 бит — это 2.1 В скз на выходе PCM5102A, а фон в комнате около −72 dBFS и речь около −50 dBFS — без усиления это доли и единицы милливольт.

Усиление ЦАП с кнопки

Усиление ветви ЦАП переключает кнопка S1 на плате (H11): каждое нажатие — следующая ступень, ровно +6.02 дБ (сдвиг отсчёта на разряд), после DAC_GAIN_MAX снова 0. После включения питания усиление 0 — та же шкала, что на UART. На UART всегда уходят исходные отсчёты, кнопка на них не влияет.

DAC_GAIN_MAX в Top.vhd по умолчанию 8, то есть до +48 дБ: фон комнаты там уже на −24 dBFS, а при чувствительности микрофона −39 dBFS (на 94 дБ SPL) всё громче примерно 85 дБ SPL упирается в шкалу. Предел — 15 (+90 дБ). Выше шкалы отсчёт ограничивается, признака этого нигде нет.

Выбранную ступень показывает светодиод: пауза, N коротких вспышек (для 0 — одна длинная, 1 с), пауза, и он возвращается к обычной индикации. Нажатие во время показа начинает показ заново. Дребезг отсекается: уровень кнопки принимается, только если продержался 20 мс.

Уровень на выходе ЦАП при ступени N больше уровня у микрофона на 6.02·N дБ минус спад интерполяции (−0.18 дБ на 1 кГц). Постоянная составляющая микрофона около −90 dBFS и не мешает даже на верхних ступенях.

Распиновка

Полная таблица с банками, местом на плате и тем, куда идёт каждая цепь, — в docs/stand.md, привязка — в src/DigMic.cst. Кратко: clk E2, MIC_CLK K8, MIC_DAT K9, ЦАП G1/G2/L4/L3 (SCK/BCK/LRCK/DIN), UART_TX C3, кнопка H11, светодиод E8, отладочный выход PLL E3.

Точка выборки через линию LVDS

Кабель, драйверы и приёмники SN65LVDS на обоих концах и задержка выдачи самого микрофона сдвигают данные относительно MIC_CLK, и точка выборки для микрофона на коротких проводах сюда не подходит. Фаза измерена на плате: MIC_CLK на K8 и гистограмма смен уровня K9 по 16 тактам периода. Все смены — только между тактами 1 и 2, то есть через 40–60 нс после спада MIC_CLK (с точностью до целых периодов), в остальное время данные стоят: пока микрофон отпускает вывод, уровень держит вход драйвера LVDS на его плате. Поэтому бит берётся в противоположной фазе, MIC_SMP_CNT = 12 в Top.vhd, — около 150 нс запаса в обе стороны. При смене кабеля или микросхем линии фазу надо перемерить; для микрофона на коротких проводах MIC_SMP_CNT = -1 возвращает прежнюю точку.

Светодиод для поиска обрыва

LED_SRC в Top.vhd выбирает, от чего мигает светодиод: 0 — отсчёты ФНЧ (рабочий режим), 1 — отсчёты CIC, 2 — биты PDM, 3 — маяк ровно 1 Гц прямо от clk, мимо всего тракта. Режимы 0...2 ищут обрыв тракта: быстрое мерцание значит, что выбранный участок живой. Режим 3 меряет сам clk: период не 1 с означает, что системная частота не 50 МГц, и тогда врут все производные от неё.

Два ограничения кристалла, добытые на железе

Скорее всего, оба — следствие сборки под чужую ревизию. На плате стоит GW5A-25 ревизии A, а проект до 2026-09-30 собирался под B. Проверено на этой плате: один и тот же проект под B не выдаёт ни одного байта (с любым --device у программатора), под A — 250 кадров/с без ошибок CRC. JTAG-код у ревизий одинаковый, программатор расхождения не замечает. Под A оба ограничения ещё не перепроверялись.

Оба проявляются одинаково: синтез и трассировка проходят без замечаний, тайминг чистый, симуляция верная, а в кристалле тракт молча отдаёт нули.

Разрядность аккумуляторов CIC не выше 18 бит. Проверено перебором на плате с приёмом по UART: 26, 24 и 20 бит — тракт мёртв, 18, 16 и 10 бит — работает. Отсюда порядок 3: требуется ORDER*log2(DECIM)+1, для 3 и 50 это ровно 18, для 4 было бы 24. На аккумулятор ФНЧ (40 бит) ограничение не распространяется — он проверен на железе и считает верно.

Сброс по включению ставить нельзя. Добавление счётчика, держащего rst_n в нуле первые 256 тактов, убивает тракт целиком. С константой '1' всё работает. Проверено прошивкой в обе стороны.

Сборка

Проект собирается под GW5A-LV25MG121NC1/I0, Device Version A (gw5a25a-002), см. выше. В Gowin IDE — открыть DigMic.gprj и Run All. Битстрим — impl/pnr/DigMic.fs.

Выводы clk (E2) и LED (E8) на этом корпусе сидят на двойных функциях (SSPI/CPU и READY), поэтому в свойствах проекта их надо разрешить как обычные выводы, иначе размещение падает с PR2017. Это уже сделано в impl/DigMic_process_config.json, который лежит в репозитории; в IDE это флажки CPU, SSPI и READY, в gw_sh — set_option -use_cpu_as_gpio 1 -use_sspi_as_gpio 1 -use_ready_as_gpio 1.

IP-ядро src/gowin_pll (с src/pll_init.v) нужно только отладочному выходу clk_80MHz на E3, тракт от него не зависит. Ядро сгенерировано ещё под ревизию B; под A проект с ним собирается, но перегенерировать его стоит.

Занято на GW5A-25 вместе с ветвью ЦАП и кнопкой: 1179 LUT/ALU (6%), 804 регистра (4%), 5 BSRAM (9%), 3 DSP (11%). Тайминг сходится с запасом, но полагаться на него как на признак исправности нельзя — см. раздел про ограничения кристалла выше.

Состав

Файл Назначение
src/Top.vhd верхний уровень: только экземпляры и связи
src/pdm_rx.vhd тактирование микрофона и выборка бита в нужной фазе
src/cic_decim.vhd CIC sinc^3, децимация 50
src/fir_decim.vhd ФНЧ с децимацией 5, последовательная свёртка
src/fir_coefs_pkg.vhd коэффициенты ФНЧ, генерируется
src/fifo_top.vhd буфер отсчётов, заглушка под IP-ядро Gowin
src/Packets_creator.vhd сборка кадра с CRC-16
src/UART.vhd передатчик
src/dac_stream.v ветвь ЦАП: усиление, пересчёт в 48 828 Гц, I2S и SCK
src/i2s_transmitter.v передатчик I2S, без изменений из прошивки проверки ЦАП
src/led_status.vhd индикация частотой мигания
src/gain_button.vhd кнопка усиления ЦАП: дребезг, ступени, показ светодиодом
src/gowin_pll/, src/pll_init.v PLL для отладочного выхода 80 МГц
src/DigMic.cst привязка выводов
src/DigMic.sdc ограничение клока 50 МГц
tb/pdm_mic_model.vhd поведенческая модель PDM-микрофона
tb/tb_top.vhd сквозной тест: модель -> линия LVDS -> ПЛИС -> UART -> разбор кадра
tb/tb_dac_stream.v тест ветви ЦАП: отсчёты -> I2S на выводах -> разбор слов
tb/tb_gain_button.vhd тест кнопки: дребезг, ступени по кругу, вспышки
tools/gen_fir_coefs.py расчёт коэффициентов ФНЧ
tools/uart_capture.py приём кадров, запись .raw и .bin
tools/uart_live.py живой просмотр: осциллограмма, спектр, счётчики
tools/uart_audio.py прослушивание: поток на звуковую карту ПК
tools/Bin_to_wav.py перевод записанного .bin в WAV
tools/requirements.txt пакеты Python для скриптов

Симуляция

Сквозной тест, GHDL:

mkdir -p build && ghdl -a --std=08 --workdir=build src/fifo_top.vhd src/UART.vhd src/led_status.vhd src/gain_button.vhd src/pdm_rx.vhd src/cic_decim.vhd src/fir_coefs_pkg.vhd src/fir_decim.vhd src/Packets_creator.vhd src/Top.vhd tb/pdm_mic_model.vhd tb/tb_top.vhd && ghdl --elab-run --std=08 --workdir=build tb_top --stop-time=400ms

--elab-run работает с любой сборкой GHDL, в том числе с mcode, которая не даёт исполняемого файла. Ожидаемый итог — TEST PASSED.

Микрофон в тестбенче подключён как на плате, через модель линии LVDS: задержка в обе стороны и удержание уровня, пока микрофон отпускает вывод. Задержка подобрана так, что смена данных попадает между тактами 1 и 2 — туда же, где её видно на железе, — поэтому проверяется та же точка выборки.

Модель прогоняет четыре режима. Три из них дают точно вычислимый ответ: постоянные '1' и '0' — плюс и минус полная шкала, чередование 1010 — ровно ноль (эта частота попадает в нуль АЧХ CIC). Четвёртый — синус, у него проверяется размах. Тракт настраивается около 10 мс, поэтому в каждом режиме кадры читаются до первого сошедшегося.

Ветвь ЦАП и PLL написаны на Verilog, поэтому GHDL их не собирает и предупреждает, что dac_stream_inst и PLL_dbg не привязаны; тракт UART от этого не зависит. Ветвь ЦАП проверяется отдельно, в Icarus Verilog:

mkdir -p build && iverilog -g2012 -o build/tb_dac_stream tb/tb_dac_stream.v src/dac_stream.v src/i2s_transmitter.v && vvp build/tb_dac_stream

Тестбенч разбирает I2S на выводах так же, как ЦАП, и сверяет каждое слово с эталонной интерполяцией бит в бит, с учётом момента, в который оно взято. Параметры -Ptb_dac_stream.GAIN=2 и -Ptb_dac_stream.WAVE=1 (меандр между крайними кодами) проверяют ограничение по шкале и разрядности, -Ptb_dac_stream.GAIN_STEP=3 — переключение усиления на ходу по всем 16 значениям, в том числе выше GAIN_MAX.

Кнопка усиления — отдельно, в GHDL:

mkdir -p build && ghdl -a --std=08 --workdir=build src/gain_button.vhd tb/tb_gain_button.vhd && ghdl --elab-run --std=08 --workdir=build tb_gain_button

Такт в нём снижен до 100 кГц, чтобы секунды показа считались быстро. Проверяются дребезг и короткая помеха, ступени по кругу, долгое удержание, число и длительность вспышек и перезапуск показа нажатием.

ПО на ПК

Пакеты Python — в tools/requirements.txt:

python -m pip install -r tools/requirements.txt

COM-порт держит один процесс: запись, просмотр и прослушивание запускаются по очереди. Разбор кадров у всех трёх общий (FrameParser в uart_capture.py), так что они не могут разойтись.

Запись

python tools/uart_capture.py --list
python tools/uart_capture.py --port COM14 --seconds 10 --out test1

Получаются test1.raw (весь поток как пришёл, для отладки канала), test1.bin (только отсчёты, читается read_24bit_bin()) и test1.log. Кадр с несошедшейся CRC отбрасывается, пропуск по FRAME_CNT добивается нулями, чтобы не сдвинулась ось времени. Ключ --dump пишет только .raw, без разбора, и показывает, теряет ли байты сам канал.

.bin переводится в WAV для прослушивания или отправки:

python tools/Bin_to_wav.py test1.bin

Без аргумента файл выбирается в окне. Рядом появляется test1_gain5.wav: постоянная составляющая убрана, усиление GAIN, 12 500 Гц, PCM 16 бит, по WAV_BITS = 24 — полная разрядность. Настройки — константы в начале скрипта. Берётся именно .bin: в .raw лежат кадры целиком, с заголовками и CRC.

Живой просмотр

python tools/uart_live.py --port COM14

Осциллограмма в долях шкалы, спектр в dBFS и счётчики кадров в реальном времени. Управление кнопками внизу окна, те же действия — клавишами r, a, d, s, f, выход q. Запись встроена в просмотр по необходимости: запустить uart_capture.py на том же порту параллельно нельзя.

Прослушивание

python tools/uart_audio.py --port COM14

Поток уходит на звуковую карту ПК, то есть в джек на компьютере: ЦАП здесь она сама, внешнего железа не нужно. Отсчёты 12 500 Гц пересчитываются в частоту устройства вывода (обычно 48 000), на входе стоит ФВЧ 20 Гц — иначе постоянная составляющая микрофона съедает запас по шкале и даёт щелчок на старте. Полоса та же, что у тракта: 5 кГц, звук телефонного качества.

Выход выбирается тот, что назначен в Windows; если нужен другой — --list показывает и порты, и устройства, --device выбирает. Слушать лучше в наушниках: динамик рядом с микрофоном даёт самовозбуждение.

Постоянного отношения частот в программе нет нигде, и это главное: 12 500 Гц отмеряет кварц платы, частоту вывода — кварц звуковой карты, а расхождение в десятки ppm за минуту накапливает миллисекунды и рано или поздно опустошает буфер. Поэтому шаг пересчёта подстраивает регулятор по заполнению буфера — в строке состояния он виден как шаг ... ppm, там же недобор и клип.

Без порта проигрывается записанный .bin:

python tools/uart_audio.py --file test1.bin --gain 12

Проверка без порта и без звука — --selftest у uart_audio.py (пересчёт по спектру, непрерывность на стыках блоков, реакция регулятора, ФВЧ) и у uart_live.py (разбор и отрисовка).

Документация

Даташиты и схемы в репозиторий не кладутся (.gitignore), их держат локально в docs/:

Файл Что это
docs/SPH9855LM4H-C.PDF даташит микрофона
docs/Primer_25K/Tang_25K_60033.pdf схема платы Dock: гребёнка J3, кнопки, BL616
docs/Primer_25K/Tang_Primer_25K_52300_Schematic.pdf схема модуля Tang Primer 25K
docs/Primer_25K/Tang_Primer_25K_Specification_V1.0_en.pdf спецификация модуля
docs/DAC_sch.pdf, docs/datasheet_PCM5102APWR.pdf схема модуля ЦАП и даташит PCM5102A