Embedded HandbookПриложения
Все разделы › XXV. Мини-проекты
STM32F407 · уровень средний · 24 мин чтения
Открыть в интерактивном справочнике

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 мс

Прибор на F4-Discovery: слева LIS3DSH по SPI1, кнопка PA0 и микрофон MP45DT02 по I2S2 через DMA1 Stream3; в середине ядро — наклон в светодиод, режимы, PDM в PCM через CIC4 и КИХ 64; справа светодиоды PD12…PD15, USB CDC и CS43L22 по I2C1 и I2S3 через DMA1 Stream7; внизу общая PLLI2S 86 МГц, которая делится на 84 для микрофона и на 256 · 7 для ЦАП

Разводка — из заголовков 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 мс после конца дребезга.

Три режима по кругу:


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 Итоги

Полные исходники

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м «Мини-проект: метеостанция — датчики, журнал и экран».

Открыть в интерактивном справочнике