18з. MP3-декодер VS1053 по SPI: SCI и SDI, DREQ, загрузка потока с SD по DMA, громкость, сброс и патчи
В 16г звук давал готовый модуль DFPlayer: карта, декодер и усилитель внутри, микроконтроллер только шлёт по UART «играй файл 3». В 18а звук шёл по I2S: отсчёты считает сам STM32, а кодек их только выводит. VS1053b стоит посередине. Это декодер MP3, WAV, MIDI и других форматов с ЦАП и выходом на наушники, но файл ему отдаёт микроконтроллер — байт за байтом по SPI, со своей карты, из сети, откуда угодно. Здесь — драйвер: две шины на одном SPI, вывод DREQ, скорость SPI до и после настройки такта, сброс, громкость, чтение формата и битрейта, поток с SD-карты через кольцо и DMA так, чтобы задержка карты не рвала звук, правильная остановка трека и загрузка заплаток.
Опоры: RM0008 (DocID13902 Rev 17): без ремапа SPI1 — PA4–PA7, с. 185;
SPI1_TX — DMA1, канал 3, таблица 78, с. 281; биты MINC, с. 285, и
DIR, с. 286; SSM и SSI, с. 743; BR[2:0], с. 743; CPOL и CPHA,
с. 744; TXDMAEN и флаг BSY, с. 745; флаги TXE и RXNE, с. 746.
DocID13587 Rev 17 (STM32F103x8/xB): PA4–PA7 — без допуска 5 В, с. 29.
Устройство SPI и DMA — разделы 18 и 9, файловая система — 42, карта по
SPI — 18.10. Документа производителя на VS1053b в проекте нет, драйвера
на диске тоже. Команды SCI, номера регистров и биты, адреса в памяти
декодера, пределы скорости SPI, буфер 2048 байт, смысл DREQ, порядок
остановки и формат заплаток — по документации производителя, не
проверено; они сверены только со своей моделью микросхемы, в которую
заложены те же сведения. Все числа ниже — прогон драйвера
_verify/mp3_18i/vs18i.c, собранного arm-none-eabi-gcc 12.2.1 под
Cortex-M3 и запущенного в QEMU 7.2.22, против модели VS1053b, SD-карты и
DMA на Python 3.11; прошивка собрана armclang 6.24 под Cortex-M3.
Задержки SD-карты, время после записи регистра и поведение при слишком
быстром SPI — допущения модели: эти числа говорят о модели, а не о
микросхеме.
Как это называется в документации
Оригинал Перевод SCI — serial control interface шина команд: регистры по 16 бит, выбор xCS SDI — serial data interface шина данных: байты файла, выбор xDCS DREQ — data request вывод «примет ещё 32 байта» XTALI, CLKI кварц декодера и такт его ядра после умножителя clock multiplier (SC_MULT) умножитель такта в регистре CLOCKF software reset (SM_RESET) программный сброс битом в MODE new SDI mode (SM_SDINEW) «новый» режим: у данных свой выбор xDCS cancel playback (SM_CANCEL) остановка декодирования с выбросом буфера endFillByte байт-заполнитель после конца файла stream header (HDAT0, HDAT1) заголовок потока: формат и битрейт sine test (SM_TESTS) синус-тест: тон без файла patch, plugin (VLSI) заплатка: код в память декодера по SCI
18з.1 Устройство: две шины на одном SPI, DREQ и регистры
У VS1053b один SPI и два выбора (по документации производителя, не проверено):
- SCI — выбор xCS. Команда — 4 байта:
02— запись или03— чтение, номер регистра, 16 бит старшим байтом вперёд. При чтении слово приходит на MISO в третьем и четвёртом байте; - SDI — выбор xDCS. Это просто байты файла подряд, без команд и адресов. Декодер сам узнаёт формат по первым байтам;
- DREQ — выход декодера. 1 — в его буфере (2048 байт) свободно не меньше 32 байт, можно слать порцию. 0 — буфер полон или декодер занят командой SCI.
Отсюда правило драйвера: данные — порциями по 32 байта и только при DREQ = 1; перед записью SCI — тоже дождаться DREQ.
| регистр | номер | что в нём |
|---|---|---|
| MODE | 0 | режимы: SM_RESET 0x0004, SM_CANCEL 0x0008, SM_TESTS 0x0020, SM_SDINEW 0x0800; после сброса 0x4800 |
| STATUS | 1 | биты 7:4 — версия, у VS1053 — 4 |
| CLOCKF | 3 | SC_MULT (15:13) — умножитель кварца, SC_ADD (12:11), SC_FREQ (10:0) — нестандартный кварц |
| DECODE_TIME | 4 | секунд с начала декодирования |
| AUDATA | 5 | частота отсчётов (биты 15:1), бит 0 — стерео |
| WRAM, WRAMADDR | 6, 7 | окно в память декодера: адрес, затем слова, адрес растёт сам |
| HDAT0, HDAT1 | 8, 9 | заголовок потока: у MP3 — первые 4 байта кадра |
| AIADDR | 10 | адрес запуска заплатки |
| VOL | 11 | ослабление: старший байт — левый канал, младший — правый |
Модель — VS1053b как ведомый SPI: регистры, буфер 2048 байт, DREQ, разбор настоящих заголовков кадров MP3 с их битрейтом и длительностью, WAV и MIDI по заголовку, отмена, сброс, синус-тест, память декодера. Файлы MP3 для прогона она собирает сама: MPEG1 Layer III, 44,1 кГц, joint stereo, настоящие заголовки и длины кадров, вместо звука — байты 1…254. Звука модель не делает: «играть» для неё — забирать кадры из буфера в их темпе.
18з.2 Подключение к F103, сброс и запуск: скорость SPI до и после CLOCKF
SPI1 без ремапа — PA4–PA7 (с. 185), все четыре без допуска 5 В (с. 29),
модуль питают от 3,3 В. PA5 — SCK, PA6 — MISO, PA7 — MOSI. Выборы xCS и
xDCS — обычные выходы: PA4 и PB0, SSM = SSI = 1 (с. 743). DREQ — PB1
на вход с прерыванием EXTI1 по фронту, XRESET — PB10. Режим SPI — 0:
CPOL = CPHA = 0 (с. 744).
Скорость. Такт ядра декодера CLKI после сброса равен кварцу XTALI, 12,288 МГц. Запись SCI и SDI — не быстрее CLKI/4, чтение SCI — не быстрее CLKI/7 (по документации производителя, не проверено). Регистр CLOCKF = 0x8800 — умножитель 3,5 и запас +1,0 для тяжёлых форматов (по документации, не проверено) — поднимает CLKI до 43,008 МГц. Делитель SPI1 — степень двойки от 72 МГц, и не быстрее 18 МГц (18.3). Драйвер выбирает самый быстрый, что не превышает предела:
uint32_t vs_spi_pick(uint32_t pclk_hz, uint32_t max_hz, uint32_t *br)
{
uint32_t b;
for (b = 0; b < 7u; b++) { /* делитель 2^(b+1): 2, 4 … 256 */
uint32_t f = pclk_hz >> (b + 1u);
if (f <= max_hz && f <= SPI_MAX_HZ)
break;
}
*br = b;
return pclk_hz >> (b + 1u);
}
| CLKI | запись: предел | SPI1 | чтение: предел | SPI1 | |
|---|---|---|---|---|---|
| до CLOCKF | 12,288 МГц | 3,072 МГц | 2,25 МГц, BR = 4 |
1,7554 МГц | 1,125 МГц, BR = 5 |
| после CLOCKF | 43,008 МГц | 10,752 МГц | 9 МГц, BR = 2 |
6,144 МГц | 4,5 МГц, BR = 3 |
Команда SCI — 32 бита: 14,22 мкс на 2,25 МГц и 3,56 мкс на 9 МГц.
Запуск. Аппаратный сброс XRESET, ожидание DREQ, проверка версии, MODE с SM_SDINEW, CLOCKF и проверка обоих чтением:
int vs_init(vs_t *vs, uint32_t pclk_hz, uint32_t flags)
{
uint16_t st;
int rc;
vs->pclk_hz = pclk_hz;
vs->spi_hz = 0;
vs->dreq_waits = 0;
if ((rc = vs_hw_reset(vs)) != VS_OK)
return rc; /* DREQ не поднялся: питание, XRESET, провод */
st = vs_sci_read(vs, VS_STATUS);
vs->version = (uint8_t)((st >> 4) & 15u);
if (vs->version != 4u)
return VS_E_ID; /* 0xFFFF — MISO висит вверху: нет ответа */
if (flags & VS_FIX_GPIO_BOOT) { /* модуль стартует в MIDI: GPIO — выходы с нулём */
if ((rc = vs_wram_write(vs, VS_GPIO_DDR, 3u)) != VS_OK || (rc = vs_wram_write(vs, VS_GPIO_ODATA, 0u)) != VS_OK
|| (rc = vs_soft_reset(vs)) != VS_OK)
return rc;
} else {
if ((rc = vs_sci_write(vs, VS_MODE, VS_MODE_RUN)) != VS_OK || (rc = vs_sci_write(vs, VS_CLOCKF, VS_CLOCKF_RUN)) != VS_OK)
return rc;
}
if ((rc = vs_wait_dreq(vs, 100000u)) != VS_OK) /* PLL декодера устанавливается */
return rc;
if (vs_sci_read(vs, VS_MODE) != VS_MODE_RUN || vs_sci_read(vs, VS_CLOCKF) != VS_CLOCKF_RUN)
return VS_E_VERIFY; /* запись не дошла: SPI быстрее, чем можно */
return VS_OK;
}
Обмены запуска в модели:
| посылка SCI | ответ | SPI | что |
|---|---|---|---|
03 01 00 00 |
0040 |
1,125 МГц | STATUS: версия 4 |
02 00 08 00 |
— | 2,25 МГц | MODE = SM_SDINEW |
02 03 88 00 |
— | 2,25 МГц | CLOCKF = 0x8800 |
03 00 00 00 |
0800 |
4,5 МГц | MODE прочитан обратно |
03 03 00 00 |
8800 |
4,5 МГц | CLOCKF прочитан обратно |
Весь запуск — 2988,1 мкс, из них XRESET внизу — 1 мс, остальное — DREQ
после сброса: около 22 000 тактов XTALI, 1790,4 мкс (по документации
производителя, не проверено; в модели — так). Драйвер ждал DREQ 173 раза
по 10 мкс. Нет модуля — в модели MISO висит вверху, STATUS
читается 0xFFFF, версия 15: VS_E_ID, −2. DREQ не поднимается — после 100 мс
VS_E_TIMEOUT, −1 (в модели — через 111,0 мс вместе с опросами).
Сброс. Аппаратный — XRESET вниз и вверх; в модели он длился 2794,0 мкс и вернул CLOCKF к нулю. Программный — запись MODE с SM_RESET:
int vs_soft_reset(vs_t *vs)
{
int rc;
if ((rc = vs_sci_write(vs, VS_MODE, VS_MODE_RUN | SM_RESET)) != VS_OK) /* SM_SDINEW — обязательно */
return rc;
vs_port_delay_us(2u);
vs->clki_hz = VS_XTALI_HZ; /* снова медленно, пока не записан CLOCKF */
if ((rc = vs_wait_dreq(vs, 100000u)) != VS_OK)
return rc;
return vs_sci_write(vs, VS_CLOCKF, VS_CLOCKF_RUN);
}
В модели — 1819,8 мкс, после него MODE = 0x0800 и CLOCKF = 0x8800. Бит SM_SDINEW пишут вместе с SM_RESET: 0x0804, а не 0x0004 (18з.7). После программного сброса модель возвращает CLOCKF к нулю (допущение модели), поэтому драйвер снова опускает SPI и пишет CLOCKF заново — так верно при любом поведении микросхемы.
Модуль и питание. На распространённых платах с VS1053b стоят кварц 12,288 МГц, стабилизаторы на 3,3 В и 1,8 В — ядро микросхемы питается от 1,8 В, аналоговая часть и выводы — от 3,3 В, — гнездо наушников и иногда слот microSD на том же SPI (по документации производителя и описаниям модулей, не проверено). Общий провод наушников на выходе — не земля, а вывод GBUF с половиной питания: соединять его с землёй усилителя нельзя (по документации, не проверено). Модель напряжений не считает.
18з.3 Громкость, формат, битрейт и синус-тест
Громкость. VOL — ослабление от максимума с шагом 0,5 дБ, старший байт — левый канал, младший — правый; 0xFE — тишина (по документации производителя, не проверено):
vs_set_volume |
VOL | левый | правый |
|---|---|---|---|
| 0x00, 0x00 | 0x0000 | 0 дБ | 0 дБ |
| 0x14, 0x14 | 0x1414 | −10 дБ | −10 дБ |
| 0x28, 0x28 | 0x2828 | −20 дБ | −20 дБ |
| 0x00, 0xFE | 0x00FE | 0 дБ | −127 дБ, тишина |
| 0xFE, 0xFE | 0xFEFE | тишина | тишина |
Формат и битрейт. HDAT1 говорит, что за поток: у MP3 в нём слово синхронизации — одиннадцать единиц, версия и слой. HDAT0 — второе слово заголовка кадра: индекс битрейта в битах 15:12, индекс частоты — в 11:10. AUDATA — частота отсчётов и бит стерео, DECODE_TIME — секунды:
if ((h1 & 0xFFE0u) == 0xFFE0u) { /* слово синхронизации MP3: 11 единиц */
uint32_t id = (h1 >> 3) & 3u, idx = h0 >> 12;
in->fmt = VS_FMT_MP3;
in->mpeg = (uint8_t)(id == 3u ? 1 : id == 2u ? 2 : 25);
in->layer = (uint8_t)(4u - ((h1 >> 1) & 3u));
if (in->layer == 3u && idx < 15u)
in->kbps = in->mpeg == 1u ? KBPS_V1[idx] : KBPS_V2[idx];
} else if (h1 == 0x7665u) {
in->fmt = VS_FMT_WAV; /* "ve" */
} else if (h1 == 0x4D54u) {
in->fmt = VS_FMT_MIDI; /* "MT" */
} else if (h1 == 0x4F67u) {
in->fmt = VS_FMT_OGG; /* "Og" */
} else {
in->fmt = h1 ? VS_FMT_OTHER : VS_FMT_NONE;
}
return VS_OK;
Модель посреди трека:
| поток | HDAT1 | HDAT0 | AUDATA | драйвер прочёл |
|---|---|---|---|---|
| MP3 128 кбит/с | 0xFFFB | 0x9244 | 0xAC45 | MPEG1, Layer III, 128 кбит/с, 44 100 Гц, стерео |
| MP3 320 кбит/с | 0xFFFB | 0xE244 | 0xAC45 | MPEG1, Layer III, 320 кбит/с, 44 100 Гц, стерео |
| WAV 8 кГц, 16 бит, моно | 0x7665 | — | 0x1F40 | WAV, 8000 Гц, моно |
| MIDI | 0x4D54 | — | — | MIDI |
Индекс 9 — 128 кбит/с, 14 — 320 кбит/с (таблица MPEG1 Layer III, по документации, не проверено). На середине файла в 3 с DECODE_TIME — 1. Переключать «режим MP3 или WAV» не нужно: формат декодер узнаёт сам по первым байтам, и после SM_CANCEL (18з.6) можно сразу слать файл другого формата. MIDI-файл модель только узнаёт, но не играет.
Синус-тест. С битом SM_TESTS в MODE восемь байт 53 EF 6E n 0 0 0 0
по SDI включают тон, 45 78 69 74 0 0 0 0 — выключают. Частота —
Fs · S / 128, где Fs — по трём старшим битам n (44 100, 48 000, 32 000,
22 050, 24 000, 16 000, 11 025, 12 000 Гц), S — пять младших (по
документации производителя, не проверено):
uint32_t vs_sine_hz(uint8_t n)
{
return (uint32_t)SINE_FS[n >> 5] * (n & 31u) / 128u; /* Fs · S / 128 */
}
n = 0x44 — 32 000 · 4 / 128 = 1000 Гц, n = 0x7E — 22 050 · 30 / 128
= 5167,97 Гц (драйвер считает целыми — 5167). Модель включила тон на
той же частоте и выключила его, MODE вернулся к 0x0800. Без SM_TESTS
те же восемь байт — просто данные: тон не включился. Синус-тест
проверяет ЦАП, усилитель и наушники без файла и без SD-карты — первое,
что стоит запустить на новой плате.
18з.4 Поток с SD: кольцо, DMA и прерывание DREQ
Файл читает FatFS (раздел 42) секторами по 512 байт в кольцо в ОЗУ.
Декодеру его отдаёт vs_feed: пока DREQ = 1 и в кольце есть 32 байта,
запускать посылку по DMA. Порция всегда целая: размер кольца — степень
двойки, сектор и 32 его делят.
void vs_feed(vs_ring_t *r)
{
uint32_t n;
if (r->inflight) {
if (vs_port_sdi_busy())
return; /* позовут по концу DMA */
r->tail += r->inflight;
r->inflight = 0;
}
n = r->head - r->tail;
if (n < VS_CHUNK && !(r->eof && n)) { /* хвост файла короче 32 — отдать как есть */
if (!r->eof && vs_port_dreq())
r->starved++; /* декодер ждёт, а в кольце пусто */
return;
}
if (!vs_port_dreq())
return; /* буфер декодера полон: позовёт фронт DREQ */
if (n > VS_CHUNK)
n = VS_CHUNK;
if (feed_vs)
speed(feed_vs, VS_WRITE_DIV);
r->inflight = n;
r->chunks++;
vs_port_sdi_start(r->buf + (r->tail & (r->size - 1u)), n);
}
Главный цикл — vs_pump: есть место под сектор — читать SD, иначе
ждать:
int vs_pump(vs_ring_t *r, vs_read_fn rd, void *ctx)
{
if (!r->eof && vs_ring_free(r) >= VS_SECTOR) { /* место под сектор — читать SD */
int n = rd(ctx, r->buf + (r->head & (r->size - 1u)), VS_SECTOR);
if (n < 0)
return VS_E_READ;
r->head += (uint32_t)n;
if (n < (int)VS_SECTOR)
r->eof = 1;
vs_port_kick();
return 1;
}
if (r->eof && r->head == r->tail && !r->inflight)
return 0; /* всё ушло в декодер */
vs_port_kick();
vs_port_idle();
return 1;
}
Чтение сектора SD длится то доли миллисекунды, то десятки (18з.5). Если
vs_feed зовёт только главный цикл, на это время декодер остаётся без
данных. Поэтому в прошивке её зовут прерывания: фронт DREQ (EXTI1) и
конец посылки DMA (канал 3, SPI1_TX — с. 281):
void vs_port_sdi_start(const uint8_t *p, uint32_t n)
{
GPIOB->BRR = 1u << XDCS_PIN;
sdi_active = 1;
DMA1_Channel3->CCR = 0;
DMA1_Channel3->CMAR = (uint32_t)p;
DMA1_Channel3->CNDTR = n;
DMA1_Channel3->CCR = DMA_CCR3_MINC | DMA_CCR3_DIR | DMA_CCR3_TCIE | DMA_CCR3_EN; /* память → SPI1->DR */
SPI1->CR2 |= SPI_CR2_TXDMAEN;
}
void EXTI1_IRQHandler(void)
{
EXTI->PR = 1u << DREQ_PIN;
vs_feed(&ring);
}
DIR = 1 — из памяти в периферию, MINC — адрес памяти растёт (с. 286
и 285), TXDMAEN — запрос по TXE (с. 745). Принятое по MISO не нужно:
по концу посылки драйвер читает DR и SR, как в 18.6. Команда SCI из
главного цикла и посылка DMA делят один SPI: на время SCI порт
выключает оба прерывания и ждёт конца посылки (vs_port_bus).
Прогон: файл 3 с, кольцо 4096 байт, кормление по прерываниям, SD без долгих задержек:
| битрейт | байт | кадров | посылок по 32 | провалов | сыграно | SPI занят |
|---|---|---|---|---|---|---|
| 128 кбит/с | 48 065 | 115 | 1503 | 0 | 3004,082 мс | 1,54 % |
| 320 кбит/с | 120 163 | 115 | 3756 | 0 | 3004,082 мс | 3,74 % |
Кадр MP3 на 44,1 кГц — 1152 отсчёта, 26,122 мс; на 128 кбит/с в нём 417 или 418 байт, на 320 — 1044 или 1045. Порция 32 байта на 9 МГц — 28,44 мкс, на 2,25 МГц — 113,78 мкс: шина не узкое место, узкое — SD.
18з.5 Сколько буфера нужно: битрейт и задержки SD
Битрейт — бит в секунду, в байтах — в 8 раз меньше: 128 кбит/с — 16 000 байт/с, 320 кбит/с — 40 000 байт/с. Буфера декодера, 2048 байт, хватает на 128 мс при 128 кбит/с и на 51,2 мс при 320.
Карта отвечает неровно: обычно сектор за доли миллисекунды, но иногда — десятки миллисекунд (в модели: 0,8 мс и каждый 40-й сектор ещё 100 мс, допущение модели). Пока карта молчит, звук берётся из буферов. На 100 мс при 320 кбит/с нужно 4000 байт. Буфер декодера даёт 2048 — мало. Кольцо при чтении секторами заполнено не меньше чем на свой размер минус 511 байт: 2048 + 2048 − 511 = 3585 — тоже мало; 2048 + 4096 − 511 = 5633 — хватает. При 128 кбит/с 100 мс — 1600 байт, и хватает одного буфера декодера. Модель, файл 4 с:
| битрейт | кормление | кольцо 1024 | 2048 | 4096 | 8192 |
|---|---|---|---|---|---|
| 128 кбит/с, 3 задержки | из цикла | 0 | 0 | 0 | 0 |
| 128 кбит/с, 3 задержки | прерывания | 0 | 0 | 0 | 0 |
| 320 кбит/с, 7 задержек | из цикла | 7, 353,151 мс | 7, 353,183 мс | 7, 353,151 мс | 7, 354,984 мс |
| 320 кбит/с, 7 задержек | прерывания | 7, 270,582 мс | 4, 92,187 мс | 0 | 0 |
В клетке — сколько раз декодер остался без кадра и сколько длилась
тишина. Кормление из главного цикла на 320 кбит/с не спасает никакое
кольцо: пока цикл стоит в f_read, кольцо никто не отдаёт. С
прерываниями хватает 4096 байт — у BluePill это пятая часть ОЗУ.
Чем больше задержки карты, тем больше кольцо; их длину и частоту стоит
замерить на своей карте.
18з.6 Остановка трека: endFillByte, SM_CANCEL и заплатки
Конец файла. Последний кадр декодер доиграет, только если за ним придут ещё байты. Поэтому после файла шлют 2052 байта endFillByte — его значение лежит в памяти декодера по адресу 0x1E06, — затем ставят SM_CANCEL и шлют по 32 байта, пока бит не снимется; не снялся за 2048 байт — программный сброс (по документации производителя, не проверено):
int vs_finish(vs_t *vs)
{
uint8_t f[VS_CHUNK];
uint8_t efb = (uint8_t)vs_wram_read(vs, VS_PAR_END_FILL);
int rc;
for (uint32_t i = 0; i < VS_CHUNK; i++)
f[i] = efb;
for (uint32_t i = 0; i < VS_FILL_END; i += VS_CHUNK) /* 2052 байта endFillByte: декодер доиграет */
if ((rc = sdi_send(vs, f, VS_FILL_END - i < VS_CHUNK ? VS_FILL_END - i : VS_CHUNK)) != VS_OK)
return rc;
if ((rc = vs_sci_write(vs, VS_MODE, (uint16_t)(vs->mode | SM_CANCEL))) != VS_OK)
return rc;
for (uint32_t sent = 0; sent < VS_CANCEL_MAX; sent += VS_CHUNK) {
if ((rc = sdi_send(vs, f, VS_CHUNK)) != VS_OK)
return rc;
if (!(vs_sci_read(vs, VS_MODE) & SM_CANCEL))
return (int)(sent / VS_CHUNK + 1u); /* сколько порций после SM_CANCEL */
}
(void)vs_soft_reset(vs); /* не снялся за 2048 байт */
return VS_E_CANCEL;
}
В модели endFillByte — 0x00. После 3-секундного трека SM_CANCEL снялся после первой же порции, все 115 кадров сыграны, в буфере — 0 байт, HDAT1 обнулён.
Посреди трека. vs_cancel останавливает кормление, ставит SM_CANCEL
и шлёт остаток файла из кольца, проверяя бит после каждой порции; когда
он снялся, HDAT0 и HDAT1 должны быть нулями, иначе — сброс. В модели
остановка через 1 с: бит снялся после 14 порций — декодер выбросил
буфер на конце звучащего кадра, — сыграно 35 кадров, HDAT0 = HDAT1 = 0.
Без этого. Тот же трек просто бросили — перестали слать данные и
сразу начали следующий. Модель: через 300 мс в буфере так и лежали
374 байта — неполный последний кадр ждал продолжения. Следующий файл
дописался к нему: вышел один кадр из двух файлов, затем 373 байта мусора
до ближайшего заголовка — первый кадр второго трека пропал. Испорченный
кадр — это щелчок. С vs_cancel перед следующим файлом — 0 испорченных
кадров.
Заплатки. VLSI выпускает для VS1053b заплатки и плагины — исправления и новые возможности, например декодеры других форматов. Это код в память декодера; его грузят по SCI после каждого сброса, потому что память стирается. Формат — массив 16-битных слов: номер регистра, число, значения; старший бит числа — повторить одно значение (по документации производителя, не проверено):
int vs_load_patch(vs_t *vs, const uint16_t *p, uint32_t n)
{
uint32_t i = 0;
int rc;
while (i < n) {
uint16_t addr, cnt;
if (i + 2u > n)
return VS_E_ARG;
addr = p[i++];
cnt = p[i++];
if (cnt & 0x8000u) { /* сжатие: одно значение cnt раз */
cnt &= 0x7FFFu;
if (i >= n)
return VS_E_ARG;
while (cnt--)
if ((rc = vs_sci_write(vs, (uint8_t)addr, p[i])) != VS_OK)
return rc;
i++;
} else { /* cnt значений подряд в один регистр */
if (i + cnt > n)
return VS_E_ARG;
while (cnt--)
if ((rc = vs_sci_write(vs, (uint8_t)addr, p[i++])) != VS_OK)
return rc;
}
}
return VS_OK;
}
Настоящих заплаток VLSI на диске нет, загрузчик проверен своей: адрес
0x8010 в WRAMADDR, четыре слова подряд, ещё три — одним повтором, и
запуск AIADDR = 0x0050. Модель получила 9 записей SCI, в памяти —
1111, 2222, 3333, 4444, 00AB, 00AB, 00AB, адрес запуска 0x0050.
Обрезанный массив — VS_E_ARG, −5.
18з.7 Грабли
Быстрый SPI до CLOCKF. Сразу 9 МГц после сброса: в модели обе записи
— MODE и CLOCKF — потерялись (допущение модели: слишком быстрая запись
теряется), CLOCKF остался 0x0000, а MODE, прочитанный на 9 МГц, — 0x2400
вместо 0x4800 (допущение: слишком быстрое чтение сдвигает слово). Тот же
MODE на 1,125 МГц — 0x4800. vs_init читает MODE и CLOCKF обратно: на
такой ошибке он вернёт VS_E_VERIFY, −3.
Перепутаны xCS и xDCS. Запись громкости 02 0B 20 20 ушла на xDCS:
VOL остался 0x0000, а четыре байта легли в буфер потока как данные.
Начало кадра MP3 FF FB 90 44 ушло на xCS: декодер не узнал команду и
промолчал, HDAT1 = 0x0000. Ни ошибки, ни звука.
Не ждать DREQ. Наивный цикл: сектор с SD — 16 порций подряд без проверки DREQ. Файл 2 с, 32 182 байта; в модели из них 28 473 байта пропали на полном буфере, декодер нашёл 8 кадров из 77 и 3 раза сбился. DREQ — не пожелание, а условие.
«Старый» режим SDI. Программный сброс записью 0x0004 снял SM_SDINEW:
MODE = 0x0000. Без него xDCS не работает (по документации производителя;
в модели байты без SM_SDINEW пропадают). Секундный файл ушёл в никуда:
0 кадров, DECODE_TIME 0, SM_CANCEL не снялся за 2048 байт, и vs_finish
сделал программный сброс, VS_E_CANCEL, −4. В MODE всегда пишут
SM_SDINEW: 0x0800, при сбросе — 0x0804.
Модуль стартует в MIDI. У части модулей вывод GPIO1 подтянут, и
декодер просыпается в режиме MIDI реального времени: байты MP3 он
принимает за ноты (известная беда таких модулей, не проверено). В модели
такой модуль сыграл 0 кадров из 38. Исправление — в vs_init с флагом
VS_FIX_GPIO_BOOT: в WRAM 0xC017 = 3 и 0xC019 = 0, то есть GPIO0 и
GPIO1 — выходы с нулём, затем программный сброс; после него — 38 кадров
из 38.
Щелчок при остановке. Трек бросили без endFillByte и SM_CANCEL —
хвост кадра смешался со следующим файлом (18з.6). Остановка — только
через vs_finish или vs_cancel.
Кормление из главного цикла. На 320 кбит/с любая задержка SD длиннее 51,2 мс — тишина, и кольцо не помогает (18з.5). Посылки — из прерываний DREQ и DMA.
SCI посреди посылки DMA. Чтение HDAT или запись VOL во время
посылки SDI портит обе: выборы разные, а SPI один. В драйвере SCI берёт
шину через vs_port_bus: прерывания кормления выключены, посылка DMA
дожата.
18з.8 Итоги
- Один SPI, два выбора: SCI (xCS) — команда 4 байта, SDI (xDCS) — данные порциями по 32 байта при DREQ = 1; буфер декодера 2048 байт.
- Скорость: до CLOCKF запись 2,25 МГц и чтение 1,125 МГц; после CLOCKF = 0x8800 (CLKI 43,008 МГц) — 9 и 4,5 МГц.
- Запуск: XRESET, DREQ, версия 4 в STATUS, MODE 0x0800, CLOCKF 0x8800, проверка чтением — 2988,1 мкс.
- VOL: шаг 0,5 дБ, 0x1414 — −10 дБ, 0xFEFE — тишина.
- HDAT1 0xFFFB, HDAT0 0x9244 — MPEG1 Layer III, 128 кбит/с, 44,1 кГц; WAV — 0x7665, MIDI — 0x4D54.
- 128 кбит/с — 16 000 байт/с, буфер декодера — 128 мс; 320 кбит/с — 40 000 байт/с, 51,2 мс; SPI занят на 1,54 и 3,74 %.
- Задержка SD 100 мс на 320 кбит/с: кормление по прерываниям и кольцо 4096 байт — 0 провалов; из главного цикла — 7.
- Конец трека: 2052 байта endFillByte, SM_CANCEL, порции по 32 байта — снялся после 1 порции; без этого — испорченный кадр и 373 байта мусора.
Полные исходники
vs18i.h — регистры и биты VS1053b, структуры, интерфейс драйвера и платформы.
/* vs18i.h — раздел 18з: драйвер MP3-декодера VS1053b по SPI без регистров STM32.
* Две шины на одном SPI: SCI (команды, выбор xCS, регистры по 16 бит) и SDI (поток файла, выбор xDCS).
* Платформа даёт обмен SCI, запуск посылки SDI по DMA, вывод DREQ, вывод XRESET, паузу и скорость SPI:
* в прошивке fw18i.c — SPI1, DMA1 и выводы STM32F103, в стенде host18i.c — модель model18i.py.
* Номера регистров, биты, адреса в памяти и порядок действий — по документации производителя (описания
* VS1053b в проекте нет, не проверено), сверены только с моделью. */
#ifndef VS18I_H
#define VS18I_H
#include <stdint.h>
/* ── регистры SCI ─────────────────────────────────────────────────────── */
#define VS_MODE 0x0u /* режимы: SM_RESET, SM_CANCEL, SM_TESTS, SM_SDINEW */
#define VS_STATUS 0x1u /* SS_VER (биты 7:4) — 4 у VS1053 */
#define VS_BASS 0x2u
#define VS_CLOCKF 0x3u /* SC_MULT (15:13), SC_ADD (12:11), SC_FREQ (10:0) */
#define VS_DECODE_TIME 0x4u /* секунд с начала декодирования */
#define VS_AUDATA 0x5u /* частота отсчётов (15:1), бит 0 — стерео */
#define VS_WRAM 0x6u /* окно в память декодера */
#define VS_WRAMADDR 0x7u /* адрес для WRAM, растёт сам */
#define VS_HDAT0 0x8u /* заголовок потока: у MP3 — битрейт, частота, режим */
#define VS_HDAT1 0x9u /* у MP3 — слово синхронизации, версия, слой */
#define VS_AIADDR 0xAu /* адрес запуска заплатки */
#define VS_VOL 0xBu /* ослабление: старший байт — левый, младший — правый, шаг 0,5 дБ */
#define VS_SCI_WRITE 0x02u
#define VS_SCI_READ 0x03u
/* ── биты MODE ────────────────────────────────────────────────────────── */
#define SM_RESET 0x0004u /* программный сброс, сам снимается */
#define SM_CANCEL 0x0008u /* остановить декодирование, сам снимается */
#define SM_TESTS 0x0020u /* разрешить тесты (синус) */
#define SM_SDINEW 0x0800u /* «новый» режим SDI: свой выбор xDCS */
#define VS_MODE_RUN SM_SDINEW /* что пишет драйвер в MODE в работе */
/* ── тактирование и скорость SPI ──────────────────────────────────────── */
#define VS_XTALI_HZ 12288000u /* кварц модуля */
#define VS_CLOCKF_RUN 0x8800u /* SC_MULT = 3,5×, SC_ADD = +1,0× */
#define VS_WRITE_DIV 4u /* запись SCI и SDI: не быстрее CLKI/4 */
#define VS_READ_DIV 7u /* чтение SCI: не быстрее CLKI/7 */
/* ── память декодера ──────────────────────────────────────────────────── */
#define VS_PAR_END_FILL 0x1E06u /* endFillByte: младший байт */
#define VS_GPIO_DDR 0xC017u /* направление GPIO */
#define VS_GPIO_ODATA 0xC019u /* выходы GPIO */
/* ── поток ────────────────────────────────────────────────────────────── */
#define VS_CHUNK 32u /* DREQ = 1 — примет не меньше 32 байт */
#define VS_SECTOR 512u /* порция чтения с SD */
#define VS_FILL_END 2052u /* endFillByte после конца файла */
#define VS_CANCEL_MAX 2048u /* SM_CANCEL не снялся за 2048 байт — программный сброс */
enum { VS_OK = 0, VS_E_TIMEOUT = -1, VS_E_ID = -2, VS_E_VERIFY = -3, VS_E_CANCEL = -4, VS_E_ARG = -5, VS_E_READ = -6 };
enum { VS_FMT_NONE = 0, VS_FMT_MP3, VS_FMT_WAV, VS_FMT_MIDI, VS_FMT_OGG, VS_FMT_OTHER };
#define VS_FIX_GPIO_BOOT 1u /* флаг vs_init: модуль стартует в MIDI — прижать GPIO и сбросить */
typedef struct {
uint32_t clki_hz; /* такт ядра декодера: XTALI до CLOCKF, 3,5 × XTALI после */
uint32_t spi_hz; /* что сейчас стоит на SPI */
uint32_t pclk_hz; /* такт шины SPI в STM32, для выбора делителя */
uint16_t mode; /* копия MODE */
uint8_t version; /* SS_VER из STATUS */
uint32_t dreq_waits; /* сколько раз ждали DREQ */
} vs_t;
typedef struct {
uint8_t *buf;
uint32_t size; /* степень двойки, кратна 512 */
volatile uint32_t head; /* счётчик записанных байт: пишет главный цикл (SD) */
volatile uint32_t tail; /* счётчик отданных декодеру: пишет vs_feed */
volatile uint32_t inflight; /* байт в посылке DMA, 0 — DMA свободен */
volatile uint32_t starved; /* DREQ = 1, а в кольце меньше 32 байт */
uint32_t eof; /* файл дочитан */
uint32_t chunks; /* сколько посылок по 32 байта ушло */
} vs_ring_t;
typedef struct {
uint8_t fmt; /* VS_FMT_… по HDAT1 */
uint8_t mpeg; /* 1 — MPEG1, 2 — MPEG2, 25 — MPEG2.5 */
uint8_t layer;
uint8_t channels; /* 1 или 2 — по AUDATA */
uint16_t kbps; /* битрейт MP3 из HDAT0 */
uint32_t fs; /* частота отсчётов из AUDATA */
uint16_t hdat0, hdat1, audata, dtime;
} vs_info_t;
typedef int (*vs_read_fn)(void *ctx, uint8_t *dst, uint32_t n); /* сколько прочитано, < 0 — ошибка */
/* ── платформа: прошивка или стенд ────────────────────────────────────── */
void vs_port_sci(const uint8_t tx[4], uint8_t rx[4]); /* xCS вниз, 4 байта в обе стороны, xCS вверх */
void vs_port_sdi_start(const uint8_t *p, uint32_t n); /* xDCS вниз, n байт по DMA; xDCS вверх — по концу */
int vs_port_sdi_busy(void); /* посылка SDI ещё идёт */
int vs_port_dreq(void); /* уровень DREQ */
void vs_port_xreset(int level);
void vs_port_delay_us(uint32_t us);
void vs_port_spi_hz(uint32_t hz, uint32_t br); /* поставить SPI на hz (делитель 2^(br+1)) */
void vs_port_bus(int take); /* 1 — SPI нужен для SCI: остановить кормление */
void vs_port_kick(void); /* вызвать vs_feed: прерывание или прямо */
void vs_port_idle(void); /* делать нечего: ждать события */
/* ── скорость SPI ─────────────────────────────────────────────────────── */
uint32_t vs_spi_pick(uint32_t pclk_hz, uint32_t max_hz, uint32_t *br); /* самый быстрый делитель ≤ max_hz */
uint32_t vs_clki(uint16_t clockf); /* CLKI по CLOCKF, Гц */
/* ── SCI ──────────────────────────────────────────────────────────────── */
int vs_wait_dreq(vs_t *vs, uint32_t timeout_us);
int vs_sci_write(vs_t *vs, uint8_t reg, uint16_t v);
uint16_t vs_sci_read(vs_t *vs, uint8_t reg);
uint16_t vs_wram_read(vs_t *vs, uint16_t addr);
int vs_wram_write(vs_t *vs, uint16_t addr, uint16_t v);
/* ── управление ───────────────────────────────────────────────────────── */
int vs_hw_reset(vs_t *vs);
int vs_soft_reset(vs_t *vs);
int vs_init(vs_t *vs, uint32_t pclk_hz, uint32_t flags);
int vs_set_volume(vs_t *vs, uint8_t left, uint8_t right); /* шаги по 0,5 дБ; 0xFE — тишина */
int vs_read_info(vs_t *vs, vs_info_t *in);
uint32_t vs_sine_hz(uint8_t n); /* частота синус-теста для байта n */
int vs_sine_test(vs_t *vs, uint8_t n, int on);
int vs_load_patch(vs_t *vs, const uint16_t *p, uint32_t n); /* сжатый формат заплаток: addr, n, данные */
/* ── поток ────────────────────────────────────────────────────────────── */
void vs_stream_bind(vs_t *vs); /* чей SPI настраивать в vs_feed */
void vs_ring_init(vs_ring_t *r, uint8_t *buf, uint32_t size);
uint32_t vs_ring_free(const vs_ring_t *r);
void vs_feed(vs_ring_t *r); /* из прерываний DREQ и DMA или из цикла */
int vs_pump(vs_ring_t *r, vs_read_fn rd, void *ctx); /* шаг главного цикла: 1 — играет, 0 — всё отдано */
int vs_finish(vs_t *vs); /* конец файла: endFillByte и SM_CANCEL */
int vs_cancel(vs_t *vs, vs_ring_t *r); /* остановка посреди трека */
#endif
vs18i.c — SCI, скорость SPI, сброс и запуск, громкость, формат, синус-тест, заплатки, кольцо, DMA и остановка.
/* vs18i.c — раздел 18з: драйвер VS1053b. Только SCI, SDI и DREQ; шину и выводы даёт платформа. */
#include "vs18i.h"
#define SPI_MAX_HZ 18000000u /* F103: SPI не быстрее 18 МГц (раздел 18.3) */
static const uint8_t SC_MULT_X10[8] = {10, 20, 25, 30, 35, 40, 45, 50};
static const uint16_t KBPS_V1[15] = {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320};
static const uint16_t KBPS_V2[15] = {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
static const uint16_t SINE_FS[8] = {44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000};
/* ── скорость SPI ─────────────────────────────────────────────────────── */
uint32_t vs_spi_pick(uint32_t pclk_hz, uint32_t max_hz, uint32_t *br)
{
uint32_t b;
for (b = 0; b < 7u; b++) { /* делитель 2^(b+1): 2, 4 … 256 */
uint32_t f = pclk_hz >> (b + 1u);
if (f <= max_hz && f <= SPI_MAX_HZ)
break;
}
*br = b;
return pclk_hz >> (b + 1u);
}
uint32_t vs_clki(uint16_t clockf)
{
uint32_t sc_freq = clockf & 0x7FFu;
uint32_t xtali = sc_freq ? 8000000u + sc_freq * 4000u : VS_XTALI_HZ; /* SC_FREQ = 0 — кварц 12,288 МГц */
return xtali / 10u * SC_MULT_X10[clockf >> 13];
}
static void speed(vs_t *vs, uint32_t div)
{
uint32_t br, hz = vs_spi_pick(vs->pclk_hz, vs->clki_hz / div, &br);
if (hz != vs->spi_hz) {
vs_port_spi_hz(hz, br);
vs->spi_hz = hz;
}
}
/* ── SCI ──────────────────────────────────────────────────────────────── */
int vs_wait_dreq(vs_t *vs, uint32_t timeout_us)
{
for (uint32_t t = 0; !vs_port_dreq(); t += 10u) {
if (t >= timeout_us)
return VS_E_TIMEOUT;
vs->dreq_waits++;
vs_port_delay_us(10u);
}
return VS_OK;
}
int vs_sci_write(vs_t *vs, uint8_t reg, uint16_t v)
{
uint8_t tx[4] = {VS_SCI_WRITE, reg, (uint8_t)(v >> 8), (uint8_t)v}, rx[4];
int rc;
vs_port_bus(1); /* SPI занят: кормление по DMA ждёт */
if ((rc = vs_wait_dreq(vs, 100000u)) != VS_OK) { /* DREQ = 0 — прошлая команда ещё идёт */
vs_port_bus(0);
return rc;
}
speed(vs, VS_WRITE_DIV);
vs_port_sci(tx, rx);
vs_port_bus(0);
if (reg == VS_MODE)
vs->mode = (uint16_t)(v & ~(SM_RESET | SM_CANCEL));
if (reg == VS_CLOCKF)
vs->clki_hz = vs_clki(v); /* дальше SPI можно быстрее */
return VS_OK;
}
uint16_t vs_sci_read(vs_t *vs, uint8_t reg)
{
uint8_t tx[4] = {VS_SCI_READ, reg, 0, 0}, rx[4];
vs_port_bus(1);
speed(vs, VS_READ_DIV); /* чтение медленнее записи: CLKI/7 */
vs_port_sci(tx, rx);
vs_port_bus(0);
return (uint16_t)(rx[2] << 8 | rx[3]);
}
uint16_t vs_wram_read(vs_t *vs, uint16_t addr)
{
if (vs_sci_write(vs, VS_WRAMADDR, addr) != VS_OK)
return 0xFFFFu;
return vs_sci_read(vs, VS_WRAM);
}
int vs_wram_write(vs_t *vs, uint16_t addr, uint16_t v)
{
int rc = vs_sci_write(vs, VS_WRAMADDR, addr);
return rc != VS_OK ? rc : vs_sci_write(vs, VS_WRAM, v);
}
/* ── сброс и запуск ───────────────────────────────────────────────────── */
int vs_hw_reset(vs_t *vs)
{
vs_port_xreset(0);
vs_port_delay_us(1000u);
vs_port_xreset(1);
vs->clki_hz = VS_XTALI_HZ; /* после сброса CLKI = XTALI */
vs->mode = VS_MODE_RUN;
return vs_wait_dreq(vs, 100000u); /* DREQ поднимется, когда декодер готов */
}
int vs_soft_reset(vs_t *vs)
{
int rc;
if ((rc = vs_sci_write(vs, VS_MODE, VS_MODE_RUN | SM_RESET)) != VS_OK) /* SM_SDINEW — обязательно */
return rc;
vs_port_delay_us(2u);
vs->clki_hz = VS_XTALI_HZ; /* снова медленно, пока не записан CLOCKF */
if ((rc = vs_wait_dreq(vs, 100000u)) != VS_OK)
return rc;
return vs_sci_write(vs, VS_CLOCKF, VS_CLOCKF_RUN);
}
int vs_init(vs_t *vs, uint32_t pclk_hz, uint32_t flags)
{
uint16_t st;
int rc;
vs->pclk_hz = pclk_hz;
vs->spi_hz = 0;
vs->dreq_waits = 0;
if ((rc = vs_hw_reset(vs)) != VS_OK)
return rc; /* DREQ не поднялся: питание, XRESET, провод */
st = vs_sci_read(vs, VS_STATUS);
vs->version = (uint8_t)((st >> 4) & 15u);
if (vs->version != 4u)
return VS_E_ID; /* 0xFFFF — MISO висит вверху: нет ответа */
if (flags & VS_FIX_GPIO_BOOT) { /* модуль стартует в MIDI: GPIO — выходы с нулём */
if ((rc = vs_wram_write(vs, VS_GPIO_DDR, 3u)) != VS_OK || (rc = vs_wram_write(vs, VS_GPIO_ODATA, 0u)) != VS_OK
|| (rc = vs_soft_reset(vs)) != VS_OK)
return rc;
} else {
if ((rc = vs_sci_write(vs, VS_MODE, VS_MODE_RUN)) != VS_OK || (rc = vs_sci_write(vs, VS_CLOCKF, VS_CLOCKF_RUN)) != VS_OK)
return rc;
}
if ((rc = vs_wait_dreq(vs, 100000u)) != VS_OK) /* PLL декодера устанавливается */
return rc;
if (vs_sci_read(vs, VS_MODE) != VS_MODE_RUN || vs_sci_read(vs, VS_CLOCKF) != VS_CLOCKF_RUN)
return VS_E_VERIFY; /* запись не дошла: SPI быстрее, чем можно */
return VS_OK;
}
int vs_set_volume(vs_t *vs, uint8_t left, uint8_t right)
{
return vs_sci_write(vs, VS_VOL, (uint16_t)(left << 8 | right));
}
int vs_read_info(vs_t *vs, vs_info_t *in)
{
uint16_t h1 = vs_sci_read(vs, VS_HDAT1), h0 = vs_sci_read(vs, VS_HDAT0);
in->hdat0 = h0;
in->hdat1 = h1;
in->audata = vs_sci_read(vs, VS_AUDATA);
in->dtime = vs_sci_read(vs, VS_DECODE_TIME);
in->fs = in->audata & 0xFFFEu;
in->channels = (uint8_t)((in->audata & 1u) ? 2 : 1);
in->mpeg = in->layer = 0;
in->kbps = 0;
if ((h1 & 0xFFE0u) == 0xFFE0u) { /* слово синхронизации MP3: 11 единиц */
uint32_t id = (h1 >> 3) & 3u, idx = h0 >> 12;
in->fmt = VS_FMT_MP3;
in->mpeg = (uint8_t)(id == 3u ? 1 : id == 2u ? 2 : 25);
in->layer = (uint8_t)(4u - ((h1 >> 1) & 3u));
if (in->layer == 3u && idx < 15u)
in->kbps = in->mpeg == 1u ? KBPS_V1[idx] : KBPS_V2[idx];
} else if (h1 == 0x7665u) {
in->fmt = VS_FMT_WAV; /* "ve" */
} else if (h1 == 0x4D54u) {
in->fmt = VS_FMT_MIDI; /* "MT" */
} else if (h1 == 0x4F67u) {
in->fmt = VS_FMT_OGG; /* "Og" */
} else {
in->fmt = h1 ? VS_FMT_OTHER : VS_FMT_NONE;
}
return VS_OK;
}
/* ── посылка SDI с ожиданием: тесты и конец трека ─────────────────────── */
static int sdi_send(vs_t *vs, const uint8_t *p, uint32_t n)
{
int rc;
vs_port_bus(1);
if ((rc = vs_wait_dreq(vs, 100000u)) == VS_OK) {
speed(vs, VS_WRITE_DIV);
vs_port_sdi_start(p, n);
while (vs_port_sdi_busy()) {
}
}
vs_port_bus(0);
return rc;
}
uint32_t vs_sine_hz(uint8_t n)
{
return (uint32_t)SINE_FS[n >> 5] * (n & 31u) / 128u; /* Fs · S / 128 */
}
int vs_sine_test(vs_t *vs, uint8_t n, int on)
{
static const uint8_t off_seq[8] = {0x45, 0x78, 0x69, 0x74, 0, 0, 0, 0}; /* "Exit" */
uint8_t on_seq[8] = {0x53, 0xEF, 0x6E, n, 0, 0, 0, 0};
int rc;
if (on) {
if ((rc = vs_sci_write(vs, VS_MODE, VS_MODE_RUN | SM_TESTS)) != VS_OK) /* без SM_TESTS — просто данные */
return rc;
return sdi_send(vs, on_seq, 8);
}
if ((rc = sdi_send(vs, off_seq, 8)) != VS_OK)
return rc;
return vs_sci_write(vs, VS_MODE, VS_MODE_RUN);
}
int vs_load_patch(vs_t *vs, const uint16_t *p, uint32_t n)
{
uint32_t i = 0;
int rc;
while (i < n) {
uint16_t addr, cnt;
if (i + 2u > n)
return VS_E_ARG;
addr = p[i++];
cnt = p[i++];
if (cnt & 0x8000u) { /* сжатие: одно значение cnt раз */
cnt &= 0x7FFFu;
if (i >= n)
return VS_E_ARG;
while (cnt--)
if ((rc = vs_sci_write(vs, (uint8_t)addr, p[i])) != VS_OK)
return rc;
i++;
} else { /* cnt значений подряд в один регистр */
if (i + cnt > n)
return VS_E_ARG;
while (cnt--)
if ((rc = vs_sci_write(vs, (uint8_t)addr, p[i++])) != VS_OK)
return rc;
}
}
return VS_OK;
}
/* ── поток: кольцо, DMA и DREQ ────────────────────────────────────────── */
static vs_t *feed_vs; /* для скорости SPI в vs_feed */
void vs_ring_init(vs_ring_t *r, uint8_t *buf, uint32_t size)
{
r->buf = buf;
r->size = size;
r->head = r->tail = r->inflight = r->starved = 0;
r->eof = r->chunks = 0;
}
uint32_t vs_ring_free(const vs_ring_t *r)
{
return r->size - (r->head - r->tail); /* байты в полёте DMA ещё заняты */
}
void vs_feed(vs_ring_t *r)
{
uint32_t n;
if (r->inflight) {
if (vs_port_sdi_busy())
return; /* позовут по концу DMA */
r->tail += r->inflight;
r->inflight = 0;
}
n = r->head - r->tail;
if (n < VS_CHUNK && !(r->eof && n)) { /* хвост файла короче 32 — отдать как есть */
if (!r->eof && vs_port_dreq())
r->starved++; /* декодер ждёт, а в кольце пусто */
return;
}
if (!vs_port_dreq())
return; /* буфер декодера полон: позовёт фронт DREQ */
if (n > VS_CHUNK)
n = VS_CHUNK;
if (feed_vs)
speed(feed_vs, VS_WRITE_DIV);
r->inflight = n;
r->chunks++;
vs_port_sdi_start(r->buf + (r->tail & (r->size - 1u)), n);
}
int vs_pump(vs_ring_t *r, vs_read_fn rd, void *ctx)
{
if (!r->eof && vs_ring_free(r) >= VS_SECTOR) { /* место под сектор — читать SD */
int n = rd(ctx, r->buf + (r->head & (r->size - 1u)), VS_SECTOR);
if (n < 0)
return VS_E_READ;
r->head += (uint32_t)n;
if (n < (int)VS_SECTOR)
r->eof = 1;
vs_port_kick();
return 1;
}
if (r->eof && r->head == r->tail && !r->inflight)
return 0; /* всё ушло в декодер */
vs_port_kick();
vs_port_idle();
return 1;
}
int vs_finish(vs_t *vs)
{
uint8_t f[VS_CHUNK];
uint8_t efb = (uint8_t)vs_wram_read(vs, VS_PAR_END_FILL);
int rc;
for (uint32_t i = 0; i < VS_CHUNK; i++)
f[i] = efb;
for (uint32_t i = 0; i < VS_FILL_END; i += VS_CHUNK) /* 2052 байта endFillByte: декодер доиграет */
if ((rc = sdi_send(vs, f, VS_FILL_END - i < VS_CHUNK ? VS_FILL_END - i : VS_CHUNK)) != VS_OK)
return rc;
if ((rc = vs_sci_write(vs, VS_MODE, (uint16_t)(vs->mode | SM_CANCEL))) != VS_OK)
return rc;
for (uint32_t sent = 0; sent < VS_CANCEL_MAX; sent += VS_CHUNK) {
if ((rc = sdi_send(vs, f, VS_CHUNK)) != VS_OK)
return rc;
if (!(vs_sci_read(vs, VS_MODE) & SM_CANCEL))
return (int)(sent / VS_CHUNK + 1u); /* сколько порций после SM_CANCEL */
}
(void)vs_soft_reset(vs); /* не снялся за 2048 байт */
return VS_E_CANCEL;
}
int vs_cancel(vs_t *vs, vs_ring_t *r)
{
uint8_t f[VS_CHUNK];
uint8_t efb = (uint8_t)vs_wram_read(vs, VS_PAR_END_FILL);
uint32_t pos, end = r->head;
int rc;
r->eof = 1;
r->head = r->tail + r->inflight; /* vs_feed больше ничего не возьмёт */
while (r->inflight)
vs_port_kick();
pos = r->tail;
for (uint32_t i = 0; i < VS_CHUNK; i++)
f[i] = efb;
if ((rc = vs_sci_write(vs, VS_MODE, (uint16_t)(vs->mode | SM_CANCEL))) != VS_OK)
return rc;
for (uint32_t sent = 0; sent < VS_CANCEL_MAX; sent += VS_CHUNK) {
const uint8_t *p = f;
if (end - pos >= VS_CHUNK) { /* сначала — файл из кольца, потом endFillByte */
p = r->buf + (pos & (r->size - 1u));
pos += VS_CHUNK;
}
if ((rc = sdi_send(vs, p, VS_CHUNK)) != VS_OK)
return rc;
if (!(vs_sci_read(vs, VS_MODE) & SM_CANCEL)) { /* снялся: проверить, что заголовок обнулён */
if (vs_sci_read(vs, VS_HDAT0) || vs_sci_read(vs, VS_HDAT1))
break;
return (int)(sent / VS_CHUNK + 1u);
}
}
(void)vs_soft_reset(vs); /* не снялся за 2048 байт */
return VS_E_CANCEL;
}
void vs_stream_bind(vs_t *vs)
{
feed_vs = vs;
}
fw18i.c — прошивка F103: SPI1, DMA1 канал 3, EXTI1 на DREQ, FatFS, три трека по кругу.
/* fw18i.c — раздел 18з: MP3-плеер на VS1053b, STM32F103 (BluePill), 72 МГц, APB2 = 72 МГц.
* SPI1 (режим 0): PA5 — SCK, PA6 — MISO, PA7 — MOSI; PA4 — xCS (SCI), PB0 — xDCS (SDI), PB1 — DREQ (вход,
* прерывание EXTI1 по фронту), PB10 — XRESET. Посылки SDI по 32 байта — DMA1, канал 3 (SPI1_TX), его прерывание
* и фронт DREQ зовут vs_feed. Файлы — FatFS с SD-карты (диск — раздел 42 и 18.10, здесь не повторяется).
* Через отладчик: volume — ослабление в шагах 0,5 дБ, stop_req = 1 — остановить трек. Тактирование до 72 МГц —
* как в разделе 6. */
#include "stm32f10x.h"
#include "ff.h"
#include "vs18i.h"
#define CPU_HZ 72000000u
#define XCS_PIN 4u /* PA4 */
#define XDCS_PIN 0u /* PB0 */
#define DREQ_PIN 1u /* PB1 */
#define XRESET_PIN 10u /* PB10 */
static vs_t vs;
static vs_ring_t ring;
static uint8_t ringbuf[4096]; /* 102,4 мс при 320 кбит/с сверх 2048 байт декодера */
static FATFS fs;
static FIL fil;
static volatile uint8_t sdi_active;
static uint8_t vol_set;
volatile uint8_t volume = 0x14, stop_req;
volatile int32_t vs_rc = 1, track_rc;
volatile uint32_t tracks_played;
static int expired(uint32_t t0, uint32_t us)
{
return DWT->CYCCNT - t0 >= us * (CPU_HZ / 1000000u);
}
void vs_port_delay_us(uint32_t us)
{
uint32_t t0 = DWT->CYCCNT;
while (!expired(t0, us)) {
}
}
static void hw_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_SPI1EN;
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
GPIOA->BSRR = 1u << XCS_PIN; /* xCS вверх до настройки вывода */
GPIOB->BSRR = 1u << XDCS_PIN;
/* PA4 — выход 50 МГц (0011), PA5 и PA7 — альтернативная функция (1011), PA6 — вход (0100) */
GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu) | 0xB4B30000u;
/* PB0 — выход (0011), PB1 — вход с подтяжкой вниз (1000, ODR = 0) */
GPIOB->CRL = (GPIOB->CRL & 0xFFFFFF00u) | 0x00000083u;
GPIOB->BRR = 1u << DREQ_PIN;
GPIOB->CRH = (GPIOB->CRH & 0xFFFFF0FFu) | 0x00000300u; /* PB10 — выход */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR; /* режим 0, делитель 256 до vs_init */
SPI1->CR1 |= SPI_CR1_SPE;
DMA1_Channel3->CPAR = (uint32_t)&SPI1->DR;
AFIO->EXTICR[0] = (AFIO->EXTICR[0] & ~AFIO_EXTICR1_EXTI1) | AFIO_EXTICR1_EXTI1_PB;
EXTI->RTSR |= 1u << DREQ_PIN; /* DREQ: 0 → 1 — декодер готов принять */
EXTI->IMR |= 1u << DREQ_PIN;
NVIC_EnableIRQ(EXTI1_IRQn);
NVIC_EnableIRQ(DMA1_Channel3_IRQn);
}
/* ── порт драйвера ────────────────────────────────────────────────────── */
void vs_port_spi_hz(uint32_t hz, uint32_t br)
{
(void)hz;
while (SPI1->SR & SPI_SR_BSY) {
}
SPI1->CR1 &= ~SPI_CR1_SPE; /* BR меняют при выключенном SPI */
SPI1->CR1 = (SPI1->CR1 & ~SPI_CR1_BR) | (br << 3);
SPI1->CR1 |= SPI_CR1_SPE;
}
void vs_port_sci(const uint8_t tx[4], uint8_t rx[4])
{
GPIOA->BRR = 1u << XCS_PIN;
for (int i = 0; i < 4; i++) {
while (!(SPI1->SR & SPI_SR_TXE)) {
}
SPI1->DR = tx[i];
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
rx[i] = (uint8_t)SPI1->DR;
}
while (SPI1->SR & SPI_SR_BSY) {
}
GPIOA->BSRR = 1u << XCS_PIN;
}
void vs_port_sdi_start(const uint8_t *p, uint32_t n)
{
GPIOB->BRR = 1u << XDCS_PIN;
sdi_active = 1;
DMA1_Channel3->CCR = 0;
DMA1_Channel3->CMAR = (uint32_t)p;
DMA1_Channel3->CNDTR = n;
DMA1_Channel3->CCR = DMA_CCR3_MINC | DMA_CCR3_DIR | DMA_CCR3_TCIE | DMA_CCR3_EN; /* память → SPI1->DR */
SPI1->CR2 |= SPI_CR2_TXDMAEN;
}
static void sdi_done(void)
{
if (!sdi_active || !(DMA1->ISR & DMA_ISR_TCIF3))
return;
while (!(SPI1->SR & SPI_SR_TXE) || (SPI1->SR & SPI_SR_BSY)) {
} /* последний байт ушёл целиком */
GPIOB->BSRR = 1u << XDCS_PIN;
DMA1->IFCR = DMA_IFCR_CTCIF3;
DMA1_Channel3->CCR = 0;
SPI1->CR2 &= ~SPI_CR2_TXDMAEN;
(void)SPI1->DR; /* принятое не нужно: сбросить RXNE и OVR */
(void)SPI1->SR;
sdi_active = 0;
}
int vs_port_sdi_busy(void)
{
sdi_done();
return sdi_active;
}
int vs_port_dreq(void)
{
return (GPIOB->IDR >> DREQ_PIN) & 1u;
}
void vs_port_xreset(int level)
{
if (level)
GPIOB->BSRR = 1u << XRESET_PIN;
else
GPIOB->BRR = 1u << XRESET_PIN;
}
void vs_port_bus(int take)
{
if (take) { /* SCI: кормление по DMA подождёт */
NVIC_DisableIRQ(EXTI1_IRQn);
NVIC_DisableIRQ(DMA1_Channel3_IRQn);
while (vs_port_sdi_busy()) {
}
} else {
NVIC_EnableIRQ(EXTI1_IRQn);
NVIC_EnableIRQ(DMA1_Channel3_IRQn);
}
}
void vs_port_kick(void)
{
NVIC_SetPendingIRQ(EXTI1_IRQn); /* vs_feed — только в прерывании, без гонок */
}
void vs_port_idle(void)
{
__WFI();
}
void EXTI1_IRQHandler(void)
{
EXTI->PR = 1u << DREQ_PIN;
vs_feed(&ring);
}
void DMA1_Channel3_IRQHandler(void)
{
sdi_done();
vs_feed(&ring);
}
static int sd_read(void *ctx, uint8_t *dst, uint32_t n)
{
UINT br;
return f_read((FIL *)ctx, dst, n, &br) == FR_OK ? (int)br : -1;
}
static int play(const char *name)
{
int rc;
if (f_open(&fil, name, FA_READ) != FR_OK)
return VS_E_READ;
vs_ring_init(&ring, ringbuf, sizeof ringbuf);
while ((rc = vs_pump(&ring, sd_read, &fil)) == 1) {
if (stop_req) {
stop_req = 0;
rc = vs_cancel(&vs, &ring); /* посреди трека: SM_CANCEL */
break;
}
if (volume != vol_set) { /* громкость сменили через отладчик */
vol_set = volume;
(void)vs_set_volume(&vs, vol_set, vol_set);
}
}
if (rc == 0)
rc = vs_finish(&vs); /* конец файла: endFillByte и SM_CANCEL */
f_close(&fil);
return rc;
}
int main(void)
{
static const char *const list[] = {"01.mp3", "02.mp3", "03.mp3"};
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
hw_init();
vs_stream_bind(&vs);
while ((vs_rc = vs_init(&vs, CPU_HZ, VS_FIX_GPIO_BOOT)) != VS_OK)
vs_port_delay_us(100000u); /* нет ответа: питание, XRESET, xCS, MISO */
vol_set = volume;
(void)vs_set_volume(&vs, vol_set, vol_set);
while (f_mount(&fs, "", 1) != FR_OK)
vs_port_delay_us(500000u); /* нет карты */
for (;;) {
for (unsigned i = 0; i < sizeof list / sizeof list[0]; i++) {
track_rc = play(list[i]);
tracks_played++;
}
}
}
Дальше: раздел 19 «Шина CAN» — интерфейс для связи между платами и узлами.