Fix FM RDS: station name/RadioText never decoded, three stacked bugs

GET /api/tuner/status and the full FM band scan always returned an
empty station_name, on every frequency, regardless of signal quality
-- confirmed live on multiple >35 dB SNR channels before this fix.
Three independent problems, found by reading the Si4684 datasheet
(AN649 Rev.2.0) instead of guessing further:

1. FM_RDS_CONFIG (property 0x3C02) was enabled (RDSEN=1) but with both
   block-error thresholds at 0 ("no block errors"), so almost any
   real-world RDS group -- routine with ordinary multipath/noise, even
   on a strong signal -- got rejected from the FIFO outright. Raised
   to the datasheet's most tolerant recommended setting (2, "3-5 bit
   errors detected and corrected").

2. FM_RDS_INTERRUPT_FIFO_COUNT (property 0x3C01) was never set at all,
   defaulting to 0 -- which the datasheet states disables RDSFIFOINT
   permanently. Si4684Driver::readFmRds()'s "received" flag reads
   exactly that bit (FM_RDS_STATUS RESP4 bit 0), so it could never be
   true, and Si4684Tuner::refreshStatus()'s RDS poll loop broke out on
   its first iteration every single time, before ever touching the
   accumulator. Set to 1 (fire as soon as one group is queued).

3. The real bug, once groups actually started arriving:
   RdsMetadataAccumulator::applyGroup() read the two Program Service
   name characters from Block C and computed the segment index as
   (blockB >> 1) & 0x3. Per ETSI EN 62106 §3.1.5, PS characters for
   group type 0 (both 0A and 0B) are always in Block D -- Block C
   holds alternate-frequency codes (0A) or a repeated PI code (0B),
   never text -- and the segment address is blockB bits[1:0] with no
   shift. This function had no dedicated test before now, which is
   how a wrong block/bit pair could ship unnoticed: the old test
   fixture encoded the same bug in its "expected" input.

Confirmed live after all three fixes: station_name and radiotext both
populate with stable, plausible content across repeated reads (not
noise) on a real broadcast signal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 23:58:37 +02:00
co-authored by Claude Sonnet 5
parent 5d7d4919a0
commit d587032a0a
6 changed files with 168 additions and 13 deletions
@@ -66,6 +66,11 @@ constexpr std::uint16_t kSi4684I2sOutEnable = 0x8002U;
/** Si4684 volume: 0=mute, 63=max (AN649 AUDIO_ANALOG_VOLUME). */
constexpr std::uint8_t kSi4684VolumeMax = 63U;
constexpr std::uint16_t kPropFmRdsConfig = 0x3C02U;
/** AN649 §0x3C01 FM_RDS_INTERRUPT_FIFO_COUNT: DEPTH[7:0], groups needed
* before RDSFIFOINT (FM_RDS_STATUS RESP4 bit0, our readFmRds() "received"
* bit) ever sets. Default 0 disables it permanently regardless of
* FM_RDS_CONFIG -- must be nonzero for any RDS group to ever be seen. */
constexpr std::uint16_t kPropFmRdsInterruptFifoCount = 0x3C01U;
/** AN649 FM_AUDIO_DE_EMPHASIS (0x3900): 0=75us/US (chip default), 1=50us/
* Europe, 2=disabled. FM seek band/spacing above is already the European
* 87.5-107.9 MHz/100 kHz plan, so the chip must not stay on its 75us/US
@@ -557,9 +562,22 @@ std::expected<void, Si4684Error> Si4684Driver::configureAfterBoot(
return set;
}
}
if (auto rds = setProperty(kPropFmRdsConfig, 0x0001U); !rds) {
// AN649 §0x3C02 FM_RDS_CONFIG: BLETHB[7:6]/BLETHCD[5:4] block-error
// thresholds, RDSEN[0]. 0x0001 (thresholds at 0, "no block errors")
// rejected almost every real-world group -- any bit error at all
// (routine with multipath/noise, even on a strong signal) dropped
// the group from the FIFO, so accumulated station names/RadioText
// never completed. 0x00A1 keeps both thresholds at the datasheet's
// most tolerant recommended setting (2 = "3-5 bit errors detected
// and corrected"), still discarding uncorrectable (3) groups.
if (auto rds = setProperty(kPropFmRdsConfig, 0x00A1U); !rds) {
return rds;
}
if (auto rdsFifo =
setProperty(kPropFmRdsInterruptFifoCount, 0x0001U);
!rdsFifo) {
return rdsFifo;
}
if (auto deEmph = setProperty(kPropFmAudioDeEmphasis,
kFmAudioDeEmphasisEurope);
!deEmph) {