18з. VS1053 MP3 decoder over SPI: SCI and SDI, DREQ, streaming from SD via DMA, volume, reset and patches
In 16г the sound came from a ready-made DFPlayer module: card, decoder and amplifier inside, and the microcontroller only sends "play file 3" over UART. In 18а the sound went out over I2S: the STM32 computes the samples itself and the codec only outputs them. The VS1053b sits in between. It is a decoder for MP3, WAV, MIDI and other formats with a DAC and a headphone output, but the file is handed to it by the microcontroller — byte after byte over SPI, from its own card, from the network, from anywhere. This section is about the driver: two buses on one SPI, the DREQ pin, the SPI speed before and after the clock is set up, reset, volume, reading the format and bit rate, a stream from an SD card through a ring and DMA so that card latency does not break the sound, stopping a track properly and loading patches.
Sources: RM0008 (DocID13902 Rev 17): SPI1 without remap — PA4–PA7,
p. 185; SPI1_TX — DMA1, channel 3, table 78, p. 281; bits MINC, p. 285,
and DIR, p. 286; SSM and SSI, p. 743; BR[2:0], p. 743; CPOL
and CPHA, p. 744; TXDMAEN and the BSY flag, p. 745; flags TXE
and RXNE, p. 746. DocID13587 Rev 17 (STM32F103x8/xB): PA4–PA7 — not
5 V tolerant, p. 29. How SPI and DMA work is in sections 18 and 9, the
file system in 42, the card over SPI in 18.10. There is no manufacturer
document on the VS1053b in the project, and no driver on disk either.
The SCI commands, register numbers and bits, addresses in the decoder's
memory, the SPI speed limits, the 2048-byte buffer, the meaning of DREQ,
the stopping procedure and the patch format are taken from the
manufacturer's documentation and not verified; they are checked only
against our own model of the chip, which was built from the same
information. All numbers below come from running the driver
_verify/mp3_18i/vs18i.c, built with arm-none-eabi-gcc 12.2.1 for
Cortex-M3 and run in QEMU 7.2.22, against a model of the VS1053b, the SD
card and the DMA in Python 3.11; the firmware is built with armclang 6.24
for Cortex-M3. SD card latencies, the time after a register write and
the behaviour with a too fast SPI are model assumptions: these numbers
describe the model, not the chip.
What it is called in the documentation
Original Meaning SCI — serial control interface command bus: 16-bit registers, select xCS SDI — serial data interface data bus: the file's bytes, select xDCS DREQ — data request pin "will take 32 more bytes" XTALI, CLKI the decoder's crystal and its core clock after the multiplier clock multiplier (SC_MULT) clock multiplier in the CLOCKF register software reset (SM_RESET) reset by a bit in MODE new SDI mode (SM_SDINEW) "new" mode: data has its own select xDCS cancel playback (SM_CANCEL) stop decoding and discard the buffer endFillByte filler byte after the end of a file stream header (HDAT0, HDAT1) stream header: format and bit rate sine test (SM_TESTS) sine test: a tone without a file patch, plugin (VLSI) patch: code loaded into the decoder's memory over SCI
18з.1 Structure: two buses on one SPI, DREQ and the registers
The VS1053b has one SPI and two selects (manufacturer's documentation, not verified):
- SCI — select xCS. A command is 4 bytes:
02— write or03— read, the register number, 16 bits most significant byte first. On a read the word comes back on MISO in the third and fourth bytes; - SDI — select xDCS. These are just the file's bytes in a row, with no commands and no addresses. The decoder recognises the format by the first bytes;
- DREQ — a decoder output. 1 — at least 32 bytes are free in its buffer (2048 bytes), a portion may be sent. 0 — the buffer is full or the decoder is busy with an SCI command.
Hence the driver's rule: data goes in 32-byte portions and only when DREQ = 1; before an SCI write, wait for DREQ as well.
| register | number | contents |
|---|---|---|
| MODE | 0 | modes: SM_RESET 0x0004, SM_CANCEL 0x0008, SM_TESTS 0x0020, SM_SDINEW 0x0800; 0x4800 after reset |
| STATUS | 1 | bits 7:4 — version, 4 for the VS1053 |
| CLOCKF | 3 | SC_MULT (15:13) — crystal multiplier, SC_ADD (12:11), SC_FREQ (10:0) — non-standard crystal |
| DECODE_TIME | 4 | seconds since decoding started |
| AUDATA | 5 | sample rate (bits 15:1), bit 0 — stereo |
| WRAM, WRAMADDR | 6, 7 | window into the decoder's memory: address, then words, the address increments |
| HDAT0, HDAT1 | 8, 9 | stream header: for MP3 — the first 4 bytes of a frame |
| AIADDR | 10 | start address of a patch |
| VOL | 11 | attenuation: high byte — left channel, low byte — right |
The model is the VS1053b as an SPI slave: registers, a 2048-byte buffer, DREQ, parsing of real MP3 frame headers with their bit rate and duration, WAV and MIDI by header, cancel, reset, the sine test, the decoder's memory. It builds the MP3 files for the run itself: MPEG1 Layer III, 44.1 kHz, joint stereo, real headers and frame lengths, and bytes 1…254 instead of sound. The model makes no sound: to "play" means taking frames out of the buffer at their own pace.
18з.2 Connecting to the F103, reset and start-up: SPI speed before and after CLOCKF
SPI1 without remap is PA4–PA7 (p. 185), all four are not 5 V tolerant
(p. 29), so the module is powered from 3.3 V. PA5 — SCK, PA6 — MISO,
PA7 — MOSI. The selects xCS and xDCS are plain outputs: PA4 and PB0,
SSM = SSI = 1 (p. 743). DREQ is PB1, an input with an EXTI1
interrupt on the rising edge, XRESET is PB10. SPI mode 0: CPOL =
CPHA = 0 (p. 744).
Speed. After reset the decoder's core clock CLKI equals the crystal XTALI, 12.288 MHz. SCI and SDI writes — no faster than CLKI/4, SCI reads — no faster than CLKI/7 (manufacturer's documentation, not verified). CLOCKF = 0x8800 — multiplier 3.5 and +1.0 of headroom for heavy formats (documentation, not verified) — raises CLKI to 43.008 MHz. The SPI1 divider is a power of two from 72 MHz, and no faster than 18 MHz (18.3). The driver picks the fastest one that does not exceed the limit:
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 | write: limit | SPI1 | read: limit | SPI1 | |
|---|---|---|---|---|---|
| before CLOCKF | 12.288 MHz | 3.072 MHz | 2.25 MHz, BR = 4 |
1.7554 MHz | 1.125 MHz, BR = 5 |
| after CLOCKF | 43.008 MHz | 10.752 MHz | 9 MHz, BR = 2 |
6.144 MHz | 4.5 MHz, BR = 3 |
An SCI command is 32 bits: 14.22 µs at 2.25 MHz and 3.56 µs at 9 MHz.
Start-up. Hardware reset with XRESET, wait for DREQ, check the version, MODE with SM_SDINEW, CLOCKF, and read both back:
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;
}
The start-up exchanges in the model:
| SCI command | reply | SPI | what |
|---|---|---|---|
03 01 00 00 |
0040 |
1.125 MHz | STATUS: version 4 |
02 00 08 00 |
— | 2.25 MHz | MODE = SM_SDINEW |
02 03 88 00 |
— | 2.25 MHz | CLOCKF = 0x8800 |
03 00 00 00 |
0800 |
4.5 MHz | MODE read back |
03 03 00 00 |
8800 |
4.5 MHz | CLOCKF read back |
The whole start-up takes 2988.1 µs: XRESET is low for 1 ms, the rest is
DREQ after reset — about 22 000 XTALI cycles, 1790.4 µs (manufacturer's
documentation, not verified; so in the model). The driver waited for
DREQ 173 times, 10 µs each. No module — in the model MISO floats high, STATUS
reads 0xFFFF, version 15: VS_E_ID, −2. DREQ never rises — after 100 ms
VS_E_TIMEOUT, −1 (in the model after 111.0 ms including the polls).
Reset. Hardware — XRESET low and high; in the model it took 2794.0 µs and returned CLOCKF to zero. Software — a MODE write with 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);
}
In the model — 1819.8 µs, after it MODE = 0x0800 and CLOCKF = 0x8800. SM_SDINEW is written together with SM_RESET: 0x0804, not 0x0004 (18з.7). After a software reset the model returns CLOCKF to zero (a model assumption), so the driver lowers the SPI again and rewrites CLOCKF — this is right whatever the chip does.
Module and power. Common VS1053b boards carry a 12.288 MHz crystal, regulators for 3.3 V and 1.8 V — the chip's core runs from 1.8 V, the analogue part and the pins from 3.3 V, — a headphone jack and sometimes a microSD slot on the same SPI (manufacturer's documentation and module descriptions, not verified). The common wire of the headphone output is not ground but the GBUF pin at half the supply: it must not be tied to an amplifier's ground (documentation, not verified). The model does not compute voltages.
18з.3 Volume, format, bit rate and the sine test
Volume. VOL is attenuation from maximum in 0.5 dB steps, the high byte for the left channel, the low byte for the right; 0xFE is silence (manufacturer's documentation, not verified):
vs_set_volume |
VOL | left | right |
|---|---|---|---|
| 0x00, 0x00 | 0x0000 | 0 dB | 0 dB |
| 0x14, 0x14 | 0x1414 | −10 dB | −10 dB |
| 0x28, 0x28 | 0x2828 | −20 dB | −20 dB |
| 0x00, 0xFE | 0x00FE | 0 dB | −127 dB, silence |
| 0xFE, 0xFE | 0xFEFE | silence | silence |
Format and bit rate. HDAT1 says what the stream is: for MP3 it holds the sync word — eleven ones, the version and the layer. HDAT0 is the second word of the frame header: the bit rate index in bits 15:12, the sample rate index in 11:10. AUDATA is the sample rate and the stereo bit, DECODE_TIME — seconds:
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;
The model in the middle of a track:
| stream | HDAT1 | HDAT0 | AUDATA | the driver read |
|---|---|---|---|---|
| MP3 128 kbit/s | 0xFFFB | 0x9244 | 0xAC45 | MPEG1, Layer III, 128 kbit/s, 44 100 Hz, stereo |
| MP3 320 kbit/s | 0xFFFB | 0xE244 | 0xAC45 | MPEG1, Layer III, 320 kbit/s, 44 100 Hz, stereo |
| WAV 8 kHz, 16 bit, mono | 0x7665 | — | 0x1F40 | WAV, 8000 Hz, mono |
| MIDI | 0x4D54 | — | — | MIDI |
Index 9 is 128 kbit/s, 14 is 320 kbit/s (the MPEG1 Layer III table, documentation, not verified). In the middle of a 3 s file DECODE_TIME is 1. There is no "MP3 or WAV mode" to switch: the decoder recognises the format by the first bytes, and after SM_CANCEL (18з.6) a file of another format may follow at once. The model only recognises a MIDI file, it does not play it.
Sine test. With SM_TESTS set in MODE, eight bytes 53 EF 6E n 0 0 0 0
over SDI switch a tone on and 45 78 69 74 0 0 0 0 switch it off. The
frequency is Fs · S / 128, where Fs is given by the three upper bits of
n (44 100, 48 000, 32 000, 22 050, 24 000, 16 000, 11 025, 12 000 Hz)
and S by the five lower ones (manufacturer's documentation, not
verified):
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 Hz, n = 0x7E — 22 050 · 30 / 128
= 5167.97 Hz (the driver uses integers — 5167). The model switched the
tone on at the same frequency and off again, MODE went back to 0x0800.
Without SM_TESTS the same eight bytes are just data: no tone. The sine
test checks the DAC, the amplifier and the headphones without a file and
without an SD card — the first thing to run on a new board.
18з.4 Streaming from SD: ring, DMA and the DREQ interrupt
FatFS (section 42) reads the file in 512-byte sectors into a ring in
RAM. vs_feed hands it to the decoder: while DREQ = 1 and the ring has
32 bytes, start a DMA portion. A portion is always contiguous: the ring
size is a power of two, and both the sector and 32 divide it.
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);
}
The main loop is vs_pump: room for a sector — read the SD, otherwise
wait:
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;
}
Reading an SD sector takes sometimes a fraction of a millisecond,
sometimes tens of milliseconds (18з.5). If only the main loop calls
vs_feed, the decoder gets no data for that time. So in the firmware it
is called from interrupts: the DREQ rising edge (EXTI1) and the end of
the DMA portion (channel 3, SPI1_TX — p. 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 — memory to peripheral, MINC — the memory address
increments (p. 286 and 285), TXDMAEN — a request on TXE (p. 745).
What arrives on MISO is not needed: at the end of a portion the driver
reads DR and SR, as in 18.6. An SCI command from the main loop and a
DMA portion share one SPI: for the SCI the port disables both interrupts
and waits for the portion to end (vs_port_bus).
The run: a 3 s file, a 4096-byte ring, feeding from interrupts, an SD without long delays:
| bit rate | bytes | frames | 32-byte portions | dropouts | played | SPI busy |
|---|---|---|---|---|---|---|
| 128 kbit/s | 48 065 | 115 | 1503 | 0 | 3004.082 ms | 1.54 % |
| 320 kbit/s | 120 163 | 115 | 3756 | 0 | 3004.082 ms | 3.74 % |
An MP3 frame at 44.1 kHz is 1152 samples, 26.122 ms; at 128 kbit/s it has 417 or 418 bytes, at 320 — 1044 or 1045. A 32-byte portion at 9 MHz takes 28.44 µs, at 2.25 MHz — 113.78 µs: the bus is not the bottleneck, the SD is.
18з.5 How much buffer is needed: bit rate and SD delays
Bit rate is bits per second; in bytes it is 8 times less: 128 kbit/s is 16 000 bytes/s, 320 kbit/s is 40 000 bytes/s. The decoder's buffer, 2048 bytes, lasts 128 ms at 128 kbit/s and 51.2 ms at 320.
A card answers unevenly: usually a sector in a fraction of a millisecond, but sometimes in tens of milliseconds (in the model: 0.8 ms, and every 40th sector 100 ms more, a model assumption). While the card is silent, the sound comes from the buffers. 100 ms at 320 kbit/s needs 4000 bytes. The decoder's buffer gives 2048 — not enough. When reading in sectors the ring is at least its size minus 511 bytes full: 2048 + 2048 − 511 = 3585 — still not enough; 2048 + 4096 − 511 = 5633 — enough. At 128 kbit/s 100 ms is 1600 bytes, and the decoder's buffer alone is enough. The model, a 4 s file:
| bit rate | feeding | ring 1024 | 2048 | 4096 | 8192 |
|---|---|---|---|---|---|
| 128 kbit/s, 3 delays | from the loop | 0 | 0 | 0 | 0 |
| 128 kbit/s, 3 delays | interrupts | 0 | 0 | 0 | 0 |
| 320 kbit/s, 7 delays | from the loop | 7, 353.151 ms | 7, 353.183 ms | 7, 353.151 ms | 7, 354.984 ms |
| 320 kbit/s, 7 delays | interrupts | 7, 270.582 ms | 4, 92.187 ms | 0 | 0 |
Each cell is how many times the decoder ran out of frames and how long
the silence lasted. Feeding from the main loop at 320 kbit/s is not
saved by any ring: while the loop sits in f_read, nobody hands the
ring over. With interrupts 4096 bytes are enough — a fifth of the
BluePill's RAM. The longer the card's delays, the bigger the ring; their
length and frequency are worth measuring on your own card.
18з.6 Stopping a track: endFillByte, SM_CANCEL and patches
End of file. The decoder finishes the last frame only if more bytes follow it. So after the file 2052 bytes of endFillByte are sent — its value lives in the decoder's memory at 0x1E06, — then SM_CANCEL is set and 32 bytes are sent at a time until the bit clears; not cleared within 2048 bytes — software reset (manufacturer's documentation, not verified):
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;
}
In the model endFillByte is 0x00. After a 3-second track SM_CANCEL cleared after the very first portion, all 115 frames were played, 0 bytes left in the buffer, HDAT1 cleared.
In the middle of a track. vs_cancel stops the feeding, sets
SM_CANCEL and sends the rest of the file from the ring, checking the bit
after every portion; when it clears, HDAT0 and HDAT1 must be zero,
otherwise — reset. In the model, a stop after 1 s: the bit cleared after
14 portions — the decoder discarded the buffer at the end of the frame
being played, — 35 frames were played, HDAT0 = HDAT1 = 0.
Without it. The same track was simply abandoned — data stopped and
the next file began at once. The model: after 300 ms 374 bytes were
still lying in the buffer — an incomplete last frame waiting for the
rest. The next file was appended to it: one frame made of two files came
out, then 373 bytes of garbage up to the nearest header — the first frame
of the second track was lost. A damaged frame is a click. With
vs_cancel before the next file — 0 damaged frames.
Patches. VLSI publishes patches and plugins for the VS1053b — fixes and new features, for example decoders of other formats. This is code for the decoder's memory; it is loaded over SCI after every reset, because the memory is erased. The format is an array of 16-bit words: register number, count, values; the top bit of the count means repeat one value (manufacturer's documentation, not verified):
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;
}
There are no real VLSI patches on disk; the loader was checked with our
own: address 0x8010 into WRAMADDR, four words in a row, three more as a
single repeat, and the start AIADDR = 0x0050. The model received 9 SCI
writes, the memory holds 1111, 2222, 3333, 4444, 00AB, 00AB, 00AB, the
start address 0x0050. A truncated array — VS_E_ARG, −5.
18з.7 Pitfalls
Fast SPI before CLOCKF. 9 MHz straight after reset: in the model
both writes — MODE and CLOCKF — were lost (a model assumption: a too fast
write is lost), CLOCKF stayed 0x0000, and MODE read at 9 MHz gave 0x2400
instead of 0x4800 (assumption: a too fast read shifts the word). The
same MODE at 1.125 MHz reads 0x4800. vs_init reads MODE and CLOCKF
back: on such an error it returns VS_E_VERIFY, −3.
xCS and xDCS swapped. The volume write 02 0B 20 20 went to xDCS:
VOL stayed 0x0000, and the four bytes landed in the stream buffer as
data. The start of an MP3 frame FF FB 90 44 went to xCS: the decoder
did not recognise a command and stayed silent, HDAT1 = 0x0000. No error,
no sound.
Not waiting for DREQ. A naive loop: an SD sector — 16 portions in a row without checking DREQ. A 2 s file, 32 182 bytes; in the model 28 473 of them were lost on a full buffer, the decoder found 8 frames out of 77 and lost sync 3 times. DREQ is not a hint but a condition.
"Old" SDI mode. A software reset by writing 0x0004 cleared
SM_SDINEW: MODE = 0x0000. Without it xDCS does not work (manufacturer's
documentation; in the model bytes without SM_SDINEW are lost). A
one-second file went nowhere: 0 frames, DECODE_TIME 0, SM_CANCEL did not
clear within 2048 bytes, and vs_finish did a software reset,
VS_E_CANCEL, −4. MODE is always written with SM_SDINEW: 0x0800, on
reset — 0x0804.
The module starts in MIDI. On some modules the GPIO1 pin is pulled
up, and the decoder wakes up in real-time MIDI mode: it takes MP3 bytes
for notes (a known problem of such modules, not verified). In the model
such a module played 0 frames out of 38. The fix is in vs_init with
the VS_FIX_GPIO_BOOT flag: WRAM 0xC017 = 3 and 0xC019 = 0, i.e. GPIO0
and GPIO1 become outputs driving zero, then a software reset; after it —
38 frames out of 38.
A click on stop. A track abandoned without endFillByte and SM_CANCEL
— the tail of a frame mixed with the next file (18з.6). Stop only
through vs_finish or vs_cancel.
Feeding from the main loop. At 320 kbit/s any SD delay longer than 51.2 ms is silence, and the ring does not help (18з.5). Portions — from the DREQ and DMA interrupts.
SCI in the middle of a DMA portion. Reading HDAT or writing VOL during
an SDI portion spoils both: the selects differ, but the SPI is one. In
the driver the SCI takes the bus through vs_port_bus: the feeding
interrupts are off, the DMA portion has finished.
18з.8 Summary
- One SPI, two selects: SCI (xCS) — a 4-byte command, SDI (xDCS) — data in 32-byte portions when DREQ = 1; the decoder's buffer is 2048 bytes.
- Speed: before CLOCKF writes at 2.25 MHz and reads at 1.125 MHz; after CLOCKF = 0x8800 (CLKI 43.008 MHz) — 9 and 4.5 MHz.
- Start-up: XRESET, DREQ, version 4 in STATUS, MODE 0x0800, CLOCKF 0x8800, read back — 2988.1 µs.
- VOL: 0.5 dB steps, 0x1414 — −10 dB, 0xFEFE — silence.
- HDAT1 0xFFFB, HDAT0 0x9244 — MPEG1 Layer III, 128 kbit/s, 44.1 kHz; WAV — 0x7665, MIDI — 0x4D54.
- 128 kbit/s is 16 000 bytes/s, the decoder's buffer — 128 ms; 320 kbit/s is 40 000 bytes/s, 51.2 ms; the SPI is busy 1.54 and 3.74 %.
- A 100 ms SD delay at 320 kbit/s: feeding from interrupts and a 4096-byte ring — 0 dropouts; from the main loop — 7.
- End of a track: 2052 bytes of endFillByte, SM_CANCEL, 32-byte portions — cleared after 1 portion; without it — a damaged frame and 373 bytes of garbage.
Full sources
vs18i.h — VS1053b registers and bits, structures, driver and platform interface.
/* 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 speed, reset and start-up, volume, format, sine test, patches, ring, DMA and stopping.
/* 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 firmware: SPI1, DMA1 channel 3, EXTI1 on DREQ, FatFS, three tracks in a loop.
/* 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++;
}
}
}
Next: section 19 "The CAN bus" — an interface for communication between boards and nodes.