198л. Мини-проект: всё, что есть на F4-Discovery — акселерометр, аудио-ЦАП, микрофон и USB
На плате STM32F407G-DISC1 кроме кристалла стоят акселерометр LIS3DSH, аудио-ЦАП с усилителем CS43L22, цифровой микрофон MP45DT02, четыре светодиода, кнопка и разъём USB OTG. Обычно каждую деталь разбирают отдельным примером. Здесь они собраны в один прибор: наклон платы зажигает светодиод, кнопка переключает режим, микрофон показывает уровень звука полоской светодиодов, CS43L22 играет тон, высота которого зависит от наклона, а раз в 100 мс строка состояния уходит в USB CDC.
Самое интересное внутри — микрофон. Он отдаёт не отсчёты, а поток единиц и нулей на частоте около 1 МГц (PDM), и превратить его в звук должна программа: у F407 нет блока DFSDM, как у L4 (раздел 18б). Второе место, где легко ошибиться, — такты: у двух интерфейсов I2S одна звуковая ФАПЧ на двоих.
Проверено без платы. Ядро прибора (disco_core.c) не трогает железо:
опыт sim_disco.c собран gcc на ПК и arm-none-eabi-gcc для Cortex-M4,
прогнан в qemu-system-arm -M netduinoplus2 (вывод через semihosting,
раздел 35а) — вывод ПК и QEMU совпал строка в строку. Микрофон в опыте —
модель дельта-сигма модулятора второго порядка, не сам MP45DT02. Модель
на Python (disco_host.py) повторяет ядро бит в бит (совпали CRC-32
всех отсчётов) и по этим отсчётам считает отношение сигнал/шум и
частотную характеристику. Прошивка disco_f407.c собрана armclang по
заголовкам пака F4 без предупреждений; на плате не запускалась.
Опоры: BSP STM32F4-Discovery и драйверы lis3dsh, cs43l22 из архива
STM32CubeF4 1.28.0 — выводы, регистры и порядок настройки; руководства
UM1472 и схемы платы на диске нет, поэтому разводка — по BSP, номера
ножек корпуса LQFP100 — по таблице выводов даташита DS8626; RM0090 —
формулы и таблица 127 частот I2S, регистр RCC_PLLI2SCFGR (7.3.23),
SPI_I2SCFGR (28.5.8), таблица 42 запросов DMA1. I2S — раздел 18а,
USB CDC — разделы 20 и 198б, фильтры КИХ — 81а, выводы платы —
приложение E.3. Проверка — _verify/disco198l_check.py.
Как это называется в документации
Оригинал Перевод PDM, pulse density modulation импульсно-плотностная модуляция: один бит на такт sigma-delta modulator дельта-сигма модулятор: так микрофон получает поток бит CIC filter, integrator, comb фильтр CIC: цепочка интеграторов и «гребёнок» без умножений decimation factor коэффициент прореживания droop завал АЧХ у края полосы aliasing наложение спектров DC blocker срез постоянной составляющей dBFS децибелы от полной шкалы PLLI2S, I2SDIV, ODD, MCKOE звуковая ФАПЧ, делитель I2S, бит нечётности, выход MCK WHO_AM_I регистр-паспорт датчика
198л.1 Что делает прибор и из чего он
| Функция | Как |
|---|---|
| наклон | LIS3DSH по SPI1 (5,25 МГц), ±2g, 100 отсчётов в секунду |
| светодиоды | PD12…PD15: наклон больше 200 мг по большей оси — светодиод этой стороны |
| кнопка | PA0, опрос 1 мс, 20 мс без изменений — нажатие; три режима по кругу |
| микрофон | MP45DT02 по I2S2: такт 1,0238 МГц, CIC 4-го порядка ÷16, КИХ 64 отвода ÷4 — 15 997 отсчётов в секунду |
| уровень | средний квадрат по 256 отсчётам → целые дБ; полоска: −50, −40, −30, −20 дБ |
| звук | CS43L22: команды по I2C1, отсчёты по I2S3 через DMA1 Stream7 — 47 991 Гц, тон 440 Гц ± 440 Гц на 1 g |
| связь с ПК | USB OTG FS, класс CDC: строка 61 байт раз в 100 мс |
Разводка — из заголовков BSP (stm32f4_discovery.h,
stm32f4_discovery_audio.h, usbd_conf.c демонстрации), номера ножек —
из таблицы выводов DS8626 (столбец LQFP100):
| Узел | Сигнал | Вывод | Ножка | Периферия |
|---|---|---|---|---|
| светодиоды | LD4 зелёный, LD3 оранжевый, LD5 красный, LD6 синий | PD12, PD13, PD14, PD15 | 59, 60, 61, 62 | GPIO |
| кнопка B1 | нажатие — высокий уровень | PA0 | 23 | GPIO |
| LIS3DSH | SCK, MISO, MOSI | PA5, PA6, PA7 | 30, 31, 32 | SPI1, AF5 |
| LIS3DSH | CS, INT1, INT2 | PE3, PE0, PE1 | 2, 97, 98 | GPIO |
| CS43L22 | SCL, SDA | PB6, PB9 | 92, 96 | I2C1, AF4 |
| CS43L22 | сброс | PD4 | 85 | GPIO |
| CS43L22 | MCK, CK, SD, WS | PC7, PC10, PC12, PA4 | 64, 78, 80, 29 | I2S3, AF6 |
| MP45DT02 | такт, данные | PB10, PC3 | 47, 18 | I2S2, AF5 |
| USB OTG FS | VBUS, ID, D−, D+ | PA9, PA10, PA11, PA12 | 68, 69, 70, 71 | AF10 |
| USB, режим хоста | включение питания VBUS | PC0 | 15 | GPIO |
Всё это занимает SPI1, I2C1, SPI2 и SPI3 (как I2S) и два потока DMA1 — свободные остаются для своих задач.
198л.2 Такты: одна звуковая ФАПЧ на два I2S
Частоту I2S даёт PLLI2S: вход 1 МГц (HSE 8 МГц / PLLM = 8), умножение
на PLLI2SN, деление на PLLI2SR, дальше делитель внутри I2S
(I2SDIV, ODD). По RM0090 (7.3.23) ГУН — от 100 до 432 МГц,
PLLI2SR — от 2 до 7, частота для I2S — не выше 192 МГц. С выводом MCK
отсчётов в секунду получается I2SCLK / (256 · (2 · I2SDIV + ODD)),
без MCK при 16-битном кадре — I2SCLK / (32 · (2 · I2SDIV + ODD)).
BSP держит для выхода таблицу PLLI2SN/PLLI2SR, а делитель считает
HAL (stm32f4xx_hal_i2s.c) с округлением. Тот же расчёт в модели:
| Режим | N / R | I2SCLK |
Делитель | Частота | Ошибка |
|---|---|---|---|---|---|
| выход 48 кГц | 258 / 3 | 86 МГц | 2·3 + 1 | 47 991,071 Гц | −0,0186 % |
| выход 44,1 кГц | 271 / 6 | 45,17 МГц | 2·2 + 0 | 44 108,073 Гц | +0,0183 % |
| выход 16 кГц | 213 / 4 | 53,25 МГц | 2·6 + 1 | 16 000,601 Гц | +0,0038 % |
| запись 16 кГц (BSP) | 192 / 6 | 32 МГц | 2·15 + 1 | PCM 16 129,03 Гц | +0,806 % |
Первые три строки дают те же частоты, что таблица 127 RM0090, а полный перебор делителей при входе 1 МГц лучше не находит: 0,0186 % для 48 кГц и 0,0183 % для 44,1 кГц. Запись в BSP идёт с ошибкой почти в процент: HAL округляет делитель 31,25 до 31.
Главное: ФАПЧ одна. BSP_AUDIO_OUT_ClockConfig ставит 258/3,
BSP_AUDIO_IN_ClockConfig — 192/6. Кто настроил последним, тот и прав:
если после запуска выхода на 48 кГц включить запись, делители I2S3
останутся от 86 МГц, а частота станет 32 МГц / (256 · 7) =
17 857,1 Гц. Поэтому в приборе у ФАПЧ одна настройка на оба I2S: 86 МГц,
выход — 47 991 Гц, микрофону — I2SDIV = 42 без MCK:
SPI2->I2SPR = MIC_ODD << SPI_I2SPR_ODD_Pos | MIC_DIV;
Такт микрофона 86 / 84 = 1,0238 МГц, после прореживания в 64 раза —
15 997 отсчётов в секунду, та же ошибка −0,0186 %, что у выхода. Для
семейства 44,1 кГц (271/6) делитель 44 дал бы 16 039,3 Гц (+0,2456 %).
Прошивка проверяет все эти числа при сборке через _Static_assert:
168 МГц ядру, ровно 48 МГц USB, ГУН и PLLI2SR в пределах RM0090,
47 991 Гц и 1 023 809 Гц.
198л.3 Акселерометр и светодиоды
LIS3DSH сидит на SPI1: делитель 16 от 84 МГц — 5,25 МГц, режим 0, выбор
кристалла — PE3 вручную. Настройка — как у BSP: паспорт WHO_AM_I
(регистр 0x0F) должен вернуть 0x3F, в CTRL_REG4 (0x20) пишется 0x67 —
100 отсчётов в секунду, оси X, Y, Z, в CTRL_REG5 (0x24) — 0: ±2g.
BSP платы понимает и другой датчик, LIS302DL: он отвечает 0x3B, и оси X
и Y у него переставлены. Чувствительность при ±2g — 0,06 мг на единицу
(LIS3DSH_SENSITIVITY_0_06G), то есть 1 g — около 16 667 единиц.
Светодиоды — по правилу примера ST (mems.c): берётся ось, по которой
наклон больше, и если он больше 200 мг, горит светодиод этой стороны:
X больше 200 — LD5 красный, меньше −200 — LD4 зелёный; Y меньше −200 —
LD6 синий, больше 200 — LD3 оранжевый. 200 мг — это наклон 11,54°:
| Угол | Отсчёт | мг | Горит по X | Горит по Y |
|---|---|---|---|---|
| −45° | −11 786 | −707 | LD4 | LD6 |
| −12° | −3466 | −207 | LD4 | LD6 |
| −11° | −3181 | −190 | нет | нет |
| 11° | 3179 | 190 | нет | нет |
| 12° | 3464 | 207 | LD5 | LD3 |
| 45° | 11 785 | 707 | LD5 | LD3 |
Отсчёт — два байта, младший и старший, по одному чтению на байт, как в BSP:
static void lis_read(int16_t xyz[3])
{
for (unsigned a = 0; a < 3u; a++) { /* по одному байту, как BSP */
uint8_t lo = lis_rw((uint8_t)(0x80u | (0x28u + 2u * a)), 0);
uint8_t hi = lis_rw((uint8_t)(0x80u | (0x29u + 2u * a)), 0);
xyz[a] = lis_mg(lo, hi); /* байты без знака: не как в lis3dsh.c */
}
}
Ошибка в драйвере BSP. LIS3DSH_ReadACC в lis3dsh.c хранит байты
в int8_t и складывает (старший << 8) + младший. Младший байт от 0x80
и выше становится отрицательным, и к отсчёту прибавляется −256 единиц,
то есть −15,36 мг. Опыт перебрал все 65 536 кодов: неверна ровно
половина, 32 768.
| Отсчёт | Байты | Правильно, мг | BSP, мг |
|---|---|---|---|
| 127 | 7F 00 | 7 | 7 |
| 128 | 80 00 | 7 | −7 |
| 384 | 80 01 | 23 | 7 |
| −100 | 9C FF | −6 | −21 |
| −16 667 | E5 BE | −1000 | −1015 |
Для порога 200 мг это мелочь, для измерений — ступенька в 15 мг, которая то есть, то нет. Байты надо собирать без знака.
198л.4 Кнопка и режимы
На Discovery кнопка тянется к земле резистором на плате, нажатие — 1 (приложение E.3). Кнопка опрашивается из SysTick раз в миллисекунду; нажатием считается переход в 1, продержавшийся 20 опросов подряд. Опыт: нажатие с дребезгом 3 мс, удержание 80 мс, отпускание с дребезгом, потом помеха — иглы по 1 мс. Фронтов на входе 7, нажатие одно — на 73-й миллисекунде, через 20 мс после конца дребезга.
Три режима по кругу:
- 0 — наклон на светодиоды;
- 1 — уровень с микрофона полоской: −50, −40, −30, −20 дБ — один, два, три, четыре светодиода;
- 2 — тон в наушники: 440 Гц ровно, по оси X ±440 Гц на 1 g, в пределах 220…880 Гц (при 250 мг — 550 Гц), светодиоды — снова наклон.
198л.5 Микрофон: из потока бит — звук
PDM-микрофон выдаёт один бит на такт: чем больше единиц подряд, тем выше давление. Полезный звук — в низкочастотной части потока, шум квантования модулятор выталкивает вверх. Задача — фильтр нижних частот и прореживание в 64 раза. Делается это в два шага.
CIC 4-го порядка, ÷16. Четыре интегратора на частоте бит и четыре «гребёнки» на частоте в 16 раз ниже, без единого умножения. Одно 16-битное слово I2S — ровно один отсчёт CIC, 64 кГц. Коэффициент передачи 16⁴ = 65 536, поэтому интеграторы переполняются — и это не ошибка: в арифметике по модулю 2³² гребёнки снимают переполнение, если результат помещается в разрядность.
for (int k = 15; k >= 0; k--) { /* I2S отдаёт старший бит первым */
i0 += (word >> k & 1u) ? 1u : (uint32_t)-1;
i1 += i0;
i2 += i1;
i3 += i2;
}
КИХ 64 отвода, ÷4. Окно Кайзера (beta = 7), срез 8 кГц на частоте
64 кГц, коэффициенты Q15 с суммой ровно 32 768 — постоянная проходит
без потерь. Считается только каждый четвёртый выход: 64 умножения на
отсчёт, 1 024 000 умножений в секунду; у CIC — 4 096 000 сложений в
секунду. После КИХ — срез постоянной y += x − x₁ − y/256: частота
среза 9,95 Гц.
Опыт: модель микрофона на 1 024 000 бит в секунду, по 0,256 с на тон, постоянная 2 % шкалы. Отношение сигнал/шум — по второй половине записи, в полосе до 8 кГц:
| Тон 1 кГц, доля шкалы | CIC + КИХ | «сосчитать единицы» |
|---|---|---|
| 0,25 | 67,1 дБ | 21,9 дБ |
| 0,5 | 71,5 дБ | 27,5 дБ |
| 0,8 | 70,7 дБ | — |
| 0,9 | 64,8 дБ | — |
| 0,95 | 52,3 дБ | — |
«Сосчитать единицы» в 64 битах — это CIC первого порядка: проще некуда и на 44 дБ хуже. Выше 0,8 шкалы шум растёт — перегружается модулятор второго порядка, а не фильтр. Тишина на входе даёт на выходе ровно 0, уровень −88 дБ — ниже измеритель не различает.
Частотная характеристика, тон 0,5 шкалы; выше 8 кГц тон после прореживания попадает в полосу на другой частоте:
| Тон | Слышен как | Прогон | Модель |
|---|---|---|---|
| 100 Гц | 100 Гц | −0,03 дБ | −0,03 дБ |
| 3 кГц | 3 кГц | −0,10 дБ | −0,11 дБ |
| 5 кГц | 5 кГц | −0,33 дБ | −0,33 дБ |
| 6 кГц | 6 кГц | −0,51 дБ | −0,52 дБ |
| 7 кГц | 7 кГц | −1,76 дБ | −1,77 дБ |
| 7,5 кГц | 7,5 кГц | −3,63 дБ | −3,65 дБ |
| 9 кГц | 7 кГц | −19,52 дБ | −19,54 дБ |
| 12 кГц | 4 кГц | −83,09 дБ | −82,79 дБ |
| 20 кГц | 4 кГц | −80,63 дБ | −80,39 дБ |
| 30 кГц | 2 кГц | −97,90 дБ | −96,96 дБ |
Полоса ровная до 6 кГц (−0,5 дБ). Слабое место — 8…10 кГц: на 8 кГц характеристика по модели опускается всего на 6,9 дБ, и тон 9 кГц приходит на 7 кГц ослабленным только на 19,5 дБ. Нужна чистая полоса до 8 кГц — длиннее КИХ или частота на выходе выше.
Библиотека ST. В архиве Cube есть готовая PDM2PCM
(libPDMFilter_CM4_GCC_wc16_soft.a). Сравнить её с нашим фильтром на
том же потоке в QEMU не вышло: PDM_Filter_Init читает два адреса из
отладочной области Cortex-M4: 0xE0002000 — регистр FP_CTRL блока FPB
(аппаратные точки останова ядра) и 0xE0042000 — DBGMCU_IDCODE
(код кристалла, этот регистр добавляет ST). Ни того ни другого
netduinoplus2 не моделирует, и функция не возвращается за 15 с. Это привязка
библиотеки к кристаллу ST; BSP к тому же включает для неё тактирование
CRC («Enable CRC peripheral to unlock the PDM library»).
198л.6 Звук на CS43L22 и строка в USB
CS43L22 управляется по I2C1 на 100 кГц (CCR = 210 при 42 МГц),
адрес 0x94, сброс — PD4. Порядок записей взят из cs43l22_Init и
cs43l22_Play BSP для наушников: 17 записей, от POWER_CTL1 = 0x01
(выключен) до POWER_CTL1 = 0x9E (включён), громкость 70 из 100 по
формуле BSP — 0xCB. Формат — I2S Philips, 16 бит (INTERFACE_CTL1 = 0x04,
CODEC_STANDARD в BSP).
Отсчёты идут по I2S3 с выводом MCK: I2SDIV = 3, ODD = 1, 47 991 Гц.
DMA1 Stream7, канал 0 — запрос SPI3_TX по таблице 42 RM0090 (микрофон —
Stream3, канал 0, SPI2_RX). Буфер — две половины по 48 стереокадров
(1 мс): пока одна играет, прерывание заполняет другую тоном или нулями.
Тон — таблица синуса на 1024 точки с линейной интерполяцией и
32-битный фазовый аккумулятор: для 440 Гц шаг 39 370 533. Шаг
считается от 48 000, а кадров на самом деле 47 991,07 — тон выходит
439,918 Гц, на те же −0,0186 %.
Раз в 100 мс ядро собирает строку и отдаёт её CDC_Transmit_FS —
стек USB и класс CDC те же, что в разделах 20.7 и 198б:
t=12300 mode=2 x=250 y=-30 z=968 led=4 mic=-23dB tone=550Hz
61 байт вместе с \r\n десять раз в секунду — 610 байт/с, далеко от
потолка CDC. armclang 6.24 собрал disco_f407.c и disco_core.c без
предупреждений: 1834 и 1112 байт кода, 2690 байт таблиц ядра (синус,
КИХ, пороги уровня), 1117 байт ОЗУ.
198л.7 Грабли
Две настройки на одну PLLI2S. Выход и запись BSP ставят ФАПЧ по-своему (258/3 и 192/6), и вторая сбивает первую: 48 кГц превращаются в 17 857 Гц. Одна частота ФАПЧ на оба I2S и делители под неё.
Байты акселерометра со знаком. Как в lis3dsh.c: половина кодов
сдвинута на −15,36 мг. Собирать отсчёт из uint8_t.
Не проверить WHO_AM_I. BSP платы готов к двум датчикам: LIS3DSH
(0x3F) и LIS302DL (0x3B) с другими регистрами и переставленными осями —
и различает их только по этому регистру.
«Сосчитать единицы» вместо фильтра. Проще некуда, но 27,5 дБ вместо 71,5: шум квантования, который модулятор вытолкнул вверх, заворачивается обратно в полосу.
КИХ короткий — наложение у края. 64 отвода пропускают 9 кГц как 7 кГц с ослаблением 19,5 дБ. Полоса до 6 кГц чистая, выше — считать.
Порядок бит. I2S отдаёт старший бит первым (RM0090): первый по времени бит PDM — бит 15 слова. Перепутать — значит развернуть каждые 16 бит во времени.
Режим I2S при включённом I2SE. Поля SPI_I2SCFGR меняются при
выключенном I2S (RM0090, 28.5.8): сначала настройка, потом I2SE.
Громкий звук у микрофона. Модулятор второго порядка у 0,95 шкалы перегружается: 52,3 дБ вместо 71,5, а измеритель показывает всего −1 дБ. Держать сигнал не выше 0,8 шкалы (−2 дБ по измерителю).
Библиотека PDM2PCM в эмуляторе. Висит в PDM_Filter_Init: проверяет
кристалл. Сравнивать с ней — только на плате.
198л.8 Итоги
- Одна PLLI2S 1 МГц × 258 / 3 = 86 МГц: выход 47 991 Гц (−0,0186 %), такт микрофона 1,0238 МГц, PCM 15 997 Гц; запись по настройкам BSP (192/6) — 16 129 Гц (+0,806 %) и сбивает выход до 17 857 Гц.
- Наклон: 0,06 мг на единицу, порог 200 мг = 11,54°; драйвер BSP ошибается на −15,36 мг в 32 768 кодах из 65 536.
- Кнопка: 7 фронтов с дребезгом и помехой — одно нажатие через 20 мс.
- PDM → PCM: CIC 4-го порядка ÷16 и КИХ 64 отвода ÷4 — 71,5 дБ на тоне 0,5 шкалы против 27,5 дБ у подсчёта единиц; полоса ровная до 6 кГц (−0,51 дБ), 12…30 кГц подавлены на 80…98 дБ, 9 кГц — только на 19,5.
- Модель на Python совпала с ядром на C во всех 16 опытах с микрофоном, прогон — с моделью АЧХ до сотых долей децибела в полосе.
- CS43L22: 17 записей по I2C1, тон 440 Гц через I2S3 и DMA1 Stream7; строка 61 байт в USB CDC раз в 100 мс.
- armclang: 1834 + 1112 байт кода, 2690 байт таблиц, 1117 байт ОЗУ.
Полные исходники
disco_f407.c — вся прошивка F4-Discovery: акселерометр, кнопка, микрофон, звук и строка в USB.
/* Раздел 198л: всё, что есть на STM32F407G-DISC1, в одной прошивке.
* LIS3DSH по SPI1 → наклон на светодиоды PD12…PD15; кнопка PA0 меняет режим; микрофон MP45DT02 по I2S2 (PDM) →
* ядро disco_core (CIC + КИХ) → уровень в дБ; CS43L22: управление по I2C1, звук по I2S3 (48 кГц) — тон, высота
* от наклона; раз в 100 мс строка состояния в USB CDC (CDC_Transmit_FS из стека USB, раздел 20.7 и 198б).
* Такты: HSE 8 МГц, SYSCLK 168 МГц, APB1 42 МГц, APB2 84 МГц; одна PLLI2S на оба I2S — 86 МГц.
* Выводы и настройки периферии — по BSP STM32F4-Discovery из STM32CubeF4 1.28.0; регистры — RM0090. */
#include "stm32f4xx.h"
#include "disco_core.h"
#define HSE_HZ 8000000u
#define PLL_M 8u
#define PLL_N 336u
#define PLL_P 2u
#define PLL_Q 7u
#define SYSCLK_HZ (HSE_HZ / PLL_M * PLL_N / PLL_P)
#define USB_HZ (HSE_HZ / PLL_M * PLL_N / PLL_Q)
#define PCLK1_HZ (SYSCLK_HZ / 4u)
#define PCLK2_HZ (SYSCLK_HZ / 2u)
#define I2S_N 258u /* как у BSP для 48 кГц и в табл. 127 RM0090 */
#define I2S_R 3u
#define I2SCLK_HZ (HSE_HZ / PLL_M * I2S_N / I2S_R)
#define OUT_DIV 3u /* I2S3: MCK включён, 16 бит → fs = I2SCLK / (256 · (2·3 + 1)) */
#define OUT_ODD 1u
#define FS_OUT_HZ (I2SCLK_HZ / (256u * (2u * OUT_DIV + OUT_ODD)))
#define MIC_DIV 42u /* I2S2: MCK выключен → CK = I2SCLK / (2·42) — такт микрофона */
#define MIC_ODD 0u
#define MIC_CK_HZ (I2SCLK_HZ / (2u * MIC_DIV + MIC_ODD))
#define SPI1_DIV 16u /* как SPI_BAUDRATEPRESCALER_16 в BSP */
#define I2C_HZ 100000u /* BSP_I2C_SPEED */
#define CODEC_ADDR 0x94u /* AUDIO_I2C_ADDRESS */
#define MIC_HALF 64u /* слов I2S на половину буфера: 64 · 16 бит = 1 мс, 16 отсчётов PCM */
#define OUT_HALF 48u /* стереокадров на половину: 1 мс при 48 кГц */
_Static_assert(SYSCLK_HZ == 168000000u && USB_HZ == 48000000u, "168 МГц ядру и ровно 48 МГц USB (раздел 6, E.3)");
_Static_assert(HSE_HZ / PLL_M * PLL_N >= 100000000u && HSE_HZ / PLL_M * PLL_N <= 432000000u, "RM0090: ГУН PLL 100…432 МГц");
_Static_assert(I2S_N >= 50u && I2S_N <= 432u && HSE_HZ / PLL_M * I2S_N >= 100000000u && HSE_HZ / PLL_M * I2S_N <= 432000000u,
"RM0090 7.3.23: PLLI2SN 50…432, ГУН 100…432 МГц");
_Static_assert(I2S_R >= 2u && I2S_R <= 7u && I2SCLK_HZ <= 192000000u, "RM0090 7.3.23: PLLI2SR 2…7, I2S не выше 192 МГц");
_Static_assert(FS_OUT_HZ == 47991u, "48 кГц выходят как 47 991 Гц: ошибка −0,0186 %, как в табл. 127");
_Static_assert(MIC_CK_HZ == 1023809u && MIC_CK_HZ / 64u == 15997u, "такт микрофона 1,0238 МГц, PCM 15 997 Гц");
_Static_assert(OUT_DIV >= 2u && MIC_DIV >= 2u && MIC_DIV <= 255u, "RM0090: I2SDIV 2…255");
_Static_assert(PCLK2_HZ / SPI1_DIV == 5250000u, "SPI1 к LIS3DSH — 5,25 МГц");
_Static_assert(PCLK1_HZ / (2u * I2C_HZ) == 210u, "I2C1: CCR = 210 для 100 кГц при 42 МГц");
_Static_assert(SPI_I2SCFGR_I2SCFG_Pos == 8u && SPI_I2SCFGR_I2SSTD_Pos == 4u && SPI_I2SCFGR_CKPOL_Pos == 3u
&& SPI_I2SPR_ODD_Pos == 8u && SPI_I2SPR_MCKOE_Pos == 9u, "поля SPI_I2SCFGR и SPI_I2SPR как в RM0090");
_Static_assert(RCC_PLLI2SCFGR_PLLI2SN_Pos == 6u && RCC_PLLI2SCFGR_PLLI2SR_Pos == 28u, "поля RCC_PLLI2SCFGR как в RM0090");
extern uint8_t CDC_Transmit_FS(uint8_t *buf, uint16_t len); /* класс CDC из стека USB ST (раздел 20.7) */
static uint16_t mic_buf[2 * MIC_HALF];
static int16_t out_buf[2 * 2 * OUT_HALF];
static int16_t pcm[MIC_HALF / FIR_DEC];
static pdm_t pdm;
static lvl_t lvl = {.db = -96};
static tone_t tone;
static btn_t btn;
static volatile unsigned mode; /* 0 — наклон, 1 — уровень звука, 2 — тон по наклону */
static volatile uint32_t ms;
static int16_t acc[3];
static char line[96];
static void pin_mode(GPIO_TypeDef *g, unsigned pin, unsigned mode_, unsigned af)
{
g->MODER = (g->MODER & ~(3u << 2u * pin)) | mode_ << 2u * pin;
g->OSPEEDR |= 2u << 2u * pin;
g->AFR[pin >> 3] = (g->AFR[pin >> 3] & ~(15u << 4u * (pin & 7u))) | af << 4u * (pin & 7u);
}
static void clock_init(void)
{
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY)) {
}
FLASH->ACR = FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_5WS;
RCC->CFGR = RCC_CFGR_PPRE1_DIV4 | RCC_CFGR_PPRE2_DIV2;
RCC->PLLCFGR = PLL_M | PLL_N << RCC_PLLCFGR_PLLN_Pos | (PLL_P / 2u - 1u) << RCC_PLLCFGR_PLLP_Pos
| RCC_PLLCFGR_PLLSRC_HSE | PLL_Q << RCC_PLLCFGR_PLLQ_Pos;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY)) {
}
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) {
}
RCC->PLLI2SCFGR = I2S_N << RCC_PLLI2SCFGR_PLLI2SN_Pos | I2S_R << RCC_PLLI2SCFGR_PLLI2SR_Pos; /* одна на I2S2 и I2S3 */
RCC->CR |= RCC_CR_PLLI2SON;
while (!(RCC->CR & RCC_CR_PLLI2SRDY)) {
}
}
static uint8_t lis_rw(uint8_t addr, uint8_t val)
{
uint8_t r = 0;
GPIOE->BSRR = GPIO_BSRR_BR3; /* CS акселерометра — PE3 */
for (unsigned i = 0; i < 2u; i++) {
while (!(SPI1->SR & SPI_SR_TXE)) {
}
*(volatile uint8_t *)&SPI1->DR = i ? val : addr;
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
r = *(volatile uint8_t *)&SPI1->DR;
}
while (SPI1->SR & SPI_SR_BSY) {
}
GPIOE->BSRR = GPIO_BSRR_BS3;
return r;
}
static void lis_read(int16_t xyz[3])
{
for (unsigned a = 0; a < 3u; a++) { /* по одному байту, как BSP */
uint8_t lo = lis_rw((uint8_t)(0x80u | (0x28u + 2u * a)), 0);
uint8_t hi = lis_rw((uint8_t)(0x80u | (0x29u + 2u * a)), 0);
xyz[a] = lis_mg(lo, hi); /* байты без знака: не как в lis3dsh.c */
}
}
static int i2c_write(uint8_t reg, uint8_t val)
{
uint32_t t = 100000u;
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB))
if (!--t)
return -1;
I2C1->DR = CODEC_ADDR;
while (!(I2C1->SR1 & (I2C_SR1_ADDR | I2C_SR1_AF)))
if (!--t)
return -1;
if (I2C1->SR1 & I2C_SR1_AF) {
I2C1->SR1 &= ~I2C_SR1_AF;
I2C1->CR1 |= I2C_CR1_STOP;
return -1;
}
(void)I2C1->SR2;
I2C1->DR = reg;
while (!(I2C1->SR1 & I2C_SR1_TXE))
if (!--t)
return -1;
I2C1->DR = val;
while (!(I2C1->SR1 & I2C_SR1_BTF))
if (!--t)
return -1;
I2C1->CR1 |= I2C_CR1_STOP;
return 0;
}
/* Порядок записей — как у cs43l22_Init (наушники, громкость 70 → 0xCB) и cs43l22_Play в BSP. */
static const uint8_t CODEC_SEQ[][2] = {
{0x02, 0x01}, {0x04, 0xAF}, {0x05, 0x81}, {0x06, 0x04}, {0x20, 0xCB}, {0x21, 0xCB}, {0x0A, 0x00}, {0x0E, 0x04},
{0x27, 0x00}, {0x1F, 0x0F}, {0x1A, 0x0A}, {0x1B, 0x0A}, {0x0E, 0x06}, {0x22, 0x00}, {0x23, 0x00}, {0x04, 0xAF},
{0x02, 0x9E}};
static int codec_init(void)
{
int err = 0;
GPIOD->BSRR = GPIO_BSRR_BS4; /* PD4 — снять сброс CS43L22 */
for (unsigned i = 0; i < sizeof CODEC_SEQ / sizeof CODEC_SEQ[0]; i++)
err |= i2c_write(CODEC_SEQ[i][0], CODEC_SEQ[i][1]);
return err;
}
static void dma_ring(DMA_Stream_TypeDef *s, volatile uint32_t *periph, void *mem, uint32_t n, uint32_t dir)
{
s->CR = 0;
while (s->CR & DMA_SxCR_EN) {
}
s->PAR = (uint32_t)periph;
s->M0AR = (uint32_t)mem;
s->NDTR = n;
s->CR = (0u << DMA_SxCR_CHSEL_Pos) | dir | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0
| DMA_SxCR_PL_1 | DMA_SxCR_HTIE | DMA_SxCR_TCIE;
s->CR |= DMA_SxCR_EN;
}
void disco_start(void)
{
clock_init();
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN
| RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_DMA1EN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN | RCC_APB1ENR_SPI2EN | RCC_APB1ENR_SPI3EN;
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
for (unsigned p = 12; p <= 15u; p++)
pin_mode(GPIOD, p, 1u, 0); /* светодиоды */
pin_mode(GPIOD, 4, 1u, 0); /* сброс кодека */
pin_mode(GPIOE, 3, 1u, 0); /* CS LIS3DSH */
GPIOE->BSRR = GPIO_BSRR_BS3;
for (unsigned p = 5; p <= 7u; p++)
pin_mode(GPIOA, p, 2u, 5u); /* SPI1: PA5 SCK, PA6 MISO, PA7 MOSI */
pin_mode(GPIOB, 6, 2u, 4u); /* I2C1: PB6 SCL, PB9 SDA */
pin_mode(GPIOB, 9, 2u, 4u);
GPIOB->OTYPER |= GPIO_OTYPER_OT6 | GPIO_OTYPER_OT9;
pin_mode(GPIOC, 7, 2u, 6u); /* I2S3: PC7 MCK, PC10 CK, PC12 SD, PA4 WS */
pin_mode(GPIOC, 10, 2u, 6u);
pin_mode(GPIOC, 12, 2u, 6u);
pin_mode(GPIOA, 4, 2u, 6u);
pin_mode(GPIOB, 10, 2u, 5u); /* I2S2: PB10 CK микрофона, PC3 данные */
pin_mode(GPIOC, 3, 2u, 5u);
/* PA0 — вход без подтяжки: на плате своя подтяжка вниз, нажатие — 1 (приложение E.3) */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | (3u << SPI_CR1_BR_Pos) | SPI_CR1_SPE; /* /16, режим 0 */
int ok_acc = lis_rw(0x80u | 0x0Fu, 0) == 0x3Fu; /* WHO_AM_I = 0x3F у LIS3DSH */
if (ok_acc) {
lis_rw(0x20u, 0x67u); /* CTRL_REG4: 100 Гц, оси X, Y, Z */
lis_rw(0x24u, 0x00u); /* CTRL_REG5: ±2g, полоса 800 Гц */
}
I2C1->CR2 = PCLK1_HZ / 1000000u;
I2C1->CCR = PCLK1_HZ / (2u * I2C_HZ);
I2C1->TRISE = PCLK1_HZ / 1000000u + 1u;
I2C1->CR1 = I2C_CR1_PE;
int ok_codec = codec_init() == 0;
tone_set(&tone, 440u, 48000u, 8192);
dma_ring(DMA1_Stream7, &SPI3->DR, out_buf, 2u * 2u * OUT_HALF, DMA_SxCR_DIR_0); /* SPI3_TX: поток 7, канал 0 */
SPI3->CR2 = SPI_CR2_TXDMAEN;
SPI3->I2SPR = SPI_I2SPR_MCKOE | OUT_ODD << SPI_I2SPR_ODD_Pos | OUT_DIV;
SPI3->I2SCFGR = SPI_I2SCFGR_I2SMOD | 2u << SPI_I2SCFGR_I2SCFG_Pos; /* ведущий, передача, Philips, 16 бит */
SPI3->I2SCFGR |= SPI_I2SCFGR_I2SE; /* включать — после настройки (RM0090, 28.5.8) */
dma_ring(DMA1_Stream3, &SPI2->DR, mic_buf, 2u * MIC_HALF, 0); /* SPI2_RX: поток 3, канал 0 */
SPI2->CR2 = SPI_CR2_RXDMAEN;
SPI2->I2SPR = MIC_ODD << SPI_I2SPR_ODD_Pos | MIC_DIV;
SPI2->I2SCFGR = SPI_I2SCFGR_I2SMOD | 3u << SPI_I2SCFGR_I2SCFG_Pos | 2u << SPI_I2SCFGR_I2SSTD_Pos
| SPI_I2SCFGR_CKPOL; /* ведущий, приём, LSB, CPOL = 1 — как BSP */
SPI2->I2SCFGR |= SPI_I2SCFGR_I2SE;
NVIC_SetPriority(DMA1_Stream3_IRQn, 2);
NVIC_SetPriority(DMA1_Stream7_IRQn, 1);
NVIC_EnableIRQ(DMA1_Stream3_IRQn);
NVIC_EnableIRQ(DMA1_Stream7_IRQn);
SysTick_Config(SYSCLK_HZ / 1000u);
GPIOD->BSRR = (ok_acc && ok_codec) ? 0u : GPIO_BSRR_BS14; /* красный — что-то не ответило */
}
/* Микрофон: готова половина — 64 слова PDM → 16 отсчётов PCM → уровень. */
void DMA1_Stream3_IRQHandler(void)
{
uint32_t isr = DMA1->LISR;
DMA1->LIFCR = DMA_LIFCR_CHTIF3 | DMA_LIFCR_CTCIF3;
const uint16_t *half = (isr & DMA_LISR_TCIF3) ? mic_buf + MIC_HALF : mic_buf;
size_t n = pdm_feed(&pdm, half, MIC_HALF, pcm);
lvl_feed(&lvl, pcm, n);
}
/* Выход: пока DMA играет одну половину, вторую заполняем тоном или тишиной. */
void DMA1_Stream7_IRQHandler(void)
{
uint32_t isr = DMA1->HISR;
DMA1->HIFCR = DMA_HIFCR_CHTIF7 | DMA_HIFCR_CTCIF7;
int16_t *half = (isr & DMA_HISR_TCIF7) ? out_buf + 2u * OUT_HALF : out_buf;
if (mode == 2u)
tone_fill(&tone, half, OUT_HALF);
else
for (unsigned i = 0; i < 2u * OUT_HALF; i++)
half[i] = 0;
}
void SysTick_Handler(void)
{
ms++;
if (btn_tick(&btn, (int)(GPIOA->IDR & GPIO_IDR_ID0)))
mode = (mode + 1u) % 3u;
}
void disco_loop(void)
{
uint32_t last = 0;
for (;;) {
__WFI();
if (ms - last < 10u)
continue;
last = ms;
lis_read(acc); /* 100 раз в секунду, как ODR датчика */
uint32_t hz = tone_hz_from_tilt(acc[0]);
tone_set(&tone, hz, 48000u, 8192);
uint8_t leds = mode == 1u ? lvl_leds(lvl.db) : tilt_leds(acc[0], acc[1]);
GPIOD->BSRR = (uint32_t)leds << 12 | (uint32_t)(~leds & 0xFu) << (12 + 16);
if (last % 100u == 0u) {
size_t n = app_line(line, sizeof line, last, mode, acc[0], acc[1], acc[2], leds, lvl.db, mode == 2u ? hz : 0u);
CDC_Transmit_FS((uint8_t *)line, (uint16_t)n);
}
}
}
Дальше: раздел 198м «Мини-проект: метеостанция — датчики, журнал и экран».