Files
DigiRadio/Software/main/esp32_i2s_test_tone.cpp
micheleandClaude Sonnet 5 fae2305164 Fix Si4684 pitch distortion: uncalibrated crystal (CTUN/XTAL_FREQ)
Root cause of FM/DAB "stonata" audio: xtalCtun=31/nominal XTAL_FREQ
defaults were never measured against this board's actual crystal
(Abracon ABM8-19.200MHZ-10-1-U-T, CL=10pF, two external 15pF load
caps). Fixed via CTUN trim by ear (31->0) plus a new live calibration
loop that reads the Si4684's own FM_RSQ FREQOFF field (broadcast
carriers are GPS-locked, so it's a free precision frequency reference)
to trim XTAL_FREQ with no lab equipment. Converged to CTUN=0,
XTAL_FREQ=19199750 Hz, residual -3/-4 ppm cross-checked on two
stations.

New tools/infrastructure (kept, not one-off):
- Si4684Driver::recalibrateXtal() + POST /api/tuner/xtal-calibrate:
  live crystal re-trim without an ESP32 reflash.
- FM_RSQ FREQOFF exposed as "freqoff_ppm" in GET /api/tuner/status.
- tools/si4684_xtal_calibration.py: automates the trim loop.
- tools/si4684_antenna_calibration.py: AN851 Appendix A ANTCAP/VARM/VARB
  sweep tool (same session, separate calibration).
- CONFIG_ESP32_I2S_TEST_TONE (off by default): isolates Si4684-specific
  audio issues from shared-downstream ones by writing a tone directly
  over the I2S bus the Si4684 also uses.
- SIGMA_WRITE_REGISTER_BLOCK/sigma_safeload_block now retry on NACK and
  verify via read-back instead of firing I2C writes blind.
- FM de-emphasis set to European 50us (was left at the US 75us default).
- DAB_ACF_ENABLE restored to its previous 0x0000 with a citation
  explaining why (tested the datasheet default of 0x0003, made things
  audibly worse).

See docs/si4684-rf-investigation-report.md's 2026-08-23 entry for the
full elimination chain and docs/TODO.md for follow-up work (persisting
calibration results to EEPROM instead of requiring a firmware edit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 20:30:42 +02:00

58 lines
1.5 KiB
C++

/**
* @file esp32_i2s_test_tone.cpp
* @brief esp32_i2s_test_tone implementation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp32_i2s_test_tone.hpp"
#include "esp32_i2s_sink.hpp"
#include <cmath>
#include <cstdint>
#include <numbers>
namespace esp32_i2s_test_tone {
namespace {
constexpr int kSampleRateHz = 48000;
constexpr int kFramesPerBlock = 256;
/** writeSamples() wants top-aligned 24-bit in a 32-bit slot -- 8-bit shift. */
constexpr int kTopAlignShift = 8;
/** Modest amplitude: audible but not a full-scale blast. */
constexpr float kAmplitude = 0.4F * 8388607.0F;
constexpr float kTwoPi = 2.0F * std::numbers::pi_v<float>;
} // namespace
[[noreturn]] void run(float freqHz)
{
esp32_i2s_sink::tryAcquire();
const float phaseStep = kTwoPi * freqHz / static_cast<float>(kSampleRateHz);
float phase = 0.0F;
std::int32_t buf[kFramesPerBlock * 2];
for (;;)
{
for (int i = 0; i < kFramesPerBlock; ++i)
{
phase += phaseStep;
if (phase > kTwoPi)
{
phase -= kTwoPi;
}
const auto sample =
static_cast<std::int32_t>(std::sin(phase) * kAmplitude)
<< kTopAlignShift;
buf[(i * 2) + 0] = sample;
buf[(i * 2) + 1] = sample;
}
esp32_i2s_sink::writeSamples(buf, kFramesPerBlock * 2);
}
}
} // namespace esp32_i2s_test_tone